import { log } from "builder-util" import { CancellationToken, Nullish } from "builder-util-runtime" type Handler = (...args: any[]) => Promise | void export type HandlerType = "system" | "user" type Handle = { handler: Handler; type: HandlerType } export type EventMap = { [key: string]: Handler } interface TypedEventEmitter { on(event: E, listener: Events[E] | Nullish, type: HandlerType): this off(event: E, listener: Events[E] | Nullish): this emit(event: E, ...args: Parameters): Promise<{ emittedSystem: boolean; emittedUser: boolean }> filterListeners(event: E, type: HandlerType): Handle[] clear(): void } export class AsyncEventEmitter implements TypedEventEmitter { private readonly listeners: Map = new Map() private readonly cancellationToken = new CancellationToken() on(event: E, listener: T[E] | Nullish, type: HandlerType = "system"): this { if (!listener) { return this } const listeners = this.listeners.get(event) ?? [] listeners.push({ handler: listener, type }) this.listeners.set(event, listeners) return this } off(event: E, listener: T[E] | Nullish): this { const listeners = this.listeners.get(event)?.filter(l => l.handler !== listener) if (!listeners?.length) { this.listeners.delete(event) return this } this.listeners.set(event, listeners) return this } async emit(event: E, ...args: Parameters): Promise<{ emittedSystem: boolean; emittedUser: boolean }> { const result = { emittedSystem: false, emittedUser: false } const eventListeners = this.listeners.get(event) || [] if (!eventListeners.length) { log.debug({ event }, "no event listeners found") return result } const emitInternal = async (listeners: Handle[]) => { for (const listener of listeners) { if (this.cancellationToken.cancelled) { return false } const handler = await Promise.resolve(listener.handler) await Promise.resolve(handler?.(...args)) } return true } result.emittedSystem = await emitInternal(eventListeners.filter(l => l.type === "system")) // user handlers are always last result.emittedUser = await emitInternal(eventListeners.filter(l => l.type === "user")) return result } filterListeners(event: E, type: HandlerType | undefined): Handle[] { const listeners = this.listeners.get(event) ?? [] return type ? listeners.filter(l => l.type === type) : listeners } clear() { this.listeners.clear() } }