UNPKG

@jadermme/orus-core

Version:

ORUS Core Framework - Universal framework for 6 Pillars assessment, domain-agnostic

305 lines 8.93 kB
/** * ORUS Core - Event Emitter * * Simple, type-safe event emitter for ORUS Core events. * Enables decoupled communication between components. * * @remarks * This is a lightweight implementation focused on: * - Type safety (full TypeScript support) * - Simplicity (easy to understand and debug) * - Performance (minimal overhead) * - Memory safety (automatic cleanup on error) */ /** * ORUS Event Emitter * * @remarks * Simple pub/sub event system for ORUS Core. * Supports typed events, one-time listeners, and wildcard listeners. * * @example * ```typescript * const emitter = new OrusEventEmitter(); * * // Subscribe to events * emitter.on(OrusEventType.PILLAR_SCORE_UPDATED, (event) => { * console.log('Score updated:', event.payload); * }); * * // Emit event * emitter.emit({ * type: OrusEventType.PILLAR_SCORE_UPDATED, * timestamp: new Date(), * payload: { pillarId: 'PILLAR_1', newScore: 7.5 } * }); * ``` */ export class OrusEventEmitter { /** * Creates a new event emitter * * @param maxListeners - Maximum listeners per event (default: 10) */ constructor(maxListeners = 10) { this.listeners = new Map(); this.maxListeners = maxListeners; } /** * Subscribes to an event * * @param type - Event type to listen for * @param handler - Event handler function * @returns Unsubscribe function * * @remarks * - Handler is called each time the event is emitted * - Returns a function to unsubscribe * - Throws if max listeners exceeded * * @example * ```typescript * const unsubscribe = emitter.on( * OrusEventType.PILLAR_SCORE_UPDATED, * (event) => console.log(event.payload) * ); * * // Later: stop listening * unsubscribe(); * ``` */ on(type, handler) { const listeners = this.listeners.get(type) || []; // Check max listeners if (listeners.length >= this.maxListeners) { throw new Error(`Max listeners (${this.maxListeners}) exceeded for event type: ${type}`); } const listener = { handler: handler, once: false }; listeners.push(listener); this.listeners.set(type, listeners); // Return unsubscribe function return () => this.off(type, handler); } /** * Subscribes to an event (one-time only) * * @param type - Event type to listen for * @param handler - Event handler function * @returns Unsubscribe function * * @remarks * - Handler is called once and then automatically removed * - Useful for one-time reactions * * @example * ```typescript * emitter.once(OrusEventType.ASSESSMENT_COMPLETED, (event) => { * console.log('Assessment completed!', event.payload); * // This handler will only run once * }); * ``` */ once(type, handler) { const listeners = this.listeners.get(type) || []; // Check max listeners if (listeners.length >= this.maxListeners) { throw new Error(`Max listeners (${this.maxListeners}) exceeded for event type: ${type}`); } const listener = { handler: handler, once: true }; listeners.push(listener); this.listeners.set(type, listeners); // Return unsubscribe function return () => this.off(type, handler); } /** * Unsubscribes from an event * * @param type - Event type to stop listening for * @param handler - Handler to remove * * @remarks * - Removes the specific handler for the given event type * - No-op if handler is not found * * @example * ```typescript * const handler = (event) => console.log(event); * emitter.on(OrusEventType.PILLAR_SCORE_UPDATED, handler); * * // Later: remove listener * emitter.off(OrusEventType.PILLAR_SCORE_UPDATED, handler); * ``` */ off(type, handler) { const listeners = this.listeners.get(type); if (!listeners) return; const index = listeners.findIndex((l) => l.handler === handler); if (index !== -1) { listeners.splice(index, 1); } if (listeners.length === 0) { this.listeners.delete(type); } } /** * Emits an event to all subscribers * * @param event - Event to emit * @returns Promise that resolves when all handlers complete * * @remarks * - Calls all registered handlers for this event type * - Also calls wildcard ('*') handlers * - Handlers are called in registration order * - One-time handlers are automatically removed after execution * - Errors in handlers are caught and logged (don't stop other handlers) * - Returns promise for async handlers * * @example * ```typescript * await emitter.emit({ * type: OrusEventType.PILLAR_SCORE_UPDATED, * timestamp: new Date(), * payload: { * pillarId: 'PILLAR_1', * oldScore: 5.0, * newScore: 7.5 * } * }); * ``` */ async emit(event) { // Get specific listeners const specificListeners = this.listeners.get(event.type) || []; // Get wildcard listeners const wildcardListeners = this.listeners.get('*') || []; // Combine all listeners const allListeners = [...specificListeners, ...wildcardListeners]; // Track one-time listeners to remove const toRemove = []; // Call all handlers const promises = allListeners.map(async (listener) => { try { await listener.handler(event); // Mark one-time listeners for removal if (listener.once) { toRemove.push({ type: event.type, handler: listener.handler }); } } catch (error) { console.error(`Error in event handler for ${event.type}:`, error); } }); // Wait for all handlers to complete await Promise.all(promises); // Remove one-time listeners toRemove.forEach(({ type, handler }) => { this.off(type, handler); }); } /** * Removes all listeners for a specific event type * * @param type - Event type to clear (optional, clears all if not provided) * * @remarks * - Removes all listeners for the given type * - If no type provided, removes ALL listeners * * @example * ```typescript * // Remove all listeners for one event type * emitter.removeAllListeners(OrusEventType.PILLAR_SCORE_UPDATED); * * // Remove ALL listeners * emitter.removeAllListeners(); * ``` */ removeAllListeners(type) { if (type) { this.listeners.delete(type); } else { this.listeners.clear(); } } /** * Gets count of listeners for an event type * * @param type - Event type to count * @returns Number of listeners * * @example * ```typescript * const count = emitter.listenerCount(OrusEventType.PILLAR_SCORE_UPDATED); * console.log(`${count} listeners registered`); * ``` */ listenerCount(type) { const listeners = this.listeners.get(type); return listeners ? listeners.length : 0; } /** * Gets all event types with active listeners * * @returns Array of event types * * @example * ```typescript * const types = emitter.eventTypes(); * console.log('Active event types:', types); * ``` */ eventTypes() { return Array.from(this.listeners.keys()); } /** * Sets maximum listeners per event type * * @param max - Maximum listeners (must be > 0) * * @remarks * - Prevents memory leaks from unbounded listener registration * - Default is 10 * - Set to 0 for unlimited (not recommended) */ setMaxListeners(max) { if (max < 0) { throw new Error('Max listeners must be >= 0'); } this.maxListeners = max; } /** * Gets maximum listeners setting * * @returns Current max listeners value */ getMaxListeners() { return this.maxListeners; } } /** * Creates a new event emitter instance * * @param maxListeners - Maximum listeners per event (default: 10) * @returns New event emitter * * @example * ```typescript * const emitter = createEventEmitter(20); * ``` */ export function createEventEmitter(maxListeners) { return new OrusEventEmitter(maxListeners); } //# sourceMappingURL=emitter.js.map