UNPKG

@ai2070/l0

Version:

L0: The Missing Reliability Substrate for AI

171 lines (170 loc) 4.38 kB
import { uuidv7 } from "../utils/uuid"; function deepCloneAndFreeze(obj) { if (obj === null || typeof obj !== "object") { return obj; } if (Array.isArray(obj)) { const cloned2 = obj.map((item) => deepCloneAndFreeze(item)); return Object.freeze(cloned2); } const cloned = {}; for (const key of Object.keys(obj)) { cloned[key] = deepCloneAndFreeze(obj[key]); } return Object.freeze(cloned); } class EventDispatcher { handlers = []; streamId; _context; highPerformance; batchSize; batchFlushMs; eventBatch = []; batchTimeout = null; constructor(contextOrConfig = {}) { this.streamId = uuidv7(); const isConfig = "highPerformance" in contextOrConfig || "batchSize" in contextOrConfig || "batchFlushMs" in contextOrConfig || "context" in contextOrConfig; if (isConfig) { const config = contextOrConfig; this.highPerformance = config.highPerformance ?? false; this.batchSize = config.batchSize ?? 10; this.batchFlushMs = config.batchFlushMs ?? 5; this._context = this.highPerformance ? Object.freeze(config.context ?? {}) : deepCloneAndFreeze(config.context ?? {}); } else { this.highPerformance = false; this.batchSize = 10; this.batchFlushMs = 5; this._context = deepCloneAndFreeze( contextOrConfig ); } } /** * Register an event handler */ onEvent(handler) { this.handlers.push(handler); } /** * Remove an event handler */ offEvent(handler) { const index = this.handlers.indexOf(handler); if (index !== -1) { this.handlers.splice(index, 1); } } /** * Flush any pending batched events immediately */ flush() { if (this.batchTimeout) { clearTimeout(this.batchTimeout); this.batchTimeout = null; } if (this.eventBatch.length === 0) return; const batch = this.eventBatch; this.eventBatch = []; for (const event of batch) { this.dispatchToHandlers(event); } } /** * Internal: dispatch a single event to all handlers */ dispatchToHandlers(event) { for (const handler of [...this.handlers]) { queueMicrotask(() => { try { const result = handler(event); if (result && typeof result === "object" && "catch" in result) { result.catch(() => { }); } } catch { } }); } } /** * Emit an event to all handlers * - Adds ts, streamId, context automatically * - Calls handlers via microtasks (fire-and-forget) * - Never throws from handler failures * - In high-performance mode, batches events for reduced overhead */ emit(type, payload) { if (this.handlers.length === 0) return; const event = { type, ts: Date.now(), streamId: this.streamId, context: this._context, ...payload }; if (this.highPerformance && this.batchSize > 1) { this.eventBatch.push(event); if (this.eventBatch.length >= this.batchSize) { this.flush(); } else if (!this.batchTimeout) { this.batchTimeout = setTimeout(() => { this.batchTimeout = null; this.flush(); }, this.batchFlushMs); } return; } this.dispatchToHandlers(event); } /** * Emit an event synchronously (for critical path events) * Use sparingly - prefer emit() for most cases */ emitSync(type, payload) { if (this.handlers.length === 0) return; const event = { type, ts: Date.now(), streamId: this.streamId, context: this._context, ...payload }; for (const handler of [...this.handlers]) { try { const result = handler(event); if (result && typeof result === "object" && "catch" in result) { result.catch(() => { }); } } catch { } } } /** * Get the stream ID for this session */ getStreamId() { return this.streamId; } /** * Get the context for this session */ getContext() { return this._context; } /** * Get the number of registered handlers */ getHandlerCount() { return this.handlers.length; } } function createEventDispatcher(context = {}) { return new EventDispatcher(context); } export { EventDispatcher, createEventDispatcher }; //# sourceMappingURL=event-dispatcher.js.map