# `Chronicle.ReadModels`
[🔗](https://github.com/Cratis/Chronicle.Elixir/blob/main/lib/chronicle/read_models.ex#L4)

Idiomatic Elixir interface for Chronicle read models.

Read models are the query side of Chronicle. They are produced by projections
and reducers and can be queried in two complementary ways:

- `get/3`, `get_instance_by_id/3`, `all/2`, and `get_instances/2` replay the
  configured event sequence in the same style as the C# and TypeScript
  `IReadModels` APIs.
- `query/2` queries the materialized read-model container directly for paging
  and occurrence-oriented inspection.

The module also exposes helper queries for metadata and history:

- `snapshots/3` / `get_snapshots_by_id/3`
- `occurrences/2` / `get_occurrences/2`
- `definitions/1` / `get_definitions/1`

## Common options

Most functions accept these options:

- `:client` — the client name (default: `Chronicle.Client`)
- `:namespace` — overrides the client's configured namespace
- `:event_sequence_id` — event sequence identifier (default: `"event-log"`)

## Single instance lookup

    {:ok, account} = Chronicle.ReadModels.get(MyApp.ReadModels.Account, "account-1")
    {:ok, account} = Chronicle.ReadModels.get_instance_by_id(MyApp.ReadModels.Account, "account-1")

Use `:session_id` when reading from a dehydrated or session-specific projection:

    {:ok, account} =
      Chronicle.ReadModels.get_instance_by_id(
        MyApp.ReadModels.Account,
        "account-1",
        session_id: "session-42"
      )

## Replay all instances

    {:ok, accounts} = Chronicle.ReadModels.all(MyApp.ReadModels.Account)
    {:ok, accounts} = Chronicle.ReadModels.get_instances(MyApp.ReadModels.Account, event_count: 500)

## Query the materialized container

    {:ok, result} = Chronicle.ReadModels.query(MyApp.ReadModels.Account, page: 1, page_size: 25)

    result.instances
    result.total_count

## Query metadata and history

    {:ok, snapshots} = Chronicle.ReadModels.snapshots(MyApp.ReadModels.Account, "account-1")
    {:ok, occurrences} = Chronicle.ReadModels.occurrences(MyApp.ReadModels.Account)
    {:ok, definitions} = Chronicle.ReadModels.definitions()

# `all`

```elixir
@spec all(module(), keyword()) :: {:ok, [struct()]} | {:error, term()}
```

Replays and returns all instances for a read model.

This is the short alias for `get_instances/2`.

## Options

  * `:client` — the client name (default: `Chronicle.Client`)
  * `:namespace` — overrides the client's default namespace
  * `:event_sequence_id` — event sequence identifier (default: `"event-log"`)
  * `:event_count` — maximum number of events to process (default: unlimited)

# `definitions`

```elixir
@spec definitions(keyword()) ::
  {:ok, [Chronicle.ReadModels.Definition.t()]} | {:error, term()}
```

Returns the registered read-model definitions for the current event store.

This is useful for tooling, diagnostics, and documentation scenarios where you
want to inspect the schema and owning observer for each read model.

## Options

  * `:client` — the client name (default: `Chronicle.Client`)

# `dehydrate_session`

```elixir
@spec dehydrate_session(module(), String.t(), String.t(), keyword()) ::
  :ok | {:error, term()}
```

Explicitly cleans up a materialized read-model session.

Sessions are created implicitly when reading with a `:session_id` (see
`get_instance_by_id/3`); call this once a session's caller is done with it to
release the resources Chronicle held for it, rather than waiting for it to
expire on its own.

## Options

  * `:client` — the client name (default: `Chronicle.Client`)
  * `:namespace` — overrides the client's default namespace
  * `:event_sequence_id` — event sequence identifier (default: `"event-log"`)

# `get`

```elixir
@spec get(module(), String.t(), keyword()) :: {:ok, struct() | nil} | {:error, term()}
```

Fetches a read model instance by key.

This is the short alias for `get_instance_by_id/3`.

## Options

  * `:client` — the client name (default: `Chronicle.Client`)
  * `:namespace` — overrides the client's default namespace
  * `:event_sequence_id` — event sequence identifier (default: `"event-log"`)
  * `:session_id` — optional read-model session identifier

Returns `{:ok, model_struct}` on success, or `{:ok, nil}` if no instance was found.

# `get_definitions`

```elixir
@spec get_definitions(keyword()) ::
  {:ok, [Chronicle.ReadModels.Definition.t()]} | {:error, term()}
```

Returns the registered read-model definitions for the current event store.

# `get_instance_by_id`

```elixir
@spec get_instance_by_id(module(), String.t(), keyword()) ::
  {:ok, struct() | nil} | {:error, term()}
```

Fetches a read model instance by key.

Mirrors the `getInstanceById()` naming used by the Chronicle C# and TypeScript
clients while returning idiomatic Elixir tuples.

Accepts the options of `get/3`, plus `:event_source_type`, `:event_stream_type`,
`:event_stream_id` and `:event_source_id` to narrow the lookup to a specific event stream
instead of the whole event source (used internally by `Chronicle.ReadModels.DecisionReads`).

# `get_instances`

```elixir
@spec get_instances(module(), keyword()) :: {:ok, [struct()]} | {:error, term()}
```

Replays and returns all instances for a read model.

This matches the Chronicle `getInstances()` naming used in the C# and
TypeScript clients.

# `get_occurrences`

```elixir
@spec get_occurrences(module(), keyword()) ::
  {:ok, [Chronicle.ReadModels.Occurrence.t()]} | {:error, term()}
```

Returns replay occurrences for a read model.

# `get_snapshots_by_id`

```elixir
@spec get_snapshots_by_id(module(), String.t(), keyword()) ::
  {:ok, [Chronicle.ReadModels.Snapshot.t()]} | {:error, term()}
```

Returns historical snapshots for a read model instance.

This mirrors the `getSnapshotsById()` naming from the Chronicle C# and
TypeScript clients.

# `observe`

```elixir
@spec observe(module(), keyword()) :: {:ok, pid()} | {:error, term()}
```

Observes a collection of read model instances: the calling process receives the page of
instances now, and again every time it changes. Mirrors .NET's `ObserveInstances<T>()`.

Messages (`result` is a `Chronicle.ReadModels.QueryResult`):

    {:chronicle_read_model_instances, MyApp.ReadModels.AccountInfo, result}
    {:chronicle_read_model_observe_error, MyApp.ReadModels.AccountInfo, reason}

After an error message the observation has ended; call `observe/2` again to resume. Stop it
with `unobserve/1`. The observer also ends when the subscribing process exits.

## Options

  * `:client` - the client name (default: `Chronicle.Client`)
  * `:namespace` - overrides the client's default namespace
  * `:occurrence` - observe a specific occurrence of a replayed read model
  * `:page` / `:page_size` - the page to observe (defaults `0` / `50`)
  * `:filter` - keeps only matching instances in each delivered page; same shape as `query/2`'s
    `:filter`. An unknown field returns `{:error, {:unknown_field, field}}`.
  * `:sort` - sorts the instances in each delivered page; same shape as `query/2`'s `:sort`.

# `observe_stream`

```elixir
@spec observe_stream(module(), keyword()) :: Enumerable.t()
```

Returns a `Stream` of `Chronicle.ReadModels.QueryResult` built on `observe/2`: the current page,
and again every time it changes. Ends the underlying observation when the stream is done
(including when the consuming process stops iterating it), and raises
`Chronicle.ReadModels.ObserveError` if the kernel stream fails.

Accepts the same options as `observe/2`.

    MyApp.ReadModels.AccountInfo
    |> Chronicle.ReadModels.observe_stream(page_size: 25)
    |> Enum.each(fn result -> IO.inspect(result.instances) end)

# `occurrences`

```elixir
@spec occurrences(module(), keyword()) ::
  {:ok, [Chronicle.ReadModels.Occurrence.t()]} | {:error, term()}
```

Returns replay occurrences for a read model.

Occurrences represent replayed or rebuilt versions of a read model and are
useful when inspecting rebuilds or alternative containers.

## Options

  * `:client` — the client name (default: `Chronicle.Client`)
  * `:namespace` — overrides the client's default namespace

# `query`

```elixir
@spec query(module(), keyword()) ::
  {:ok, Chronicle.ReadModels.QueryResult.t()} | {:error, term()}
```

Queries the materialized read-model container directly.

This complements the replay-oriented `all/2` and `get_instances/2` helpers.
Use it when you need paging or want to inspect a specific replay occurrence.

## Options

  * `:client` — the client name (default: `Chronicle.Client`)
  * `:namespace` — overrides the client's default namespace
  * `:occurrence` — optional occurrence/container name for a replayed read model
  * `:page` — zero-based page number (default: `0`, the first page)
  * `:page_size` — page size (default: `50`)
  * `:filter` — keeps only matching instances: a keyword list of `field: condition` pairs, all
    of which must match. A condition is a value (equality) or one of `{:gt, v}`, `{:gte, v}`,
    `{:lt, v}`, `{:lte, v}`, `{:ne, v}`, `{:in, [v]}` or `{:contains, v}` (substring of a
    string, member of a list). An unknown field returns `{:error, {:unknown_field, field}}`.
  * `:sort` — `field` or `{field, :asc | :desc}`; applied with `:filter` (see below)
  * `:max_scan` — with `:filter`/`:sort`, the most instances to examine (default: `10_000`)

## Filtering is applied by the client

The Chronicle query contract pages materialized instances but cannot filter or sort them, so
`:filter` and `:sort` page through the read model and evaluate in the client. `total_count`
then counts the *matching* instances. That is correct and convenient for read models of
modest size; a read model larger than `:max_scan` returns
`{:error, {:scan_limit_exceeded, max_scan}}` rather than a silently partial answer. Use
`:occurrence`/paging without a filter, or a purpose-built projection, for large data.

    Chronicle.ReadModels.query(MyApp.ReadModels.Entry,
      filter: [schema_id: "invoice", amount: {:gt, 100}],
      sort: {:amount, :desc},
      page_size: 20
    )

# `snapshots`

```elixir
@spec snapshots(module(), String.t(), keyword()) ::
  {:ok, [Chronicle.ReadModels.Snapshot.t()]} | {:error, term()}
```

Returns historical snapshots for a read model instance.

Snapshots group the projected state together with the event batch that caused
it, making this useful for troubleshooting projections and reducers.

## Options

  * `:client` — the client name (default: `Chronicle.Client`)
  * `:namespace` — overrides the client's default namespace
  * `:event_sequence_id` — event sequence identifier (default: `"event-log"`)

# `subscribe`

```elixir
@spec subscribe(module(), keyword()) :: {:ok, pid()} | {:error, term()}
```

Subscribes the calling process to a **shared** watch for a read model, established once per
`{namespace, read model, event sequence}` and reused by every subscriber of that key, instead
of opening a new kernel stream per call the way `watch/2` does.

Sends the calling process the same messages as `watch/2`
(`{:chronicle_read_model_changed, model_module, changeset}`), plus two the shared watcher adds:

  * `{:chronicle_read_model_watch_ended, model_module, reason}` — the shared stream failed; every
    subscriber gets this once, and the watcher reconnects on its own with exponential backoff
    (500 ms up to 30 s). Changes that happen while the stream is down are not replayed.
  * `{:chronicle_read_model_watch_resumed, model_module}` — the stream reconnected.

Returns `{:ok, watcher}` — pass it to `unsubscribe/1` to stop. The subscription also ends, and the
stream closes once no subscriber is left, when the subscribing process exits.

## Options

  * `:client` — the client name (default: `Chronicle.Client`)
  * `:namespace` — overrides the client's default namespace
  * `:event_sequence_id` — event sequence identifier (default: `"event-log"`)

# `unobserve`

```elixir
@spec unobserve(pid()) :: :ok
```

Stops an observation started by `observe/2`.

# `unsubscribe`

```elixir
@spec unsubscribe(pid()) :: :ok
```

Stops a subscription started by `subscribe/2`.

# `unwatch`

```elixir
@spec unwatch(pid()) :: :ok
```

Stops a subscription started by `watch/2`.

# `watch`

```elixir
@spec watch(module(), keyword()) :: {:ok, pid()} | {:error, term()}
```

Watches a read model for live changes.

Mirrors the C# and TypeScript clients' `Watch<TReadModel>()`, which returns an
`IObservable`/async-iterable of live changesets. Elixir has no equivalent
built-in, so this follows the same message-based subscription idiom as
`Chronicle.Connections.Lifecycle.subscribe/1`: the calling process receives a
`{:chronicle_read_model_changed, model_module, %Chronicle.ReadModels.Changeset{}}`
message for every change (added/modified/removed, for any instance of the
read model), until `unwatch/1` is called.

On a stream failure, the calling process receives
`{:chronicle_read_model_watch_error, model_module, reason}` and the watch ends;
call `watch/2` again to resume.

Returns `{:ok, watcher}`, where `watcher` identifies the background watch
process — pass it to `unwatch/1` to stop.

## Options

  * `:client` — the client name (default: `Chronicle.Client`)
  * `:namespace` — overrides the client's default namespace
  * `:event_sequence_id` — event sequence identifier (default: `"event-log"`)

---

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