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

Macro for defining Chronicle read models with embedded model-bound projections.

Use `Chronicle.ReadModels.ReadModel` in a struct module to define both the read model
shape and how Chronicle should project events into it. The projection
definition lives right next to the struct fields — no separate projection
module is needed.

## Quick Example

    defmodule MyApp.ReadModels.Account do
      use Chronicle.ReadModels.ReadModel

      defstruct account_id: nil, owner_name: nil, balance: 0, transaction_count: 0

      from MyApp.Events.AccountOpened,
        set: [
          account_id: :event_source_id,
          owner_name: :owner_name,
          balance: :initial_balance
        ]

      from MyApp.Events.FundsDeposited,
        add: [balance: :amount, transaction_count: 1]

      from MyApp.Events.FundsWithdrawn,
        subtract: [balance: :amount],
        add: [transaction_count: 1]
    end

## Projection Macros

### `from/2`

Maps properties from an event onto the read model.

    from MyApp.Events.AccountOpened,
      set: [account_id: :event_source_id, owner_name: :owner_name],
      count: :transaction_count

Options:
  * `:key` — key expression identifying the model instance.
    Defaults to `"$eventSourceId"` when omitted. Use `{:composite, parts}` for an
    ordered list of key-part mappings, and `{:event_context, "Occurred.Week"}`
    to map a key part to an event-context property path.
  * `:parent_key` — parent key for nested models
  * `:set` — keyword list of `field: expression` pairs to set directly
  * `:add` — keyword list of `field: expression` pairs to add to
  * `:subtract` — keyword list of `field: expression` pairs to subtract from
  * `:count` — field atom to increment by 1 on each event occurrence

### `join/2`

Joins a secondary event onto the model by a matching field.

    join MyApp.Events.AccountVerified,
      on: "accountId",
      set: [verified: true]

Options:
  * `:on` — **(required)** the field name in the event to join on
  * `:key` — key expression (defaults to `"$eventSourceId"`)
  * `:set`, `:add`, `:subtract` — property mappings

### `removed_with/2`

Removes the model instance when the given event occurs.

    removed_with MyApp.Events.AccountClosed,
      []

Options:
  * `:key` — key expression (defaults to `"$eventSourceId"`)
  * `:parent_key` — parent key for nested models

### `removed_with_join/2`

Removes the model instance when a secondary event — from a different event source — matches it
by a property instead of by the model's own key. Mirrors the .NET client's
`RemovedWithJoin<TEvent>` attribute.

    removed_with_join MyApp.Events.AccountFlaggedForClosure,
      key: :account_id

Options:
  * `:key` — the property on the event that identifies the instance to remove.
    Defaults to `"$eventSourceId"`.

### `clear_with/2`

Sets a field back to no value when the given event occurs — sugar for
`from event, clear: [...]`.

    clear_with MyApp.Events.DiscountRemoved, [:discount_code]

Equivalent to:

    from MyApp.Events.DiscountRemoved, clear: [:discount_code]

Options:
  * `:key`, `:parent_key` — same as `from/2`

### `from_every/1`

Applies property mappings on every event, regardless of type.

    from_every set: [last_activity: "Occurred"]

For dictionary properties keyed by event context values:

    from_every increment: [event_counts: {:event_context, :type}]

This increments a map field `event_counts` with keys derived from the event type
identifier, producing one counter per distinct event type.

Options:
  * `:set`, `:add`, `:subtract` — property mappings applied to every event
  * `:count` — field atom to increment by 1 on each event occurrence
  * `:increment` — keyword list for incrementing fields; use `{field: {:event_context, :property}}`
    for dictionary fields keyed by event context values
  * `:decrement` — keyword list for decrementing fields; use `{field: {:event_context, :property}}`
    for dictionary fields keyed by event context values

## Composite Keys

Key parts use the same event-field atoms, explicit expression strings, and
literals as other keys. Target names are preserved and parts are emitted in
list order. A composite key requires a non-empty list of `{name, part}` pairs
with string or atom names; nested composite keys are not supported.
Event-context paths must be strings containing only letters, dots and
parentheses, matching the kernel's accepted characters. Invalid keys raise
`ArgumentError` identifying the read model and offending part.
Paths are passed to the kernel unchanged, without adding method parentheses:

    from MyApp.Events.AccountOpened,
      key: {:composite, [
        {"Year", {:event_context, "Occurred.Year"}},
        {"Week", {:event_context, "Occurred.Week"}}
      ]},
      count: :transaction_count

This emits
`$composite(Year=$eventContext(Occurred.Year),Week=$eventContext(Occurred.Week))`,
matching the .NET client. The kernel evaluates the date parts; the client does
not calculate them. `Occurred.Week` is the ISO 8601 week number (1–53): weeks
start on Monday and week 1 contains the first Thursday.

`Occurred.Year` is the calendar year, not the ISO week-based year. Pairing it
with `Occurred.Week` can split a week around New Year. A week-based-year
accessor is tracked in https://github.com/Cratis/Chronicle/issues/4505.

These key options also work in `Chronicle.Projections.Projection` declarations.

## Property Expressions

Values in `set:`, `add:`, and `subtract:` lists can be atoms, strings, or
literals:

- Use atoms for event fields (preferred): `:owner_name`, `:amount`
- Use `:event_source_id` and `:occurred` for built-in context values
- Use strings for advanced Chronicle expressions when needed
- Use numbers and booleans as literal values

| Expression | Meaning |
|-----------|---------|
| `:owner_name` | The `owner_name` field from the event |
| `:event_source_id` | The event source identifier |
| `:occurred` | When the event was recorded |
| `1` | A literal integer constant |
| `"$add(amount, balance)"` | An explicit Chronicle expression |

## Registering with Chronicle.Client

    {Chronicle.Client,
      ...
      read_models: [MyApp.ReadModels.Account]}

## Introspection

    MyApp.ReadModels.Account.__chronicle_read_model__(:id)
    MyApp.ReadModels.Account.__chronicle_read_model__(:from)
    MyApp.ReadModels.Account.__chronicle_read_model__(:join)
    MyApp.ReadModels.Account.__chronicle_read_model__(:removed_with)
    MyApp.ReadModels.Account.__chronicle_read_model__(:from_every)
    MyApp.ReadModels.Account.__chronicle_read_model__(:has_projection?)
    MyApp.ReadModels.Account.__chronicle_read_model__(:passive?)

## Passive Read Models

Pass `passive: true` to mark a read model as passive. A passive projection does not
actively observe events and never writes to a materialized sink — its instances are
resolved on demand via immediate projection when looked up by id. This is useful for
read models that only need to be queried by key (for example, from within a reactor).

    defmodule MyApp.ReadModels.Application do
      use Chronicle.ReadModels.ReadModel, passive: true

      defstruct id: nil, name: nil

      from MyApp.Events.ApplicationCreated,
        set: [id: :event_source_id, name: :name]
    end

# `clear_with`
*macro* 

Sets a field back to no value when the given event occurs. Sugar for `from event, clear: [...]`.

See the `Chronicle.ReadModels.ReadModel` module documentation for full options.

# `enters_on`
*macro* 

Names an event that may create or resurrect this read model variant. Repeatable — a variant
may enter on more than one event. Every other event this variant projects from (whether
declared with `from/2` directly, or merged in from a `Chronicle.Projections.GlobalHandler`) is
automatically reclassified into an update-only join: it can bring an already-active instance
up to date, but it can never create or resurrect one.

    enters_on MyApp.Events.IssueCreated

Options:
  * `:key` — event property used as the key for this entering event. Defaults to
    `:event_source_id`.

# `from`
*macro* 

Declares how an event maps onto this read model.

See the `Chronicle.ReadModels.ReadModel` module documentation for full options.

# `from_every`
*macro* 

Applies property mappings on every event, regardless of type.

See the `Chronicle.ReadModels.ReadModel` module documentation for full options.

# `join`
*macro* 

Declares a join from a secondary event onto this read model.

See the `Chronicle.ReadModels.ReadModel` module documentation for full options.

# `no_auto_map`
*macro* 

Disables Chronicle's automatic mapping of matching event property names onto this read model.

Called with no arguments it turns AutoMap off for the whole projection, so every property has
to be mapped explicitly. Called with a list of fields it leaves AutoMap on but excludes just
those fields, which is what you want when a single property is set from something other than
the event property that happens to share its name.

    no_auto_map()
    no_auto_map([:balance, :status])

Without either, AutoMap is left at the kernel's default and cannot be turned off.

# `no_auto_map`
*macro* 

Excludes specific fields from Chronicle's automatic property mapping.

See `no_auto_map/0` for the whole-projection form.

# `removed_with`
*macro* 

Declares that this model is removed when the given event occurs.

See the `Chronicle.ReadModels.ReadModel` module documentation for full options.

# `removed_with_join`
*macro* 

Declares that this model is removed when a secondary event matches it by a property, instead
of by its own key.

See the `Chronicle.ReadModels.ReadModel` module documentation for full options.

# `resolve_no_auto_map`

```elixir
@spec resolve_no_auto_map([:all | [atom()]]) ::
  {:inherit | :enabled | :disabled, [atom()]}
```

Resolves accumulated `no_auto_map` declarations into the AutoMap setting and the excluded fields.

Returns `{:disabled, []}` when AutoMap is off for the whole projection, `{:enabled, fields}`
when specific fields are excluded, and `{:inherit, []}` when nothing was declared - which
leaves the kernel's own default in place, exactly as before this could be declared at all.

# `variant_of`
*macro* 

Declares this read model to be one of several mutually exclusive representations of the same
logical entity. Entering one variant removes the entity from every sibling variant of the same
identity.

    variant_of MyApp.ReadModels.WorkItem, key: :id

Options:
  * `:key` — **(required)** the field on this variant used as its own key, and as the
    correlation property for reclassified joins.

---

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