// node:events, in Node's own shape: a function constructor (so `EventEmitter.call(this)` // and prototype mixins work), listeners stored on `this._events` as a function or an // array, once-wrappers carrying `.listener`. An 'error' with no listener throws. import { AsyncResource } from "./async_hooks"; import { nextTick } from "./ticks"; type Listener = (...args: any[]) => void; type Wrapped = Listener & { listener?: Listener }; type Key = string | symbol; export interface EventEmitter { _events: Record | undefined; _eventsCount: number; _maxListeners: number | undefined; /** Node's kCapture, under our own name since nothing outside this file keys off * the symbol's identity. */ _captureRejections: boolean; on(event: Key, fn: Listener): this; addListener(event: Key, fn: Listener): this; prependListener(event: Key, fn: Listener): this; once(event: Key, fn: Listener): this; prependOnceListener(event: Key, fn: Listener): this; off(event: Key, fn: Listener): this; removeListener(event: Key, fn: Listener): this; removeAllListeners(event?: Key): this; emit(event: Key, ...args: any[]): boolean; listeners(event: Key): Listener[]; rawListeners(event: Key): Listener[]; listenerCount(event: Key, fn?: Listener): number; eventNames(): Key[]; setMaxListeners(n: number): this; getMaxListeners(): number; } export interface EventEmitterConstructor { new (options?: { captureRejections?: boolean }): EventEmitter; (this: unknown, options?: unknown): void; prototype: EventEmitter; EventEmitter: EventEmitterConstructor; defaultMaxListeners: number; usingDomains: boolean; readonly errorMonitor: symbol; readonly captureRejectionSymbol: symbol; captureRejections: boolean; once: typeof once; on: typeof on; getEventListeners: typeof getEventListeners; getMaxListeners: typeof getMaxListeners; setMaxListeners: typeof setMaxListeners; addAbortListener: typeof addAbortListener; EventEmitterAsyncResource: typeof EventEmitterAsyncResource; listenerCount(emitter: EventEmitter, event: Key): number; init(this: EventEmitter, options?: { captureRejections?: boolean }): void; } function unhandledError(er: unknown): Error { if (er instanceof Error) return er; let shown: string; try { shown = typeof er === "string" ? `'${er}'` : String(er); } catch { shown = "[object]"; } return Object.assign(new Error(`Unhandled error. (${shown})`), { code: "ERR_UNHANDLED_ERROR", context: er }); } function checkListener(fn: unknown): asserts fn is Listener { if (typeof fn !== "function") { throw Object.assign(new TypeError(`The "listener" argument must be of type function. Received ${typeof fn}`), { code: "ERR_INVALID_ARG_TYPE", }); } } function eventsOf(self: EventEmitter): Record { if (self._events === undefined || self._events === Object.getPrototypeOf(self)._events) { self._events = Object.create(null) as Record; self._eventsCount = 0; } return self._events; } function listOf(self: EventEmitter, event: Key): Wrapped[] { const entry = self._events?.[event]; return entry === undefined ? [] : typeof entry === "function" ? [entry] : entry; } function store(self: EventEmitter, event: Key, list: Wrapped[]): void { const events = eventsOf(self); const had = events[event] !== undefined; if (list.length === 0) { delete events[event]; if (had) self._eventsCount--; } else { events[event] = list.length === 1 ? list[0]! : list; if (!had) self._eventsCount++; } } function add(self: EventEmitter, event: Key, fn: Wrapped, prepend: boolean): EventEmitter { checkListener(fn.listener ?? fn); if (self._events?.newListener !== undefined) self.emit("newListener", event, fn.listener ?? fn); const list = [...listOf(self, event)]; if (prepend) list.unshift(fn); else list.push(fn); store(self, event, list); return self; } function onceWrapper(self: EventEmitter, event: Key, fn: Listener): Wrapped { checkListener(fn); let fired = false; const wrapped: Wrapped = function (this: unknown, ...args: unknown[]) { if (fired) return undefined; fired = true; self.removeListener(event, wrapped); return fn.apply(this, args); }; wrapped.listener = fn; return wrapped; } function invalidArgType(name: string, expected: string, value: unknown): TypeError { const got = value === null ? "null" : value === undefined ? "undefined" : `type ${typeof value}`; return Object.assign(new TypeError(`The "${name}" argument must be ${expected}. Received ${got}`), { code: "ERR_INVALID_ARG_TYPE" }); } function init(this: EventEmitter, opts?: { captureRejections?: boolean }): void { eventsOf(this); this._maxListeners ??= undefined; if (opts?.captureRejections !== undefined) { if (typeof opts.captureRejections !== "boolean") throw invalidArgType("options.captureRejections", "of type boolean", opts.captureRejections); this._captureRejections = opts.captureRejections; } else { this._captureRejections = EventEmitter.prototype._captureRejections; } } export const EventEmitter = function EventEmitter(this: EventEmitter, opts?: { captureRejections?: boolean }) { init.call(this, opts); } as unknown as EventEmitterConstructor; EventEmitter.init = init; EventEmitter.EventEmitter = EventEmitter; EventEmitter.defaultMaxListeners = 10; EventEmitter.usingDomains = false; Object.defineProperty(EventEmitter, "errorMonitor", { value: Symbol("events.errorMonitor"), enumerable: true }); Object.defineProperty(EventEmitter, "captureRejectionSymbol", { value: Symbol.for("nodejs.rejection"), enumerable: true }); Object.defineProperty(EventEmitter, "captureRejections", { enumerable: true, get(): boolean { return EventEmitter.prototype._captureRejections; }, set(value: unknown): void { if (typeof value !== "boolean") throw invalidArgType("EventEmitter.captureRejections", "of type boolean", value); EventEmitter.prototype._captureRejections = value; }, }); EventEmitter.listenerCount = (emitter, event) => emitter.listenerCount(event); const proto = EventEmitter.prototype; proto._events = undefined; proto._eventsCount = 0; proto._maxListeners = undefined; proto._captureRejections = false; proto.on = proto.addListener = function (event, fn) { return add(this, event, fn, false); }; proto.prependListener = function (event, fn) { return add(this, event, fn, true); }; // once routes through this.on, as in Node, so a subclass's `on` override (Readable // resuming on 'data') sees once-listeners too. proto.once = function (event, fn) { return this.on(event, onceWrapper(this, event, fn)); }; proto.prependOnceListener = function (event, fn) { return this.prependListener(event, onceWrapper(this, event, fn)); }; proto.off = proto.removeListener = function (event, fn) { checkListener(fn); const list = listOf(this, event); // Node removes the most recently added match, wrapped or not. for (let i = list.length - 1; i >= 0; i--) { const l = list[i]!; if (l !== fn && l.listener !== fn) continue; const next = [...list]; next.splice(i, 1); store(this, event, next); if (this._events?.removeListener !== undefined) this.emit("removeListener", event, l.listener ?? l); break; } return this; }; proto.removeAllListeners = function (event) { // With 'removeListener' listeners, Node removes one at a time (last first) so each // removal is reported, and removes the 'removeListener' listeners themselves last. if (this._events?.removeListener !== undefined) { const keys = event === undefined ? Reflect.ownKeys(this._events).filter((k) => k !== "removeListener") : [event]; for (const key of keys) { for (let list = listOf(this, key); list.length; list = listOf(this, key)) this.removeListener(key, list.at(-1)!); } if (event !== undefined) return this; } if (event === undefined) { this._events = Object.create(null) as Record; this._eventsCount = 0; } else { store(this, event, []); } return this; }; proto.emit = function (event, ...args) { if (event === "error") for (const l of listOf(this, EventEmitter.errorMonitor)) l.apply(this, args); const list = listOf(this, event); if (list.length === 0) { if (event === "error") throw unhandledError(args[0]); return false; } const capture = this._captureRejections; for (const l of list) { const result = l.apply(this, args); if (capture && result && typeof (result as { then?: unknown }).then === "function") { addCaptureCatch(this, result as Promise, event, args); } } return true; }; proto.listeners = function (event) { return listOf(this, event).map((l) => l.listener ?? l); }; proto.rawListeners = function (event) { return [...listOf(this, event)]; }; proto.listenerCount = function (event, fn) { const list = listOf(this, event); return fn ? list.filter((l) => l === fn || l.listener === fn).length : list.length; }; proto.eventNames = function () { return this._events ? Reflect.ownKeys(this._events) : []; }; /** Leak warnings are not implemented; the limit is stored and reported back. */ proto.setMaxListeners = function (n) { this._maxListeners = n; return this; }; proto.getMaxListeners = function () { return this._maxListeners ?? EventEmitter.defaultMaxListeners; }; /** events.once: resolves with the args of the next `event`, rejects on 'error'. */ export function once(emitter: EventEmitter, event: Key): Promise { return new Promise((resolve, reject) => { const onError = (err: unknown): void => { emitter.off(event, onEvent); reject(err); }; const onEvent = (...args: unknown[]): void => { if (event !== "error") emitter.off("error", onError); resolve(args); }; emitter.once(event, onEvent); if (event !== "error") emitter.once("error", onError); }); } EventEmitter.once = once; /** When captureRejections is on, a listener's rejected promise becomes an 'error' * (or the captureRejectionSymbol handler, if the emitter has one) a tick later, * matching Node's addCatch/emitUnhandledRejectionOrErr. */ function addCaptureCatch(emitter: EventEmitter, result: Promise, event: Key, args: unknown[]): void { result.then(undefined, (err: unknown) => { nextTick(() => { const withRejection = emitter as unknown as Record void) | undefined>; const handler = withRejection[EventEmitter.captureRejectionSymbol]; if (typeof handler === "function") { handler.call(emitter, err, event, ...args); return; } const prev = emitter._captureRejections; try { emitter._captureRejections = false; emitter.emit("error", err); } finally { emitter._captureRejections = prev; } }); }); } function invalidArgTypeList(name: string, types: string[], value: unknown): TypeError { const got = value === null ? "null" : value === undefined ? "undefined" : typeof value === "object" ? `an instance of ${(value as object).constructor?.name ?? "Object"}` : `type ${typeof value}`; return Object.assign(new TypeError(`The "${name}" argument must be one of type ${types.join(" or ")}. Received ${got}`), { code: "ERR_INVALID_ARG_TYPE", }); } /** events.getEventListeners: EventEmitter's own list for an EventEmitter. The browser's * native EventTarget keeps no public listener list, so that side refuses (see this * chunk's report: no platform API for it). */ export function getEventListeners(emitterOrTarget: unknown, type: Key): Listener[] { const e = emitterOrTarget as { listeners?: (t: Key) => Listener[] }; if (typeof e.listeners === "function") return e.listeners(type); if (emitterOrTarget instanceof EventTarget) { throw Object.assign( new Error("ERR_NIB_UNSUPPORTED: node:events getEventListeners() is not supported for a native EventTarget (no public listener list)"), { code: "ERR_NIB_UNSUPPORTED" }, ); } throw invalidArgTypeList("emitter", ["EventEmitter", "EventTarget"], emitterOrTarget); } const targetMaxListeners = new WeakMap(); /** events.getMaxListeners: the EventEmitter's own limit, or the stored (unenforced) * limit for a native EventTarget — see proto.setMaxListeners's comment. */ export function getMaxListeners(emitterOrTarget: unknown): number { const e = emitterOrTarget as { getMaxListeners?: () => number }; if (typeof e.getMaxListeners === "function") return e.getMaxListeners(); if (emitterOrTarget instanceof EventTarget) return targetMaxListeners.get(emitterOrTarget) ?? EventEmitter.defaultMaxListeners; throw invalidArgTypeList("emitter", ["EventEmitter", "EventTarget"], emitterOrTarget); } function validateNumber(n: unknown, name: string): asserts n is number { if (typeof n !== "number") throw invalidArgTypeList(name, ["number"], n); if (Number.isNaN(n) || n < 0) { throw Object.assign(new RangeError(`The value of "${name}" is out of range. It must be >= 0. Received ${n}`), { code: "ERR_OUT_OF_RANGE" }); } } /** events.setMaxListeners(n, ...targets): targets' own limit, or (with none) * EventEmitter.defaultMaxListeners for every emitter that hasn't set its own. */ export function setMaxListeners(n: number = EventEmitter.defaultMaxListeners, ...eventTargets: unknown[]): void { validateNumber(n, "setMaxListeners"); if (eventTargets.length === 0) { EventEmitter.defaultMaxListeners = n; return; } for (const target of eventTargets) { if (target instanceof EventTarget) { targetMaxListeners.set(target, n); } else if (typeof (target as { setMaxListeners?: unknown }).setMaxListeners === "function") { (target as EventEmitter).setMaxListeners(n); } else { throw invalidArgTypeList("eventTargets", ["EventEmitter", "EventTarget"], target); } } } /** events.addAbortListener: fires once, immediately if already aborted; returns a * disposable that removes the listener. */ export function addAbortListener(signal: AbortSignal, listener: (this: AbortSignal, ev: Event) => void): { [Symbol.dispose](): void } { if (signal === undefined) throw invalidArgTypeList("signal", ["AbortSignal"], signal); if (!(signal instanceof AbortSignal)) throw invalidArgTypeList("signal", ["AbortSignal"], signal); if (typeof listener !== "function") throw invalidArgTypeList("listener", ["function"], listener); let remove: (() => void) | undefined; if (signal.aborted) { queueMicrotask(() => (listener as unknown as () => void).call(signal)); } else { const handler = (): void => (listener as unknown as () => void).call(signal); signal.addEventListener("abort", handler, { once: true }); remove = () => signal.removeEventListener("abort", handler); } return { [Symbol.dispose]: () => remove?.() }; } EventEmitter.addAbortListener = addAbortListener; const kWatermarkData = Symbol.for("nodejs.watermarkData"); /** events.on(emitter, event, options): an async iterator of an event's arguments, * queued between reads, pausing the emitter past highWaterMark if it is Readable-like * (Node calls emitter.pause()/resume(), which only exist on streams). */ export function on(emitter: EventEmitter, event: Key, options: { signal?: AbortSignal; highWaterMark?: number; lowWaterMark?: number; close?: Key[]; } = {}): AsyncIterableIterator { const signal = options.signal; if (signal !== undefined && !(signal instanceof AbortSignal)) throw invalidArgTypeList("options.signal", ["AbortSignal"], signal); if (signal?.aborted) throw Object.assign(new Error("The operation was aborted"), { name: "AbortError", code: "ABORT_ERR", cause: signal.reason }); const highWaterMark = options.highWaterMark ?? Number.MAX_SAFE_INTEGER; const lowWaterMark = options.lowWaterMark ?? 1; const queue: unknown[][] = []; const waiting: Array<{ resolve: (r: IteratorResult) => void; reject: (e: unknown) => void }> = []; let paused = false; let error: unknown = null; let finished = false; const done = (): IteratorResult => ({ value: undefined, done: true }); const pausable = emitter as unknown as { pause?: () => void; resume?: () => void }; const onEvent = (...args: unknown[]): void => { if (waiting.length) { waiting.shift()!.resolve({ value: args, done: false }); return; } queue.push(args); if (!paused && queue.length > highWaterMark) { paused = true; pausable.pause?.(); } }; const onError = (err: unknown): void => { if (waiting.length) waiting.shift()!.reject(err); else error = err; void iterator.return!(); }; const closeHandler = (): Promise> => { removeAll(); finished = true; paused = false; const result = done(); while (waiting.length) waiting.shift()!.resolve(result); return Promise.resolve(result); }; const removeAll = (): void => { emitter.removeListener(event, onEvent); if (event !== "error") emitter.removeListener("error", onError); for (const closeEvent of options.close ?? []) emitter.removeListener(closeEvent, closeHandler); abortDisposable?.[Symbol.dispose](); }; emitter.on(event, onEvent); if (event !== "error") emitter.on("error", onError); for (const closeEvent of options.close ?? []) emitter.on(closeEvent, closeHandler); const abortDisposable = signal ? addAbortListener(signal, () => onError(Object.assign( new Error("The operation was aborted"), { name: "AbortError", code: "ABORT_ERR", cause: signal.reason }, ))) : null; const iterator = { next(): Promise> { if (queue.length) { const value = queue.shift()!; if (paused && queue.length < lowWaterMark) { paused = false; pausable.resume?.(); } return Promise.resolve({ value, done: false }); } if (error) { const e = error; error = null; return Promise.reject(e); } if (finished) return closeHandler(); return new Promise((resolve, reject) => waiting.push({ resolve, reject })); }, return(): Promise> { return closeHandler(); }, throw(err: unknown): Promise { error = err; removeAll(); return Promise.reject(err); }, [Symbol.asyncIterator]() { return this; }, get [kWatermarkData]() { return { size: queue.length, low: lowWaterMark, high: highWaterMark, isPaused: paused }; }, }; return iterator as unknown as AsyncIterableIterator; } EventEmitter.on = on; EventEmitter.getEventListeners = getEventListeners; EventEmitter.getMaxListeners = getMaxListeners; EventEmitter.setMaxListeners = setMaxListeners; /** events.EventEmitterAsyncResource: an EventEmitter whose emit() runs inside its own * AsyncResource scope (see async_hooks.ts) — ported from Node's addaleax/ * eventemitter-asyncresource, as Node's own lazyEventEmitterAsyncResource is. */ export class EventEmitterAsyncResource extends (EventEmitter as unknown as { new (opts?: unknown): EventEmitter }) { #resource: AsyncResource; constructor(options?: string | { name?: string; triggerAsyncId?: number; requireManualDestroy?: boolean; captureRejections?: boolean }) { let name: string | undefined; let opts = options; if (typeof opts === "string") { name = opts; opts = undefined; } else { const o = opts as { name?: string } | undefined; if (new.target === EventEmitterAsyncResource && typeof o?.name !== "string") { throw Object.assign(new TypeError('The "options.name" argument must be of type string.'), { code: "ERR_INVALID_ARG_TYPE" }); } name = o?.name || new.target.name; } super(opts); this.#resource = new AsyncResource(name, opts); } override emit(event: Key, ...args: unknown[]): boolean { const resource = this.#resource; return resource.runInAsyncScope(() => (EventEmitter.prototype.emit as (this: EventEmitter, e: Key, ...a: unknown[]) => boolean).call(this, event, ...args)); } emitDestroy(): void { this.#resource.emitDestroy(); } get asyncId(): number { return this.#resource.asyncId(); } get triggerAsyncId(): number { return this.#resource.triggerAsyncId(); } get asyncResource(): AsyncResource { return this.#resource; } } Object.defineProperty(EventEmitter, "EventEmitterAsyncResource", { value: EventEmitterAsyncResource, enumerable: true, configurable: true });