/** * Event Emitter with wildcard pattern matching support. * * Supports wildcard patterns for event subscriptions: * - Exact match: `'widget.show'` matches `'widget.show'` * - Wildcard suffix: `'widget.*'` matches `'widget.show'`, `'widget.hide'`, etc. * - Wildcard prefix: `'*.show'` matches `'widget.show'`, `'modal.show'`, etc. * - Match all: `'*'` matches all events * * Uses regex compilation for efficient pattern matching. */ /** * Subscription entry storing the pattern and handler. */ interface Subscription { pattern: string; compiledPattern: RegExp; handler: (...args: any[]) => void; } export class Emitter { private subscriptions: Subscription[] = []; /** * Subscribe to an event or pattern. * * @param event - Event name or wildcard pattern * @param handler - Event handler function * @returns Unsubscribe function * * @example * ```typescript * const emitter = new Emitter(); * * // Exact match * emitter.on('user.login', (data) => console.log('Login:', data)); * * // Wildcard patterns * emitter.on('user.*', (data) => console.log('User event:', data)); * emitter.on('*.error', (data) => console.log('Error:', data)); * emitter.on('*', (data) => console.log('Any event:', data)); * * // Unsubscribe * const unsub = emitter.on('test', handler); * unsub(); * ``` */ on(event: string, handler: (...args: any[]) => void): () => void { if (typeof handler !== 'function') { throw new TypeError('handler must be a function'); } const subscription: Subscription = { pattern: event, compiledPattern: this.compilePattern(event), handler, }; this.subscriptions.push(subscription); // Return unsubscribe function return () => this.off(event, handler); } /** * Unsubscribe from an event or pattern. * * @param event - Event name or pattern to unsubscribe from * @param handler - Handler function to remove */ off(event: string, handler: (...args: any[]) => void): void { this.subscriptions = this.subscriptions.filter( (sub) => !(sub.pattern === event && sub.handler === handler) ); } /** * Emit an event to all matching subscribers. * * Notifies all handlers whose patterns match the event name. * Handlers are called with the provided arguments. * Errors in handlers are caught and logged to prevent breaking the event flow. * * @param event - Event name to emit * @param args - Arguments to pass to handlers * * @example * ```typescript * emitter.emit('user.login', { userId: '123' }); * emitter.emit('widget.show', { id: 'modal-1' }, { animate: true }); * ``` */ emit(event: string, ...args: any[]): void { for (const subscription of this.subscriptions) { if (subscription.compiledPattern.test(event)) { try { subscription.handler(...args); } catch (err) { console.error(`Error in event handler for "${event}":`, err); } } } } /** * Remove all event listeners. * * Useful for cleanup when destroying the SDK instance. */ removeAllListeners(): void { this.subscriptions = []; } /** * Compile a pattern string into a RegExp for matching. * * Converts wildcard patterns into regular expressions: * - `widget.*` → `/^widget\.(.*?)$/` * - `*.show` → `/^(.*?)\.show$/` * - `*` → `/^(.*?)$/` * * @param pattern - Pattern string with optional wildcards * @returns Compiled RegExp for matching event names * @private */ private compilePattern(pattern: string): RegExp { // Escape all regex special characters const escaped = this.escapeRegExp(pattern); // Replace escaped asterisks with wildcard capture groups const withWildcards = escaped.replace(/\\\*/g, '(.*?)'); // Anchor to start and end return new RegExp(`^${withWildcards}$`); } /** * Escape special regex characters in a string. * * @param str - String to escape * @returns Escaped string safe for use in RegExp * @private */ private escapeRegExp(str: string): string { return str.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&'); } }