@btravstack/outbox / index
index
Classes
OutboxPublisher
Defined in: outbox/src/outbox.ts:106
Extends
PortInstance<"OutboxPublisher",OutboxPublisherService>
Constructors
Constructor
new OutboxPublisher(): OutboxPublisher;Defined in: di/dist/index.d.mts:16
Returns
Inherited from
Port("OutboxPublisher")<OutboxPublisherService>.constructorProperties
| Property | Modifier | Type | Inherited from | Defined in |
|---|---|---|---|---|
[ID] | readonly | "OutboxPublisher" | Port("OutboxPublisher").[ID] | di/dist/index.d.mts:12 |
[SERVICE] | readonly | OutboxPublisherService | Port("OutboxPublisher").[SERVICE] | di/dist/index.d.mts:13 |
portId | readonly | "OutboxPublisher" | Port("OutboxPublisher").portId | di/dist/index.d.mts:17 |
OutboxStore
Defined in: outbox/src/outbox.ts:85
Where the relay reads from. An application provides it — from @btravstack/outbox/prisma's prismaOutboxStore, memoryOutboxStore, or an adapter of its own — beside the transaction that writes the rows, which stays the application's.
Extends
PortInstance<"OutboxStore",OutboxStoreService>
Constructors
Constructor
new OutboxStore(): OutboxStore;Defined in: di/dist/index.d.mts:16
Returns
Inherited from
Port("OutboxStore")<OutboxStoreService>.constructorProperties
| Property | Modifier | Type | Inherited from | Defined in |
|---|---|---|---|---|
[ID] | readonly | "OutboxStore" | Port("OutboxStore").[ID] | di/dist/index.d.mts:12 |
[SERVICE] | readonly | OutboxStoreService | Port("OutboxStore").[SERVICE] | di/dist/index.d.mts:13 |
portId | readonly | "OutboxStore" | Port("OutboxStore").portId | di/dist/index.d.mts:17 |
Type Aliases
MemoryOutboxStore
type MemoryOutboxStore = OutboxStoreService & object;Defined in: outbox/src/memory.ts:10
The in-process store, plus the write half an application's in-memory adapter calls.
Type Declaration
| Name | Type | Defined in |
|---|---|---|
append() | (message) => void | outbox/src/memory.ts:11 |
OutboxAppend
type OutboxAppend = Omit<OutboxMessage, "id" | "occurredAt">;Defined in: outbox/src/memory.ts:7
What an in-memory write appends: the envelope, without the two fields the store assigns.
OutboxMessage
type OutboxMessage = object;Defined in: outbox/src/outbox.ts:15
One committed fact awaiting broadcast: a row of the outbox table, as the relay reads it.
The row IS the envelope. tenantId is whose fact it is, and the unit the relay claims by; kind is which sort of thing changed; subjectId is which one, and the key a reader compacts on; payload is the application's own encoding, handed to the publisher untouched — a null payload is the tombstone, the last word about a subject. id is the outbox sequence, and the order the relay publishes a tenant's facts in.
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
id | readonly | number | outbox/src/outbox.ts:16 |
kind | readonly | string | outbox/src/outbox.ts:18 |
occurredAt | readonly | Date | outbox/src/outbox.ts:21 |
payload | readonly | string | null | outbox/src/outbox.ts:20 |
subjectId | readonly | string | outbox/src/outbox.ts:19 |
tenantId | readonly | string | outbox/src/outbox.ts:17 |
OutboxOptions
type OutboxOptions = object;Defined in: outbox/src/relay.ts:48
What outbox is handed. Each field pins the variable named beside it.
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
clock? | readonly | Clock | What the poll sleeps on and the lag is measured against (default: the kernel's systemClock). | outbox/src/relay.ts:64 |
concurrency? | readonly | number | How many tenants hold a claim at once — OUTBOX_CONCURRENCY (default 4). | outbox/src/relay.ts:62 |
maxLagMs? | readonly | number | The oldest pending age /healthz tolerates — OUTBOX_MAX_LAG_MS (default 60_000). | outbox/src/relay.ts:54 |
pollMs? | readonly | number | The idle sleep between sweeps — OUTBOX_POLL_MS (default 200). | outbox/src/relay.ts:52 |
retainMs? | readonly | number | false | How long a published row is kept before the relay deletes it — OUTBOX_RETAIN_MS (default 0: kept). false pins it off whatever the deployment sets. | outbox/src/relay.ts:60 |
tenants? | readonly | readonly string[] | The tenants this relay serves — OUTBOX_TENANTS, comma-separated, required. | outbox/src/relay.ts:50 |
OutboxPublisherService
type OutboxPublisherService = object;Defined in: outbox/src/outbox.ts:102
What "publish" means, which only the application knows: the transport, the contract and the decoding of payload.
Any Err is a message left pending: the relay stops the tenant's batch there, so a later fact never overtakes it, and tries again after its back-off.
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
publish | readonly | (message) => AsyncResult<void, PublishRefused> | outbox/src/outbox.ts:103 |
OutboxStoreService
type OutboxStoreService = object;Defined in: outbox/src/outbox.ts:29
What a store answers for the relay. Every operation promises never: a database that will not answer is a defect the relay observes and retries, not a domain outcome.
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
claim | readonly | (tenantId, limit, relay) => AsyncResult<void, never> | Claims a tenant's oldest unpublished messages, hands them to relay, and marks published exactly the ids relay answers — all under one claim, so no other caller holding the same store is handed the same tenant until it is released. A tenant another caller has claimed is skipped, not waited for: the call answers Ok without running relay. A defect from relay releases the claim and marks nothing. | outbox/src/outbox.ts:58 |
oldestPending | readonly | (tenantIds) => AsyncResult<readonly object[], never> | When each of tenantIds' oldest unpublished message was written, for the tenants that have one — on ONE connection however many tenants are asked about, because the health check asks on every /healthz, and a connection per tenant would queue the pool behind the probe meant to report it. | outbox/src/outbox.ts:45 |
pending | readonly | (tenantId, limit) => AsyncResult<readonly OutboxMessage[], never> | A tenant's oldest unpublished messages, in outbox order, at most limit. A read and nothing more — it claims nothing, which is what lets the health check and a spec look without racing the relay. | outbox/src/outbox.ts:35 |
prune | readonly | (tenantId, olderThanMs, limit) => AsyncResult<number, never> | Deletes a tenant's messages published more than olderThanMs ago, at most limit, and answers how many went. The age is measured on the clock that stamped the publish — the database's own, for a database — so a host clock's skew can neither age a row early nor keep it late. Published rows only: a pending one is never touched. It holds the tenant as a claim does, and a tenant another caller holds is skipped, answering 0: across replicas, one prunes a tenant at a time, and never while it is claimed. | outbox/src/outbox.ts:72 |
PublishRefused
type PublishRefused = object;Defined in: outbox/src/outbox.ts:92
Whatever tagged error the publisher models — a broker that refused, a message the contract rejects. The relay reports it and acts on none of it, so the type asks for a tag and nothing else.
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
_tag | readonly | string | outbox/src/outbox.ts:92 |
Functions
memoryOutboxStore()
function memoryOutboxStore(clock?): MemoryOutboxStore;Defined in: outbox/src/memory.ts:20
An outbox held in the process: for tests, and for an application whose in-memory repositories need somewhere to record their facts. Its claim is a per-tenant flag, so two relays over ONE instance behave as two replicas over one table do — which is the only sharing an in-process store can have.
Parameters
| Parameter | Type | Default value |
|---|---|---|
clock | Clock | systemClock |
Returns
outbox()
function outbox<O>(options?): Module<HealthChecks, ConfigInvalid,
| OutboxPublisher
| OutboxStore
| EnvReading<Unpinned<O, "tenants", "OUTBOX_TENANTS">,
| MaybePinned<O, "tenants", "OUTBOX_TENANTS">
| "OUTBOX_POLL_MS"
| "OUTBOX_MAX_LAG_MS"
| "OUTBOX_RETAIN_MS"
| "OUTBOX_CONCURRENCY">
| Scope>;Defined in: outbox/src/relay.ts:275
The outbox relay: the other half of a write that recorded its fact in the same transaction as the row it describes. It claims each tenant's oldest pending messages, hands them to the application's OutboxPublisher in outbox order, marks published what was published, and sleeps.
outbox({ tenants: ["acme"] });At-least-once, with replicas taking turns. A crash between a publish and its mark re-publishes on the next claim, and so does a claiming session the database ends mid-batch, so a subscriber must tolerate a repeat, keyed by the outbox id. What the claim rules out is replicas racing: a tenant is held by one relay while its claiming session lives and skipped by the rest, so its committed facts go out in outbox order — which is not commit order: an id still in an open transaction surfaces after a higher one that committed.
A refused publish stops the tenant's batch and backs that tenant off, doubling from the poll interval to 30 seconds, so a later fact never overtakes an earlier one. A message the publisher refuses forever therefore holds its tenant's outbox — which is what the health check is for: it reports a tenant whose oldest pending message is older than maxLagMs.
At most concurrency tenants hold a claim or a prune at once, each on one pooled connection for its whole batch; the rest wait their turn in order. The default of 4 leaves most of a 10-connection pool to the publisher and the requests sharing it. A publish that never settles holds its slot, so a publisher that can hang answers Err on a timeout of its own.
Started as the graph builds, before the runtime accepts anything, and stopped when the application scope closes, after the runtime has drained. Every claim and publish is reported to Observers; the module holds no logger of its own.
Type Parameters
| Type Parameter | Default type |
|---|---|
O extends OutboxOptions | Record<never, never> |
Parameters
| Parameter | Type |
|---|---|
options | O & OptionsAs<O, OutboxOptions> |
Returns
Module<HealthChecks, ConfigInvalid, | OutboxPublisher | OutboxStore | EnvReading<Unpinned<O, "tenants", "OUTBOX_TENANTS">, | MaybePinned<O, "tenants", "OUTBOX_TENANTS"> | "OUTBOX_POLL_MS" | "OUTBOX_MAX_LAG_MS" | "OUTBOX_RETAIN_MS" | "OUTBOX_CONCURRENCY"> | Scope>