# `Runic.Runner.Store`
[🔗](https://github.com/zblanco/runic/blob/main/lib/runic/runner/store.ex#L1)

Behaviour for workflow persistence adapters.

Adapters handle saving and loading workflow event logs for
durability across process restarts.

## Stream Semantics (Event-Sourced)

The preferred interface uses `append/3` and `stream/2` for incremental
event persistence. Events are appended after each execution cycle and
streamed on recovery to rebuild workflow state via `Workflow.from_events/1`.

Stores that implement `append/3` and `stream/2` get automatic
event-sourced checkpointing and recovery from the Worker.

## Legacy Semantics (Snapshot)

The `save/3` and `load/2` callbacks persist the full workflow log as a
snapshot. These remain the required baseline interface for backward
compatibility. Stores that only implement `save/load` continue to work
unchanged.

## Optional Capabilities

- **Snapshots** (`save_snapshot/4`, `load_snapshot/3`): Point-in-time
  workflow snapshots for faster recovery (replay from snapshot + events
  after cursor instead of full replay).
- **Fact storage** (`save_fact/3`, `load_fact/2`): Content-addressed fact
  value storage for hybrid rehydration without loading all values into memory.

# `cursor`

```elixir
@type cursor() :: non_neg_integer()
```

# `event`

```elixir
@type event() :: struct()
```

# `log`

```elixir
@type log() :: [struct()]
```

# `state`

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

# `workflow_id`

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

# `append`
*optional* 

```elixir
@callback append(workflow_id(), events :: [event()], state()) ::
  {:ok, cursor()} | {:error, term()}
```

# `checkpoint`
*optional* 

```elixir
@callback checkpoint(workflow_id(), log(), state()) :: :ok | {:error, term()}
```

# `delete`
*optional* 

```elixir
@callback delete(workflow_id(), state()) :: :ok | {:error, term()}
```

# `exists?`
*optional* 

```elixir
@callback exists?(workflow_id(), state()) :: boolean()
```

# `init_store`

```elixir
@callback init_store(opts :: keyword()) :: {:ok, state()} | {:error, term()}
```

# `list`
*optional* 

```elixir
@callback list(state()) :: {:ok, [workflow_id()]} | {:error, term()}
```

# `load`

```elixir
@callback load(workflow_id(), state()) :: {:ok, log()} | {:error, :not_found | term()}
```

# `load_fact`
*optional* 

```elixir
@callback load_fact(fact_hash :: term(), state()) ::
  {:ok, term()} | {:error, :not_found | term()}
```

# `load_snapshot`
*optional* 

```elixir
@callback load_snapshot(workflow_id(), state()) ::
  {:ok, {cursor(), binary()}} | {:error, :not_found | term()}
```

# `save`

```elixir
@callback save(workflow_id(), log(), state()) :: :ok | {:error, term()}
```

# `save_fact`
*optional* 

```elixir
@callback save_fact(fact_hash :: term(), value :: term(), state()) ::
  :ok | {:error, term()}
```

# `save_snapshot`
*optional* 

```elixir
@callback save_snapshot(workflow_id(), cursor(), snapshot :: binary(), state()) ::
  :ok | {:error, term()}
```

# `stream`
*optional* 

```elixir
@callback stream(workflow_id(), state()) ::
  {:ok, Enumerable.t()} | {:error, :not_found | term()}
```

# `supports_stream?`

```elixir
@spec supports_stream?(module()) :: boolean()
```

Returns true if the store module supports event-sourced stream semantics.

---

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