# Eventium Core Core abstractions for building event-sourced applications in Haskell. ## Overview `eventium-core` is the foundational package of the Eventium framework. It defines storage-agnostic interfaces and pure types -- no database drivers, no I/O beyond what the caller provides via monad parameters. ## Key Types ### StreamEvent ```haskell data StreamEvent key position event = StreamEvent { key :: key , position :: position , metadata :: EventMetadata , payload :: event } ``` Every stored event carries a stream key, a position, metadata (event type, correlation/causation IDs, timestamp), and the domain payload. ### EventStoreReader / EventStoreWriter ```haskell newtype EventStoreReader key position m event = EventStoreReader { getEvents :: QueryRange key position -> m [event] } newtype EventStoreWriter key position m event = EventStoreWriter { storeEvents :: key -> ExpectedPosition position -> [event] -> m (Either (EventWriteError position) EventVersion) } ``` Polymorphic over key, position, monad, and event types. `runEventStoreReaderUsing` / `runEventStoreWriterUsing` lift between monads. `codecEventStoreReader` / `codecEventStoreWriter` wrap with a `Codec`. ### Projection ```haskell data Projection state event = Projection { seed :: state , eventHandler :: state -> event -> state } ``` Pure fold for rebuilding state from events. Used for both aggregates (write side) and read models (query side). `getLatestStreamProjection` loads events from a reader and applies them. ### CommandHandler ```haskell data CommandHandler state event command err = CommandHandler { decide :: state -> command -> Either err [event] , projection :: Projection state event } ``` Validates a command against current state, returning either a domain error or new events. `applyCommandHandler` orchestrates the full load-decide-write cycle. ### ProcessManager ```haskell data ProcessManager state event command = ProcessManager { projection :: Projection state (VersionedStreamEvent event) , react :: state -> VersionedStreamEvent event -> [ProcessManagerEffect command] } data ProcessManagerEffect command = IssueCommand UUID command | IssueCommandWithCompensation UUID command (Text -> [ProcessManagerEffect command]) ``` Coordinates cross-aggregate workflows. `react` is pure; `runProcessManagerEffects` dispatches the resulting commands via a `CommandDispatcher`. `IssueCommandWithCompensation` triggers compensation effects when a command fails. ### CommandDispatcher ```haskell newtype CommandDispatcher m command = CommandDispatcher { dispatchCommand :: UUID -> command -> m CommandDispatchResult } data CommandDispatchResult = CommandSucceeded | CommandFailed Text ``` Routes commands to aggregates and reports outcomes. Construct with `mkCommandDispatcher`, or use `fireAndForgetDispatcher` for legacy callbacks. `commandHandlerDispatcher` (from `Eventium.CommandDispatcher`) builds a dispatcher from a list of `AggregateHandler`s for multi-aggregate routing. `processManagerEventHandler` wires a `ProcessManager` to a global reader and dispatcher, producing a ready-to-use `EventHandler`. ### ProjectionCache ```haskell data ProjectionCache key position encoded m = ProjectionCache { storeSnapshot :: key -> position -> encoded -> m () , loadSnapshot :: key -> m (Maybe (position, encoded)) } ``` Snapshot store for aggregate state, avoiding full event replay on every load. Two flavors: - **VersionedProjectionCache** -- keyed by `UUID` + `EventVersion`, one snapshot per aggregate instance. - **GlobalProjectionCache** -- keyed by `()` + `SequenceNumber`, singleton snapshot for global projections. Wiring helpers: - `snapshotEventHandler` -- `EventHandler` that auto-updates a `VersionedProjectionCache` on each event. Compose with `publishingEventStoreWriter` for transparent snapshotting. - `snapshotGlobalEventHandler` -- same for `GlobalProjectionCache`. - `applyCommandHandlerWithCache` -- like `applyCommandHandler` but loads from cache and updates after write. ### ReadModel ```haskell data ReadModel m event = ReadModel { initialize :: m () , eventHandler :: EventHandler m (GlobalStreamEvent event) , checkpointStore :: CheckpointStore m SequenceNumber , reset :: m () } ``` Abstraction for queryable persistent views driven by the global event stream. Users define their own schema and event handler; the library manages the event pipeline and checkpointing. ReadModels always consume the global stream (cross-aggregate views need total ordering). - `runReadModel` -- polling subscription that keeps the view updated (runs forever). - `rebuildReadModel` -- reset + replay all events (one-shot rebuild). - `combineReadModels` -- fan-out events to multiple read models. ### EventHandler / EventPublisher / EventSubscription - **EventHandler** -- composable event consumer (`Contravariant`, `Semigroup`, `Monoid`). - **EventPublisher** -- decouples post-write dispatch. `publishingEventStoreWriter` wraps a writer for auto-publish. - **EventSubscription** -- push-based delivery. `pollingSubscription` polls the global stream with a `CheckpointStore`. ### Codec ```haskell data Codec a b = Codec { encode :: a -> b , decode :: b -> Maybe a } ``` Value-level bidirectional conversion. Composable via `composeCodecs`. `Eventium.TH` generates sum-type codecs and JSON instances. ### TypeEmbedding ```haskell data TypeEmbedding a b = TypeEmbedding { embed :: a -> b , extract :: b -> Maybe a } ``` Embeds one sum type into another (e.g. aggregate events into an application-wide event type). Separate from `Codec` to distinguish type-level subset relationships from wire-format encoding. ## Modules | Module | Purpose | |--------|---------| | `Eventium.Store.Class` | Reader/Writer interfaces, monad lifting, codec wrappers | | `Eventium.Store.Types` | `StreamEvent`, `EventMetadata`, `EventVersion`, `SequenceNumber`, `ExpectedPosition` | | `Eventium.Store.Queries` | `QueryRange` builders (`allEvents`, `eventsStartingAt`, etc.) | | `Eventium.Projection` | `Projection`, `StreamProjection`, `getLatestStreamProjection` | | `Eventium.CommandHandler` | `CommandHandler`, `applyCommandHandler`, `applyCommandHandlerWithCache` | | `Eventium.ProcessManager` | `ProcessManager`, `ProcessManagerEffect`, `CommandDispatcher`, `CommandDispatchResult`, `runProcessManagerEffects`, `processManagerEventHandler` | | `Eventium.CommandDispatcher` | `AggregateHandler`, `mkAggregateHandler`, `commandHandlerDispatcher` | | `Eventium.EventHandler` | `EventHandler`, `handleEvents` | | `Eventium.EventPublisher` | `EventPublisher`, `publishingEventStoreWriter`, `synchronousPublisher` | | `Eventium.EventSubscription` | `EventSubscription`, `pollingSubscription`, `CheckpointStore` | | `Eventium.ReadModel` | `ReadModel`, `runReadModel`, `rebuildReadModel`, `combineReadModels` | | `Eventium.ProjectionCache.Cache` | `ProjectionCache` helpers: `snapshotEventHandler`, `snapshotGlobalEventHandler`, `getLatestVersionedProjectionWithCache`, `updateVersionedProjectionCache` | | `Eventium.ProjectionCache.Types` | `ProjectionCache`, `VersionedProjectionCache`, `GlobalProjectionCache` | | `Eventium.Codec` | `Codec`, `jsonCodec`, `jsonTextCodec`, `composeCodecs` | | `Eventium.UUID` | UUID utilities (`uuidNextRandom`, `uuidFromText`, `uuidFromInteger`) | | `Eventium.TH` | Template Haskell: `deriveJSON`, `mkSumTypeCodec`, `mkSumTypeEmbedding`, `makeProjection` | ## Usage ```yaml dependencies: - eventium-core >= 0.1.0 ``` Then pick a storage backend: `eventium-memory`, `eventium-sqlite`, or `eventium-postgresql`. ## Documentation - [Main README](../README.md) - [Design](../DESIGN.md) - [Examples](../examples/) ## License MIT -- see [LICENSE.md](LICENSE.md)