diff options
Diffstat (limited to 'src/bun.js/builtins')
-rw-r--r-- | src/bun.js/builtins/cpp/NodeEventsBuiltins.cpp | 111 | ||||
-rw-r--r-- | src/bun.js/builtins/cpp/NodeEventsBuiltins.h | 9 | ||||
-rw-r--r-- | src/bun.js/builtins/js/NodeEvents.js | 100 |
3 files changed, 197 insertions, 23 deletions
diff --git a/src/bun.js/builtins/cpp/NodeEventsBuiltins.cpp b/src/bun.js/builtins/cpp/NodeEventsBuiltins.cpp index dca0ee431..852dfd6ee 100644 --- a/src/bun.js/builtins/cpp/NodeEventsBuiltins.cpp +++ b/src/bun.js/builtins/cpp/NodeEventsBuiltins.cpp @@ -51,7 +51,7 @@ 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 = 4455; +const int s_nodeEventsOnAsyncIteratorCodeLength = 4565; static const JSC::Intrinsic s_nodeEventsOnAsyncIteratorCodeIntrinsic = JSC::NoIntrinsic; const char* const s_nodeEventsOnAsyncIteratorCode = "(function (emitter, event, options) {\n" \ @@ -59,18 +59,21 @@ const char* const s_nodeEventsOnAsyncIteratorCode = "\n" \ " var { AbortSignal, Symbol, Number, Error } = globalThis;\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" \ + " function makeAbortError(msg, opts = void 0) {\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" \ - " super(message, options);\n" \ - " this.code = \"ABORT_ERR\";\n" \ - " this.name = \"AbortError\";\n" \ - " }\n" \ - " };\n" \ + " };\n" \ + " return new AbortError(msg, opts);\n" \ + " }\n" \ "\n" \ " if (@isUndefinedOrNull(emitter)) @throwTypeError(\"emitter is required\");\n" \ " //\n" \ @@ -86,7 +89,7 @@ const char* const s_nodeEventsOnAsyncIteratorCode = "\n" \ " if (signal?.aborted) {\n" \ " //\n" \ - " throw new AbortError(@undefined, { cause: signal?.reason });\n" \ + " throw makeAbortError(@undefined, { cause: signal?.reason });\n" \ " }\n" \ "\n" \ " var highWatermark = options.highWatermark ?? Number.MAX_SAFE_INTEGER;\n" \ @@ -107,7 +110,7 @@ const char* const s_nodeEventsOnAsyncIteratorCode = " var listeners = [];\n" \ "\n" \ " function abortListener() {\n" \ - " errorHandler(new AbortError(@undefined, { cause: signal?.reason }));\n" \ + " errorHandler(makeAbortError(@undefined, { cause: signal?.reason }));\n" \ " }\n" \ "\n" \ " function eventHandler(value) {\n" \ @@ -222,6 +225,90 @@ const char* const s_nodeEventsOnAsyncIteratorCode = "})\n" \ ; +const JSC::ConstructAbility s_nodeEventsOncePromiseCodeConstructAbility = JSC::ConstructAbility::CannotConstruct; +const JSC::ConstructorKind s_nodeEventsOncePromiseCodeConstructorKind = JSC::ConstructorKind::None; +const JSC::ImplementationVisibility s_nodeEventsOncePromiseCodeImplementationVisibility = JSC::ImplementationVisibility::Public; +const int s_nodeEventsOncePromiseCodeLength = 2418; +static const JSC::Intrinsic s_nodeEventsOncePromiseCodeIntrinsic = JSC::NoIntrinsic; +const char* const s_nodeEventsOncePromiseCode = + "(function (emitter, name, options) {\n" \ + " \"use strict\";\n" \ + "\n" \ + " var { AbortSignal, Error } = globalThis;\n" \ + "\n" \ + " function makeAbortError(msg, opts = void 0) {\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" \ + " return new AbortError(msg, opts);\n" \ + " }\n" \ + "\n" \ + " if (@isUndefinedOrNull(emitter)) return @Promise.@reject(@makeTypeError(\"emitter is required\"));\n" \ + " //\n" \ + " if (!(@isObject(emitter) && @isCallable(emitter.emit) && @isCallable(emitter.on)))\n" \ + " return @Promise.@reject(@makeTypeError(\"emitter must be an EventEmitter\"));\n" \ + "\n" \ + " if (@isUndefinedOrNull(options)) options = {};\n" \ + "\n" \ + " //\n" \ + " var signal = options.signal;\n" \ + " if (signal !== @undefined && (!@isObject(signal) || !(signal instanceof AbortSignal)))\n" \ + " return @Promise.@reject(@makeTypeError(\"options.signal must be an AbortSignal\"));\n" \ + "\n" \ + " if (signal?.aborted) {\n" \ + " //\n" \ + " return @Promise.@reject(makeAbortError(@undefined, { cause: signal?.reason }));\n" \ + " }\n" \ + "\n" \ + " var eventPromiseCapability = @newPromiseCapability(@Promise);\n" \ + "\n" \ + " var errorListener = (err) => {\n" \ + " emitter.removeListener(name, resolver);\n" \ + " if (!@isUndefinedOrNull(signal)) {\n" \ + " signal.removeEventListener(\"abort\", abortListener);\n" \ + " }\n" \ + " eventPromiseCapability.@reject.@call(@undefined, err);\n" \ + " };\n" \ + "\n" \ + " var resolver = (...args) => {\n" \ + " if (@isCallable(emitter.removeListener)) {\n" \ + " emitter.removeListener(\"error\", errorListener);\n" \ + " }\n" \ + " if (!@isUndefinedOrNull(signal)) {\n" \ + " signal.removeEventListener(\"abort\", abortListener);\n" \ + " }\n" \ + " eventPromiseCapability.@resolve.@call(@undefined, args);\n" \ + " };\n" \ + " \n" \ + " emitter.once(name, resolver);\n" \ + " if (name !== \"error\" && @isCallable(emitter.once)) {\n" \ + " //\n" \ + " //\n" \ + " emitter.once(\"error\", errorListener);\n" \ + " }\n" \ + "\n" \ + " function abortListener() {\n" \ + " emitter.removeListener(name, resolver);\n" \ + " emitter.removeListener(\"error\", errorListener);\n" \ + " eventPromiseCapability.@reject.@call(@undefined, makeAbortError(@undefined, { cause: signal?.reason }));\n" \ + " }\n" \ + "\n" \ + " if (!@isUndefinedOrNull(signal))\n" \ + " signal.addEventListener(\"abort\", abortListener, { once: true });\n" \ + "\n" \ + " return eventPromiseCapability.@promise;\n" \ + "})\n" \ +; + #define DEFINE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \ JSC::FunctionExecutable* codeName##Generator(JSC::VM& vm) \ diff --git a/src/bun.js/builtins/cpp/NodeEventsBuiltins.h b/src/bun.js/builtins/cpp/NodeEventsBuiltins.h index 11b70ea92..6a3f15403 100644 --- a/src/bun.js/builtins/cpp/NodeEventsBuiltins.h +++ b/src/bun.js/builtins/cpp/NodeEventsBuiltins.h @@ -52,17 +52,26 @@ extern const int s_nodeEventsOnAsyncIteratorCodeLength; extern const JSC::ConstructAbility s_nodeEventsOnAsyncIteratorCodeConstructAbility; extern const JSC::ConstructorKind s_nodeEventsOnAsyncIteratorCodeConstructorKind; extern const JSC::ImplementationVisibility s_nodeEventsOnAsyncIteratorCodeImplementationVisibility; +extern const char* const s_nodeEventsOncePromiseCode; +extern const int s_nodeEventsOncePromiseCodeLength; +extern const JSC::ConstructAbility s_nodeEventsOncePromiseCodeConstructAbility; +extern const JSC::ConstructorKind s_nodeEventsOncePromiseCodeConstructorKind; +extern const JSC::ImplementationVisibility s_nodeEventsOncePromiseCodeImplementationVisibility; #define WEBCORE_FOREACH_NODEEVENTS_BUILTIN_DATA(macro) \ macro(onAsyncIterator, nodeEventsOnAsyncIterator, 3) \ + macro(oncePromise, nodeEventsOncePromise, 3) \ #define WEBCORE_BUILTIN_NODEEVENTS_ONASYNCITERATOR 1 +#define WEBCORE_BUILTIN_NODEEVENTS_ONCEPROMISE 1 #define WEBCORE_FOREACH_NODEEVENTS_BUILTIN_CODE(macro) \ macro(nodeEventsOnAsyncIteratorCode, onAsyncIterator, ASCIILiteral(), s_nodeEventsOnAsyncIteratorCodeLength) \ + macro(nodeEventsOncePromiseCode, oncePromise, ASCIILiteral(), s_nodeEventsOncePromiseCodeLength) \ #define WEBCORE_FOREACH_NODEEVENTS_BUILTIN_FUNCTION_NAME(macro) \ macro(onAsyncIterator) \ + macro(oncePromise) \ #define DECLARE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \ JSC::FunctionExecutable* codeName##Generator(JSC::VM&); diff --git a/src/bun.js/builtins/js/NodeEvents.js b/src/bun.js/builtins/js/NodeEvents.js index ded8d1af7..14af767bf 100644 --- a/src/bun.js/builtins/js/NodeEvents.js +++ b/src/bun.js/builtins/js/NodeEvents.js @@ -28,16 +28,19 @@ function onAsyncIterator(emitter, event, options) { var { AbortSignal, Symbol, Number, Error } = globalThis; - 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)}`); + function makeAbortError(msg, opts = void 0) { + 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"; } - super(message, options); - this.code = "ABORT_ERR"; - this.name = "AbortError"; - } - }; + }; + return new AbortError(msg, opts); + } if (@isUndefinedOrNull(emitter)) @throwTypeError("emitter is required"); // TODO: Do a more accurate check @@ -53,7 +56,7 @@ function onAsyncIterator(emitter, event, options) { if (signal?.aborted) { // TODO: Make this a builtin - throw new AbortError(@undefined, { cause: signal?.reason }); + throw makeAbortError(@undefined, { cause: signal?.reason }); } var highWatermark = options.highWatermark ?? Number.MAX_SAFE_INTEGER; @@ -74,7 +77,7 @@ function onAsyncIterator(emitter, event, options) { var listeners = []; function abortListener() { - errorHandler(new AbortError(@undefined, { cause: signal?.reason })); + errorHandler(makeAbortError(@undefined, { cause: signal?.reason })); } function eventHandler(value) { @@ -187,3 +190,78 @@ function onAsyncIterator(emitter, event, options) { }); return iterator; } + +function oncePromise(emitter, name, options) { + "use strict"; + + var { AbortSignal, Error } = globalThis; + + function makeAbortError(msg, opts = void 0) { + 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"; + } + }; + return new AbortError(msg, opts); + } + + if (@isUndefinedOrNull(emitter)) return @Promise.@reject(@makeTypeError("emitter is required")); + // TODO: Do a more accurate check + if (!(@isObject(emitter) && @isCallable(emitter.emit) && @isCallable(emitter.on))) + return @Promise.@reject(@makeTypeError("emitter must be an EventEmitter")); + + if (@isUndefinedOrNull(options)) options = {}; + + // Parameters validation + var signal = options.signal; + if (signal !== @undefined && (!@isObject(signal) || !(signal instanceof AbortSignal))) + return @Promise.@reject(@makeTypeError("options.signal must be an AbortSignal")); + + if (signal?.aborted) { + // TODO: Make this a builtin + return @Promise.@reject(makeAbortError(@undefined, { cause: signal?.reason })); + } + + var eventPromiseCapability = @newPromiseCapability(@Promise); + + var errorListener = (err) => { + emitter.removeListener(name, resolver); + if (!@isUndefinedOrNull(signal)) { + signal.removeEventListener("abort", abortListener); + } + eventPromiseCapability.@reject.@call(@undefined, err); + }; + + var resolver = (...args) => { + if (@isCallable(emitter.removeListener)) { + emitter.removeListener("error", errorListener); + } + if (!@isUndefinedOrNull(signal)) { + signal.removeEventListener("abort", abortListener); + } + eventPromiseCapability.@resolve.@call(@undefined, args); + }; + + emitter.once(name, resolver); + if (name !== "error" && @isCallable(emitter.once)) { + // EventTarget does not have `error` event semantics like Node + // EventEmitters, we listen to `error` events only on EventEmitters. + emitter.once("error", errorListener); + } + + function abortListener() { + emitter.removeListener(name, resolver); + emitter.removeListener("error", errorListener); + eventPromiseCapability.@reject.@call(@undefined, makeAbortError(@undefined, { cause: signal?.reason })); + } + + if (!@isUndefinedOrNull(signal)) + signal.addEventListener("abort", abortListener, { once: true }); + + return eventPromiseCapability.@promise; +} |