EventStreamEntry
Defined in: packages/contracts/src/event-stream.ts:66
A registered Event stream: its payload contract and its claim→identity stamping rule.
payload is a strict zod schema, and strictness is ENFORCED, not merely advertised: an object-rooted
payload (including every object inside a union or discriminated union) must carry a never catchall —
.strict() or z.strictObject() — or defineSyncRegistry rejects the registration. A stripping object
would let a misspelled or newly-added key vanish between the caller and the consumer with no verdict
anywhere, which is exactly the silence the Event lane’s “everything in the Outbox is well-formed”
invariant exists to prevent. A NON-object root (a string, array, record, a transform pipeline, …) is
accepted unchanged and follows ordinary parse-contract semantics: what the schema ACCEPTS is what
appendEvent validates, and the JSON-NORMALIZED form of what the schema PRODUCES is what the consumer
receives.
The schema EXECUTES at both boundaries; only ONE execution’s output is taken. appendEvent runs it as
pure validation — the result is discarded and the Outbox stores the value the caller supplied — and the
ingestion endpoint runs it again as the one AUTHORITATIVE parse, whose output is what is enqueued and what
the consumer callback receives. A transform callback therefore runs twice, so transforms must be pure
and deterministic: an effectful or environment-dependent one is unsupported, because the server’s run is
the one that counts and nothing reconciles it against the client’s. Parse output the JSON value domain
cannot carry (a BigInt, undefined) is a terminal per-event rejected at ingest, never a batch fault.
What is enqueued is the JSON-NORMALIZED parse output, not the parse output itself. Ingest serializes
the authoritative parse’s result and enqueues the round-trip, so every queue backend — the in-memory fake
and real pgmq’s jsonb alike — delivers the identical value. The consequence is worth designing against:
a transform producing a Date reaches the consumer as that date’s ISO STRING, and a nested undefined
property is dropped (an undefined array member becomes null). A transform that must round-trip as a
rich type has to encode it itself.
Schema evolution is compatibility-bound (ADR-0053 decision 1): a payload schema may evolve only
backward-compatibly — it must keep accepting every previously-valid payload, because events written
offline under the old schema are still in flight. An incompatible change requires a NEW Event-stream
name. The registry lock records a hash of this entry so a change is a reviewable risky diff; the hash
DETECTS change, it cannot judge compatibility (see registryEventStreams).
Type Parameters
Section titled “Type Parameters”TPayload
Section titled “TPayload”TPayload extends z.ZodType = z.ZodType
Properties
Section titled “Properties”identity
Section titled “identity”identity:
Record<string,EventStreamIdentityField>
Defined in: packages/contracts/src/event-stream.ts:72
The identity fields stamped server-side from verified claims, keyed by field name. The stamped record (field name → value) is what the consumer callback receives on every event.
payload
Section titled “payload”payload:
TPayload
Defined in: packages/contracts/src/event-stream.ts:67
revision?
Section titled “revision?”
optionalrevision?:number
Defined in: packages/contracts/src/event-stream.ts:85
An opaque version counter for the part of this payload contract the lock’s hash cannot see — the same
role RowFilterSpec.revision plays for a customWhere closure. The lock hashes the payload schema as
a JSON Schema, and refinements/transforms are simply not representable there: a reviewer
demonstrated that z.string().refine(v => v.length >= 3) and the INCOMPATIBLE >= 10 version produce
the identical stream hash, so the risky diff the ADR relies on never fires.
So: whenever you change acceptance logic a JSON Schema cannot express (any .refine/.superRefine
threshold, a cross-field check, a transform), you MUST bump revision (positive integer). That is
what surfaces the change in the lock diff, where the compatibility rule can actually be reviewed.
Leaving it unchanged after a refinement change silently bypasses the gate.