import { Signal } from './signal.js'; /** * Basic type safe event emitter * @example * ```ts * const ms = new EventEmitter<{ onChange: { id: 'onChange' }; onDelete: { id: 'onDelete' } }>(); * ms.subscribe('onChange', (event) => { * event.id; // 'onChange' * }); * ms.subscribe('onDelete', (event) => { * event.id; // 'onDelete' * }); * * ms.notify('onChange', { id: 'onChange' }); // event is type safe * ``` * @example payload type mismatch * ```ts * ms.notify('onChange', { id: 'onDelete' }); // ERROR!!! * ``` * @example payload type mismatch * ```ts * ms.notify('onSomethingElse', { id: 'onDelete' }); // ERROR!!! * ``` */ export class EventEmitter { protected events = new Map>(); /** * Check if an event has subscribers * @returns true if event has subscribers */ hasSubscribers = (event: EventId) => this.events.has(event); /** * Subscribe a handler for event * * @returns unsubscribe fn */ subscribe = (event: Event, handler: (data: Events[Event]) => void) => { const bucket = this.events.get(event); bucket ? bucket.subscribe(handler) : this.events.set(event, new Signal([handler])); return () => this.unsubscribe(event, handler); }; /** * {@inheritDoc EventEmitter.subscribe} */ on = this.subscribe; /** * Adds a handler that will be called at most once. * * @returns unsubscribe fn */ once = (event: Event, handler: (data: Events[Event]) => void) => { let bucket = this.events.get(event); if (!bucket) { bucket = new Signal(); this.events.set(event, bucket); } bucket.once(handler); return () => this.unsubscribe(event, handler); }; /** * Unsubscribe a handler from event */ unsubscribe = (event: Event, handler: (data: Events[Event]) => void) => { const bucket = this.events.get(event); bucket?.unsubscribe(handler); if (bucket?.size === 0) { this.events.delete(event); } }; /** * {@inheritDoc EventEmitter.unsubscribe} */ off = this.unsubscribe; /** * Drop all subscriptions of a signal * @param event - signal id */ delete = (event: Event) => { this.events.delete(event); }; /** * Drop all subscriptions */ clear = () => { this.events.forEach((bucket) => bucket.clear()); this.events = new Map>(); }; /** * {@inheritDoc Signal.notify} * @param event - eve * @param data - event data */ notify = (event: Event, data: Events[Event]) => { this.events.get(event)?.notify(data); }; /** * {@inheritDoc EventEmitter.notify} */ emit = this.notify; } export type IEventEmitter> = Pick, keyof EventEmitter>;