From 1e12d702a1d2c172cf6ea8bd14cc93d080fe6287 Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Fri, 12 Jun 2026 16:47:39 -0400 Subject: [PATCH] feat(09-01): create outboxTrigger.ts zero-dependency EventEmitter signal module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Module-level singleton EventEmitter, single subscriber, no setMaxListeners - Export signalOutboxDrain(): void — fire-and-forget drain signal (D-04) - Export onOutboxDrain(handler): () => void — register/unsubscribe listener - Only imports node:events; zero internal dependencies (no circular import risk) --- apps/api/src/lib/outboxTrigger.ts | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 apps/api/src/lib/outboxTrigger.ts 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); +}