aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bun.js/bindings/headers-cpp.h2
-rw-r--r--src/bun.js/bindings/headers.h2
-rw-r--r--src/bun.js/bindings/webcore/JSEventEmitter.cpp52
-rw-r--r--src/bun.js/builtins/BunBuiltinNames.h1
-rw-r--r--src/bun.js/builtins/README.md4
-rw-r--r--src/bun.js/builtins/WebCoreJSBuiltins.cpp1
-rw-r--r--src/bun.js/builtins/cpp/NodeEventsBuiltins.cpp254
-rw-r--r--src/bun.js/builtins/cpp/NodeEventsBuiltins.h128
-rw-r--r--src/bun.js/builtins/cpp/WebCoreJSBuiltins.h4
-rw-r--r--src/bun.js/builtins/js/ConsoleObject.js2
-rw-r--r--src/bun.js/builtins/js/NodeEvents.js207
-rw-r--r--src/bun.js/builtins/js/ReadableStreamInternals.js2
-rw-r--r--src/bun.js/modules/EventsModule.h19
-rw-r--r--test/js/node/events/event-emitter.test.ts170
-rw-r--r--test/js/node/events/node-builtins.test.js18
-rw-r--r--test/js/node/events/node-events.node.test.ts386
16 files changed, 1116 insertions, 136 deletions
diff --git a/src/bun.js/bindings/headers-cpp.h b/src/bun.js/bindings/headers-cpp.h
index dafdaed17..cb352a76a 100644
--- a/src/bun.js/bindings/headers-cpp.h
+++ b/src/bun.js/bindings/headers-cpp.h
@@ -1,4 +1,4 @@
-//-- AUTOGENERATED FILE -- 1679200292
+//-- AUTOGENERATED FILE -- 1679596954
// clang-format off
#pragma once
diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h
index 5c727f397..f076b4719 100644
--- a/src/bun.js/bindings/headers.h
+++ b/src/bun.js/bindings/headers.h
@@ -1,5 +1,5 @@
// clang-format off
-//-- AUTOGENERATED FILE -- 1679530947
+//-- AUTOGENERATED FILE -- 1679596954
#pragma once
#include <stddef.h>
diff --git a/src/bun.js/bindings/webcore/JSEventEmitter.cpp b/src/bun.js/bindings/webcore/JSEventEmitter.cpp
index 003313b95..aa9fb4832 100644
--- a/src/bun.js/bindings/webcore/JSEventEmitter.cpp
+++ b/src/bun.js/bindings/webcore/JSEventEmitter.cpp
@@ -599,31 +599,31 @@ JSC_DEFINE_HOST_FUNCTION(Events_functionOnce,
RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(argument0));
}
-JSC_DEFINE_HOST_FUNCTION(Events_functionOn,
- (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
-{
- auto& vm = JSC::getVM(lexicalGlobalObject);
- auto throwScope = DECLARE_THROW_SCOPE(vm);
- UNUSED_PARAM(throwScope);
- UNUSED_PARAM(callFrame);
-
- if (UNLIKELY(callFrame->argumentCount() < 3))
- return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
- auto argument0 = jsEventEmitterCastFast(vm, lexicalGlobalObject, callFrame->uncheckedArgument(0));
- if (UNLIKELY(!argument0)) {
- throwException(lexicalGlobalObject, throwScope, createError(lexicalGlobalObject, "Expected EventEmitter"_s));
- return JSValue::encode(JSC::jsUndefined());
- }
- auto& impl = argument0->wrapped();
- auto eventType = callFrame->uncheckedArgument(1).toPropertyKey(lexicalGlobalObject);
- RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
- EnsureStillAliveScope argument2 = callFrame->uncheckedArgument(2);
- auto listener = convert<IDLNullable<IDLEventListener<JSEventListener>>>(*lexicalGlobalObject, argument2.value(), *argument0, [](JSC::JSGlobalObject& lexicalGlobalObject, JSC::ThrowScope& scope) { throwArgumentMustBeObjectError(lexicalGlobalObject, scope, 2, "listener", "EventEmitter", "removeListener"); });
- RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
- auto result = JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.addListenerForBindings(WTFMove(eventType), WTFMove(listener), false, false); }));
- RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
- vm.writeBarrier(argument0, argument2.value());
- return result;
-}
+// JSC_DEFINE_HOST_FUNCTION(Events_functionOn,
+// (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
+// {
+// auto& vm = JSC::getVM(lexicalGlobalObject);
+// auto throwScope = DECLARE_THROW_SCOPE(vm);
+// UNUSED_PARAM(throwScope);
+// UNUSED_PARAM(callFrame);
+
+// if (UNLIKELY(callFrame->argumentCount() < 3))
+// return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
+// auto argument0 = jsEventEmitterCastFast(vm, lexicalGlobalObject, callFrame->uncheckedArgument(0));
+// if (UNLIKELY(!argument0)) {
+// throwException(lexicalGlobalObject, throwScope, createError(lexicalGlobalObject, "Expected EventEmitter"_s));
+// return JSValue::encode(JSC::jsUndefined());
+// }
+// auto& impl = argument0->wrapped();
+// auto eventType = callFrame->uncheckedArgument(1).toPropertyKey(lexicalGlobalObject);
+// RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+// EnsureStillAliveScope argument2 = callFrame->uncheckedArgument(2);
+// auto listener = convert<IDLNullable<IDLEventListener<JSEventListener>>>(*lexicalGlobalObject, argument2.value(), *argument0, [](JSC::JSGlobalObject& lexicalGlobalObject, JSC::ThrowScope& scope) { throwArgumentMustBeObjectError(lexicalGlobalObject, scope, 2, "listener", "EventEmitter", "removeListener"); });
+// RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+// auto result = JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.addListenerForBindings(WTFMove(eventType), WTFMove(listener), false, false); }));
+// RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+// vm.writeBarrier(argument0, argument2.value());
+// return result;
+// }
}
diff --git a/src/bun.js/builtins/BunBuiltinNames.h b/src/bun.js/builtins/BunBuiltinNames.h
index ebc2c2c05..c687e145a 100644
--- a/src/bun.js/builtins/BunBuiltinNames.h
+++ b/src/bun.js/builtins/BunBuiltinNames.h
@@ -147,6 +147,7 @@ using namespace JSC;
macro(nextTick) \
macro(normalize) \
macro(on) \
+ macro(onAsyncIterator) \
macro(once) \
macro(options) \
macro(origin) \
diff --git a/src/bun.js/builtins/README.md b/src/bun.js/builtins/README.md
index c9288f47d..5aa25469f 100644
--- a/src/bun.js/builtins/README.md
+++ b/src/bun.js/builtins/README.md
@@ -4,7 +4,7 @@ TLDR:
```bash
# Delete the built files
-make clean-bindings generate-bindings && \
+make clean-bindings generate-builtins && \
# Compile all the C++ files which live in ../bindings
make bindings -j10 && \
# Re-link the binary without compiling zig (so it's faster)
@@ -31,7 +31,7 @@ The `js` directory is necessary for the bindings generator to work.
To regenerate the builtins, run this from Bun's project root (where the `Makefile` is)
```bash
-make builtins
+make generate-builtins
```
You'll want to also rebuild all the C++ bindings or you will get strange crashes on start
diff --git a/src/bun.js/builtins/WebCoreJSBuiltins.cpp b/src/bun.js/builtins/WebCoreJSBuiltins.cpp
index f5b25a463..86dea58e3 100644
--- a/src/bun.js/builtins/WebCoreJSBuiltins.cpp
+++ b/src/bun.js/builtins/WebCoreJSBuiltins.cpp
@@ -39,6 +39,7 @@
#include "ImportMetaObjectBuiltins.cpp"
#include "JSBufferConstructorBuiltins.cpp"
#include "JSBufferPrototypeBuiltins.cpp"
+#include "NodeEventsBuiltins.cpp"
#include "ProcessObjectInternalsBuiltins.cpp"
#include "ReadableByteStreamControllerBuiltins.cpp"
#include "ReadableByteStreamInternalsBuiltins.cpp"
diff --git a/src/bun.js/builtins/cpp/NodeEventsBuiltins.cpp b/src/bun.js/builtins/cpp/NodeEventsBuiltins.cpp
new file mode 100644
index 000000000..1e26a934b
--- /dev/null
+++ b/src/bun.js/builtins/cpp/NodeEventsBuiltins.cpp
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2015 Igalia
+ * Copyright (c) 2015 Igalia S.L.
+ * Copyright (c) 2015 Igalia.
+ * Copyright (c) 2015, 2016 Canon Inc. All rights reserved.
+ * Copyright (c) 2015, 2016, 2017 Canon Inc.
+ * Copyright (c) 2016, 2020 Apple Inc. All rights reserved.
+ * Copyright (c) 2022 Codeblog Corp. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+// DO NOT EDIT THIS FILE. It is automatically generated from JavaScript files for
+// builtins by the script: Source/JavaScriptCore/Scripts/generate-js-builtins.py
+
+#include "config.h"
+#include "NodeEventsBuiltins.h"
+
+#include "WebCoreJSClientData.h"
+#include <JavaScriptCore/HeapInlines.h>
+#include <JavaScriptCore/IdentifierInlines.h>
+#include <JavaScriptCore/ImplementationVisibility.h>
+#include <JavaScriptCore/Intrinsic.h>
+#include <JavaScriptCore/JSCJSValueInlines.h>
+#include <JavaScriptCore/JSCellInlines.h>
+#include <JavaScriptCore/StructureInlines.h>
+#include <JavaScriptCore/VM.h>
+
+namespace WebCore {
+
+const JSC::ConstructAbility s_nodeEventsOnAsyncIteratorCodeConstructAbility = JSC::ConstructAbility::CannotConstruct;
+const JSC::ConstructorKind s_nodeEventsOnAsyncIteratorCodeConstructorKind = JSC::ConstructorKind::None;
+const JSC::ImplementationVisibility s_nodeEventsOnAsyncIteratorCodeImplementationVisibility = JSC::ImplementationVisibility::Public;
+const int s_nodeEventsOnAsyncIteratorCodeLength = 4291;
+static const JSC::Intrinsic s_nodeEventsOnAsyncIteratorCodeIntrinsic = JSC::NoIntrinsic;
+const char* const s_nodeEventsOnAsyncIteratorCode =
+ "(function (emitter, event, options) {\n" \
+ " \"use strict\";\n" \
+ "\n" \
+ " var AbortError = class AbortError extends Error {\n" \
+ " constructor(message = \"The operation was aborted\", options = void 0) {\n" \
+ " if (options !== void 0 && typeof options !== \"object\") {\n" \
+ " throw new Error(`Invalid AbortError options:\\n" \
+ "\\n" \
+ "${JSON.stringify(options, null, 2)}`);\n" \
+ " }\n" \
+ " super(message, options);\n" \
+ " this.code = \"ABORT_ERR\";\n" \
+ " this.name = \"AbortError\";\n" \
+ " }\n" \
+ " };\n" \
+ "\n" \
+ " var { AbortSignal, Object, Number } = globalThis;\n" \
+ "\n" \
+ " if (@isUndefinedOrNull(options)) options = {};\n" \
+ "\n" \
+ " //\n" \
+ " var signal = options.signal;\n" \
+ " if (!@isUndefinedOrNull(signal) && !(signal instanceof AbortSignal))\n" \
+ " @throwTypeError(\"options.signal must be an AbortSignal\");\n" \
+ "\n" \
+ " if (signal?.aborted) {\n" \
+ " //\n" \
+ " throw new AbortError(@undefined, { cause: signal?.reason });\n" \
+ " }\n" \
+ "\n" \
+ " var highWatermark = options.highWatermark ?? Number.MAX_SAFE_INTEGER;\n" \
+ " if (highWatermark < 1) \n" \
+ " @throwRangeError(\"options.highWatermark must be >= 1\");\n" \
+ "\n" \
+ " var lowWatermark = options.lowWatermark ?? 1;\n" \
+ " if (lowWatermark < 1) \n" \
+ " @throwRangeError(\"options.lowWatermark must be >= 1\");\n" \
+ "\n" \
+ " var unconsumedEvents = @createFIFO();\n" \
+ " var unconsumedPromises = @createFIFO();\n" \
+ "\n" \
+ " var paused = false;\n" \
+ " var error = null;\n" \
+ " var finished = false;\n" \
+ " var size = 0;\n" \
+ "\n" \
+ " function abortListener() {\n" \
+ " errorHandler(new AbortError(@undefined, { cause: signal?.reason }));\n" \
+ " }\n" \
+ "\n" \
+ " function eventHandler(value) {\n" \
+ " if (unconsumedPromises.isEmpty()) {\n" \
+ " size++;\n" \
+ " if (!paused && size > highWatermark) {\n" \
+ " paused = true;\n" \
+ " emitter.pause();\n" \
+ " }\n" \
+ " unconsumedEvents.push(value);\n" \
+ " } else unconsumedPromises.shift().@resolve.@call(@undefined, { value, done: false });\n" \
+ " }\n" \
+ "\n" \
+ " function closeHandler() {\n" \
+ " removeAllListeners(listeners);\n" \
+ " finished = true;\n" \
+ " var doneResult = { value: @undefined, done: true };\n" \
+ " while (!unconsumedPromises.isEmpty()) {\n" \
+ " unconsumedPromises.shift().@resolve.@call(@undefined, doneResult);\n" \
+ " }\n" \
+ " \n" \
+ " return @createFulfilledPromise(doneResult);\n" \
+ " }\n" \
+ "\n" \
+ " function errorHandler(err) {\n" \
+ " if (unconsumedPromises.isEmpty()) error = err;\n" \
+ " else unconsumedPromises.shift().@reject.@call(@undefined, err);\n" \
+ " \n" \
+ " closeHandler();\n" \
+ " }\n" \
+ " \n" \
+ " function addEventListener(emitter, event, handler) {\n" \
+ " emitter.on(event, handler);\n" \
+ " @arrayPush(listeners, emitter, event, handler);\n" \
+ " }\n" \
+ " \n" \
+ " function removeAllListeners() {\n" \
+ " while (listeners.length > 0) {\n" \
+ " var [emitter, event, handler] = @arrayPop(listeners);\n" \
+ " emitter.off(event, handler);\n" \
+ " }\n" \
+ " }\n" \
+ "\n" \
+ " var iterator = async function* NodeEventsOnAsyncIterator() {\n" \
+ " //\n" \
+ " if (size) {\n" \
+ " var value = unconsumedEvents.shift();\n" \
+ " size--;\n" \
+ " if (paused && size < lowWatermark) {\n" \
+ " emitter.resume();\n" \
+ " paused = false;\n" \
+ " }\n" \
+ " yield @createFulfilledPromise({ value, done: false });\n" \
+ " }\n" \
+ "\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " if (error) {\n" \
+ " var p = @Promise.@reject(error);\n" \
+ " //\n" \
+ " error = null;\n" \
+ " yield p;\n" \
+ " }\n" \
+ "\n" \
+ " //\n" \
+ " if (finished) yield closeHandler();\n" \
+ "\n" \
+ " //\n" \
+ " var nextEventPromiseCapability = @newPromiseCapability(@Promise);\n" \
+ " unconsumedPromises.push(nextEventPromiseCapability);\n" \
+ " yield nextEventPromiseCapability.@promise;\n" \
+ " };\n" \
+ "\n" \
+ " //\n" \
+ " Object.defineProperties(iterator, {\n" \
+ " \"throw\": {\n" \
+ " value: (err) => {\n" \
+ " //\n" \
+ " if (err === undefined || err === null || !(err instanceof Error)) {\n" \
+ " @throwTypeError(\"The argument must be an instance of Error\");\n" \
+ " }\n" \
+ " errorHandler(err);\n" \
+ " },\n" \
+ " },\n" \
+ " \"return\": {\n" \
+ " value: () => {\n" \
+ " return closeHandler();\n" \
+ " }\n" \
+ " },\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " });\n" \
+ "\n" \
+ " //\n" \
+ " var listeners = [];\n" \
+ " addEventListener(emitter, event, eventHandler);\n" \
+ " if (event !== \"error\" && typeof emitter.on === \"function\") {\n" \
+ " addEventListener(emitter, \"error\", errorHandler);\n" \
+ " }\n" \
+ " var closeEvents = options?.close;\n" \
+ " if (closeEvents?.length) {\n" \
+ " for (var i = 0; i < closeEvents.length; i++) {\n" \
+ " addEventListener(emitter, closeEvents[i], closeHandler);\n" \
+ " }\n" \
+ " }\n" \
+ "\n" \
+ " if (signal)\n" \
+ " signal.once(\"abort\", abortListener);\n" \
+ "\n" \
+ " return iterator;\n" \
+ "})\n" \
+;
+
+
+#define DEFINE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \
+JSC::FunctionExecutable* codeName##Generator(JSC::VM& vm) \
+{\
+ JSVMClientData* clientData = static_cast<JSVMClientData*>(vm.clientData); \
+ return clientData->builtinFunctions().nodeEventsBuiltins().codeName##Executable()->link(vm, nullptr, clientData->builtinFunctions().nodeEventsBuiltins().codeName##Source(), std::nullopt, s_##codeName##Intrinsic); \
+}
+WEBCORE_FOREACH_NODEEVENTS_BUILTIN_CODE(DEFINE_BUILTIN_GENERATOR)
+#undef DEFINE_BUILTIN_GENERATOR
+
+
+} // namespace WebCore
diff --git a/src/bun.js/builtins/cpp/NodeEventsBuiltins.h b/src/bun.js/builtins/cpp/NodeEventsBuiltins.h
new file mode 100644
index 000000000..11b70ea92
--- /dev/null
+++ b/src/bun.js/builtins/cpp/NodeEventsBuiltins.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2015 Igalia
+ * Copyright (c) 2015 Igalia S.L.
+ * Copyright (c) 2015 Igalia.
+ * Copyright (c) 2015, 2016 Canon Inc. All rights reserved.
+ * Copyright (c) 2015, 2016, 2017 Canon Inc.
+ * Copyright (c) 2016, 2020 Apple Inc. All rights reserved.
+ * Copyright (c) 2022 Codeblog Corp. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+// DO NOT EDIT THIS FILE. It is automatically generated from JavaScript files for
+// builtins by the script: Source/JavaScriptCore/Scripts/generate-js-builtins.py
+
+#pragma once
+
+#include <JavaScriptCore/BuiltinUtils.h>
+#include <JavaScriptCore/Identifier.h>
+#include <JavaScriptCore/JSFunction.h>
+#include <JavaScriptCore/UnlinkedFunctionExecutable.h>
+
+namespace JSC {
+class FunctionExecutable;
+}
+
+namespace WebCore {
+
+/* NodeEvents */
+extern const char* const s_nodeEventsOnAsyncIteratorCode;
+extern const int s_nodeEventsOnAsyncIteratorCodeLength;
+extern const JSC::ConstructAbility s_nodeEventsOnAsyncIteratorCodeConstructAbility;
+extern const JSC::ConstructorKind s_nodeEventsOnAsyncIteratorCodeConstructorKind;
+extern const JSC::ImplementationVisibility s_nodeEventsOnAsyncIteratorCodeImplementationVisibility;
+
+#define WEBCORE_FOREACH_NODEEVENTS_BUILTIN_DATA(macro) \
+ macro(onAsyncIterator, nodeEventsOnAsyncIterator, 3) \
+
+#define WEBCORE_BUILTIN_NODEEVENTS_ONASYNCITERATOR 1
+
+#define WEBCORE_FOREACH_NODEEVENTS_BUILTIN_CODE(macro) \
+ macro(nodeEventsOnAsyncIteratorCode, onAsyncIterator, ASCIILiteral(), s_nodeEventsOnAsyncIteratorCodeLength) \
+
+#define WEBCORE_FOREACH_NODEEVENTS_BUILTIN_FUNCTION_NAME(macro) \
+ macro(onAsyncIterator) \
+
+#define DECLARE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \
+ JSC::FunctionExecutable* codeName##Generator(JSC::VM&);
+
+WEBCORE_FOREACH_NODEEVENTS_BUILTIN_CODE(DECLARE_BUILTIN_GENERATOR)
+#undef DECLARE_BUILTIN_GENERATOR
+
+class NodeEventsBuiltinsWrapper : private JSC::WeakHandleOwner {
+public:
+ explicit NodeEventsBuiltinsWrapper(JSC::VM& vm)
+ : m_vm(vm)
+ WEBCORE_FOREACH_NODEEVENTS_BUILTIN_FUNCTION_NAME(INITIALIZE_BUILTIN_NAMES)
+#define INITIALIZE_BUILTIN_SOURCE_MEMBERS(name, functionName, overriddenName, length) , m_##name##Source(JSC::makeSource(StringImpl::createWithoutCopying(s_##name, length), { }))
+ WEBCORE_FOREACH_NODEEVENTS_BUILTIN_CODE(INITIALIZE_BUILTIN_SOURCE_MEMBERS)
+#undef INITIALIZE_BUILTIN_SOURCE_MEMBERS
+ {
+ }
+
+#define EXPOSE_BUILTIN_EXECUTABLES(name, functionName, overriddenName, length) \
+ JSC::UnlinkedFunctionExecutable* name##Executable(); \
+ const JSC::SourceCode& name##Source() const { return m_##name##Source; }
+ WEBCORE_FOREACH_NODEEVENTS_BUILTIN_CODE(EXPOSE_BUILTIN_EXECUTABLES)
+#undef EXPOSE_BUILTIN_EXECUTABLES
+
+ WEBCORE_FOREACH_NODEEVENTS_BUILTIN_FUNCTION_NAME(DECLARE_BUILTIN_IDENTIFIER_ACCESSOR)
+
+ void exportNames();
+
+private:
+ JSC::VM& m_vm;
+
+ WEBCORE_FOREACH_NODEEVENTS_BUILTIN_FUNCTION_NAME(DECLARE_BUILTIN_NAMES)
+
+#define DECLARE_BUILTIN_SOURCE_MEMBERS(name, functionName, overriddenName, length) \
+ JSC::SourceCode m_##name##Source;\
+ JSC::Weak<JSC::UnlinkedFunctionExecutable> m_##name##Executable;
+ WEBCORE_FOREACH_NODEEVENTS_BUILTIN_CODE(DECLARE_BUILTIN_SOURCE_MEMBERS)
+#undef DECLARE_BUILTIN_SOURCE_MEMBERS
+
+};
+
+#define DEFINE_BUILTIN_EXECUTABLES(name, functionName, overriddenName, length) \
+inline JSC::UnlinkedFunctionExecutable* NodeEventsBuiltinsWrapper::name##Executable() \
+{\
+ if (!m_##name##Executable) {\
+ JSC::Identifier executableName = functionName##PublicName();\
+ if (overriddenName)\
+ executableName = JSC::Identifier::fromString(m_vm, overriddenName);\
+ m_##name##Executable = JSC::Weak<JSC::UnlinkedFunctionExecutable>(JSC::createBuiltinExecutable(m_vm, m_##name##Source, executableName, s_##name##ImplementationVisibility, s_##name##ConstructorKind, s_##name##ConstructAbility), this, &m_##name##Executable);\
+ }\
+ return m_##name##Executable.get();\
+}
+WEBCORE_FOREACH_NODEEVENTS_BUILTIN_CODE(DEFINE_BUILTIN_EXECUTABLES)
+#undef DEFINE_BUILTIN_EXECUTABLES
+
+inline void NodeEventsBuiltinsWrapper::exportNames()
+{
+#define EXPORT_FUNCTION_NAME(name) m_vm.propertyNames->appendExternalName(name##PublicName(), name##PrivateName());
+ WEBCORE_FOREACH_NODEEVENTS_BUILTIN_FUNCTION_NAME(EXPORT_FUNCTION_NAME)
+#undef EXPORT_FUNCTION_NAME
+}
+
+} // namespace WebCore
diff --git a/src/bun.js/builtins/cpp/WebCoreJSBuiltins.h b/src/bun.js/builtins/cpp/WebCoreJSBuiltins.h
index 598e0948e..0737f1a70 100644
--- a/src/bun.js/builtins/cpp/WebCoreJSBuiltins.h
+++ b/src/bun.js/builtins/cpp/WebCoreJSBuiltins.h
@@ -41,6 +41,7 @@
#include "ImportMetaObjectBuiltins.h"
#include "JSBufferConstructorBuiltins.h"
#include "JSBufferPrototypeBuiltins.h"
+#include "NodeEventsBuiltins.h"
#include "ProcessObjectInternalsBuiltins.h"
#include "ReadableByteStreamControllerBuiltins.h"
#include "ReadableByteStreamInternalsBuiltins.h"
@@ -71,6 +72,7 @@ public:
, m_importMetaObjectBuiltins(m_vm)
, m_jsBufferConstructorBuiltins(m_vm)
, m_jsBufferPrototypeBuiltins(m_vm)
+ , m_nodeEventsBuiltins(m_vm)
, m_processObjectInternalsBuiltins(m_vm)
, m_readableByteStreamControllerBuiltins(m_vm)
, m_readableByteStreamInternalsBuiltins(m_vm)
@@ -101,6 +103,7 @@ public:
ImportMetaObjectBuiltinsWrapper& importMetaObjectBuiltins() { return m_importMetaObjectBuiltins; }
JSBufferConstructorBuiltinsWrapper& jsBufferConstructorBuiltins() { return m_jsBufferConstructorBuiltins; }
JSBufferPrototypeBuiltinsWrapper& jsBufferPrototypeBuiltins() { return m_jsBufferPrototypeBuiltins; }
+ NodeEventsBuiltinsWrapper& nodeEventsBuiltins() { return m_nodeEventsBuiltins; }
ProcessObjectInternalsBuiltinsWrapper& processObjectInternalsBuiltins() { return m_processObjectInternalsBuiltins; }
ReadableByteStreamControllerBuiltinsWrapper& readableByteStreamControllerBuiltins() { return m_readableByteStreamControllerBuiltins; }
ReadableByteStreamInternalsBuiltinsWrapper& readableByteStreamInternalsBuiltins() { return m_readableByteStreamInternalsBuiltins; }
@@ -126,6 +129,7 @@ private:
ImportMetaObjectBuiltinsWrapper m_importMetaObjectBuiltins;
JSBufferConstructorBuiltinsWrapper m_jsBufferConstructorBuiltins;
JSBufferPrototypeBuiltinsWrapper m_jsBufferPrototypeBuiltins;
+ NodeEventsBuiltinsWrapper m_nodeEventsBuiltins;
ProcessObjectInternalsBuiltinsWrapper m_processObjectInternalsBuiltins;
ReadableByteStreamControllerBuiltinsWrapper m_readableByteStreamControllerBuiltins;
ReadableByteStreamInternalsBuiltinsWrapper m_readableByteStreamInternalsBuiltins;
diff --git a/src/bun.js/builtins/js/ConsoleObject.js b/src/bun.js/builtins/js/ConsoleObject.js
index 8601748c6..4eda757b6 100644
--- a/src/bun.js/builtins/js/ConsoleObject.js
+++ b/src/bun.js/builtins/js/ConsoleObject.js
@@ -112,4 +112,4 @@ function write(input) {
writer.flush(true);
return wrote;
-} \ No newline at end of file
+}
diff --git a/src/bun.js/builtins/js/NodeEvents.js b/src/bun.js/builtins/js/NodeEvents.js
new file mode 100644
index 000000000..b94d30725
--- /dev/null
+++ b/src/bun.js/builtins/js/NodeEvents.js
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2022 Codeblog Corp. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+function onAsyncIterator(emitter, event, options) {
+ "use strict";
+
+ var AbortError = class AbortError extends Error {
+ constructor(message = "The operation was aborted", options = void 0) {
+ if (options !== void 0 && typeof options !== "object") {
+ throw new Error(`Invalid AbortError options:\n\n${JSON.stringify(options, null, 2)}`);
+ }
+ super(message, options);
+ this.code = "ABORT_ERR";
+ this.name = "AbortError";
+ }
+ };
+
+ var { AbortSignal, Object, Number } = globalThis;
+
+ if (@isUndefinedOrNull(options)) options = {};
+
+ // Parameters validation
+ var signal = options.signal;
+ if (!@isUndefinedOrNull(signal) && !(signal instanceof AbortSignal))
+ @throwTypeError("options.signal must be an AbortSignal");
+
+ if (signal?.aborted) {
+ // TODO: Make this a builtin
+ throw new AbortError(@undefined, { cause: signal?.reason });
+ }
+
+ var highWatermark = options.highWatermark ?? Number.MAX_SAFE_INTEGER;
+ if (highWatermark < 1)
+ @throwRangeError("options.highWatermark must be >= 1");
+
+ var lowWatermark = options.lowWatermark ?? 1;
+ if (lowWatermark < 1)
+ @throwRangeError("options.lowWatermark must be >= 1");
+
+ var unconsumedEvents = @createFIFO();
+ var unconsumedPromises = @createFIFO();
+
+ var paused = false;
+ var error = null;
+ var finished = false;
+ var size = 0;
+
+ function abortListener() {
+ errorHandler(new AbortError(@undefined, { cause: signal?.reason }));
+ }
+
+ function eventHandler(value) {
+ if (unconsumedPromises.isEmpty()) {
+ size++;
+ if (!paused && size > highWatermark) {
+ paused = true;
+ emitter.pause();
+ }
+ unconsumedEvents.push(value);
+ } else unconsumedPromises.shift().@resolve.@call(@undefined, { value, done: false });
+ }
+
+ function closeHandler() {
+ removeAllListeners(listeners);
+ finished = true;
+ var doneResult = { value: @undefined, done: true };
+ while (!unconsumedPromises.isEmpty()) {
+ unconsumedPromises.shift().@resolve.@call(@undefined, doneResult);
+ }
+
+ return @createFulfilledPromise(doneResult);
+ }
+
+ function errorHandler(err) {
+ if (unconsumedPromises.isEmpty()) error = err;
+ else unconsumedPromises.shift().@reject.@call(@undefined, err);
+
+ closeHandler();
+ }
+
+ function addEventListener(emitter, event, handler) {
+ emitter.on(event, handler);
+ @arrayPush(listeners, emitter, event, handler);
+ }
+
+ function removeAllListeners() {
+ while (listeners.length > 0) {
+ var [emitter, event, handler] = @arrayPop(listeners);
+ emitter.off(event, handler);
+ }
+ }
+
+ var iterator = async function* NodeEventsOnAsyncIterator() {
+ // First, we consume all unread events
+ if (size) {
+ var value = unconsumedEvents.shift();
+ size--;
+ if (paused && size < lowWatermark) {
+ emitter.resume();
+ paused = false;
+ }
+ yield @createFulfilledPromise({ value, done: false });
+ }
+
+ // Then we error, if an error happened
+ // This happens one time if at all, because after 'error'
+ // we stop listening
+ if (error) {
+ var p = @Promise.@reject(error);
+ // Only the first element errors
+ error = null;
+ yield p;
+ }
+
+ // If the iterator is finished, resolve to done
+ if (finished) yield closeHandler();
+
+ // Wait until an event happens
+ var nextEventPromiseCapability = @newPromiseCapability(@Promise);
+ unconsumedPromises.push(nextEventPromiseCapability);
+ yield nextEventPromiseCapability.@promise;
+ };
+
+ // TODO: Use builtin
+ Object.defineProperties(iterator, {
+ "throw": {
+ value: (err) => {
+ // TODO: Use Error builtin?
+ if (err === undefined || err === null || !(err instanceof Error)) {
+ @throwTypeError("The argument must be an instance of Error");
+ }
+ errorHandler(err);
+ },
+ },
+ "return": {
+ value: () => {
+ return closeHandler();
+ }
+ },
+ // [kWatermarkData]: {
+ // /**
+ // * The current queue size
+ // */
+ // get size() {
+ // return size;
+ // },
+ // /**
+ // * The low watermark. The emitter is resumed every time size is lower than it
+ // */
+ // get low() {
+ // return lowWatermark;
+ // },
+ // /**
+ // * The high watermark. The emitter is paused every time size is higher than it
+ // */
+ // get high() {
+ // return highWatermark;
+ // },
+ // /**
+ // * It checks whether the emitter is paused by the watermark controller or not
+ // */
+ // get isPaused() {
+ // return paused;
+ // },
+ // },
+ });
+
+ // Adding event handlers
+ var listeners = [];
+ addEventListener(emitter, event, eventHandler);
+ if (event !== "error" && typeof emitter.on === "function") {
+ addEventListener(emitter, "error", errorHandler);
+ }
+ var closeEvents = options?.close;
+ if (closeEvents?.length) {
+ for (var i = 0; i < closeEvents.length; i++) {
+ addEventListener(emitter, closeEvents[i], closeHandler);
+ }
+ }
+
+ if (signal)
+ signal.once("abort", abortListener);
+
+ return iterator;
+}
diff --git a/src/bun.js/builtins/js/ReadableStreamInternals.js b/src/bun.js/builtins/js/ReadableStreamInternals.js
index ca7a1c355..e98d675b2 100644
--- a/src/bun.js/builtins/js/ReadableStreamInternals.js
+++ b/src/bun.js/builtins/js/ReadableStreamInternals.js
@@ -2206,4 +2206,4 @@ function readableStreamDefineLazyIterators(prototype) {
@Object.@defineProperty(prototype, asyncIterator, { value: createAsyncIterator });
@Object.@defineProperty(prototype, "values", { value: createValues });
return prototype;
-} \ No newline at end of file
+}
diff --git a/src/bun.js/modules/EventsModule.h b/src/bun.js/modules/EventsModule.h
index 7d53ff838..e60624ac9 100644
--- a/src/bun.js/modules/EventsModule.h
+++ b/src/bun.js/modules/EventsModule.h
@@ -28,10 +28,6 @@ inline void generateEventsSourceCode(JSC::JSGlobalObject *lexicalGlobalObject,
exportValues.append(JSC::JSFunction::create(
vm, lexicalGlobalObject, 0, MAKE_STATIC_STRING_IMPL("once"),
Events_functionOnce, ImplementationVisibility::Public));
- exportNames.append(JSC::Identifier::fromString(vm, "on"_s));
- exportValues.append(JSC::JSFunction::create(
- vm, lexicalGlobalObject, 0, MAKE_STATIC_STRING_IMPL("on"),
- Events_functionOn, ImplementationVisibility::Public));
exportNames.append(
JSC::Identifier::fromString(vm, "captureRejectionSymbol"_s));
exportValues.append(Symbol::create(
@@ -41,16 +37,23 @@ inline void generateEventsSourceCode(JSC::JSGlobalObject *lexicalGlobalObject,
jsCast<JSFunction *>(WebCore::JSEventEmitter::getConstructor(
vm, reinterpret_cast<Zig::GlobalObject *>(globalObject)));
+ for (size_t i = 0; i < exportNames.size(); i++) {
+ eventEmitterModuleCJS->putDirect(vm, exportNames[i], exportValues.at(i), 0);
+ }
+
+ exportNames.append(JSC::Identifier::fromString(vm, "on"_s));
+ auto *onAsyncIterFnPtr = eventEmitterModuleCJS->putDirectBuiltinFunction(
+ vm, globalObject, JSC::Identifier::fromString(vm, "on"_s),
+ nodeEventsOnAsyncIteratorCodeGenerator(vm),
+ PropertyAttribute::Builtin | PropertyAttribute::DontDelete);
+ exportValues.append(onAsyncIterFnPtr);
+
eventEmitterModuleCJS->putDirect(
vm,
PropertyName(
Identifier::fromUid(vm.symbolRegistry().symbolForKey("CommonJS"_s))),
jsNumber(0), 0);
- for (size_t i = 0; i < exportNames.size(); i++) {
- eventEmitterModuleCJS->putDirect(vm, exportNames[i], exportValues.at(i), 0);
- }
-
exportNames.append(JSC::Identifier::fromString(vm, "default"_s));
exportValues.append(eventEmitterModuleCJS);
}
diff --git a/test/js/node/events/event-emitter.test.ts b/test/js/node/events/event-emitter.test.ts
index 401ccf605..13e739032 100644
--- a/test/js/node/events/event-emitter.test.ts
+++ b/test/js/node/events/event-emitter.test.ts
@@ -1,11 +1,61 @@
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";
+const waysOfCreating = [
+ () => Object.create(EventEmitter.prototype),
+ () => new EventEmitter(),
+ () => new (class extends EventEmitter {})(),
+ () => {
+ class MyEmitter extends EventEmitter {}
+ return new MyEmitter();
+ },
+ () => {
+ var foo = {};
+ Object.setPrototypeOf(foo, EventEmitter.prototype);
+ return foo;
+ },
+ () => {
+ function FakeEmitter(this: any) {
+ return EventEmitter.call(this);
+ }
+ Object.setPrototypeOf(FakeEmitter.prototype, EventEmitter.prototype);
+ Object.setPrototypeOf(FakeEmitter, EventEmitter);
+ return new (FakeEmitter as any)();
+ },
+ () => {
+ const FakeEmitter: any = function FakeEmitter(this: any) {
+ EventEmitter.call(this);
+ } as any;
+ Object.assign(FakeEmitter.prototype, EventEmitter.prototype);
+ Object.assign(FakeEmitter, EventEmitter);
+ return new FakeEmitter();
+ },
+ () => {
+ var foo = {};
+ Object.assign(foo, EventEmitter.prototype);
+ return foo;
+ },
+];
+
describe("EventEmitter", () => {
+ const emitters = [EventEmitter, require("events")];
+ 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);
+ }
+ });
it("captureRejectionSymbol", () => {
expect(EventEmitter.captureRejectionSymbol).toBeDefined();
expect(captureRejectionSymbol).toBeDefined();
@@ -74,91 +124,55 @@ describe("EventEmitter", () => {
emitter.on("wow", () => done());
setTimeout(() => emitter.emit("wow"), 1);
});
-});
-
-const waysOfCreating = [
- () => Object.create(EventEmitter.prototype),
- () => new EventEmitter(),
- () => new (class extends EventEmitter {})(),
- () => {
- class MyEmitter extends EventEmitter {}
- return new MyEmitter();
- },
- () => {
- var foo = {};
- Object.setPrototypeOf(foo, EventEmitter.prototype);
- return foo;
- },
- () => {
- function FakeEmitter(this: any) {
- return EventEmitter.call(this);
- }
- Object.setPrototypeOf(FakeEmitter.prototype, EventEmitter.prototype);
- Object.setPrototypeOf(FakeEmitter, EventEmitter);
- return new (FakeEmitter as any)();
- },
- () => {
- const FakeEmitter: any = function FakeEmitter(this: any) {
- EventEmitter.call(this);
- } as any;
- Object.assign(FakeEmitter.prototype, EventEmitter.prototype);
- Object.assign(FakeEmitter, EventEmitter);
- return new FakeEmitter();
- },
- () => {
- var foo = {};
- Object.assign(foo, EventEmitter.prototype);
- return foo;
- },
-];
-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);
+ 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(firstEvents).toBe(myEmitter._events);
+ expect(called).toBe(true);
});
- var firstEvents = myEmitter._events;
- expect(myEmitter.listenerCount("event")).toBe(1);
-
- expect(myEmitter.emit("event")).toBe(true);
- expect(myEmitter.listenerCount("event")).toBe(0);
+ }
- expect(firstEvents).toBe(myEmitter._events);
- expect(called).toBe(true);
+ test("EventEmitter.on", () => {
+ var myEmitter = new EventEmitter();
+ expect(myEmitter.on("foo", () => {})).toBe(myEmitter);
});
-}
-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);
-});
+ 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();
+ // 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);
- }
+ 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");
- })();
+ 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);
+ 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
deleted file mode 100644
index 67050f31a..000000000
--- a/test/js/node/events/node-builtins.test.js
+++ /dev/null
@@ -1,18 +0,0 @@
-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);
- }
- });
-});
diff --git a/test/js/node/events/node-events.node.test.ts b/test/js/node/events/node-events.node.test.ts
new file mode 100644
index 000000000..10b109578
--- /dev/null
+++ b/test/js/node/events/node-events.node.test.ts
@@ -0,0 +1,386 @@
+import { EventEmitter, on } from "node:events";
+import { createTest } from "node-harness";
+
+const { expect, assert, describe, it, createCallCheckCtx, createDoneDotAll } = createTest(import.meta.path);
+// const NodeEventTarget = globalThis.EventTarget;
+
+describe("node:events.on (EE async iterator)", () => {
+ it("should return an async iterator", async () => {
+ const ee = new EventEmitter();
+ process.nextTick(() => {
+ ee.emit("foo", "bar");
+ // 'bar' is a spurious event, we are testing
+ // that it does not show up in the iterable
+ ee.emit("bar", 24);
+ ee.emit("foo", 42);
+ });
+
+ const iterable = on(ee, "foo");
+ const expected = [["bar"], [42]];
+
+ for await (const event of iterable) {
+ const current = expected.shift();
+
+ assert.deepStrictEqual(current, event);
+
+ if (expected.length === 0) {
+ break;
+ }
+ }
+ assert.strictEqual(ee.listenerCount("foo"), 0);
+ assert.strictEqual(ee.listenerCount("error"), 0);
+ });
+
+ // async function invalidArgType() {
+ // assert.throws(
+ // () => on({}, "foo"),
+ // common.expectsError({
+ // code: "ERR_INVALID_ARG_TYPE",
+ // name: "TypeError",
+ // }),
+ // );
+ // }
+
+ it("should throw an error when an error event is emitted", async () => {
+ const ee = new EventEmitter();
+ const _err = new Error("kaboom");
+ process.nextTick(() => {
+ ee.emit("error", _err);
+ });
+
+ const iterable = on(ee, "foo");
+ let looped = false;
+ let thrown = false;
+
+ try {
+ // eslint-disable-next-line no-unused-vars
+ for await (const event of iterable) {
+ looped = true;
+ }
+ } catch (err) {
+ thrown = true;
+ assert.strictEqual(err, _err);
+ }
+ assert.strictEqual(thrown, true);
+ assert.strictEqual(looped, false);
+ });
+
+ // async function errorDelayed() {
+ // const ee = new EventEmitter();
+ // const _err = new Error("kaboom");
+ // process.nextTick(() => {
+ // ee.emit("foo", 42);
+ // ee.emit("error", _err);
+ // });
+
+ // const iterable = on(ee, "foo");
+ // const expected = [[42]];
+ // let thrown = false;
+
+ // try {
+ // for await (const event of iterable) {
+ // const current = expected.shift();
+ // assert.deepStrictEqual(current, event);
+ // }
+ // } catch (err) {
+ // thrown = true;
+ // assert.strictEqual(err, _err);
+ // }
+ // assert.strictEqual(thrown, true);
+ // assert.strictEqual(ee.listenerCount("foo"), 0);
+ // assert.strictEqual(ee.listenerCount("error"), 0);
+ // }
+
+ // async function throwInLoop() {
+ // const ee = new EventEmitter();
+ // const _err = new Error("kaboom");
+
+ // process.nextTick(() => {
+ // ee.emit("foo", 42);
+ // });
+
+ // try {
+ // for await (const event of on(ee, "foo")) {
+ // assert.deepStrictEqual(event, [42]);
+ // throw _err;
+ // }
+ // } catch (err) {
+ // assert.strictEqual(err, _err);
+ // }
+
+ // assert.strictEqual(ee.listenerCount("foo"), 0);
+ // assert.strictEqual(ee.listenerCount("error"), 0);
+ // }
+
+ // async function next() {
+ // const ee = new EventEmitter();
+ // const iterable = on(ee, "foo");
+
+ // process.nextTick(function () {
+ // ee.emit("foo", "bar");
+ // ee.emit("foo", 42);
+ // iterable.return();
+ // });
+
+ // const results = await Promise.all([iterable.next(), iterable.next(), iterable.next()]);
+
+ // assert.deepStrictEqual(results, [
+ // {
+ // value: ["bar"],
+ // done: false,
+ // },
+ // {
+ // value: [42],
+ // done: false,
+ // },
+ // {
+ // value: undefined,
+ // done: true,
+ // },
+ // ]);
+
+ // assert.deepStrictEqual(await iterable.next(), {
+ // value: undefined,
+ // done: true,
+ // });
+ // }
+
+ // async function nextError() {
+ // const ee = new EventEmitter();
+ // const iterable = on(ee, "foo");
+ // const _err = new Error("kaboom");
+ // process.nextTick(function () {
+ // ee.emit("error", _err);
+ // });
+ // const results = await Promise.allSettled([iterable.next(), iterable.next(), iterable.next()]);
+ // assert.deepStrictEqual(results, [
+ // {
+ // status: "rejected",
+ // reason: _err,
+ // },
+ // {
+ // status: "fulfilled",
+ // value: {
+ // value: undefined,
+ // done: true,
+ // },
+ // },
+ // {
+ // status: "fulfilled",
+ // value: {
+ // value: undefined,
+ // done: true,
+ // },
+ // },
+ // ]);
+ // assert.strictEqual(ee.listeners("error").length, 0);
+ // }
+
+ // async function iterableThrow() {
+ // const ee = new EventEmitter();
+ // const iterable = on(ee, "foo");
+
+ // process.nextTick(() => {
+ // ee.emit("foo", "bar");
+ // ee.emit("foo", 42); // lost in the queue
+ // iterable.throw(_err);
+ // });
+
+ // const _err = new Error("kaboom");
+ // let thrown = false;
+
+ // assert.throws(
+ // () => {
+ // // No argument
+ // iterable.throw();
+ // },
+ // {
+ // message: 'The "EventEmitter.AsyncIterator" property must be' + " an instance of Error. Received undefined",
+ // name: "TypeError",
+ // },
+ // );
+
+ // const expected = [["bar"], [42]];
+
+ // try {
+ // for await (const event of iterable) {
+ // assert.deepStrictEqual(event, expected.shift());
+ // }
+ // } catch (err) {
+ // thrown = true;
+ // assert.strictEqual(err, _err);
+ // }
+ // assert.strictEqual(thrown, true);
+ // assert.strictEqual(expected.length, 0);
+ // assert.strictEqual(ee.listenerCount("foo"), 0);
+ // assert.strictEqual(ee.listenerCount("error"), 0);
+ // }
+
+ // // async function eventTarget() {
+ // // const et = new EventTarget();
+ // // const tick = () => et.dispatchEvent(new Event("tick"));
+ // // const interval = setInterval(tick, 0);
+ // // let count = 0;
+ // // for await (const [event] of on(et, "tick")) {
+ // // count++;
+ // // assert.strictEqual(event.type, "tick");
+ // // if (count >= 5) {
+ // // break;
+ // // }
+ // // }
+ // // assert.strictEqual(count, 5);
+ // // clearInterval(interval);
+ // // }
+
+ // async function errorListenerCount() {
+ // const et = new EventEmitter();
+ // on(et, "foo");
+ // assert.strictEqual(et.listenerCount("error"), 1);
+ // }
+
+ // // async function nodeEventTarget() {
+ // // const et = new NodeEventTarget();
+ // // const tick = () => et.dispatchEvent(new Event("tick"));
+ // // const interval = setInterval(tick, 0);
+ // // let count = 0;
+ // // for await (const [event] of on(et, "tick")) {
+ // // count++;
+ // // assert.strictEqual(event.type, "tick");
+ // // if (count >= 5) {
+ // // break;
+ // // }
+ // // }
+ // // assert.strictEqual(count, 5);
+ // // clearInterval(interval);
+ // // }
+
+ // async function abortableOnBefore() {
+ // it("should ");
+ // const ee = new EventEmitter();
+ // const abortedSignal = AbortSignal.abort();
+ // [1, {}, null, false, "hi"].forEach((signal: any) => {
+ // assert.throws(() => on(ee, "foo", { signal }), {
+ // code: "ERR_INVALID_ARG_TYPE",
+ // });
+ // });
+ // assert.throws(() => on(ee, "foo", { signal: abortedSignal }), {
+ // name: "AbortError",
+ // });
+ // }
+
+ // async function eventTargetAbortableOnBefore() {
+ // const et = new EventTarget();
+ // const abortedSignal = AbortSignal.abort();
+ // [1, {}, null, false, "hi"].forEach(signal => {
+ // assert.throws(() => on(et, "foo", { signal }), {
+ // code: "ERR_INVALID_ARG_TYPE",
+ // });
+ // });
+ // assert.throws(() => on(et, "foo", { signal: abortedSignal }), {
+ // name: "AbortError",
+ // });
+ // }
+
+ // it("should NOT throw an `AbortError` when done iterating over events", async done => {
+ // const ee = new EventEmitter();
+ // const ac = new AbortController();
+
+ // const i = setInterval(() => ee.emit("foo", "foo"), 1);
+ // let count = 0;
+
+ // async function foo() {
+ // for await (const f of on(ee, "foo", { signal: ac.signal })) {
+ // assert.strictEqual(f[0], "foo");
+ // if (++count === 5) break;
+ // }
+ // ac.abort(); // No error will occur
+ // }
+
+ // foo().finally(() => {
+ // clearInterval(i);
+ // expect(true).toBe(true);
+ // done();
+ // });
+ // });
+
+ // it("should throw an `AbortError` when NOT done iterating over events", async done => {
+ // const createDone = createDoneDotAll(done);
+ // const { mustCall, closeTimers } = createCallCheckCtx(createDone());
+ // const finalDone = createDone();
+
+ // const ee = new EventEmitter();
+ // const ac = new AbortController();
+
+ // const i = setInterval(() => ee.emit("foo", "foo"), 10);
+
+ // async function foo() {
+ // for await (const f of on(ee, "foo", { signal: ac.signal })) {
+ // assert.strictEqual(f, "foo");
+ // }
+ // }
+
+ // foo()
+ // .catch(
+ // mustCall(error => {
+ // assert.strictEqual(error.name, "AbortError");
+ // }),
+ // )
+ // .finally(() => {
+ // clearInterval(i);
+ // finalDone();
+ // closeTimers();
+ // });
+
+ // process.nextTick(() => ac.abort());
+ // });
+
+ // async function eventTargetAbortableOnAfter() {
+ // const et = new EventTarget();
+ // const ac = new AbortController();
+
+ // const i = setInterval(() => et.dispatchEvent(new Event("foo")), 10);
+
+ // async function foo() {
+ // for await (const f of on(et, "foo", { signal: ac.signal })) {
+ // assert(f);
+ // }
+ // }
+
+ // foo()
+ // .catch(
+ // common.mustCall(error => {
+ // assert.strictEqual(error.name, "AbortError");
+ // }),
+ // )
+ // .finally(() => {
+ // clearInterval(i);
+ // });
+
+ // process.nextTick(() => ac.abort());
+ // }
+
+ // async function eventTargetAbortableOnAfter2() {
+ // const et = new EventTarget();
+ // const ac = new AbortController();
+
+ // const i = setInterval(() => et.dispatchEvent(new Event("foo")), 10);
+
+ // async function foo() {
+ // for await (const f of on(et, "foo", { signal: ac.signal })) {
+ // assert(f);
+ // // Cancel after a single event has been triggered.
+ // ac.abort();
+ // }
+ // }
+
+ // foo()
+ // .catch(
+ // common.mustCall(error => {
+ // assert.strictEqual(error.name, "AbortError");
+ // }),
+ // )
+ // .finally(() => {
+ // clearInterval(i);
+ // });
+ // }
+});