# `Chronicle.Reducers.Reducer`
[🔗](https://github.com/Cratis/Chronicle.Elixir/blob/main/lib/chronicle/reducers/reducer.ex#L4)

Behaviour and macro for defining Chronicle reducers.

Reducers build and maintain read models by folding events into state.
Each event type that the reducer handles is applied in order, progressively
updating the read model from its initial state.

## Defining a reducer

Use `Chronicle.Reducers.Reducer` in a module, declare which events it handles with
`@handles`, and implement the `reduce/3` callback.

    defmodule MyApp.Reducers.AccountReducer do
      use Chronicle.Reducers.Reducer, model: MyApp.ReadModels.Account

      @handles MyApp.Events.AccountOpened
      @handles MyApp.Events.FundsDeposited
      @handles MyApp.Events.FundsWithdrawn

      @impl true
      def reduce(%MyApp.Events.AccountOpened{} = event, _model, _context) do
        %MyApp.ReadModels.Account{
          account_id: event.account_id,
          owner_name: event.owner_name,
          balance: event.initial_balance
        }
      end

      def reduce(%MyApp.Events.FundsDeposited{} = event, model, _context) do
        %{model | balance: model.balance + event.amount}
      end

      def reduce(%MyApp.Events.FundsWithdrawn{} = event, model, _context) do
        %{model | balance: model.balance - event.amount}
      end
    end

## Options for `use Chronicle.Reducers.Reducer`

  * `:model` — **(required)** the read model module this reducer produces.
  * `:id` — a stable string identifier. Defaults to the module's full name. A defaulted
    id (pin it explicitly to silence this) emits a one-time warning when discovered —
    see `Chronicle.Discovery.DefaultedIdWarning`.
  * `:active` — whether Chronicle actively keeps this reducer's read model up
    to date as events are appended. Defaults to `true`. Set to `false` for a
    **passive** reducer: its read model is only computed on demand (e.g. via
    `Chronicle.ReadModels.get/3`) and is not kept warm in the background —
    useful for a reducer whose only purpose is a command-side decision
    that's read rarely, where continuous observation would be wasted work.
  * `:generation_delivery` — `:compatibility` (default) or `:pinned`. See
    "Pinned generation delivery" below.

## Pinned generation delivery

By default (`:compatibility`), Chronicle upgrades every event to the handled module's own
generation before delivery, so `reduce/3` always sees that generation's shape regardless of
which generation was actually appended. Set `generation_delivery: :pinned` to receive each
event exactly as its own generation was appended instead - useful when a reducer is written
against an older generation and must see its original content rather than the upgraded one.
`context.appended_generation` then tells you which generation was actually appended (`0`
means unknown - an older kernel, or an event appended outside a registered event source).

A client-wide default applies to every reducer that doesn't set `:generation_delivery`
itself: pass `event_generation_delivery: :pinned` to `Chronicle.Client`. A reducer's own
`:generation_delivery` option always wins over the client default.

With `:pinned`, a reducer that handles two generations of the same event type id has no
single generation to pin to and raises
`Chronicle.Observers.MultipleGenerationsOfEventTypeInObserver` on start.

## Registering with Chronicle.Client

    {Chronicle.Client,
      ...
      reducers: [MyApp.Reducers.AccountReducer]}

## How reducers work

When Chronicle needs the current state of a read model, it:

1. Fetches the stored read model JSON (or starts with `nil`)
2. Fetches all new events for the relevant event source
3. Sends them to the reducer as a `ReduceOperationMessage`
4. The reducer applies each event via `reduce/3` and returns the final state

The model is stored by Chronicle between calls, so `reduce/3` only receives
events that occurred since the last successful reduction.

## Event context

The third argument to `reduce/3` is a `Chronicle.Events.EventContext` struct (a map, so
pattern matches on keys such as `:event_source_id` keep working). Besides the position
(`:sequence_number`, `:event_source_id`, `:occurred`) it carries `:caused_by` - the identity
that caused the event - the `:causation` chain, `:correlation_id`, `:tags`, `:hash`,
`:subject`, the event type and stream, and the `:observation_state`. A reducer can therefore
record *who* caused a change without the author being copied into every event payload.

## Replay lifecycle (optional)

Implement any of `on_replay_begin/0`, `on_replay_end/0`,
`on_partition_replay_begin/1`, `on_partition_replay_end/1` to be notified when
Chronicle starts or finishes replaying this reducer — either as a whole, or
for a single partition (event source). All four are optional; implement only
the ones you need. These are notifications, not events to reduce — they run
outside the normal `reduce/3` dispatch, and a raised exception is logged and
swallowed rather than reported to Chronicle.

# `on_partition_replay_begin`
*optional* 

```elixir
@callback on_partition_replay_begin(partition :: String.t()) :: any()
```

Called when replay of a single partition (event source) begins.

# `on_partition_replay_end`
*optional* 

```elixir
@callback on_partition_replay_end(partition :: String.t()) :: any()
```

Called when replay of a single partition (event source) ends.

# `on_replay_begin`
*optional* 

```elixir
@callback on_replay_begin() :: any()
```

Called when a full replay of this reducer begins.

# `on_replay_end`
*optional* 

```elixir
@callback on_replay_end() :: any()
```

Called when a full replay of this reducer ends.

# `reduce`

```elixir
@callback reduce(event :: struct(), model :: struct() | nil, context :: map()) :: struct()
```

Applies an event to the current read model state.

Receives the event struct, the current model (or `nil` on first event), and
a context map. Returns the updated read model struct.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
