meld
Version:
Meld: A template language for LLM prompts
120 lines (106 loc) • 3.76 kB
text/typescript
import { IStateEventService, StateEvent, StateEventType, StateEventHandler, StateEventHandlerOptions } from './IStateEventService.js';
import { stateLogger as logger } from '@core/utils/logger.js';
/**
* @package
* Core event system implementation for state tracking.
*
* @remarks
* Provides event emission and handling for state operations.
* Implements filtering and async event handling.
*/
export class StateEventService implements IStateEventService {
private handlers: Map<StateEventType, Array<{
handler: StateEventHandler;
options?: StateEventHandlerOptions;
}>> = new Map();
constructor() {
// Initialize handler arrays for each event type
const eventTypes: StateEventType[] = ['create', 'clone', 'transform', 'merge', 'error'];
eventTypes.forEach(type => this.handlers.set(type, []));
}
/**
* Register an event handler with optional filtering
*/
on(type: StateEventType, handler: StateEventHandler, options?: StateEventHandlerOptions): void {
const handlers = this.handlers.get(type);
if (!handlers) {
throw new Error(`Invalid event type: ${type}`);
}
handlers.push({ handler, options });
logger.debug(`Registered handler for ${type} events`, {
type,
hasFilter: !!options?.filter
});
}
/**
* Remove an event handler
*/
off(type: StateEventType, handler: StateEventHandler): void {
const handlers = this.handlers.get(type);
if (!handlers) {
throw new Error(`Invalid event type: ${type}`);
}
const index = handlers.findIndex(h => h.handler === handler);
if (index !== -1) {
handlers.splice(index, 1);
logger.debug(`Removed handler for ${type} events`);
}
}
/**
* Emit a state event
*/
async emit(event: StateEvent): Promise<void> {
const handlers = this.handlers.get(event.type);
if (!handlers) {
throw new Error(`Invalid event type: ${event.type}`);
}
logger.debug(`Emitting ${event.type} event`, {
stateId: event.stateId,
source: event.source
});
// Group handlers by their filter conditions to prevent duplicate processing
const handlerGroups = new Map<string, Array<{ handler: StateEventHandler; options?: StateEventHandlerOptions }>>();
for (const handlerEntry of handlers) {
// Create a key based on the filter condition
const filterKey = handlerEntry.options?.filter ?
`${event.source}-${event.stateId}-${event.location?.file || ''}` :
'no-filter';
const group = handlerGroups.get(filterKey) || [];
group.push(handlerEntry);
handlerGroups.set(filterKey, group);
}
// Process each group once
for (const [_, groupHandlers] of handlerGroups) {
// Only execute if the first handler's filter passes
const firstHandler = groupHandlers[0];
if (firstHandler.options?.filter && !firstHandler.options.filter(event)) {
continue;
}
// Execute all handlers in the group
for (const { handler } of groupHandlers) {
try {
await Promise.resolve(handler(event));
} catch (error) {
// Log error but continue processing other handlers
logger.error(`Error in ${event.type} event handler`, {
error: error instanceof Error ? error.message : String(error),
stateId: event.stateId
});
}
}
}
}
/**
* Get all registered handlers for an event type
*/
getHandlers(type: StateEventType): Array<{
handler: StateEventHandler;
options?: StateEventHandlerOptions;
}> {
const handlers = this.handlers.get(type);
if (!handlers) {
throw new Error(`Invalid event type: ${type}`);
}
return [...handlers];
}
}