# `Hume.Projection`
[🔗](https://github.com/zen-en-tonal/hume/blob/main/lib/hume/projection.ex#L1)

Behaviour and macros for defining event-sourced state machines.

This module provides a behaviour that can be implemented by modules to define
event-sourced state machines. It also includes macros to automatically generate
boilerplate code for managing state, handling events, taking snapshots, and
persisting events.

## Usage
To use this module, define a new module and use `Hume.Projection` with the
desired options. Implement the required callbacks to define the initial state,
event handling logic, and snapshot management.

## Options
When using `Hume.Projection`, you can provide the following options:
  - `:store` (required): The module implementing the event store behaviour.
  - `:use_ets`: Use ETS for snapshot storage (default is false).
  - `:snapshot_every`: Number of events after which to take a snapshot (default is 100).
  - `:snapshot_after`: Time in milliseconds after which to take a snapshot (default is 30 seconds).
  - `:catch_up_after`: Time in milliseconds after which to check for new events (default is 30 seconds).
  - `:strict_online`: Whether to enforce strict event handling during online catch-up (default is true).

## Callbacks
The following callbacks must be implemented by the module using `Hume.Projection`:
  - `init_state/1`: Initializes the state of the machine.
  - `handle_event/2`: Handles an event and updates the state.
  - `last_snapshot/1`: Retrieves the last snapshot of the machine.
  - `persist_snapshot/2`: Persists a snapshot of the current state.

## Example
    defmodule MyProjection do
      use Hume.Projection, use_ets: true, store: MyEventStore

      @impl true
      def init_state(_), do: %{}

      @impl true
      def handle_event({:add, key, value}, state),
        do: {:ok, Map.put(state || %{}, key, value)}

      @impl true
      def handle_event({:remove, key}, state),
        do: {:ok, Map.delete(state || %{}, key)}
    end

## Telemetry
This module emits telemetry events for various operations, including:
  - `[:hume, :projection, :init]`
  - `[:hume, :projection, :catch_up, :start]`
  - `[:hume, :projection, :catch_up, :stop]`
  - `[:hume, :projection, :snapshot]`
  - `[:hume, :projection, :evolve]`
  - `[:hume, :projection, :replay]`
  - `[:hume, :projection, :on_caught_up]`

## Shutdown
On `:normal` termination, the projection will automatically persist its current snapshot.

# `event`

```elixir
@type event() :: {seq(), term()}
```

# `macro_option`

```elixir
@type macro_option() ::
  {:use_ets, boolean()}
  | {:store, module()}
  | {:snapshot_every, non_neg_integer()}
  | {:snapshot_after, non_neg_integer()}
  | {:catch_up_after, non_neg_integer()}
  | {:strict_online, boolean()}
```

# `offset`

```elixir
@type offset() :: seq()
```

# `option`

```elixir
@type option() ::
  GenServer.option()
  | {:stream, stream() | [stream()]}
  | {:projection, projection()}
```

# `projection`

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

# `seq`

```elixir
@type seq() :: integer()
```

# `snapshot`

```elixir
@type snapshot() :: {offset(), state() | nil}
```

# `state`

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

# `stream`

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

# `handle_event`

```elixir
@callback handle_event(event :: term(), state()) :: {:ok, state()} | {:error, term()}
```

Handles an event and updates the state accordingly.

## Parameters
  - event: The event to be handled.
  - state: The current state of the machine.

## Returns
  - `{:ok, new_state}` if the event was handled successfully, where `new_state` is the updated state.
  - `{:error, reason}` if an error occurred while handling the event.

# `init_state`

```elixir
@callback init_state(projection()) :: state()
```

Initial state of the machine.

## Parameters
  - projection: The name or identifier of the machine.

## Returns
  - The initial state of the machine.

# `last_snapshot`

```elixir
@callback last_snapshot(projection()) :: snapshot() | nil
```

Retrieves the last snapshot of the machine.

## Returns
  - `snapshot` if a snapshot exists, where `snapshot` is a tuple containing the offset and state.
  - `nil` if no snapshot exists.

# `on_caught_up`
*optional* 

```elixir
@callback on_caught_up(snapshot()) :: :ok
```

Callback invoked when the projection has caught up with the event store.

This can be used to perform any actions needed after catching up, such as
notifying other parts of the system or updating internal state.
## Parameters
  - snapshot: The current snapshot of the projection after catching up.
## Returns
  - `:ok`

# `on_init`
*optional* 

```elixir
@callback on_init(projection()) :: :ok
```

Callback invoked when the projection process is initialized.
This is optional and can be used to perform any setup required.

## Parameters
  - projection: The name or identifier of the machine.

## Returns
  - `:ok`

# `persist_snapshot`

```elixir
@callback persist_snapshot(projection(), snapshot()) :: :ok | {:error, term()}
```

Persists a snapshot of the current state.

## Parameters
  - snapshot: A tuple containing the offset and the current state.

## Returns
  - `:ok` if the snapshot was taken successfully.
  - `{:error, reason}` if an error occurred while taking the snapshot.

# `catch_up`

```elixir
@spec catch_up(GenServer.server()) :: :ok
```

Requests the state machine to catch up by processing any new events.

## Parameters
  - server: The PID or name of the state machine process.

## Returns
  - `:ok` The catch-up request has been sent.

# `catch_up_sync`

```elixir
@spec catch_up_sync(GenServer.server(), timeout()) :: snapshot()
```

Synchronously requests the state machine to catch up by processing any new events.

## Parameters
  - `server`: The PID or name of the state machine process.
  - `timeout`: The maximum time to wait for a response (default is 5000 ms).
## Returns
  - `snapshot`: The current snapshot of the state machine after catching up.

# `evolve`

Evolves the state by applying a single event.

## Parameters
  - mod: The module implementing the `Hume.Projection` behaviour.
  - event: The event to be applied.
  - snapshot: A tuple containing the current offset and state.

## Returns
  - `{:ok, snapshot}` if the event is applied successfully.
  - `{:error, reason}` if an error occurs during event handling or persistence.

# `replay`

Replays a list of events starting from a given snapshot.

## Parameters
  - mod: The module implementing the `Hume.Projection` behaviour.
  - snapshot: A tuple containing the offset and the state to start replaying from.
  - events: A ordered list of events to be replayed.

## Returns
  - `{:ok, snapshot}` if all events are replayed successfully.
  - `{:error, reason}` if an error occurs during event handling.

# `snapshot`

```elixir
@spec snapshot(GenServer.server(), [{:timeout, timeout()} | :dirty]) :: snapshot()
```

Retrieves the current snapshot of the state machine.

## Parameters
  - machine: The PID or name of the state machine process.
  - opts: Options for the call (default is an empty list).
    - `:timeout`: The maximum time to wait for a response (default is 5000 ms).
    - `:dirty`: If specified, allows a dirty read of the snapshot.
## Returns
  - `snapshot`: The current snapshot of the state machine.

# `start`

```elixir
@spec start(module(), [option()]) :: {:ok, pid()} | {:error, term()}
```

# `start_link`

```elixir
@spec start_link(module(), [option()]) :: {:ok, pid()} | {:error, term()}
```

# `state`

```elixir
@spec state(GenServer.server(), [{:timeout, timeout()} | :dirty]) :: state()
```

Retrieves the current state of the state machine.

## Parameters
  - machine: The PID or name of the state machine process.
  - opts: Options for the call (default is an empty list).
    - `:timeout`: The maximum time to wait for a response (default is 5000 ms).
    - `:dirty`: If specified, allows a dirty read of the snapshot.

## Returns
  - `state`: The current state of the state machine.

# `store`

```elixir
@spec store(module()) :: module()
```

Retrieves the event store module used by the projection.

## Parameters
  - mod: The module implementing the `Hume.Projection` behaviour.

## Returns
  - `store`: The event store module.

# `take_snapshot`

```elixir
@spec take_snapshot(GenServer.server()) :: :ok
```

Requests the state machine to take a snapshot of its current state.

## Parameters
  - server: The PID or name of the state machine process.

## Returns
  - `:ok`: The snapshot request has been sent.

# `validate`

```elixir
@spec validate(module()) :: :ok | {:error, :invalid_module}
```

Validates that the given module implements the `Hume.Projection` behaviour.

## Parameters
  - mod: The module to be validated.

## Returns
  - `:ok` if the module implements the `Hume.Projection` behaviour.
  - `{:error, reason}` if the module does not implement the behaviour.

---

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