@akala/core
Version:
242 lines • 8.74 kB
JavaScript
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