Events
Events is inseam’s typed, durable, in-process event bus: a transactional outbox written in the same Store batch as the data change that produced it, plus a dispatcher that fans rows out to in-process subscribers. Plugins contribute Listeners (side effects) and Materializers (derived projections) through an Effect-free contract.
How it fits
Section titled “How it fits”emit doesn’t write directly — it appends INSERTs to a caller-supplied BatchBuilder. The caller commits via Store.batch, so the event row and the data row land atomically. Dispatch is asynchronous: subscribers run in their own loops, advance per-origin cursors, and survive crashes by reading from the outbox.
Two surfaces, split by package:
@inseam/events-contract—EventEnvelope,Listener,Materializer,EventsModule,defineEvents,HandlerResult/MaterializerResult,Cursor, plus the error hierarchy. Zero runtime deps.@inseam/core(Events contribution) —openEventsRegistry,EventsRegistry,EmitHandle,OutboxRow,BatchBuilder, plus the operator-facing halt-recovery actions. Owns migrations, per-origin sequence counter, the dispatcher loop, cursor / dead-letter / halt tables.
Key pieces
Section titled “Key pieces”Two subscriber roles, two contracts:
Listener— side-effecting, at-least-once, sequential per(listener, origin). Failures retry with exponential backoff up tomaxAttempts, then dead-letter and advance. Handlers MUST be idempotent onevent.id. Return{ ok: false, retryable }; never throw.Materializer— owns a projection. Receives ordered, gap-free delivery per origin. The projection write and the cursor advance commit in the samectx.txbatch. Failures retry; exhaustion halts the materializer on that origin (cursor stays put) until an operator skips, resumes, or rewinds.
Other pieces:
defineEvents({ name, listeners, materializers })— the singleEventsModulea plugin contributes.emit(via guarded handle) — appends to aBatchBuilder. Subscribers’typesmust use<plugin-id>.*orcore.*; anything else is rejected withInvalidEventTypeError.- Trust-bearing core types (
principal.identifier_verified,pairing.token_*,host.capability_changed, …) — core-only emit. Plugin emit paths are rejected withTrustBearingTypeError. - Operator halt recovery —
skipHaltedEvent,resumeHaltedMaterializer,rewindMaterializer. On the registry surface for hosts only; not plugin-callable.
Exact signatures: @inseam/events-contract API · arch/events/spec.md.
When to use this vs alternatives
Section titled “When to use this vs alternatives”- Owning a table? Write a
Materializer. Earn ordered, gap-free, cursor-atomic delivery; in exchange, projection writes go throughctx.txand exhausted failures halt rather than dead-letter. - Side effect only? Write a
Listener. Pay the at-least-once tax with idempotency keyed onevent.id. - Emitting from inside a write path? Get a guarded
emithandle and append to the sameBatchBuilderthe data write is using. That’s the only way to keep the event and the row atomic.
Don’t reach for Events as a pub-sub primitive for short-lived in-process messages — that’s what plain function calls are for. Events earns its weight when durability, ordering, and crash-safety matter.
See also
Section titled “See also”- LLM summary — dense reference for agents.
- Store —
emitappends to aBatchBuilderthat the caller commits viaStore.batch. - Access — two core materializers ride this bus.
- Connection — Connection lifecycle events ride this port.
arch/events/design.md— why one bus; inbound/outbound/peer-sync as one shape; security defaults.arch/events/spec.md— envelope shape, table schemas, behavior, acceptance criteria.