UNPKG

@akala/core

Version:
242 lines 8.74 kB
import { AsyncTeardownManager } from "../teardown-manager.js"; /** * Event class to manage listeners and emit events. * @template T * @template TReturnType * @template TOptions * @extends {TeardownManager} * @implements {IEvent<T, TReturnType, TOptions>} */ export class Event extends AsyncTeardownManager { maxListeners; combineReturnTypes; /** * Combines named events into a single event. * @template T * @param {T} obj - The object containing named events. * @returns {IEvent<[{ [K in keyof T]: T[K] extends IEventSink<infer X, unknown, unknown> ? X : T[K] }], void>} - The combined event. */ static combineNamed(obj) { const entries = Object.entries(obj); return new PipeEvent(Event.combine(...entries.map(e => e[1])), (...ev) => { return [Object.fromEntries(entries.map((e, i) => [e[0], ev[i]]))]; }, null); } /** * Combines multiple events into a single event. * @template T * @param {...T} events - The events to combine. * @returns {IEventSink<T, void>} - The combined event. */ static combine(...events) { const combinedEvent = new ReplayEvent(1, Event.maxListeners); let values; events = events.map(b => b instanceof Event ? b : new ReplayEvent(1, Event.maxListeners)); events.forEach((event, index) => { combinedEvent.teardown(event.addListener((...ev) => { if (!values) values = []; values[index] = ev; combinedEvent.emit(...values); })); }); return combinedEvent; } /** * Creates an instance of Event. * @param {number} [maxListeners=Event.maxListeners] - The maximum number of listeners. * @param {(args: TReturnType[]) => TReturnType} [combineReturnTypes] - Function to combine return types. */ constructor(maxListeners = Event.maxListeners, combineReturnTypes) { super(); this.maxListeners = maxListeners; this.combineReturnTypes = combineReturnTypes; } /** * Clones the event. * @returns {Event<T, TReturnType, TOptions>} - The cloned event. */ clone() { const result = new Event(this.maxListeners, this.combineReturnTypes); result.listeners.push(...this.listeners); return result; } static maxListeners = 10; listeners = []; /** * Checks if the event has listeners. * @returns {boolean} - True if the event has listeners, false otherwise. */ get hasListeners() { return !!this.listeners.length; } /** * Adds a listener to the event. * @param {Listener<T, TReturnType>} listener - The listener to add. * @param {TOptions} [options] - The event options. * @returns {Subscription} - The subscription. */ addListener(listener, options) { if (this.maxListeners && this.listeners.length > this.maxListeners) throw new Error('Possible memory leak: too many listeners are registered'); if (options?.once) { const stopListening = this.addListener((...args) => { stopListening(); return listener(...args); }); return stopListening; } else this.listeners.push(listener); return this.teardown(() => this.removeListener(listener)); } /** * Removes a listener from the event. * @param {Listener<T, TReturnType>} listener - The listener to remove. * @returns {boolean} - True if the listener was removed, false otherwise. */ removeListener(listener) { if (typeof listener == 'undefined') return !this.listeners.splice(0, this.listeners.length).length; const indexOfListener = this.listeners.indexOf(listener); return indexOfListener > -1 && !!this.listeners.splice(indexOfListener, 1).length; } /** * Emits the event. * @param {...T} args - The arguments to pass to the listeners. * @returns {TReturnType} - The return value of the listeners. */ emit(...args) { const results = this.listeners.slice(0).map(listener => listener(...args)); if (this.combineReturnTypes) return this.combineReturnTypes(results); } /** * Pipes the event to another event or emitter. * @param {IEvent<T, TReturnType, TOptions>} event - The event to pipe to. * @returns {Subscription} - The subscription. */ pipe(event) { switch (typeof event) { case 'function': const mapEvent = new Event(); mapEvent.teardown(this.teardown(this.addListener((...args) => { return mapEvent.emit(...event(...args)); }))); return mapEvent; case 'object': return this.addListener((...args) => { return event.emit(...args); }); default: throw new Error('unsupported pipe type'); } } /** * Disposes the event. */ [Symbol.dispose]() { super[Symbol.dispose](); this.listeners.length = 0; } } /** * PipeEvent class to map and pipe events. * @template T * @template U * @template TReturnType * @template TOptions * @extends {Event<U, TReturnType, TOptions>} */ export class PipeEvent extends Event { source; map; subscription; /** * Creates an instance of PipeEvent. * @param {IEventSink<T, TReturnType, TOptions>} source - The source event sink. * @param {(...args: T) => U} map - Function to map arguments. * @param {(results: TReturnType[]) => TReturnType} combineResults - Function to combine return types. */ constructor(source, map, combineResults) { super(source.maxListeners, combineResults); this.source = source; this.map = map; } /** * Subscribes to the source event if required. */ subscribeToSourceIfRequired() { if (!this.subscription) this.subscription = this.source.addListener((...args) => super.emit(...this.map(...args))); } /** * Adds a listener to the pipe event. * @param {Listener<U, TReturnType>} listener - The listener to add. * @param {TOptions} [options] - The event options. * @returns {Subscription} - The subscription. */ addListener(listener, options) { this.subscribeToSourceIfRequired(); return super.addListener(listener, options); } /** * Removes a listener from the pipe event. * @param {Listener<U, TReturnType>} listener - The listener to remove. * @returns {boolean} - True if the listener was removed, false otherwise. */ removeListener(listener) { const result = super.removeListener(listener); if (result && !this.hasListeners && this.subscription) { this.subscription(); this.subscription = null; } return result; } } /** * ReplayEvent class to manage events with a buffer. * @template T * @template TReturnType * @extends {Event<T, TReturnType>} */ export class ReplayEvent extends Event { bufferLength; buffer = []; /** * Creates an instance of ReplayEvent. * @param {number} bufferLength - The length of the buffer. * @param {number} maxListeners - The maximum number of listeners. * @param {(args: TReturnType[]) => TReturnType} [combineReturnTypes] - Function to combine return types. */ constructor(bufferLength, maxListeners, combineReturnTypes) { super(maxListeners, combineReturnTypes); this.bufferLength = bufferLength; } /** * Emits the event and stores the arguments in the buffer. * @param {...T} args - The arguments to pass to the listeners. * @returns {TReturnType} - The return value of the listeners. */ emit(...args) { this.buffer.push(args); while (this.buffer.length > this.bufferLength) this.buffer.shift(); return super.emit(...args); } /** * Adds a listener to the event and replays the buffered events. * @param {Listener<T, TReturnType>} listener - The listener to add. * @param {{ once?: boolean }} [options] - The event options. * @returns {Subscription} - The subscription. */ addListener(listener, options) { if (options?.once && this.buffer.length > 0) { listener(...this.buffer[0]); return () => true; } this.buffer.forEach(args => listener(...args)); return super.addListener(listener, options); } } //# sourceMappingURL=shared.js.map