# `Sourced.Projection`
[🔗](https://gitlab.com/dimakula/sourced/-/blob/0.2.0/lib/sourced/projection.ex#L1)

A reducer over stored events to build the current state.

A projection is built from an `:initial_state`, a map of `:handlers` keyed by
event type, and the `:tags` for filtering on events:

```elixir
  is_cancelled_projection =
    Sourced.Projection.new(
      initial_state: false,
      tags: ["order:1"],
      handlers: %{
        OrderCancelled => fn _state, _event -> true end,
        OrderReopened => fn _state, _event -> false end
      }
    )
```

The handlers declare the event types, so the query falls out of the
definition: the projection above carries
`[%{types: [OrderCancelled, OrderReopened], tags: ["order:1"]}]`. Handlers
take the state and the `Sourced.StoredEvent`, and must be pure.

`Sourced.DecisionModel` pairs a projection with the store, folding it into a
state and the append condition that keeps it valid. See
[dcb.events](https://dcb.events/topics/projections) for the model itself.

## Named handlers

Anything varying per decision, such as the id in a tag, belongs in a function
that returns a projection. Handlers can be captures of named functions, which
keeps the transitions individually testable:

```elixir
  defmodule MyApp.Orders do
    alias Sourced.Projection

    def shipped?(order_id) do
      Projection.new(
        initial_state: false,
        tags: ["order:#{order_id}"],
        handlers: %{
          OrderShipped => &shipped/2,
          OrderCancelled => &cancelled/2
        }
      )
    end

    defp shipped(_state, _event), do: true
    defp cancelled(_state, _event), do: false
  end
```

A projection outliving a code reload — one driving a read model from a
long-lived process — wants `&__MODULE__.shipped/2` instead: a local capture
is pinned to the module version that defined it and raises
`BadFunctionError` once that version is purged.

## Composing

Rather than growing one projection until it answers every question a decision
asks, write a projection per question and `compose/1` them. The composed
projection folds to a map keyed the same way, and queries the union of its
parts:

    Sourced.Projection.compose(%{
      placed: MyApp.Orders.placed?(1),
      total: MyApp.Orders.total(1)
    })

Each part is only fed the events its own query matches, so a part tagged
`"order:1"` never sees an event that arrived because a sibling asked for
`"customer:42"`. The result is a projection, so composites nest.

## Event types

Handler keys are matched against `event.type` as the store returns it: the
event *module* under a `Sourced.Middleware.Domain`, the stored string
without one, and the stored string either way for an event type the domain
does not register.

A key matching none of those never fires, and nothing raises — worth checking
first when a projection's state never moves.

# `handler`

```elixir
@type handler() :: (state(), Sourced.StoredEvent.t() -&gt; state())
```

Folds one event into the state.

# `handlers`

```elixir
@type handlers() :: %{required(Sourced.EventStore.Query.type()) =&gt; handler()}
```

Handlers keyed by the event type they fold.

# `key`

```elixir
@type key() :: term()
```

The key a composed projection files a part's state under.

# `opts`

```elixir
@type opts() :: [
  handlers: handlers(),
  initial_state: state(),
  tags: [Sourced.StoredEvent.tag()]
]
```

# `state`

```elixir
@type state() :: term()
```

Whatever a projection folds to. Opaque to the library.

# `t`

```elixir
@type t() :: %Sourced.Projection{
  handlers: handlers(),
  initial_state: state(),
  query: Sourced.EventStore.Query.t()
}
```

# `apply`

```elixir
@spec apply(t(), state(), Sourced.StoredEvent.t()) :: state()
```

Folds one event into `state`.

An event the projection has no handler for leaves the state untouched.

# `compose`

```elixir
@spec compose(%{optional(key()) =&gt; t()}) :: t()
```

Combines `projections` into one that folds to a map keyed the same way.

The composed query is the union of the parts', and each part is only fed the
events its own query matches.

# `new`

```elixir
@spec new(opts()) :: t()
```

Builds a projection.

## Options

  * `:handlers` — *required* the state transitions, keyed by event type.
    Their keys become the query's `types`.
  * `:initial_state` — the state before any event has been folded in.
    Defaults to `nil`.
  * `:tags` — restricts the projection to events carrying **all** of these
    tags. Defaults to `[]`, matching every event of its types.

# `project`

```elixir
@spec project(t(), [Sourced.StoredEvent.t()]) :: state()
```

Folds `events` into the projection's `:initial_state`.

# `project`

```elixir
@spec project(t(), state(), [Sourced.StoredEvent.t()]) :: state()
```

Folds `events` into `state`, which a read model carries across the batches a
subscription delivers.

---

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