/** * Outbox delivery worker — the shipped poll/claim/deliver/mark loop. * * The runtime enqueues semantic events into an {@link OutboxStore} inside the * command's mutation boundary, but nothing delivers them until a worker runs. * This module IS that worker: consumers no longer hand-write the ~30-line * poll loop the docs used to prescribe. * * import { runOutboxWorker } from '@angriff36/manifest/outbox/worker'; * import { PostgresOutboxStore } from '@angriff36/manifest/outbox/postgres'; * * const worker = runOutboxWorker(store, async (entry) => { * await bus.publish(entry.event.channel, entry.event); * }); * // later, on shutdown: * await worker.stop(); * * Delivery semantics: **at-least-once**. An entry is marked delivered only * after `deliver` resolves; if `deliver` rejects, the entry is marked failed * with the error message and is NOT retried by this worker (the caller/store * decides retry policy). A worker that crashes after `deliver` succeeds but * before `markDelivered` commits will re-claim and re-deliver the entry. * Consumers MUST therefore be idempotent. * * Time is injectable (`setTimeoutFn`/`clearTimeoutFn`) so tests run without * real timers; nothing in this module reads the wall clock directly. */ import type { OutboxEntry, OutboxStore } from './outbox-store'; /** Deliver a single claimed entry. Reject to have the entry marked failed. */ export type OutboxDeliver = (entry: OutboxEntry) => Promise; export interface DrainOutboxOptions { /** Max entries to claim in one batch. Default: 100. */ batchSize?: number; } export interface OutboxWorkerOptions extends DrainOutboxOptions { /** Back-off between polls that find no pending entries (ms). Default: 1000. */ pollIntervalMs?: number; /** Abort signal that stops the loop (in addition to the returned `stop()`). */ signal?: AbortSignal; /** * Called when a claim/mark operation (i.e. store infrastructure, not a * single delivery) throws. The loop backs off and continues; it does not * crash. Per-entry `deliver` failures are NOT surfaced here — they are * recorded via `markFailed`. Defaults to a no-op. */ onError?: (err: unknown) => void; /** Timer injection for deterministic tests. Defaults to global setTimeout. */ setTimeoutFn?: (callback: () => void, ms: number) => unknown; /** Paired with `setTimeoutFn`. Defaults to global clearTimeout. */ clearTimeoutFn?: (handle: unknown) => void; } /** Outcome of a single {@link drainOutboxOnce} batch. */ export interface DrainOutboxResult { /** Entries claimed this batch. Zero means the store had nothing pending. */ claimed: number; /** Entries whose `deliver` resolved (marked delivered). */ delivered: number; /** Entries whose `deliver` rejected (marked failed). */ failed: number; } /** Handle for a running {@link runOutboxWorker} loop. */ export interface OutboxWorkerHandle { /** Stop the loop and resolve once the in-flight iteration settles. */ stop(): Promise; /** Resolves when the loop exits (via `stop()` or an aborted signal). */ readonly done: Promise; } /** * Claim one batch, deliver each entry, and mark the outcomes. Usable directly * from a cron route or serverless handler that wants a single pass rather than * a long-lived loop. * * Each entry is delivered under its own try/catch so one bad entry never * blocks the rest of the batch: successes are marked delivered in a single * `markDelivered` call, failures are marked individually with their own error * message. */ export declare function drainOutboxOnce(store: OutboxStore, deliver: OutboxDeliver, opts?: DrainOutboxOptions): Promise; /** * Run a continuous outbox delivery loop until stopped. Returns immediately * with a handle; the loop runs in the background. * * Cadence: after a batch that claims work, the loop polls again immediately * (drains a backlog as fast as the store allows). After a batch that finds * nothing, it backs off `pollIntervalMs`. The back-off is interruptible, so * `stop()` / an aborted signal wakes the loop promptly. * * Multiple workers over the same store are safe when the store implements * `claim` with row-level locking (e.g. PostgresOutboxStore's SELECT … FOR * UPDATE SKIP LOCKED) — each worker receives a disjoint batch. */ export declare function runOutboxWorker(store: OutboxStore, deliver: OutboxDeliver, opts?: OutboxWorkerOptions): OutboxWorkerHandle; //# sourceMappingURL=worker.d.ts.map