@boost/event
Version:
An event system with multiple emitter patterns.
125 lines (99 loc) • 2.88 kB
text/typescript
import { EVENT_NAME_PATTERN, WILDCARD_SCOPE } from './constants';
import { debug } from './debug';
import { EventError } from './EventError';
import type { Listener, Unlistener, WildstarScope } from './types';
export abstract class BaseEvent<Return, Args extends unknown[], Scope extends string = string> {
listeners = new Map<Scope | WildstarScope, Set<Listener<Args, Return>>>();
name: string;
constructor(name: string) {
this.name = this.validateName(name, 'name');
if (__DEV__) {
debug('New %S created: %s', this.constructor.name, name);
}
}
/**
* Remove all listeners from the event.
*/
clearListeners(scope?: Scope): this {
if (scope) {
this.getListeners(scope).clear();
} else {
this.listeners.clear();
}
return this;
}
/**
* Return a set of listeners for a specific event scope.
*/
getListeners(scope?: Scope): Set<Listener<Args, Return>> {
const key = this.validateName(scope ?? WILDCARD_SCOPE, 'scope');
if (!this.listeners.has(key)) {
this.listeners.set(key, new Set());
}
return this.listeners.get(key)!;
}
/**
* Return a list of all scopes with listeners.
*/
getScopes(): (Scope | WildstarScope)[] {
return [...this.listeners.keys()];
}
/**
* Register a listener to the event.
*/
listen(listener: Listener<Args, Return>, scope?: Scope): Unlistener {
if (__DEV__) {
debug('Registering "%s" listener', this.name);
}
this.getListeners(scope).add(this.validateListener(listener));
return () => {
this.unlisten(listener, scope);
};
}
/**
* Register a listener to the event that only triggers once.
*/
once(listener: Listener<Args, Return>, scope?: Scope): Unlistener {
const func = this.validateListener(listener);
const handler = ((...args: unknown[]) => {
this.unlisten(handler);
return func(...args);
}) as Listener<Args, Return>;
return this.listen(handler, scope);
}
/**
* Remove a listener from the event.
*/
unlisten(listener: Listener<Args, Return>, scope?: Scope): this {
if (__DEV__) {
debug('Unregistering "%s" listener', this.name);
}
this.getListeners(scope).delete(listener);
return this;
}
/**
* Validate the listener is a function.
*/
protected validateListener<L>(listener: L): L {
if (__DEV__ && typeof listener !== 'function') {
throw new EventError('LISTENER_INVALID', [this.name]);
}
return listener;
}
/**
* Validate the name/scope match a defined pattern.
*/
protected validateName<N extends string>(name: N, type: string): N {
if (type === 'scope' && name === WILDCARD_SCOPE) {
return name;
}
if (__DEV__ && !name.match(EVENT_NAME_PATTERN)) {
throw new EventError('NAME_INVALID', [type, name]);
}
return name;
}
/**
* Emit the event by executing all scoped listeners with the defined arguments.
*/
abstract emit(args: unknown, scope?: Scope): unknown;
}