Changelog

View Source

All notable changes to evoq will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[1.9.1] - 2026-03-08

Added

  • evoq_event_store:has_events/1: Check if a store contains at least one event. Delegates to adapter's has_events/1 if available, falls back to reading 1 event via read_all_global.
  • Catch-up diagnostic logging: evoq_store_subscription now logs each event's type and handler count during historical replay for troubleshooting.

[1.9.0] - 2026-03-06

Added

  • Catch-up historical replay: evoq_store_subscription now replays all historical events from the store before subscribing to new events. Uses read_all_global/3 to read events in batches, routing through the same path as live events.
  • evoq_event_store:read_all_global/3: Read all events across all streams in global order with offset/batch pagination. Falls back to read_all_events/2 if adapter does not implement the optional callback.

[1.8.2] - 2026-03-08

Fixed

  • evoq_store_subscription: Cross-stream checkpoint collision with $all subscriptions. The $all subscription delivers events from multiple streams, but stream-local versions overlap (stream A version 0, stream B version 0). When these were passed to evoq_projection as the version in metadata, the idempotency check EventVersion =< Checkpoint incorrectly skipped events from the second stream. Now maintains a monotonically increasing sequence counter per subscription instance. The global sequence is injected as version in metadata (for projection checkpoints), and the original stream version is preserved as stream_version.

  • Test infrastructure: Added evoq_type_provider to test helper ensure_routing_infrastructure/0, preventing ETS table crashes when evoq_event_router attempts upcasting during tests.

[1.8.1] - 2026-03-07

Fixed

  • Projection checkpoint skips first event (version 0): Initial checkpoint was 0, and the idempotency check EventVersion =< Checkpoint skipped events at version 0. Since ReckonDB stream versions are 0-based, the first event in every stream was silently dropped. Changed initial checkpoint to -1 (sentinel for "nothing processed yet"). Same fix applied to do_rebuild/1. This is the same class of bug that was fixed in aggregates in v1.3.1.

[1.8.0] - 2026-03-07

Changed

  • evoq_store_subscription: Single $all subscription for global ordering. Previously created N independent per-event-type subscriptions to ReckonDB, one per registered handler type. Each subscription had its own bridge process, so events of different types had no ordering guarantee relative to each other. Now uses a single by_stream subscription with <<"$all">> selector, receiving ALL events in global store order. Events are filtered locally by checking evoq_event_type_registry:get_handlers/1 — types with no handlers are skipped. This fixes race conditions where causally related events of different types (e.g., license_initiated_v1 before license_published_v1) could be delivered out of order to their respective projections.

[1.7.0] - 2026-03-07

Changed

  • evoq_read_model_ets shared named tables: Named ETS tables now support multiple projections writing to the same read model. If a named table already exists, new instances join it instead of crashing. This enables the vertical slicing pattern where each projection (desk) handles one event type but all project into the same read model. Anonymous (unnamed) tables remain isolated per instance as before.

[1.6.0] - 2026-03-07

Added

  • evoq_store_subscription module: Bridge between event stores and evoq's routing infrastructure. Creates per-event-type subscriptions to a reckon-db store, matching evoq's event-type-oriented architecture. Only events that have registered handlers/projections/PMs are subscribed to — filtering happens at the store level, not the application level. This is the critical missing link that connects the event store to evoq behaviours (evoq_event_handler, evoq_projection, evoq_process_manager).

    • Start one instance per store: evoq_store_subscription:start_link(my_store)
    • Automatically discovers registered event types from evoq_event_type_registry
    • Dynamically subscribes to new event types as handlers register
    • Routes events to both evoq_event_router and evoq_pm_router
  • evoq_event_type_registry:register_listener/1: Atomically returns all currently registered event types AND subscribes the caller for future type registration notifications. Race-free — no register/2 call can execute between returning types and subscribing for notifications.

  • evoq_event_type_registry:unregister_listener/1: Removes a store subscription listener.

Changed

  • evoq_event_type_registry:register/2: Now detects when an event type gets its first handler and notifies registered store subscription listeners via {new_event_type, EventType} messages.

[1.5.0] - 2026-03-05

Fixed

  • Nested event structure in append_events: Events produced by aggregates now use proper nested #{event_type, data, metadata} structure.

[1.4.0] - 2026-02-25

Added

  • evoq_subscriptions facade module: Application-level API for subscription operations, mirroring the evoq_event_store pattern. Application code should call evoq_subscriptions:subscribe/5 instead of the adapter directly. Delegates to a configured subscription_adapter (set via {evoq, [{subscription_adapter, Module}]}). Exports: subscribe/5, unsubscribe/2, ack/4, get_checkpoint/2, list/1, get_by_name/2, get_adapter/0, set_adapter/1.

[1.3.1] - 2026-02-13

Fixed

  • Aggregate replay at version 0: load_or_init/3 now uses State =/= undefined instead of Version > 0 to detect replayed events. The first event in a stream is version 0, so the previous guard skipped it and re-initialized fresh.

Added

  • event_to_map/1 exported: evoq_event_store:event_to_map/1 is now public API. Converts #evoq_event{} records to flat maps, merging business data from the data field into the top level so aggregates see consistent shapes regardless of source.

[1.3.0] - 2026-02-11

Added

  • idempotency_key field on #evoq_command{}: Optional caller-provided key for deterministic command deduplication. When set, the idempotency cache uses this key instead of command_id. Use for scenarios like "user cannot submit the same form twice" where the deduplication key should be deterministic and intent-based.

  • Auto-generated command_id: The dispatcher now auto-generates command_id via crypto:strong_rand_bytes/1 if the field is undefined. Callers no longer need to generate their own command IDs — the framework handles it.

  • evoq_command:ensure_id/1: Fills in command_id if undefined, returns command unchanged if already set.

  • evoq_command:get_idempotency_key/1 and set_idempotency_key/2: Accessors for the new idempotency_key field.

Changed

  • Idempotency cache key selection: The dispatcher now uses idempotency_key (if set) for cache lookups, falling back to command_id. This separates the concerns of command identification (tracing, unique per invocation) from command deduplication (deterministic per intent).

  • Validation relaxed: evoq_command:validate/1 no longer rejects commands with undefined command_id, since the dispatcher auto-generates it before execution.

Migration

  • No breaking changes for existing code. Commands with manually-set command_id continue to work exactly as before. The new idempotency_key field defaults to undefined.
  • Recommended: Stop generating command_id manually in dispatch modules. Let the framework handle it. If you need deduplication, use idempotency_key instead.

[1.2.1] - 2026-02-01

Added

  • Event Envelope Documentation: Comprehensive guide explaining evoq_event record structure
    • New guide: guides/event_envelope.md - Complete explanation of envelope fields
    • New diagram: assets/event-envelope-diagram.svg - Visual event lifecycle
    • Updated guides/projections.md - Clarified envelope structure in projections
    • Documents where business event payloads fit (data field)
    • Explains metadata usage (correlation_id, causation_id, etc.)
    • Event naming conventions and versioning patterns
    • Common mistakes to avoid

Documentation

  • Improved clarity on event envelope structure
  • Added visual diagrams for event lifecycle
  • Standardized metadata field documentation

[1.2.0] - 2026-01-21

Added

  • Tag-Based Querying: Cross-stream event queries using tags
    • tags field added to #evoq_event{} record in evoq_types.hrl
    • evoq_tag_match() type - Support for any (union) and all (intersection) matching
    • tags subscription type for tag-based subscriptions
    • Tags are for QUERY purposes only, NOT for concurrency control

[1.1.3] - 2026-01-19

Fixed

  • Documentation: Minor documentation improvements

[1.1.0] - 2026-01-08

Added

  • Bit Flags Module (evoq_bit_flags): Efficient bitwise flag manipulation for aggregate state

    • set/2, unset/2: Set/unset single flags
    • set_all/2, unset_all/2: Set/unset multiple flags
    • has/2, has_not/2: Check single flag state
    • has_all/2, has_any/2: Check multiple flags
    • to_list/2, to_string/2,3: Human-readable conversions with flag maps
    • decompose/1: Extract power-of-2 components
    • highest/2, lowest/2: Get highest/lowest set flag description
  • Bit Flags Guide (guides/bit_flags.md): Comprehensive documentation

    • Why use bit flags in event sourcing
    • Core operations with examples
    • Aggregate state management patterns
    • Best practices for flag definition
    • Complete function reference

Changed

  • Aggregate status fields should now use integer bit flags instead of atoms for better memory efficiency, query performance, and event store compatibility

[1.0.3] - 2026-01-06

Fixed

  • Macro guard compatibility: Added -ifndef guards around macro definitions in evoq_types.hrl to prevent redefinition errors when used alongside esdb_gater_types.hrl in adapters like reckon_evoq

[1.0.2] - 2026-01-06

Changed

  • Independence from reckon_gater: Removed direct dependency on reckon_gater
    • Introduced include/evoq_types.hrl with evoq's own type definitions
    • Adapters (like reckon_evoq) now handle type translation between evoq and backend
    • evoq is now a pure CQRS/ES framework without storage backend coupling

Fixed

  • hex.pm dependencies: Package now correctly publishes with only telemetry as dependency

[1.0.1] - 2026-01-03

Fixed

  • SVG diagrams: Updated architecture.svg, command-dispatch.svg, and event-routing.svg to reference reckon-db instead of erl-esdb

[1.0.0] - 2026-01-03

Changed

  • Stable Release: First stable release of evoq under reckon-db-org
  • All APIs considered stable and ready for production use
  • Fixed documentation links (guides/adapters.md)
  • Updated dependency references to reckon_gater

[0.3.0] - 2025-12-20

Added

  • Documentation: Comprehensive educational guides with SVG diagrams
    • Architecture overview guide with system diagram
    • Aggregates guide with lifecycle diagram
    • Event handlers guide with routing diagram
    • Process managers guide with saga flow diagram
    • Projections guide with data flow diagram
    • Adapters guide for event store integration
  • ex_doc integration: Full hex.pm documentation support via rebar3_ex_doc

Changed

  • Dependencies: Updated reckon_gater from 0.3.0 to 0.4.3

Fixed

  • EDoc errors: Fixed XML parsing issues in memory monitor documentation
  • EDoc errors: Removed invalid @doc tags before -callback declarations

[0.2.0] - 2024-12-19

Added

  • Event Store Adapter Pattern: Pluggable event store backends

    • evoq_adapter behavior for custom adapters
    • evoq_event_store facade with adapter delegation
    • Support for erl-esdb-gater integration
  • Checkpoint Store: Persistent checkpoint tracking

    • evoq_checkpoint_store behavior
    • evoq_checkpoint_store_ets ETS-based implementation
    • Position tracking for projections and handlers
  • Dead Letter Queue: Failed event handling

    • evoq_dead_letter for events that exhaust retries
    • List, retry, and discard operations
    • Telemetry integration for monitoring
  • Error Handling: Comprehensive error management

    • evoq_error_handler for centralized error processing
    • Failure context tracking via evoq_failure_context
    • Retry strategies with backoff

Changed

  • Event Router: Switched to per-event-type subscriptions
    • Handlers declare interested_in/0 for event types
    • Prevents subscription explosion with many aggregates
    • Constant memory usage regardless of aggregate count

[0.1.0] - 2024-12-18

Added

  • Initial release of evoq CQRS/Event Sourcing framework

  • Aggregates (evoq_aggregate):

    • evoq_aggregate behavior with init/execute/apply callbacks
    • Partitioned supervision across 4 supervisors
    • Configurable TTL and idle timeout
    • Snapshot support for faster loading
    • Memory pressure monitoring with adaptive TTL
  • Aggregate Lifespan (evoq_aggregate_lifespan):

    • Configurable lifecycle management
    • Default 30-minute idle timeout
    • Hibernate after 1 minute idle
    • Snapshot on passivation
  • Command Dispatch (evoq_dispatcher):

    • Command routing to aggregates
    • Middleware pipeline support
    • Consistency mode (strong/eventual)
  • Middleware (evoq_middleware):

    • Pluggable command pipeline
    • Validation middleware
    • Idempotency middleware
    • Consistency middleware
  • Event Handlers (evoq_event_handler):

    • Per-event-type subscriptions
    • Retry strategies with exponential backoff
    • Dead letter queue for failed events
    • Strong/eventual consistency modes
  • Process Managers (evoq_process_manager):

    • Long-running business process coordination
    • Event correlation and routing
    • Command dispatch from handlers
    • Compensation support for failures
  • Projections (evoq_projection):

    • Read model builders from events
    • Checkpointing for resume
    • Rebuild capability
    • Multiple storage backends
  • Event Upcasters (evoq_event_upcaster):

    • Schema evolution support
    • Event migration on replay
  • Memory Monitor (evoq_memory_monitor):

    • System memory pressure detection
    • Adaptive TTL adjustment
    • Aggregate eviction under pressure
  • Telemetry Integration:

    • Comprehensive event emission
    • Aggregate lifecycle events
    • Handler processing events
    • Projection progress events

Dependencies

  • erl_esdb_gater 0.3.0 - Gateway API and shared types
  • telemetry 1.3.0 - Observability