Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3010a97

Browse files
authored
Merge pull request #5031 from cloudflare/jasnell/nodejs-nonop-domain
2 parents abd3d4f + 16f1b69 commit 3010a97

File tree

7 files changed

+311
-0
lines changed

7 files changed

+311
-0
lines changed

src/node/domain.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright (c) 2017-2022 Cloudflare, Inc.
2+
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3+
// https://opensource.org/licenses/Apache-2.0
4+
// Copyright Joyent, Inc. and other Node contributors.
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a
7+
// copy of this software and associated documentation files (the
8+
// "Software"), to deal in the Software without restriction, including
9+
// without limitation the rights to use, copy, modify, merge, publish,
10+
// distribute, sublicense, and/or sell copies of the Software, and to permit
11+
// persons to whom the Software is furnished to do so, subject to the
12+
// following conditions:
13+
//
14+
// The above copyright notice and this permission notice shall be included
15+
// in all copies or substantial portions of the Software.
16+
//
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
20+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
21+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
22+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
23+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
24+
25+
// Of all the deprecated Node.js modules that we need to have available
26+
// but don't want to actually implement, domain is the probably the
27+
// most least likely to ever be implemented.
28+
29+
import { EventEmitter } from 'node-internal:events';
30+
EventEmitter.usingDomains = false;
31+
32+
export class Domain extends EventEmitter {
33+
members: unknown[] | undefined = undefined;
34+
35+
_errorHandler(_er: unknown): void {
36+
// Should never be called since we override everything that would call it.
37+
// But if it is, just throw the error that is passed in.
38+
throw _er;
39+
}
40+
41+
enter(): void {
42+
// Instead of throwing, we just don't do anything here
43+
}
44+
45+
exit(): void {
46+
// Insteading of throwing, we just don't do anything here.
47+
}
48+
49+
add(_ee: unknown): void {
50+
// Insteading of throwing, we just don't do anything here.
51+
}
52+
53+
remove(_ee: unknown): void {
54+
// Insteading of throwing, we just don't do anything here.
55+
}
56+
57+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
58+
run(fn: Function, ...args: unknown[]): unknown {
59+
// This is non-operational. We end up just calling the function directly.
60+
this.enter();
61+
const ret: unknown = Reflect.apply(fn, this, args);
62+
this.exit();
63+
return ret;
64+
}
65+
66+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
67+
intercept(cb: Function): Function {
68+
// This is non-operational. We end up just returning the callback directly.
69+
return cb;
70+
}
71+
72+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
73+
bind(cb: Function): Function {
74+
// This is non-operational. We end up just returning the callback directly.
75+
return cb;
76+
}
77+
}
78+
79+
export function createDomain(): Domain {
80+
return new Domain();
81+
}
82+
export const create = createDomain();
83+
84+
// The active domain is always the one that we're currently in.
85+
export const active: Domain | null = null;
86+
87+
export default {
88+
Domain,
89+
active,
90+
createDomain,
91+
create,
92+
};

src/workerd/api/node/node.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ void registerNodeJsCompatModules(Registry& registry, auto featureFlags) {
129129
return featureFlags.getEnableNodeJsPerfHooksModule();
130130
}
131131

132+
if (module.getName() == "node:domain"_kj) {
133+
return featureFlags.getEnableNodeJsDomainModule();
134+
}
135+
132136
return true;
133137
});
134138

src/workerd/api/node/tests/BUILD.bazel

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,12 @@ wd_test(
596596
data = ["vm-test.js"],
597597
)
598598

599+
wd_test(
600+
src = "domain-nodejs-test.wd-test",
601+
args = ["--experimental"],
602+
data = ["domain-nodejs-test.js"],
603+
)
604+
599605
js_binary(
600606
name = "sidecar-supervisor",
601607
entry_point = "sidecar-supervisor.mjs",
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import * as domain from 'node:domain';
2+
import { strictEqual, throws, ok } from 'node:assert';
3+
import { EventEmitter } from 'node:events';
4+
5+
export const domainTest = {
6+
test() {
7+
// Note: test was generated by claude with manual tweaks
8+
9+
// Test that Domain class exists and is a constructor
10+
strictEqual(typeof domain.Domain, 'function');
11+
12+
// Test that createDomain function exists
13+
strictEqual(typeof domain.createDomain, 'function');
14+
15+
// Test that create export exists (should be a Domain instance)
16+
ok(domain.create instanceof domain.Domain);
17+
18+
// Test that active is null (domains are non-operational)
19+
strictEqual(domain.active, null);
20+
21+
// Test creating a new domain
22+
const d = domain.createDomain();
23+
ok(d instanceof domain.Domain);
24+
ok(d instanceof EventEmitter); // Domain extends EventEmitter
25+
26+
// Test that Domain has expected properties
27+
strictEqual(d.members, undefined);
28+
29+
// Test domain methods exist and are functions
30+
strictEqual(typeof d.enter, 'function');
31+
strictEqual(typeof d.exit, 'function');
32+
strictEqual(typeof d.add, 'function');
33+
strictEqual(typeof d.remove, 'function');
34+
strictEqual(typeof d.run, 'function');
35+
strictEqual(typeof d.intercept, 'function');
36+
strictEqual(typeof d.bind, 'function');
37+
strictEqual(typeof d._errorHandler, 'function');
38+
39+
// Test enter() method - should be no-op (not throw)
40+
d.enter();
41+
42+
// Test exit() method - should be no-op (not throw)
43+
d.exit();
44+
45+
// Test add() method - should be no-op (not throw)
46+
const ee = new EventEmitter();
47+
d.add(ee);
48+
d.add(null);
49+
d.add(undefined);
50+
d.add('string');
51+
d.add(123);
52+
53+
// Test remove() method - should be no-op (not throw)
54+
d.remove(ee);
55+
d.remove(null);
56+
d.remove(undefined);
57+
d.remove('string');
58+
d.remove(123);
59+
60+
// Test run() method - should execute function and return result
61+
let callCount = 0;
62+
const testFn = function (...args) {
63+
callCount++;
64+
strictEqual(this, d); // this should be the domain
65+
return args.reduce((a, b) => a + b, 0);
66+
};
67+
68+
const result = d.run(testFn, 1, 2, 3, 4);
69+
strictEqual(result, 10);
70+
strictEqual(callCount, 1);
71+
72+
// Test run() with function that throws
73+
const errorFn = function () {
74+
throw new Error('test error');
75+
};
76+
throws(() => {
77+
d.run(errorFn);
78+
}, /test error/);
79+
80+
// Test run() with no arguments
81+
const noArgFn = function () {
82+
return 'no args';
83+
};
84+
strictEqual(d.run(noArgFn), 'no args');
85+
86+
// Test intercept() method - should return the callback directly
87+
const callback1 = function () {
88+
return 'callback1';
89+
};
90+
const intercepted = d.intercept(callback1);
91+
strictEqual(intercepted, callback1);
92+
strictEqual(intercepted(), 'callback1');
93+
94+
// Test bind() method - should return the callback directly
95+
const callback2 = function () {
96+
return 'callback2';
97+
};
98+
const bound = d.bind(callback2);
99+
strictEqual(bound, callback2);
100+
strictEqual(bound(), 'callback2');
101+
102+
// Test _errorHandler() method - should throw the error passed to it
103+
const testError = new Error('test error handler');
104+
throws(() => {
105+
d._errorHandler(testError);
106+
}, testError);
107+
108+
throws(() => {
109+
d._errorHandler('string error');
110+
}, /string error/);
111+
112+
throws(() => {
113+
d._errorHandler(null);
114+
});
115+
116+
// Test that multiple domains can be created independently
117+
const d1 = domain.createDomain();
118+
const d2 = domain.createDomain();
119+
ok(d1 !== d2);
120+
ok(d1 instanceof domain.Domain);
121+
ok(d2 instanceof domain.Domain);
122+
123+
// Test that domains inherit from EventEmitter properly
124+
let emitted = false;
125+
d1.on('test', () => {
126+
emitted = true;
127+
});
128+
d1.emit('test');
129+
strictEqual(emitted, true);
130+
131+
// Test domain with async function (should work normally)
132+
let asyncResult = null;
133+
const asyncFn = async function (value) {
134+
return Promise.resolve(value * 2);
135+
};
136+
137+
const asyncPromise = d.run(asyncFn, 5);
138+
ok(asyncPromise instanceof Promise);
139+
140+
// Test that EventEmitter.usingDomains is set to false
141+
strictEqual(EventEmitter.usingDomains, false);
142+
143+
// Test default export
144+
strictEqual(typeof domain.default, 'object');
145+
strictEqual(domain.default.Domain, domain.Domain);
146+
strictEqual(domain.default.active, domain.active);
147+
strictEqual(domain.default.createDomain, domain.createDomain);
148+
strictEqual(domain.default.create, domain.create);
149+
150+
// Test edge cases with run()
151+
// Test with null function (should throw)
152+
throws(() => {
153+
d.run(null);
154+
});
155+
156+
throws(() => {
157+
d.run(undefined);
158+
});
159+
160+
throws(() => {
161+
d.run('not a function');
162+
});
163+
164+
// Test intercept with non-function (should still return it)
165+
const nonFunction = 'not a function';
166+
strictEqual(d.intercept(nonFunction), nonFunction);
167+
168+
// Test bind with non-function (should still return it)
169+
strictEqual(d.bind(nonFunction), nonFunction);
170+
171+
// Test that domain methods can be called multiple times safely
172+
d.enter();
173+
d.enter();
174+
d.exit();
175+
d.exit();
176+
177+
// Test members property remains undefined after operations
178+
d.add(new EventEmitter());
179+
d.remove(new EventEmitter());
180+
d.enter();
181+
d.exit();
182+
strictEqual(d.members, undefined);
183+
184+
console.log('All domain module tests passed!');
185+
},
186+
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Workerd = import "/workerd/workerd.capnp";
2+
3+
const unitTests :Workerd.Config = (
4+
services = [
5+
( name = "domain-nodejs-test",
6+
worker = (
7+
modules = [
8+
(name = "worker", esModule = embed "domain-nodejs-test.js")
9+
],
10+
compatibilityDate = "2025-09-01",
11+
compatibilityFlags = ["nodejs_compat", "enable_nodejs_domain_module", "experimental"]
12+
)
13+
),
14+
],
15+
);

src/workerd/api/node/tests/v8-nodejs-test.js

Whitespace-only changes.

src/workerd/io/compatibility-date.capnp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,4 +1075,12 @@ struct CompatibilityFlags @0x8f8c1b68151b6cef {
10751075
$compatEnableDate("2025-09-21");
10761076
# Enables PerformanceEntry, PerformanceMark, PerformanceMeasure, PerformanceResourceTiming,
10771077
# PerformanceObserver and PerformanceObserverEntryList global classes.
1078+
1079+
enableNodeJsDomainModule @124 :Bool
1080+
$compatEnableFlag("enable_nodejs_domain_module")
1081+
$compatDisableFlag("disable_nodejs_domain_module")
1082+
$experimental;
1083+
# $impliedByAfterDate(name = "nodeJsCompat", date = "2025-10-15");
1084+
# Enables the Node.js non-functional stub domain module. It is required to use this flag with
1085+
# nodejs_compat (or nodejs_compat_v2).
10781086
}

0 commit comments

Comments
 (0)