aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2023-04-21 07:16:23 -0700
committerGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2023-04-21 07:16:23 -0700
commita4d0a1961abe0c6073e15cc6f7c0601b74f2e3f7 (patch)
treeb0b2f0debda72afe5349b46872e7c508b2f81b94
parent143ccdbeb6988bef73f0a465566c900b62e96103 (diff)
downloadbun-a4d0a1961abe0c6073e15cc6f7c0601b74f2e3f7.tar.gz
bun-a4d0a1961abe0c6073e15cc6f7c0601b74f2e3f7.tar.zst
bun-a4d0a1961abe0c6073e15cc6f7c0601b74f2e3f7.zip
Revert "implement `node:events` in javascript (#2604)"
This reverts commit 96a2ed1040d5a0ca51ae41267cba4f8e5d0a6142.
-rwxr-xr-xbench/bun.lockbbin35848 -> 34640 bytes
-rw-r--r--bench/emitter/implementations.mjs31
-rw-r--r--bench/emitter/microbench.mjs96
-rw-r--r--bench/emitter/microbench_once.mjs40
-rw-r--r--bench/emitter/realworld_stream.mjs63
-rw-r--r--bench/package.json10
-rwxr-xr-xbench/snippets/bun.lockbbin0 -> 1477 bytes
-rw-r--r--bench/snippets/emitter.mjs101
-rw-r--r--bench/snippets/package.json7
-rw-r--r--docs/runtime/nodejs-apis.md2
-rw-r--r--src/bun.js/events.exports.js464
-rw-r--r--src/bun.js/module_loader.zig10
-rwxr-xr-xtest/bun.lockbbin36614 -> 0 bytes
-rw-r--r--test/js/node/events/event-emitter.test.ts529
-rw-r--r--test/js/node/events/node-builtins.test.js18
15 files changed, 210 insertions, 1161 deletions
diff --git a/bench/bun.lockb b/bench/bun.lockb
index 298e2a7c9..abc5a11b4 100755
--- a/bench/bun.lockb
+++ b/bench/bun.lockb
Binary files differ
diff --git a/bench/emitter/implementations.mjs b/bench/emitter/implementations.mjs
deleted file mode 100644
index 2050ac38e..000000000
--- a/bench/emitter/implementations.mjs
+++ /dev/null
@@ -1,31 +0,0 @@
-import EventEmitter3 from "eventemitter3";
-import { group } from "mitata";
-import EventEmitterNative from "node:events";
-
-export const implementations = [
- {
- EventEmitter: EventEmitterNative,
- name: process.isBun ? (EventEmitterNative.init ? "bun" : "C++") : "node:events",
- monkey: true,
- },
- // { EventEmitter: EventEmitter3, name: "EventEmitter3" },
-].filter(Boolean);
-
-for (const impl of implementations) {
- impl.EventEmitter?.setMaxListeners?.(Infinity);
-}
-
-export function groupForEmitter(name, cb) {
- if (implementations.length === 1) {
- return cb({
- ...implementations[0],
- name: `${name}: ${implementations[0].name}`,
- });
- } else {
- return group(name, () => {
- for (let impl of implementations) {
- cb(impl);
- }
- });
- }
-}
diff --git a/bench/emitter/microbench.mjs b/bench/emitter/microbench.mjs
deleted file mode 100644
index eae59d4c1..000000000
--- a/bench/emitter/microbench.mjs
+++ /dev/null
@@ -1,96 +0,0 @@
-import { bench, run } from "mitata";
-import { groupForEmitter } from "./implementations.mjs";
-
-var id = 0;
-
-groupForEmitter("single emit", ({ EventEmitter, name }) => {
- const emitter = new EventEmitter();
-
- emitter.on("hello", event => {
- event.preventDefault();
- });
-
- bench(name, () => {
- emitter.emit("hello", {
- preventDefault() {
- id++;
- },
- });
- });
-});
-
-groupForEmitter("on x 10_000 (handler)", ({ EventEmitter, name }) => {
- const emitter = new EventEmitter();
-
- bench(name, () => {
- var cb = event => {
- event.preventDefault();
- };
- emitter.on("hey", cb);
- var called = false;
- for (let i = 0; i < 10_000; i++)
- emitter.emit("hey", {
- preventDefault() {
- id++;
- called = true;
- },
- });
-
- if (!called) throw new Error("not called");
- });
-});
-
-// for (let { impl: EventEmitter, name, monkey } of []) {
-// if (monkey) {
-// var monkeyEmitter = Object.assign({}, EventEmitter.prototype);
-// monkeyEmitter.on("hello", event => {
-// event.preventDefault();
-// });
-
-// bench(`[monkey] ${className}.emit`, () => {
-// var called = false;
-// monkeyEmitter.emit("hello", {
-// preventDefault() {
-// id++;
-// called = true;
-// },
-// });
-
-// if (!called) {
-// throw new Error("monkey failed");
-// }
-// });
-
-// bench(`[monkey] ${className}.on x 10_000 (handler)`, () => {
-// var cb = () => {
-// event.preventDefault();
-// };
-// monkeyEmitter.on("hey", cb);
-// for (let i = 0; i < 10_000; i++)
-// monkey.emit("hey", {
-// preventDefault() {
-// id++;
-// },
-// });
-// monkeyEmitter.off("hey", cb);
-// });
-// }
-// }
-
-// var target = new EventTarget();
-// target.addEventListener("hello", event => {});
-// bench("EventTarget.dispatch", () => {
-// target.dispatchEvent(event);
-// });
-
-// var hey = new Event("hey");
-
-// bench("EventTarget.on x 10_000 (handler)", () => {
-// var handler = event => {};
-// target.addEventListener("hey", handler);
-
-// for (let i = 0; i < 10_000; i++) target.dispatchEvent(hey);
-// target.removeEventListener("hey", handler);
-// });
-
-await run();
diff --git a/bench/emitter/microbench_once.mjs b/bench/emitter/microbench_once.mjs
deleted file mode 100644
index b24fb2103..000000000
--- a/bench/emitter/microbench_once.mjs
+++ /dev/null
@@ -1,40 +0,0 @@
-import { bench, run } from "mitata";
-import { groupForEmitter } from "./implementations.mjs";
-
-var id = 0;
-
-groupForEmitter("test 1", ({ EventEmitter, name }) => {
- const emitter = new EventEmitter();
-
- emitter.on("hello", event => {
- event.preventDefault();
- });
-
- bench(name, () => {
- emitter.once("hello", event => {
- event.preventDefault();
- });
- emitter.emit("hello", {
- preventDefault() {
- id++;
- },
- });
- });
-});
-
-groupForEmitter("test 2", ({ EventEmitter, name }) => {
- const emitter = new EventEmitter();
-
- bench(name, () => {
- emitter.once("hello", event => {
- event.preventDefault();
- });
- emitter.emit("hello", {
- preventDefault() {
- id++;
- },
- });
- });
-});
-
-await run();
diff --git a/bench/emitter/realworld_stream.mjs b/bench/emitter/realworld_stream.mjs
deleted file mode 100644
index e65398b49..000000000
--- a/bench/emitter/realworld_stream.mjs
+++ /dev/null
@@ -1,63 +0,0 @@
-import { bench, run } from "mitata";
-import { groupForEmitter } from "./implementations.mjs";
-
-// Psuedo RNG is derived from https://stackoverflow.com/a/424445
-let rngState = 123456789;
-function nextInt() {
- const m = 0x80000000; // 2**31;
- const a = 1103515245;
- const c = 12345;
- rngState = (a * rngState + c) % m;
- return rngState;
-}
-function nextRange(start, end) {
- // returns in range [start, end): including start, excluding end
- // can't modulu nextInt because of weak randomness in lower bits
- const rangeSize = end - start;
- const randomUnder1 = nextInt() / 0x7fffffff; // 2**31 - 1
- return start + Math.floor(randomUnder1 * rangeSize);
-}
-
-const chunks = new Array(1024).fill(null).map((_, j) => {
- const arr = new Uint8Array(1024);
- for (let i = 0; i < arr.length; i++) {
- arr[i] = nextRange(0, 256);
- }
- return arr;
-});
-
-groupForEmitter("stream simulation", ({ EventEmitter, name }) => {
- bench(name, () => {
- let id = 0;
- const stream = new EventEmitter();
-
- stream.on("start", res => {
- if (res.status !== 200) throw new Error("not 200");
- });
-
- const recived = [];
- stream.on("data", req => {
- recived.push(req);
- });
-
- stream.on("end", ev => {
- ev.preventDefault();
- });
-
- // simulate a stream
- stream.emit("start", { status: 200 });
- for (let chunk of chunks) {
- stream.emit("data", chunk);
- }
- stream.emit("end", {
- preventDefault() {
- id++;
- },
- });
-
- if (id !== 1) throw new Error("not implemented right");
- if (recived.length !== 1024) throw new Error("not implemented right");
- });
-});
-
-await run();
diff --git a/bench/package.json b/bench/package.json
index 501dd6f51..9cd47e043 100644
--- a/bench/package.json
+++ b/bench/package.json
@@ -1,13 +1,11 @@
{
"name": "bench",
"dependencies": {
- "@babel/core": "^7.16.10",
- "@babel/preset-react": "^7.16.7",
- "@swc/core": "^1.2.133",
- "benchmark": "^2.1.4",
+ "mitata": "^0.1.6",
"esbuild": "^0.14.12",
- "eventemitter3": "^5.0.0",
- "mitata": "^0.1.6"
+ "@swc/core": "^1.2.133",
+ "@babel/core": "^7.16.10",
+ "@babel/preset-react": "^7.16.7"
},
"scripts": {
"ffi": "cd ffi && bun run deps && bun run build && bun run bench",
diff --git a/bench/snippets/bun.lockb b/bench/snippets/bun.lockb
new file mode 100755
index 000000000..3acb6d075
--- /dev/null
+++ b/bench/snippets/bun.lockb
Binary files differ
diff --git a/bench/snippets/emitter.mjs b/bench/snippets/emitter.mjs
new file mode 100644
index 000000000..f708cf77a
--- /dev/null
+++ b/bench/snippets/emitter.mjs
@@ -0,0 +1,101 @@
+// **so this file can run in node**
+import { createRequire } from "node:module";
+const require = createRequire(import.meta.url);
+// --
+
+const EventEmitterNative = require("node:events").EventEmitter;
+const TypedEmitter = require("tiny-typed-emitter").TypedEmitter;
+const EventEmitter3 = require("eventemitter3").EventEmitter;
+import { bench, run } from "../../node_modules/mitata/src/cli.mjs";
+const event = new Event("hello");
+var id = 0;
+for (let [EventEmitter, className] of [
+ [EventEmitterNative, "EventEmitter"],
+ [TypedEmitter, "TypedEmitter"],
+ [EventEmitter3, "EventEmitter3"],
+]) {
+ const emitter = new EventEmitter();
+
+ emitter.on("hello", event => {
+ event.preventDefault();
+ });
+
+ bench(`${className}.emit`, () => {
+ emitter.emit("hello", {
+ preventDefault() {
+ id++;
+ },
+ });
+ });
+
+ bench(`${className}.on x 10_000 (handler)`, () => {
+ var cb = event => {
+ event.preventDefault();
+ };
+ emitter.on("hey", cb);
+ var called = false;
+ for (let i = 0; i < 10_000; i++)
+ emitter.emit("hey", {
+ preventDefault() {
+ id++;
+ called = true;
+ },
+ });
+ emitter.off("hey", cb);
+
+ if (!called) throw new Error("not called");
+ });
+
+ if (EventEmitter !== EventEmitter3) {
+ var monkey = Object.assign({}, EventEmitter.prototype);
+ monkey.on("hello", event => {
+ event.preventDefault();
+ });
+
+ bench(`[monkey] ${className}.emit`, () => {
+ var called = false;
+ monkey.emit("hello", {
+ preventDefault() {
+ id++;
+ called = true;
+ },
+ });
+
+ if (!called) {
+ throw new Error("monkey failed");
+ }
+ });
+
+ bench(`[monkey] ${className}.on x 10_000 (handler)`, () => {
+ var cb = () => {
+ event.preventDefault();
+ };
+ monkey.on("hey", cb);
+ for (let i = 0; i < 10_000; i++)
+ monkey.emit("hey", {
+ preventDefault() {
+ id++;
+ },
+ });
+ monkey.off("hey", cb);
+ });
+ }
+}
+
+var target = new EventTarget();
+target.addEventListener("hello", event => {});
+bench("EventTarget.dispatch", () => {
+ target.dispatchEvent(event);
+});
+
+var hey = new Event("hey");
+
+bench("EventTarget.on x 10_000 (handler)", () => {
+ var handler = event => {};
+ target.addEventListener("hey", handler);
+
+ for (let i = 0; i < 10_000; i++) target.dispatchEvent(hey);
+ target.removeEventListener("hey", handler);
+});
+
+await run();
diff --git a/bench/snippets/package.json b/bench/snippets/package.json
new file mode 100644
index 000000000..6244001a8
--- /dev/null
+++ b/bench/snippets/package.json
@@ -0,0 +1,7 @@
+{
+ "dependencies": {
+ "eventemitter3": "^5.0.0",
+ "tiny-typed-emitter": "latest"
+ },
+ "prettier": "../../.prettierrc.cjs"
+}
diff --git a/docs/runtime/nodejs-apis.md b/docs/runtime/nodejs-apis.md
index 9fb6e778b..cbb264486 100644
--- a/docs/runtime/nodejs-apis.md
+++ b/docs/runtime/nodejs-apis.md
@@ -81,7 +81,7 @@ This page is updated regularly to reflect compatibility status of the latest ver
- {% anchor id="node_events" %} [`node:events`](https://nodejs.org/api/events.html) {% /anchor %}
- 🟡
-- Missing `EventEmitterAsyncResource` `events.on`.
+- Missing `EventEmitterAsyncResource`. `EventEmitter` is missing `{get}set}MaxListeners` `usingDomains` `init`.
---
diff --git a/src/bun.js/events.exports.js b/src/bun.js/events.exports.js
deleted file mode 100644
index 3e4536ff2..000000000
--- a/src/bun.js/events.exports.js
+++ /dev/null
@@ -1,464 +0,0 @@
-// Reimplementation of https://nodejs.org/api/events.html
-// Reference: https://github.com/nodejs/node/blob/main/lib/events.js
-var { isPromise, Array, Object } = import.meta.primordials;
-const SymbolFor = Symbol.for;
-const ObjectDefineProperty = Object.defineProperty;
-const kCapture = Symbol("kCapture");
-const kErrorMonitor = SymbolFor("events.errorMonitor");
-const kMaxEventTargetListeners = Symbol("events.maxEventTargetListeners");
-const kMaxEventTargetListenersWarned = Symbol("events.maxEventTargetListenersWarned");
-const kWatermarkData = SymbolFor("nodejs.watermarkData");
-const kRejection = SymbolFor("nodejs.rejection");
-const captureRejectionSymbol = SymbolFor("nodejs.rejection");
-const ArrayPrototypeSlice = Array.prototype.slice;
-
-var defaultMaxListeners = 10;
-
-// EventEmitter must be a standard function because some old code will do weird tricks like `EventEmitter.apply(this)`.
-function EventEmitter(opts) {
- if (this._events === undefined || this._events === this.__proto__._events) {
- this._events = { __proto__: null };
- this._eventsCount = 0;
- }
-
- this._maxListeners ??= undefined;
- if (
- (this[kCapture] = opts?.captureRejections ? Boolean(opts?.captureRejections) : EventEmitter.prototype[kCapture])
- ) {
- this.emit = emitWithRejectionCapture;
- }
-}
-
-EventEmitter.prototype._events = undefined;
-EventEmitter.prototype._eventsCount = 0;
-EventEmitter.prototype._maxListeners = undefined;
-EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
- validateNumber(n, "setMaxListeners", 0);
- this._maxListeners = n;
- return this;
-};
-
-EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
- return this._maxListeners ?? defaultMaxListeners;
-};
-
-function emitError(emitter, args) {
- var { _events: events } = emitter;
- args[0] ??= new Error("Unhandled error.");
- if (!events) throw args[0];
- var errorMonitor = events[kErrorMonitor];
- if (errorMonitor) {
- for (var handler of ArrayPrototypeSlice.call(errorMonitor)) {
- handler.apply(emitter, args);
- }
- }
- var handlers = events.error;
- if (!handlers) throw args[0];
- for (var handler of ArrayPrototypeSlice.call(handlers)) {
- handler.apply(emitter, args);
- }
- return true;
-}
-
-function addCatch(emitter, promise, type, args) {
- promise.then(undefined, function (err) {
- // The callback is called with nextTick to avoid a follow-up rejection from this promise.
- process.nextTick(emitUnhandledRejectionOrErr, emitter, err, type, args);
- });
-}
-
-function emitUnhandledRejectionOrErr(emitter, err, type, args) {
- if (typeof emitter[kRejection] === "function") {
- emitter[kRejection](err, type, ...args);
- } else {
- // If the error handler throws, it is not catchable and it will end up in 'uncaughtException'.
- // We restore the previous value of kCapture in case the uncaughtException is present
- // and the exception is handled.
- try {
- emitter[kCapture] = false;
- emitter.emit("error", err);
- } finally {
- emitter[kCapture] = true;
- }
- }
-}
-
-const emitWithoutRejectionCapture = function emit(type, ...args) {
- if (type === "error") {
- return emitError(this, args);
- }
- var { _events: events } = this;
- if (events === undefined) return false;
- var handlers = events[type];
- if (handlers === undefined) return false;
-
- for (var handler of [...handlers]) {
- handler.apply(this, args);
- }
- return true;
-};
-
-const emitWithRejectionCapture = function emit(type, ...args) {
- if (type === "error") {
- return emitError(this, args);
- }
- var { _events: events } = this;
- if (events === undefined) return false;
- var handlers = events[type];
- if (handlers === undefined) return false;
- for (var handler of [...handlers]) {
- var result = handler.apply(this, args);
- if (result !== undefined && isPromise(result)) {
- addCatch(this, result, type, args);
- }
- }
- return true;
-};
-
-EventEmitter.prototype.emit = emitWithoutRejectionCapture;
-
-EventEmitter.prototype.addListener = function addListener(type, fn) {
- checkListener(fn);
- var events = this._events;
- if (!events) {
- events = this._events = { __proto__: null };
- this._eventsCount = 0;
- } else if (events.newListener) {
- this.emit("newListener", type, fn.listener ?? fn);
- }
- var handlers = events[type];
- if (!handlers) {
- events[type] = [fn];
- this._eventsCount++;
- } else {
- handlers.push(fn);
- var m = this._maxListeners ?? defaultMaxListeners;
- if (m > 0 && handlers.length > m && !handlers.warned) {
- overflowWarning(this, type, handlers);
- }
- }
- return this;
-};
-
-EventEmitter.prototype.on = EventEmitter.prototype.addListener;
-
-EventEmitter.prototype.prependListener = function prependListener(type, fn) {
- checkListener(fn);
- var events = this._events;
- if (!events) {
- events = this._events = { __proto__: null };
- this._eventsCount = 0;
- } else if (events.newListener) {
- this.emit("newListener", type, fn.listener ?? fn);
- }
- var handlers = events[type];
- if (!handlers) {
- events[type] = [fn];
- this._eventsCount++;
- } else {
- handlers.unshift(fn);
- var m = this._maxListeners ?? defaultMaxListeners;
- if (m > 0 && handlers.length > m && !handlers.warned) {
- overflowWarning(this, type, handlers);
- }
- }
- return this;
-};
-
-function overflowWarning(emitter, type, handlers) {
- handlers.warned = true;
- const warn = new Error(
- `Possible EventEmitter memory leak detected. ${handlers.length} ${String(type)} listeners ` +
- `added to [${emitter.constructor.name}]. Use emitter.setMaxListeners() to increase limit`,
- );
- warn.name = "MaxListenersExceededWarning";
- warn.emitter = emitter;
- warn.type = type;
- warn.count = handlers.length;
- process.emitWarning(warn);
-}
-
-function onceWrapper(type, listener, ...args) {
- this.removeListener(type, listener);
- listener.apply(this, args);
-}
-
-EventEmitter.prototype.once = function once(type, fn) {
- checkListener(fn);
- const bound = onceWrapper.bind(this, type, fn);
- bound.listener = fn;
- this.addListener(type, bound);
- return this;
-};
-
-EventEmitter.prototype.prependOnceListener = function prependOnceListener(type, fn) {
- checkListener(fn);
- const bound = onceWrapper.bind(this, type, fn);
- bound.listener = fn;
- this.prependListener(type, bound);
- return this;
-};
-
-EventEmitter.prototype.removeListener = function removeListener(type, fn) {
- checkListener(fn);
- var { _events: events } = this;
- if (!events) return this;
- var handlers = events[type];
- if (!handlers) return this;
- var length = handlers.length;
- let position = -1;
- for (let i = length - 1; i >= 0; i--) {
- if (handlers[i] === fn || handlers[i].listener === fn) {
- position = i;
- break;
- }
- }
- if (position < 0) return this;
- if (position === 0) {
- handlers.shift();
- } else {
- handlers.splice(position, 1);
- }
- if (handlers.length === 0) {
- delete events[type];
- this._eventsCount--;
- }
- return this;
-};
-
-EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
-
-EventEmitter.prototype.removeAllListeners = function removeAllListeners(type) {
- var { _events: events } = this;
- if (type && events) {
- if (events[type]) {
- delete events[type];
- this._eventsCount--;
- }
- } else {
- this._events = { __proto__: null };
- }
- return this;
-};
-
-EventEmitter.prototype.listeners = function listeners(type) {
- var { _events: events } = this;
- if (!events) return [];
- var handlers = events[type];
- if (!handlers) return [];
- return handlers.map(x => x.listener ?? x);
-};
-
-EventEmitter.prototype.rawListeners = function rawListeners(type) {
- var { _events } = this;
- if (!_events) return [];
- var handlers = _events[type];
- if (!handlers) return [];
- return handlers.slice();
-};
-
-EventEmitter.prototype.listenerCount = function listenerCount(type) {
- var { _events: events } = this;
- if (!events) return 0;
- return events[type]?.length ?? 0;
-};
-
-EventEmitter.prototype.eventNames = function eventNames() {
- return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : [];
-};
-
-EventEmitter.prototype[kCapture] = false;
-
-function once(emitter, type, { signal } = {}) {
- validateAbortSignal(signal, "options.signal");
- if (signal?.aborted) {
- throw new AbortError(undefined, { cause: signal?.reason });
- }
- return new Promise((resolve, reject) => {
- const errorListener = err => {
- emitter.removeListener(type, resolver);
- if (signal != null) {
- eventTargetAgnosticRemoveListener(signal, "abort", abortListener);
- }
- reject(err);
- };
- const resolver = (...args) => {
- if (typeof emitter.removeListener === "function") {
- emitter.removeListener("error", errorListener);
- }
- if (signal != null) {
- eventTargetAgnosticRemoveListener(signal, "abort", abortListener);
- }
- resolve(args);
- };
- eventTargetAgnosticAddListener(emitter, type, resolver, { once: true });
- if (type !== "error" && typeof emitter.once === "function") {
- // EventTarget does not have `error` event semantics like Node
- // EventEmitters, we listen to `error` events only on EventEmitters.
- emitter.once("error", errorListener);
- }
- function abortListener() {
- eventTargetAgnosticRemoveListener(emitter, type, resolver);
- eventTargetAgnosticRemoveListener(emitter, "error", errorListener);
- reject(new AbortError(undefined, { cause: signal?.reason }));
- }
- if (signal != null) {
- eventTargetAgnosticAddListener(signal, "abort", abortListener, { once: true });
- }
- });
-}
-EventEmitter.once = once;
-
-function on(emitter, type, { signal, close, highWatermark = Number.MAX_SAFE_INTEGER, lowWatermark = 1 } = {}) {
- throw new Error("events.on is not implemented. See https://github.com/oven-sh/bun/issues/2679");
-}
-EventEmitter.on = on;
-
-function getEventListeners(emitter, type) {
- if (emitter instanceof EventTarget) {
- throw new Error(
- "getEventListeners with an EventTarget is not implemented. See https://github.com/oven-sh/bun/issues/2678",
- );
- }
- return emitter.listeners(type);
-}
-EventEmitter.getEventListeners = getEventListeners;
-
-function setMaxListeners(n, ...eventTargets) {
- validateNumber(n, "setMaxListeners", 0);
- var length;
- if (eventTargets && (length = eventTargets.length)) {
- for (let i = 0; i < length; i++) {
- eventTargets[i].setMaxListeners(n);
- }
- } else {
- defaultMaxListeners = n;
- }
-}
-EventEmitter.setMaxListeners = setMaxListeners;
-
-function listenerCount(emitter, type) {
- return emitter.listenerCount(type);
-}
-EventEmitter.listenerCount = listenerCount;
-
-EventEmitter.EventEmitter = EventEmitter;
-EventEmitter.usingDomains = false;
-EventEmitter.captureRejectionSymbol = captureRejectionSymbol;
-ObjectDefineProperty(EventEmitter, "captureRejections", {
- __proto__: null,
- get() {
- return EventEmitter.prototype[kCapture];
- },
- set(value) {
- validateBoolean(value, "EventEmitter.captureRejections");
-
- EventEmitter.prototype[kCapture] = value;
- },
- enumerable: true,
-});
-EventEmitter.errorMonitor = kErrorMonitor;
-Object.defineProperties(EventEmitter, {
- defaultMaxListeners: {
- enumerable: true,
- get: () => {
- return defaultMaxListeners;
- },
- set: arg => {
- validateNumber(arg, "defaultMaxListeners", 0);
- defaultMaxListeners = arg;
- },
- },
- kMaxEventTargetListeners: {
- __proto__: null,
- value: kMaxEventTargetListeners,
- enumerable: false,
- configurable: false,
- writable: false,
- },
- kMaxEventTargetListenersWarned: {
- __proto__: null,
- value: kMaxEventTargetListenersWarned,
- enumerable: false,
- configurable: false,
- writable: false,
- },
-});
-EventEmitter.init = EventEmitter;
-EventEmitter[Symbol.for("CommonJS")] = 0;
-
-export default EventEmitter;
-
-function eventTargetAgnosticRemoveListener(emitter, name, listener, flags) {
- if (typeof emitter.removeListener === "function") {
- emitter.removeListener(name, listener);
- } else {
- emitter.removeEventListener(name, listener, flags);
- }
-}
-
-function eventTargetAgnosticAddListener(emitter, name, listener, flags) {
- if (typeof emitter.on === "function") {
- emitter.on(name, listener);
- } else {
- emitter.addEventListener(name, listener);
- }
-}
-
-class AbortError extends Error {
- constructor(message = "The operation was aborted", options = undefined) {
- if (options !== undefined && typeof options !== "object") {
- throw new codes.ERR_INVALID_ARG_TYPE("options", "Object", options);
- }
- super(message, options);
- this.code = "ABORT_ERR";
- this.name = "AbortError";
- }
-}
-
-function ERR_INVALID_ARG_TYPE(name, type, value) {
- const err = new TypeError(`The "${name}" argument must be of type ${type}. Received ${value}`);
- err.code = "ERR_INVALID_ARG_TYPE";
- return err;
-}
-
-function ERR_OUT_OF_RANGE(name, range, value) {
- const err = new RangeError(`The "${name}" argument is out of range. It must be ${range}. Received ${value}`);
- err.code = "ERR_OUT_OF_RANGE";
- return err;
-}
-
-function validateAbortSignal(signal, name) {
- if (signal !== undefined && (signal === null || typeof signal !== "object" || !("aborted" in signal))) {
- throw new ERR_INVALID_ARG_TYPE(name, "AbortSignal", signal);
- }
-}
-
-function validateNumber(value, name, min = undefined, max) {
- if (typeof value !== "number") throw new ERR_INVALID_ARG_TYPE(name, "number", value);
- if (
- (min != null && value < min) ||
- (max != null && value > max) ||
- ((min != null || max != null) && Number.isNaN(value))
- ) {
- throw new ERR_OUT_OF_RANGE(
- name,
- `${min != null ? `>= ${min}` : ""}${min != null && max != null ? " && " : ""}${max != null ? `<= ${max}` : ""}`,
- value,
- );
- }
-}
-
-function checkListener(listener) {
- if (typeof listener !== "function") {
- throw new TypeError("The listener must be a function");
- }
-}
-
-export class EventEmitterAsyncResource extends EventEmitter {
- constructor(options = undefined) {
- throw new Error("EventEmitterAsyncResource is not implemented. See https://github.com/oven-sh/bun/issues/2681");
- }
-}
-
-EventEmitter.EventEmitterAsyncResource = EventEmitterAsyncResource;
diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig
index 127bc85e3..ede5622dc 100644
--- a/src/bun.js/module_loader.zig
+++ b/src/bun.js/module_loader.zig
@@ -1712,15 +1712,7 @@ pub const ModuleLoader = struct {
.@"node:buffer" => return jsSyntheticModule(.@"node:buffer"),
.@"node:string_decoder" => return jsSyntheticModule(.@"node:string_decoder"),
.@"node:module" => return jsSyntheticModule(.@"node:module"),
- .@"node:events" => {
- return ResolvedSource{
- .allocator = null,
- .source_code = ZigString.init(jsModuleFromFile(jsc_vm.load_builtins_from_path, "events.exports.js")),
- .specifier = ZigString.init("node:events"),
- .source_url = ZigString.init("node:events"),
- .hash = 0,
- };
- },
+ .@"node:events" => return jsSyntheticModule(.@"node:events"),
.@"node:process" => return jsSyntheticModule(.@"node:process"),
.@"node:tty" => return jsSyntheticModule(.@"node:tty"),
.@"node:util/types" => return jsSyntheticModule(.@"node:util/types"),
diff --git a/test/bun.lockb b/test/bun.lockb
deleted file mode 100755
index 3b60656d2..000000000
--- a/test/bun.lockb
+++ /dev/null
Binary files differ
diff --git a/test/js/node/events/event-emitter.test.ts b/test/js/node/events/event-emitter.test.ts
index cef309d48..401ccf605 100644
--- a/test/js/node/events/event-emitter.test.ts
+++ b/test/js/node/events/event-emitter.test.ts
@@ -1,106 +1,34 @@
-import { test, describe, expect } from "bun:test";
-import { sleep } from "bun";
-
+import { test, describe, expect, it } from "bun:test";
+import { heapStats } from "bun:jsc";
+import { expectMaxObjectTypeCount, gc } from "harness";
// this is also testing that imports with default and named imports in the same statement work
// our transpiler transform changes this to a var with import.meta.require
import EventEmitter, { getEventListeners, captureRejectionSymbol } from "node:events";
-describe("node:events", () => {
- test("captureRejectionSymbol", () => {
+describe("EventEmitter", () => {
+ it("captureRejectionSymbol", () => {
expect(EventEmitter.captureRejectionSymbol).toBeDefined();
expect(captureRejectionSymbol).toBeDefined();
- expect(captureRejectionSymbol).toBe(EventEmitter.captureRejectionSymbol);
- });
-
- test("once", done => {
- const emitter = new EventEmitter();
- EventEmitter.once(emitter, "hey").then(x => {
- try {
- expect(x).toEqual([1, 5]);
- } catch (error) {
- done(error);
- }
- done();
- });
- emitter.emit("hey", 1, 5);
});
-
- test("once (abort)", done => {
- const emitter = new EventEmitter();
- const controller = new AbortController();
- EventEmitter.once(emitter, "hey", { signal: controller.signal })
- .then(() => done(new Error("Should not be called")))
- .catch(() => done());
- controller.abort();
- });
-
- test("once (two events in same tick)", done => {
- const emitter = new EventEmitter();
- EventEmitter.once(emitter, "hey").then(() => {
- EventEmitter.once(emitter, "hey").then(data => {
- try {
- expect(data).toEqual([3]);
- } catch (error) {
- done(error);
- }
- done();
- });
- setTimeout(() => {
- emitter.emit("hey", 3);
- }, 10);
- });
- emitter.emit("hey", 1);
- emitter.emit("hey", 2);
- });
-
- // TODO: extensive events.on tests
- // test("on", () => {
- // const emitter = new EventEmitter();
- // const asyncIterator = EventEmitter.on(emitter, "hey");
-
- // expect(asyncIterator.next).toBeDefined();
- // expect(asyncIterator[Symbol.asyncIterator]).toBeDefined();
-
- // const fn = async () => {
- // const { value } = await asyncIterator.next();
- // expect(value).toBe(1);
- // };
-
- // emitter.emit("hey", 1, 2, 3);
- // });
-});
-
-describe("EventEmitter", () => {
test("getEventListeners", () => {
expect(getEventListeners(new EventEmitter(), "hey").length).toBe(0);
});
-
- test("constructor", () => {
+ test("EventEmitter constructor", () => {
var emitter = new EventEmitter();
emitter.setMaxListeners(100);
expect(emitter.getMaxListeners()).toBe(100);
});
- test("removeAllListeners()", () => {
- var emitter = new EventEmitter() as any;
+ test("EventEmitter.removeAllListeners()", () => {
+ var emitter = new EventEmitter();
var ran = false;
emitter.on("hey", () => {
ran = true;
});
- emitter.on("hey", () => {
- ran = true;
- });
- emitter.on("exit", () => {
- ran = true;
- });
- const { _events } = emitter;
emitter.removeAllListeners();
expect(emitter.listenerCount("hey")).toBe(0);
- expect(emitter.listenerCount("exit")).toBe(0);
emitter.emit("hey");
- emitter.emit("exit");
expect(ran).toBe(false);
- expect(_events).not.toBe(emitter._events); // This looks wrong but node.js replaces it too
emitter.on("hey", () => {
ran = true;
});
@@ -109,366 +37,42 @@ describe("EventEmitter", () => {
expect(emitter.listenerCount("hey")).toBe(1);
});
- test("removeAllListeners(type)", () => {
- var emitter = new EventEmitter();
- var ran = false;
- emitter.on("hey", () => {
- ran = true;
- });
- emitter.on("exit", () => {
- ran = true;
- });
- expect(emitter.listenerCount("hey")).toBe(1);
- emitter.removeAllListeners("hey");
- expect(emitter.listenerCount("hey")).toBe(0);
- expect(emitter.listenerCount("exit")).toBe(1);
- emitter.emit("hey");
- expect(ran).toBe(false);
- emitter.emit("exit");
- expect(ran).toBe(true);
- });
-
// These are also tests for the done() function in the test runner.
- describe("emit", () => {
- test("different tick", done => {
- var emitter = new EventEmitter();
- emitter.on("wow", () => done());
- queueMicrotask(() => {
- emitter.emit("wow");
- });
- });
-
- // Unlike Jest, bun supports async and done
- test("async microtask before", done => {
- (async () => {
- await 1;
- var emitter = new EventEmitter();
- emitter.on("wow", () => done());
- emitter.emit("wow");
- })();
- });
-
- test("async microtask after", done => {
- (async () => {
- var emitter = new EventEmitter();
- emitter.on("wow", () => done());
- await 1;
- emitter.emit("wow");
- })();
- });
-
- test("same tick", done => {
- var emitter = new EventEmitter();
-
- emitter.on("wow", () => done());
-
+ test("EventEmitter emit (different tick)", done => {
+ var emitter = new EventEmitter();
+ emitter.on("wow", () => done());
+ queueMicrotask(() => {
emitter.emit("wow");
});
-
- test("setTimeout task", done => {
- var emitter = new EventEmitter();
- emitter.on("wow", () => done());
- setTimeout(() => emitter.emit("wow"), 1);
- });
- });
-
- test("addListener return type", () => {
- var myEmitter = new EventEmitter();
- expect(myEmitter.addListener("foo", () => {})).toBe(myEmitter);
- });
-
- test("addListener validates function", () => {
- var myEmitter = new EventEmitter();
- expect(() => myEmitter.addListener("foo", {} as any)).toThrow();
- });
-
- test("removeListener return type", () => {
- var myEmitter = new EventEmitter();
- expect(myEmitter.removeListener("foo", () => {})).toBe(myEmitter);
- });
-
- test("once", () => {
- var myEmitter = new EventEmitter();
- var calls = 0;
-
- const fn = () => {
- calls++;
- };
-
- myEmitter.once("foo", fn);
-
- expect(myEmitter.listenerCount("foo")).toBe(1);
- expect(myEmitter.listeners("foo")).toEqual([fn]);
-
- myEmitter.emit("foo");
- myEmitter.emit("foo");
-
- expect(calls).toBe(1);
- expect(myEmitter.listenerCount("foo")).toBe(0);
- });
-
- test("addListener/removeListener aliases", () => {
- expect(EventEmitter.prototype.addListener).toBe(EventEmitter.prototype.on);
- expect(EventEmitter.prototype.removeListener).toBe(EventEmitter.prototype.off);
});
- test("prependListener", () => {
- const myEmitter = new EventEmitter();
- const order: number[] = [];
-
- myEmitter.on("foo", () => {
- order.push(1);
- });
-
- myEmitter.prependListener("foo", () => {
- order.push(2);
- });
-
- myEmitter.prependListener("foo", () => {
- order.push(3);
- });
-
- myEmitter.on("foo", () => {
- order.push(4);
- });
-
- myEmitter.emit("foo");
-
- expect(order).toEqual([3, 2, 1, 4]);
- });
-
- test("prependOnceListener", () => {
- const myEmitter = new EventEmitter();
- const order: number[] = [];
-
- myEmitter.on("foo", () => {
- order.push(1);
- });
-
- myEmitter.prependOnceListener("foo", () => {
- order.push(2);
- });
- myEmitter.prependOnceListener("foo", () => {
- order.push(3);
- });
-
- myEmitter.on("foo", () => {
- order.push(4);
- });
-
- myEmitter.emit("foo");
-
- expect(order).toEqual([3, 2, 1, 4]);
-
- myEmitter.emit("foo");
-
- expect(order).toEqual([3, 2, 1, 4, 1, 4]);
- });
-
- test("listeners", () => {
- const myEmitter = new EventEmitter();
- const fn = () => {};
- myEmitter.on("foo", fn);
- expect(myEmitter.listeners("foo")).toEqual([fn]);
- const fn2 = () => {};
- myEmitter.on("foo", fn2);
- expect(myEmitter.listeners("foo")).toEqual([fn, fn2]);
- myEmitter.off("foo", fn2);
- expect(myEmitter.listeners("foo")).toEqual([fn]);
- });
-
- test("rawListeners", () => {
- const myEmitter = new EventEmitter();
- const fn = () => {};
- myEmitter.on("foo", fn);
- expect(myEmitter.listeners("foo")).toEqual([fn]);
- const fn2 = () => {};
- myEmitter.on("foo", fn2);
- expect(myEmitter.listeners("foo")).toEqual([fn, fn2]);
- myEmitter.off("foo", fn2);
- expect(myEmitter.listeners("foo")).toEqual([fn]);
- });
-
- test("eventNames", () => {
- const myEmitter = new EventEmitter();
- expect(myEmitter.eventNames()).toEqual([]);
- const fn = () => {};
- myEmitter.on("foo", fn);
- expect(myEmitter.eventNames()).toEqual(["foo"]);
- myEmitter.on("bar", () => {});
- expect(myEmitter.eventNames()).toEqual(["foo", "bar"]);
- myEmitter.off("foo", fn);
- expect(myEmitter.eventNames()).toEqual(["bar"]);
- });
-
- test("_eventsCount", () => {
- const myEmitter = new EventEmitter() as EventEmitter & {
- _eventsCount: number;
- };
- expect(myEmitter._eventsCount).toBe(0);
- myEmitter.on("foo", () => {});
- expect(myEmitter._eventsCount).toBe(1);
- myEmitter.on("foo", () => {});
- expect(myEmitter._eventsCount).toBe(1);
- myEmitter.on("bar", () => {});
- expect(myEmitter._eventsCount).toBe(2);
- myEmitter.on("foo", () => {});
- expect(myEmitter._eventsCount).toBe(2);
- myEmitter.on("bar", () => {});
- expect(myEmitter._eventsCount).toBe(2);
- myEmitter.removeAllListeners("foo");
- expect(myEmitter._eventsCount).toBe(1);
- });
-
- test("events.init", () => {
- // init is a undocumented property that is identical to the constructor except it doesn't return the instance
- // in node, EventEmitter just calls init()
- let instance = Object.create(EventEmitter.prototype);
- (EventEmitter as any).init.call(instance);
- expect(instance._eventsCount).toBe(0);
- expect(instance._maxListeners).toBeUndefined();
- expect(instance._events).toEqual({});
- expect(instance instanceof EventEmitter).toBe(true);
- });
-});
-
-describe("EventEmitter error handling", () => {
- test("unhandled error event throws on emit", () => {
- const myEmitter = new EventEmitter();
-
- expect(() => {
- myEmitter.emit("error", "Hello!");
- }).toThrow("Hello!");
- });
-
- test("unhandled error event throws on emit with no arguments", () => {
- const myEmitter = new EventEmitter();
-
- expect(() => {
- myEmitter.emit("error");
- }).toThrow("Unhandled error.");
- });
-
- test("handled error event", () => {
- const myEmitter = new EventEmitter();
-
- let handled = false;
- myEmitter.on("error", (...args) => {
- expect(args).toEqual(["Hello", "World"]);
- handled = true;
- });
-
- myEmitter.emit("error", "Hello", "World");
-
- expect(handled).toBe(true);
- });
-
- test("errorMonitor", () => {
- const myEmitter = new EventEmitter();
-
- let handled = false;
- myEmitter.on(EventEmitter.errorMonitor, (...args) => {
- expect(args).toEqual(["Hello", "World"]);
- handled = true;
- });
-
- myEmitter.on("error", () => {});
-
- myEmitter.emit("error", "Hello", "World");
-
- expect(handled).toBe(true);
- });
-
- test("errorMonitor (unhandled)", () => {
- const myEmitter = new EventEmitter();
-
- let handled = false;
- myEmitter.on(EventEmitter.errorMonitor, (...args) => {
- expect(args).toEqual(["Hello", "World"]);
- handled = true;
- });
-
- expect(() => {
- myEmitter.emit("error", "Hello", "World");
- }).toThrow("Hello");
-
- expect(handled).toBe(true);
+ // Unlike Jest, bun supports async and done
+ test("async EventEmitter emit (microtask)", async done => {
+ await 1;
+ var emitter = new EventEmitter();
+ emitter.on("wow", () => done());
+ emitter.emit("wow");
});
-});
-
-describe("EventEmitter captureRejections", () => {
- // Can't catch the unhandled rejection because we do not have process.on("unhandledRejection")
- // test("captureRejections off will not capture rejections", async () => {
- // const myEmitter = new EventEmitter();
-
- // let handled = false;
- // myEmitter.on("error", (...args) => {
- // handled = true;
- // });
-
- // myEmitter.on("action", async () => {
- // throw new Error("Hello World");
- // });
-
- // myEmitter.emit("action");
-
- // await sleep(1);
-
- // expect(handled).toBe(false);
- // });
- test("it captures rejections", async () => {
- const myEmitter = new EventEmitter({ captureRejections: true });
-
- let handled: any = null;
- myEmitter.on("error", (...args) => {
- handled = args;
- });
- myEmitter.on("action", async () => {
- throw 123;
- });
-
- myEmitter.emit("action");
-
- await sleep(5);
-
- expect(handled).toEqual([123]);
+ test("async EventEmitter emit (microtask) after", async done => {
+ var emitter = new EventEmitter();
+ emitter.on("wow", () => done());
+ await 1;
+ emitter.emit("wow");
});
- test("it does not capture successful promises", async () => {
- const myEmitter = new EventEmitter({ captureRejections: true });
- let handled: any = null;
- myEmitter.on("error", () => {
- handled = true;
- });
-
- myEmitter.on("action", async () => {
- return 123;
- });
-
- myEmitter.emit("action");
+ test("EventEmitter emit (same tick)", done => {
+ var emitter = new EventEmitter();
- await sleep(5);
+ emitter.on("wow", () => done());
- expect(handled).toEqual(null);
+ emitter.emit("wow");
});
- test("it does not capture handled rejections", async () => {
- const myEmitter = new EventEmitter({ captureRejections: true });
- let handled: any = null;
- myEmitter.on("error", () => {
- handled = true;
- });
-
- myEmitter.on("action", async () => {
- return Promise.reject(123).catch(() => 234);
- });
-
- myEmitter.emit("action");
-
- await sleep(5);
-
- expect(handled).toEqual(null);
+ test("EventEmitter emit (setTimeout task)", done => {
+ var emitter = new EventEmitter();
+ emitter.on("wow", () => done());
+ setTimeout(() => emitter.emit("wow"), 1);
});
});
@@ -508,30 +112,53 @@ const waysOfCreating = [
},
];
-describe("EventEmitter constructors", () => {
- for (let create of waysOfCreating) {
- test(`${create
- .toString()
- .slice(6, 52)
- .replaceAll("\n", "")
- .trim()
- .replaceAll(/ {2,}/g, " ")
- .replace(/^\{ ?/, "")} should work`, () => {
- var myEmitter = create();
- var called = false;
- (myEmitter as EventEmitter).once("event", function () {
- called = true;
- // @ts-ignore
- expect(this).toBe(myEmitter);
- });
- var firstEvents = myEmitter._events;
- expect(myEmitter.listenerCount("event")).toBe(1);
+for (let create of waysOfCreating) {
+ it(`${create.toString().slice(10, 40).replaceAll("\n", "\\n").trim()} should work`, () => {
+ var myEmitter = create();
+ var called = false;
+ (myEmitter as EventEmitter).once("event", function () {
+ called = true;
+ // @ts-ignore
+ expect(this).toBe(myEmitter);
+ });
+ var firstEvents = myEmitter._events;
+ expect(myEmitter.listenerCount("event")).toBe(1);
- expect(myEmitter.emit("event")).toBe(true);
- expect(myEmitter.listenerCount("event")).toBe(0);
+ expect(myEmitter.emit("event")).toBe(true);
+ expect(myEmitter.listenerCount("event")).toBe(0);
- expect(firstEvents).toEqual({ event: firstEvents.event }); // it shouldn't mutate
- expect(called).toBe(true);
- });
- }
+ expect(firstEvents).toBe(myEmitter._events);
+ expect(called).toBe(true);
+ });
+}
+
+test("EventEmitter.on", () => {
+ var myEmitter = new EventEmitter();
+ expect(myEmitter.on("foo", () => {})).toBe(myEmitter);
+});
+
+test("EventEmitter.off", () => {
+ var myEmitter = new EventEmitter();
+ expect(myEmitter.off("foo", () => {})).toBe(myEmitter);
+});
+
+// Internally, EventEmitter has a JSC::Weak with the thisValue of the listener
+test("EventEmitter GCs", async () => {
+ gc();
+
+ const startCount = heapStats().objectTypeCounts["EventEmitter"] ?? 0;
+ (function () {
+ function EventEmitterSubclass(this: any) {
+ EventEmitter.call(this);
+ }
+
+ Object.setPrototypeOf(EventEmitterSubclass.prototype, EventEmitter.prototype);
+ Object.setPrototypeOf(EventEmitterSubclass, EventEmitter);
+ // @ts-ignore
+ var myEmitter = new EventEmitterSubclass();
+ myEmitter.on("foo", () => {});
+ myEmitter.emit("foo");
+ })();
+
+ await expectMaxObjectTypeCount(expect, "EventEmitter", startCount);
});
diff --git a/test/js/node/events/node-builtins.test.js b/test/js/node/events/node-builtins.test.js
new file mode 100644
index 000000000..67050f31a
--- /dev/null
+++ b/test/js/node/events/node-builtins.test.js
@@ -0,0 +1,18 @@
+import { describe, it, expect } from "bun:test";
+
+import { EventEmitter } from "events";
+var emitters = [EventEmitter, require("events")];
+describe("EventEmitter", () => {
+ it("should emit events", () => {
+ for (let Emitter of emitters) {
+ const emitter = new Emitter();
+ var called = false;
+ const listener = () => {
+ called = true;
+ };
+ emitter.on("test", listener);
+ emitter.emit("test");
+ expect(called).toBe(true);
+ }
+ });
+});