/** * Domain event bus singleton for NoteLett backend. * * Lightweight typed pub/sub for domain events. Handlers run via * Promise.allSettled — a failing handler never blocks others. */ export interface NoteCreatedEvent { noteId: string; workspaceId: string; userId: string; title: string; } export interface NoteUpdatedEvent { noteId: string; workspaceId: string; userId: string; title: string; } export interface NoteDeletedEvent { noteId: string; workspaceId: string; userId: string; } export interface TaskCreatedEvent { taskId: string; noteId: string; workspaceId: string; userId: string; title: string; } export interface WorkspaceCreatedEvent { workspaceId: string; userId: string; name: string; } export type NoteLettEventMap = { 'note.created': NoteCreatedEvent; 'note.updated': NoteUpdatedEvent; 'note.deleted': NoteDeletedEvent; 'task.created': TaskCreatedEvent; 'workspace.created': WorkspaceCreatedEvent; }; type Handler = (payload: T) => void | Promise; class DomainEventBus { private handlers = new Map>>(); on(event: K, handler: Handler): () => void { if (!this.handlers.has(event)) this.handlers.set(event, new Set()); this.handlers.get(event)!.add(handler as Handler); return () => { this.handlers.get(event)?.delete(handler as Handler); }; } async emit(event: K, payload: NoteLettEventMap[K]): Promise { const fns = this.handlers.get(event); if (!fns || fns.size === 0) return; await Promise.allSettled([...fns].map(fn => fn(payload))); } removeAll(): void { this.handlers.clear(); } } let _bus: DomainEventBus | null = null; export function getEventBus(): DomainEventBus { if (!_bus) { _bus = new DomainEventBus(); } return _bus; } /** @internal — for testing only. */ export function _resetEventBus(): void { _bus = null; }