OUTBOX_TABLE
constOUTBOX_TABLE:"pgxsinkit_outbox"="pgxsinkit_outbox"
Defined in: packages/client/src/schema.ts:86
The Outbox (ADR-0053 decision 2) — the local-only, append-only table where client events are staged
until a flush is acknowledged. ONE table for every Event stream (a stream column, not a table per
stream), library-owned, never in the sync registry and never replicated, and stream-independent:
registering an Event stream changes nothing about this DDL, so it is emitted unconditionally beside the
engine’s other internal relations rather than derived from registry.streams.
Its SHAPE IS PUBLIC CONTRACT, because apps compose pending rows with down-synced aggregates into best-guess views. The columns:
seq— the durable, monotonically increasing local append ordinal, the ONE ordering key for Outbox selection and batch assembly (UUIDs do not order,occurred_at_uscollides at these volumes, and SQL row order is undefined). Local machinery: it is never transmitted. Assigned from OUTBOX_SEQUENCE exactly as the mutation journal’smutation_seqis.event_id— the library-stamped uuid carried on the wire; the server-side dedupe key that makes at-least-once delivery idempotent end-to-end. UNIQUE: a duplicate would be a library bug.stream— the registered Event-stream name.occurred_at_us— the library-stamped append time (microseconds), carried on the wire. Consumers needing temporal order re-sort on it (ADR-0053 decision 6).payload— the event body asjsonb, validated against the Event stream’s zod schema AT APPEND, so “everything in the Outbox is well-formed” holds for the flush loop and for best-guess views. It is EXACTLY the value the caller passed toappendEvent— never the schema’sparsed.data. The schema is used at append only to VALIDATE (its output is discarded); the one AUTHORITATIVE parse is at ingest, and that output’s JSON-NORMALIZED form is what the consumer receives (aDatebecomes its ISO string; a nestedundefinedproperty is dropped). The schema therefore executes at both boundaries — so transforms must be pure and deterministic — while a best-guess view here reads back precisely what the app appended.enqueued_at_us— when the row was durably enqueued locally (never transmitted; observability).attempt_count/next_retry_at_us— the per-row DEFERRED backoff (ADR-0053 decision 3): an Event stream the server does not yet know parks its rows here rather than deleting them. A row whosenext_retry_at_usis in the future is skipped by batch assembly. NULL = eligible now.last_reason— the most recent serverdeferredreason, kept on the row it belongs to (never a second, retention-bearing verdict table — ADR-0053 rejects that).acked_at_us— the acked ledger stamp (ADR-0060): when the server accepted the event, or NULL while it is still PENDING a verdict. NULL is the pending predicate — everything the library means by “pending” readsacked_at_us IS NULL(the drain signal, batch assembly,diagnostics().outbox, the non-forceddestroy()refusal), so a stamped row is never re-posted and never keeps the lane “non-empty”. It is stamped instead of deleted only when the client configuresevents.ackedRetentionMs > 0; at the default0anackedverdict deletes the row immediately and this column is never non-NULL. For a best-guess view it is the grace ledger: anackedrow has been accepted but NOT yet folded by the server’s consumer, and the folded aggregate has not yet synced back down — so a composition may keep counting an acked row until its own synced row’s stamp passesacked_at_us, or until the retention elapses and the sweep deletes it. That dip is the whole reason the column exists.