@jadermme/orus-core
Version:
ORUS Core Framework - Universal framework for 6 Pillars assessment, domain-agnostic
305 lines • 8.93 kB
JavaScript
/**
* 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