packages feed

eventium-core-0.2.1: README.md

# 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)