ListenersDefining Listeners

Defining Listeners

A listener is a deterministic in-process reaction to an emitted event. When an event matching a listener’s on: condition is emitted, the listener runs synchronously — in the same commit envelope, before the ledger append completes.

Listeners let you maintain consistency across related entities within a single intent’s execution, without introducing asynchronous coupling.


How listeners differ from projections and side_effects

ListenerProjectionside_effects
TimingSynchronous, same commitAsync, via KafkaAsync, fan-out
I/ONonePostgreSQL UPSERTemit, submit, schedule
PurposeCross-entity consistency in-processRead modelsFan-out to other streams
ReplayPart of rule evaluationVia Kafka replayRe-submitted intents

Use listeners when you need two entity states to update atomically within the same intent. Use projections for query-side read models. Use side_effects for fan-out that can be eventually consistent.


Syntax

listeners:
  - on: POINTS_AWARDED
    where: event.payload.apply_to_match
    resolve:
      match: lookup(match, event.payload.match_id)
    mutate:
      - op: add
        path: /stats/home_score
        value: event.payload.delta
        on: match

Fields

FieldRequiredDescription
onYesThe event type that triggers this listener.
whereNoCondition expression. Listener runs only when truthy.
resolveNoNamed lookups to load related entities.
mutateYesList of mutation operations to apply.

on:

The event type name that triggers the listener. Must match a declared event.

- on: TICKET_PURCHASED

One listener per on: event. If you need multiple reactions to the same event, declare multiple listener blocks.


where:

An optional condition evaluated against the triggering event. If the condition is falsy, the listener does not run.

- on: POINTS_AWARDED
  where: event.payload.apply_to_match

Expression context: event.payload.<field> for the triggering event’s payload, event.ts, event.entity_id.


resolve:

Named lookups that load related entities for mutation. The resolved entities are available by name in mutate: via the on: field.

resolve:
  match: lookup(match, event.payload.match_id)
  venue: lookup(venue, event.payload.venue_id)

The lookup(entityType, entityId) function loads the entity snapshot for the given type and ID. The result is available as the named variable in mutate:.


mutate:

A list of mutation operations, each with an on: specifying which resolved entity to mutate.

mutate:
  - op: add
    path: /stats/home_score
    value: event.payload.delta
    on: match
  - op: set
    path: /last_scored_at
    value: event.ts
    on: match

Supported ops in listeners: set, add, sub, push, filter, remove, unset, merge.

The on: field refers to a variable from resolve:. The primary entity (the one the triggering action was running on) is accessible as entity.


Ordering

Listeners are sorted and executed in file path order within the application. If you have multiple listener files and execution order matters, name your files with a numeric prefix:

listeners/
  01_score.listeners.causet
  02_rank.listeners.causet

Within a single file, listeners execute in declaration order.


Next steps

  • Examples — scoring and follow-counter patterns
  • Best Practices — common mistakes
  • Intents — where events that trigger listeners are emitted