fix(ext/node): polyfill node:domain module (#23088)

Closes https://github.com/denoland/deno/issues/16852

---------

Co-authored-by: Nathan Whitaker <nathan@deno.com>
This commit is contained in:
Bartek Iwańczuk 2024-04-03 20:37:10 +01:00 committed by GitHub
parent 86bc7a4381
commit 778b0b8eb5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 190 additions and 4 deletions

View File

@ -1,14 +1,87 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// Copyright Joyent and Node contributors. All rights reserved. MIT license.
// This code has been inspired by https://github.com/bevry/domain-browser/commit/8bce7f4a093966ca850da75b024239ad5d0b33c6
import { notImplemented } from "ext:deno_node/_utils.ts";
import { primordials } from "ext:core/mod.js";
const {
ArrayPrototypeSlice,
FunctionPrototypeBind,
FunctionPrototypeCall,
FunctionPrototypeApply,
} = primordials;
import { EventEmitter } from "node:events";
function emitError(e) {
this.emit("error", e);
}
export function create() {
notImplemented("domain.create");
return new Domain();
}
export class Domain {
export class Domain extends EventEmitter {
#handler;
constructor() {
notImplemented("domain.Domain.prototype.constructor");
super();
this.#handler = FunctionPrototypeBind(emitError, this);
}
add(emitter) {
emitter.on("error", this.#handler);
}
remove(emitter) {
emitter.off("error", this.#handler);
}
bind(fn) {
// deno-lint-ignore no-this-alias
const self = this;
return function () {
try {
FunctionPrototypeApply(fn, null, ArrayPrototypeSlice(arguments));
} catch (e) {
FunctionPrototypeCall(emitError, self, e);
}
};
}
intercept(fn) {
// deno-lint-ignore no-this-alias
const self = this;
return function (e) {
if (e) {
FunctionPrototypeCall(emitError, self, e);
} else {
try {
FunctionPrototypeApply(fn, null, ArrayPrototypeSlice(arguments, 1));
} catch (e) {
FunctionPrototypeCall(emitError, self, e);
}
}
};
}
run(fn) {
try {
fn();
} catch (e) {
FunctionPrototypeCall(emitError, this, e);
}
return this;
}
dispose() {
this.removeAllListeners();
return this;
}
enter() {
return this;
}
exit() {
return this;
}
}
export default {

View File

@ -64,6 +64,7 @@ util::unit_test_factory!(
crypto_sign_test = crypto / crypto_sign_test,
events_test,
dgram_test,
domain_test,
fs_test,
http_test,
http2_test,

View File

@ -0,0 +1,112 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// Copyright © Benjamin Lupton
// This code has been forked by https://github.com/bevry/domain-browser/commit/8bce7f4a093966ca850da75b024239ad5d0b33c6
import domain from "node:domain";
import { EventEmitter } from "node:events";
import { assertEquals } from "@std/assert/mod.ts";
Deno.test("should work on throws", async function () {
const deferred = Promise.withResolvers<void>();
const d = domain.create();
d.on("error", function (err) {
// @ts-ignore node:domain types are out of date
assertEquals(err && err.message, "a thrown error", "error message");
deferred.resolve();
});
d.run(function () {
throw new Error("a thrown error");
});
await deferred.promise;
});
Deno.test("should be able to add emitters", async function () {
const deferred = Promise.withResolvers<void>();
const d = domain.create();
const emitter = new EventEmitter();
d.add(emitter);
d.on("error", function (err) {
assertEquals(err && err.message, "an emitted error", "error message");
deferred.resolve();
});
emitter.emit("error", new Error("an emitted error"));
await deferred.promise;
});
Deno.test("should be able to remove emitters", async function () {
const deferred = Promise.withResolvers<void>();
const emitter = new EventEmitter();
const d = domain.create();
let domainGotError = false;
d.add(emitter);
d.on("error", function (_err) {
domainGotError = true;
});
emitter.on("error", function (err) {
assertEquals(
err && err.message,
"This error should not go to the domain",
"error message",
);
// Make sure nothing race condition-y is happening
setTimeout(function () {
assertEquals(domainGotError, false, "no domain error");
deferred.resolve();
}, 0);
});
d.remove(emitter);
emitter.emit("error", new Error("This error should not go to the domain"));
await deferred.promise;
});
Deno.test("bind should work", async function () {
const deferred = Promise.withResolvers<void>();
const d = domain.create();
d.on("error", function (err) {
assertEquals(err && err.message, "a thrown error", "error message");
deferred.resolve();
});
d.bind(function (err: Error, a: number, b: number) {
assertEquals(err && err.message, "a passed error", "error message");
assertEquals(a, 2, "value of a");
assertEquals(b, 3, "value of b");
throw new Error("a thrown error");
})(new Error("a passed error"), 2, 3);
await deferred.promise;
});
Deno.test("intercept should work", async function () {
const deferred = Promise.withResolvers<void>();
const d = domain.create();
let count = 0;
d.on("error", function (err) {
if (count === 0) {
assertEquals(err && err.message, "a thrown error", "error message");
} else if (count === 1) {
assertEquals(err && err.message, "a passed error", "error message");
deferred.resolve();
}
count++;
});
d.intercept(function (a: number, b: number) {
assertEquals(a, 2, "value of a");
assertEquals(b, 3, "value of b");
throw new Error("a thrown error");
// @ts-ignore node:domain types are out of date
})(null, 2, 3);
d.intercept(function (_a: number, _b: number) {
throw new Error("should never reach here");
// @ts-ignore node:domain types are out of date
})(new Error("a passed error"), 2, 3);
await deferred.promise;
});