Documentation Index
Fetch the complete documentation index at: https://mintlify.com/iterate/sqlfu/llms.txt
Use this file to discover all available pages before exploring further.
sqlfu/outbox is a small transactional-outbox and job-queue built on top of the same sqlfu client you already use. It gives you transactional event emission, per-consumer fan-out, retry and dead-letter handling, delayed dispatch, crash recovery, and causation chains — all in a single dependency-free module.
What the outbox provides
- Transactional emit — the event row is inserted in the same transaction as your domain write, so either both happen or neither does.
- Per-consumer fan-out — one emitted event spawns one job per registered consumer.
- Retry + DLQ — failed jobs are rescheduled according to a retry policy; once a hard attempt cap is hit, they transition to
status = 'failed'. - Delayed dispatch — a consumer can schedule its job to run
24hlater via thedelayoption. - Visibility-timeout crash recovery — if a worker dies holding a claimed job, the job becomes re-claimable by a future worker after the visibility timeout expires.
- Causation chains — an event emitted inside a handler automatically records which job and consumer caused it.
Why SQLite serialises writers
The whole module is built on the observation that SQLite serialises writers. You do not need row-locking or work-leasing: a plainbegin; select pending; update to running; commit sequence is enough to safely claim jobs without races.
Setup
Define your event types
Start with a TypeScript type map from event name to payload shape. This gives you end-to-end type safety through emit and handler.
Emit events in your domain transactions
Pass
{client: tx} to emit the event inside the same transaction as your domain write:Consumer options
Every field exceptname and handler is optional:
Ns, Nm, Nh, Nd suffix format (seconds, minutes, hours, days).
Causation chains
Handlers receive anemit helper that already knows its own job context. Events emitted through that helper automatically get context.causedBy = {eventId, consumerName, jobId} pointing back to the originating job.
This is explicit by design. sqlfu runs in browsers, edge workers, and mobile environments, so the outbox avoids any node: imports. AsyncLocalStorage would have made causation automatic for Node users but broken everywhere else. Threading emit through the handler input keeps the module dependency-free at the cost of one extra argument.
If you call outbox.emit(...) from outside a handler — for example, in response to a user action — the event is still emitted, just without a causedBy entry. That is the correct behaviour: it was not caused by another job.
Out of scope
- HTTP integration — wire-up is straightforward: consumer objects are plain data, and
outbox.tick()returns quickly. Wrap it in whatever scheduler you like. - OpenTelemetry spans per job — use the existing
instrument()hook on the sqlfu client; handlers run against the same client. - PostHog / Sentry DLQ reporting — the
onBookkeepingErrorhook and thestatus = 'failed'terminal state are the building blocks. Wiring them into your telemetry pipeline is a downstream concern.