{"version":3,"file":"index.mjs","names":["#listeners","#addListener","#proxyEvent","#matchListeners","#callListener","#isTypelessListener"],"sources":["../src/index.ts"],"sourcesContent":["import { LensList } from './lens-list'\n\nexport type DefaultEventMap = {\n [eventType: string]: TypedEvent\n}\n\n/**\n * Reserved event map containing special event types like '*' for catch-all listeners.\n */\nexport type ReservedEventMap = {\n '*': TypedEvent\n}\n\ntype IsReservedEvent = Type extends keyof ReservedEventMap\n ? true\n : false\n\nexport interface TypedEvent<\n DataType = void,\n ReturnType = void,\n EventType extends string = string,\n> extends Omit, 'type'> {\n type: EventType\n}\n\nconst kDefaultPrevented = Symbol('kDefaultPrevented')\nconst kPropagationStopped = Symbol('kPropagationStopped')\nconst kImmediatePropagationStopped = Symbol('kImmediatePropagationStopped')\n\nexport class TypedEvent<\n DataType = void,\n ReturnType = void,\n EventType extends string = string,\n>\n extends MessageEvent\n implements TypedEvent\n{\n /**\n * @note Keep a placeholder property with the return type\n * because the type must be set somewhere in order to be\n * correctly associated and inferred from the event.\n */\n #returnType: ReturnType;\n\n [kDefaultPrevented]: boolean;\n [kPropagationStopped]?: Emitter;\n [kImmediatePropagationStopped]?: boolean\n\n constructor(\n ...args: [DataType] extends [void]\n ? [type: EventType]\n : [type: EventType, init: { data: DataType }]\n ) {\n super(args[0], args[1])\n this[kDefaultPrevented] = false\n }\n\n get defaultPrevented(): boolean {\n return this[kDefaultPrevented]\n }\n\n public preventDefault(): void {\n super.preventDefault()\n this[kDefaultPrevented] = true\n }\n\n public stopImmediatePropagation(): void {\n /**\n * @note Despite `.stopPropagation()` and `.stopImmediatePropagation()` being defined\n * in Node.js, they do nothing. It is safe to re-define them.\n */\n super.stopImmediatePropagation()\n this[kImmediatePropagationStopped] = true\n }\n}\n\n/**\n * Brands a TypedEvent or its subclass while preserving its (narrower) type.\n */\ntype Brand<\n Event extends TypedEvent,\n EventType extends string,\n Loose extends boolean = false,\n> = Loose extends true\n ? Event extends TypedEvent\n ? /**\n * @note Omit the `ReturnType` so emit methods can accept type events\n * where infering the return type is impossible.\n */\n TypedEvent & {\n type: EventType\n }\n : never\n : Event & { type: EventType }\n\ntype InferEventMap> =\n Target extends Emitter ? MergedEventMap : never\n\n/**\n * Extracts only user-defined events, excluding reserved event types.\n */\ntype UserEventMap = Omit<\n EventMap,\n keyof ReservedEventMap\n>\n\n/**\n * Merges the user EventMap with the ReservedEventMap.\n * The '*' event type accepts a union of all user-defined events.\n */\ntype MergedEventMap = EventMap &\n ReservedEventMap\n\n/**\n * Creates a union of all events in the EventMap with their literal type strings.\n */\ntype AllEvents = {\n [K in keyof EventMap & string]: Brand\n}[keyof EventMap & string]\n\nexport type TypedListenerOptions = {\n once?: boolean\n signal?: AbortSignal\n}\n\nconst kListenerOptions = Symbol('kListenerOptions')\n\nexport namespace Emitter {\n /**\n * Returns an appropriate `Event` type for the given event type.\n *\n * @example\n * const emitter = new Emitter<{ greeting: TypedEvent }>()\n * type GreetingEvent = Emitter.InferEventType\n * // TypedEvent\n */\n export type EventType<\n Target extends Emitter,\n EventType extends keyof EventMap & string,\n EventMap extends DefaultEventMap = InferEventMap,\n > =\n IsReservedEvent extends true\n ? AllEvents>\n : Brand\n\n export type EventDataType<\n Target extends Emitter,\n EventType extends keyof EventMap & string,\n EventMap extends DefaultEventMap = InferEventMap,\n > = EventMap[EventType] extends TypedEvent ? DataType : never\n\n /**\n * Returns the listener type for the given event type.\n *\n * @example\n * const emitter = new Emitter<{ getTotalPrice: TypedEvent }>()\n * type Listener = Emitter.ListenerType\n * // (event: TypedEvent) => number\n */\n export type ListenerType<\n Target extends Emitter,\n EventType extends keyof EventMap & string,\n EventMap extends DefaultEventMap = InferEventMap,\n > =\n IsReservedEvent extends true\n ? (event: AllEvents>) => void\n : (\n event: Emitter.EventType,\n ) => Emitter.ListenerReturnType extends [\n void,\n ]\n ? void\n : Emitter.ListenerReturnType\n\n /**\n * Returns the return type of the listener for the given event type.\n *\n * @example\n * const emitter = new Emitter<{ getTotalPrice: TypedEvent }>()\n * type ListenerReturnType = Emitter.InferListenerReturnType\n * // number\n */\n export type ListenerReturnType<\n Target extends Emitter,\n EventType extends keyof EventMap & string,\n EventMap extends DefaultEventMap = InferEventMap,\n > =\n IsReservedEvent extends true\n ? void\n : EventMap[EventType] extends TypedEvent\n ? ReturnType\n : never\n}\n\nexport class Emitter {\n #listeners: LensList<\n Emitter.ListenerType<\n typeof this,\n keyof MergedEventMap & string,\n MergedEventMap\n >\n >\n\n constructor() {\n this.#listeners = new LensList()\n }\n\n /**\n * Adds a listener for the given event type.\n */\n public on & string>(\n type: EventType,\n listener: Emitter.ListenerType<\n typeof this,\n EventType,\n MergedEventMap\n >,\n options?: TypedListenerOptions,\n ): typeof this {\n this.#addListener(type, listener, options)\n return this\n }\n\n /**\n * Adds a one-time listener for the given event type.\n */\n public once & string>(\n type: EventType,\n listener: Emitter.ListenerType<\n typeof this,\n EventType,\n MergedEventMap\n >,\n options?: Omit,\n ): typeof this {\n return this.on(type, listener, {\n ...(options || {}),\n once: true,\n })\n }\n\n /**\n * Prepends a listener for the given event type.\n */\n public earlyOn & string>(\n type: EventType,\n listener: Emitter.ListenerType<\n typeof this,\n EventType,\n MergedEventMap\n >,\n options?: TypedListenerOptions,\n ): typeof this {\n this.#addListener(type, listener, options, 'prepend')\n return this\n }\n\n /**\n * Prepends a one-time listener for the given event type.\n */\n public earlyOnce & string>(\n type: EventType,\n listener: Emitter.ListenerType<\n typeof this,\n EventType,\n MergedEventMap\n >,\n options?: Omit,\n ): typeof this {\n return this.earlyOn(type, listener, {\n ...(options || {}),\n once: true,\n })\n }\n\n /**\n * Emits the given typed event.\n *\n * @returns {boolean} Returns `true` if the event had any listeners, `false` otherwise.\n */\n public emit(\n event: Brand,\n ): boolean {\n if (this.#listeners.size === 0) {\n return false\n }\n\n /**\n * @note Calculate matching listeners before calling them\n * since one-time listeners will self-destruct.\n */\n const hasListeners = this.listenerCount(event.type) > 0\n\n const proxiedEvent = this.#proxyEvent(event)\n\n for (const listener of this.#matchListeners(event.type)) {\n if (\n proxiedEvent.event[kPropagationStopped] != null &&\n proxiedEvent.event[kPropagationStopped] !== this\n ) {\n proxiedEvent.revoke()\n return false\n }\n\n if (proxiedEvent.event[kImmediatePropagationStopped]) {\n break\n }\n\n this.#callListener(proxiedEvent.event, listener)\n }\n\n proxiedEvent.revoke()\n\n return hasListeners\n }\n\n /**\n * Emits the given typed event and returns a promise that resolves\n * when all the listeners for that event have settled.\n *\n * @returns {Promise>} A promise that resolves\n * with the return values of all listeners.\n */\n public async emitAsPromise(\n event: Brand,\n ): Promise<\n Array>\n > {\n if (this.#listeners.size === 0) {\n return []\n }\n\n const pendingListeners: Array<\n Promise>\n > = []\n\n const proxiedEvent = this.#proxyEvent(event)\n\n for (const listener of this.#matchListeners(event.type)) {\n if (\n proxiedEvent.event[kPropagationStopped] != null &&\n proxiedEvent.event[kPropagationStopped] !== this\n ) {\n proxiedEvent.revoke()\n return []\n }\n\n if (proxiedEvent.event[kImmediatePropagationStopped]) {\n break\n }\n\n const listenerPromise = Promise.resolve(\n this.#callListener(proxiedEvent.event, listener),\n )\n\n const returnValue = await listenerPromise\n\n if (!this.#isTypelessListener(listener)) {\n pendingListeners.push(returnValue)\n }\n }\n\n proxiedEvent.revoke()\n\n return Promise.allSettled(pendingListeners).then((results) => {\n return results.map((result) =>\n result.status === 'fulfilled' ? result.value : result.reason,\n )\n })\n }\n\n /**\n * Emits the given event and returns a generator that yields\n * the result of each listener in the order of their registration.\n * This way, you stop exhausting the listeners once you get the expected value.\n */\n public *emitAsGenerator(\n event: Brand,\n ): Generator> {\n if (this.#listeners.size === 0) {\n return\n }\n\n const proxiedEvent = this.#proxyEvent(event)\n\n for (const listener of this.#matchListeners(event.type)) {\n if (\n proxiedEvent.event[kPropagationStopped] != null &&\n proxiedEvent.event[kPropagationStopped] !== this\n ) {\n proxiedEvent.revoke()\n return\n }\n\n if (proxiedEvent.event[kImmediatePropagationStopped]) {\n break\n }\n\n const returnValue = this.#callListener(proxiedEvent.event, listener)\n\n if (!this.#isTypelessListener(listener)) {\n yield returnValue\n }\n }\n\n proxiedEvent.revoke()\n }\n\n /**\n * Removes a listener for the given event type.\n */\n public removeListener<\n EventType extends keyof MergedEventMap & string,\n >(\n type: EventType,\n listener: Emitter.ListenerType<\n typeof this,\n EventType,\n MergedEventMap\n >,\n ): void {\n this.#listeners.delete(type, listener)\n }\n\n /**\n * Removes all listeners for the given event type.\n * If no event type is provided, removes all existing listeners.\n */\n public removeAllListeners<\n EventType extends keyof MergedEventMap & string,\n >(type?: EventType): void {\n if (type == null) {\n this.#listeners.clear()\n return\n }\n\n this.#listeners.deleteAll(type)\n }\n\n /**\n * Returns the list of listeners for the given event type.\n * If no even type is provided, returns all listeners.\n */\n public listeners & string>(\n type?: EventType,\n ): Array<\n Emitter.ListenerType>\n > {\n if (type == null) {\n return this.#listeners.getAll()\n }\n\n return this.#listeners.get(type)\n }\n\n /**\n * Returns the number of listeners for the given event type.\n * If no even type is provided, returns the total number of listeners.\n */\n public listenerCount<\n EventType extends keyof MergedEventMap & string,\n >(type?: EventType): number {\n if (type == null) {\n return this.#listeners.size\n }\n\n return this.listeners(type).length\n }\n\n #addListener & string>(\n type: EventType,\n listener: Emitter.ListenerType<\n typeof this,\n EventType,\n MergedEventMap\n >,\n options: TypedListenerOptions | undefined,\n insertMode: 'append' | 'prepend' = 'append',\n ): void {\n if (insertMode === 'prepend') {\n this.#listeners.prepend(type, listener)\n } else {\n this.#listeners.append(type, listener)\n }\n\n if (options) {\n Object.defineProperty(listener, kListenerOptions, {\n value: options,\n enumerable: false,\n writable: false,\n })\n\n if (options.signal) {\n options.signal.addEventListener(\n 'abort',\n () => {\n this.removeListener(type, listener)\n },\n { once: true },\n )\n }\n }\n }\n\n #proxyEvent(\n event: Event,\n ): { event: Event; revoke: () => void } {\n const { stopPropagation } = event\n\n event.stopPropagation = new Proxy(event.stopPropagation, {\n apply: (target, thisArg, argArray) => {\n event[kPropagationStopped] = this\n return Reflect.apply(target, thisArg, argArray)\n },\n })\n\n return {\n event,\n revoke() {\n event.stopPropagation = stopPropagation\n },\n }\n }\n\n #callListener(\n event: Event,\n listener: ((event: any) => any) & {\n [kListenerOptions]?: TypedListenerOptions\n },\n ) {\n const returnValue = listener.call(this, event)\n\n if (listener[kListenerOptions]?.once) {\n const key = this.#isTypelessListener(listener) ? '*' : event.type\n this.#listeners.delete(key, listener)\n }\n\n return returnValue\n }\n\n /**\n * Return a list of all event listeners relevant for the given event type.\n * This includes the explicit event listeners and also typeless event listeners.\n */\n *#matchListeners(type: EventType) {\n for (const [key, listener] of this.#listeners) {\n if (key === '*' || key === type) {\n yield listener\n }\n }\n }\n\n #isTypelessListener(listener: any): boolean {\n return this.#listeners.get('*').includes(listener)\n }\n}\n"],"mappings":"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"}