diff --git a/apps/api/src/lib/outboxTrigger.ts b/apps/api/src/lib/outboxTrigger.ts new file mode 100644 index 0000000..ea007a2 --- /dev/null +++ b/apps/api/src/lib/outboxTrigger.ts @@ -0,0 +1,39 @@ +/** + * In-process outbox drain signal (D-01 / D-03 / D-04). + * + * Module-level singleton EventEmitter — one emitter shared across all callers + * in this Node.js process. Carries a zero-payload 'drain' event: signal is + * fire-and-forget; no data crosses this boundary (D-04). + * + * Single-subscriber design: + * - signalOutboxDrain() is called only after a successful enqueue DB commit (D-01/D-03). + * - The sole listener is registered by initOutboxTrigger() in outboxWorker.ts. + * - With exactly one subscriber the default EventEmitter limit of 10 is correct. + * - Do NOT call setMaxListeners — unlike listEmitter.ts (T-04-04, 200 SSE fan-out), + * this emitter never fans out to multiple listeners. + * + * Exports: signalOutboxDrain, onOutboxDrain + */ + +import { EventEmitter } from 'node:events'; + +// Module-level singleton — one emitter shared across all route handlers +// in this Node.js process. +const emitter = new EventEmitter(); + +/** + * Fire a drain signal after a successful outbox enqueue commit. + * Synchronous and fire-and-forget — never awaited (D-04). + */ +export function signalOutboxDrain(): void { + emitter.emit('drain'); +} + +/** + * Register a handler to be invoked on each drain signal. + * Returns an unsubscribe function; call it to stop delivery to this handler. + */ +export function onOutboxDrain(handler: () => void): () => void { + emitter.on('drain', handler); + return () => emitter.off('drain', handler); +}