Skip to content
File

Blob: src/workerd/api/node/node.h

cpp342 lines
1#pragma once
2 
3#include "crypto.h"
4#include "diagnostics-channel.h"
5#include "zlib-util.h"
6 
7#include <workerd/api/node/async-hooks.h>
8#include <workerd/api/node/buffer.h>
9#include <workerd/api/node/module.h>
10#include <workerd/api/node/process.h>
11#include <workerd/api/node/sqlite.h>
12#include <workerd/api/node/timers.h>
13#include <workerd/api/node/url.h>
14#include <workerd/api/node/util.h>
15#include <workerd/io/compatibility-date.capnp.h>
16#include <workerd/jsg/jsg.h>
17#include <workerd/jsg/modules-new.h>
18#include <workerd/jsg/url.h>
19#include <workerd/rust/api/lib.rs.h>
20#include <workerd/rust/jsg/jsg.h>
21 
22#include <node/node.capnp.h>
23 
24#include <capnp/dynamic.h>
25 
26namespace workerd::api::node {
27 
28#define NODEJS_MODULES(V) \
29 V(AsyncHooksModule, "node-internal:async_hooks") \
30 V(BufferUtil, "node-internal:buffer") \
31 V(CryptoImpl, "node-internal:crypto") \
32 V(ModuleUtil, "node-internal:module") \
33 V(ProcessModule, "node-internal:process") \
34 V(UtilModule, "node-internal:util") \
35 V(DiagnosticsChannelModule, "node-internal:diagnostics_channel") \
36 V(ZlibUtil, "node-internal:zlib") \
37 V(UrlUtil, "node-internal:url") \
38 V(TimersUtil, "node-internal:timers") \
39 V(SqliteUtil, "node-internal:sqlite")
40 
41// Add to the NODEJS_MODULES_EXPERIMENTAL list any currently in-development
42// node.js compat C++ modules that should be guarded by the experimental compat
43// flag. Once they are ready to ship, move them up to the NODEJS_MODULES list.
44#define NODEJS_MODULES_EXPERIMENTAL(V)
45 
46bool isNodeJsCompatEnabled(auto featureFlags) {
47 return featureFlags.getNodeJsCompat() || featureFlags.getNodeJsCompatV2();
48}
49 
50constexpr bool isNodeJsCompatFsModule(kj::StringPtr name) {
51 return name == "node:fs"_kj;
52}
53 
54constexpr bool isNodeHttpModule(kj::StringPtr name) {
55 return name == "node:http"_kj || name == "node:_http_common"_kj ||
56 name == "node:_http_outgoing"_kj || name == "node:_http_client"_kj ||
57 name == "node:_http_incoming"_kj || name == "node:_http_agent"_kj || name == "node:https"_kj;
58}
59 
60constexpr bool isNodeHttpServerModule(kj::StringPtr name) {
61 return name == "node:_http_server"_kj;
62}
63 
64constexpr bool isNodeOsModule(kj::StringPtr name) {
65 return name == "node:os"_kj;
66}
67 
68constexpr bool isNodeHttp2Module(kj::StringPtr name) {
69 return name == "node:http2"_kj;
70}
71 
72constexpr bool isNodeConsoleModule(kj::StringPtr name) {
73 return name == "node:console"_kj;
74}
75 
76template <class Registry>
77void registerNodeJsCompatModules(Registry& registry, auto featureFlags) {
78#define V(T, N) \
79 registry.template addBuiltinModule<T>(N, workerd::jsg::ModuleRegistry::Type::INTERNAL);
80 
81 NODEJS_MODULES(V)
82 
83 if (featureFlags.getWorkerdExperimental()) {
84 NODEJS_MODULES_EXPERIMENTAL(V)
85 }
86 
87#undef V
88 
89 bool nodeJsCompatEnabled = isNodeJsCompatEnabled(featureFlags);
90 
91 registry.addBuiltinBundleFiltered(NODE_BUNDLE, [&](jsg::Module::Reader module) {
92 if (!nodeJsCompatEnabled) {
93 // If the `nodejs_compat` flag isn't enabled, only register internal modules.
94 // We need these for `console.log()`ing when running `workerd` locally.
95 return module.getType() == jsg::ModuleType::INTERNAL;
96 }
97 
98 if (isNodeJsCompatFsModule(module.getName())) {
99 return featureFlags.getEnableNodeJsFsModule();
100 }
101 
102 // We put node:http and node:https modules behind a compat flag
103 // for securing backward compatibility.
104 if (isNodeHttpModule(module.getName())) {
105 return featureFlags.getEnableNodejsHttpModules();
106 }
107 
108 // We put node:_http_server and related features behind a compat flag
109 // for securing backward compatibility.
110 if (isNodeHttpServerModule(module.getName())) {
111 return featureFlags.getEnableNodejsHttpServerModules();
112 }
113 
114 if (isNodeOsModule(module.getName())) {
115 return featureFlags.getEnableNodeJsOsModule();
116 }
117 
118 if (isNodeHttp2Module(module.getName())) {
119 return featureFlags.getEnableNodeJsHttp2Module();
120 }
121 
122 if (isNodeConsoleModule(module.getName())) {
123 return featureFlags.getEnableNodeJsConsoleModule();
124 }
125 
126 if (module.getName() == "node:vm"_kj) {
127 return featureFlags.getEnableNodeJsVmModule();
128 }
129 
130 if (module.getName() == "node:perf_hooks"_kj) {
131 return featureFlags.getEnableNodeJsPerfHooksModule();
132 }
133 
134 if (module.getName() == "node:domain"_kj) {
135 return featureFlags.getEnableNodeJsDomainModule();
136 }
137 
138 if (module.getName() == "node:child_process"_kj) {
139 return featureFlags.getEnableNodeJsChildProcessModule();
140 }
141 
142 if (module.getName() == "node:v8"_kj) {
143 return featureFlags.getEnableNodeJsV8Module();
144 }
145 
146 if (module.getName() == "node:tty"_kj) {
147 return featureFlags.getEnableNodeJsTtyModule();
148 }
149 
150 if (module.getName() == "node:punycode"_kj) {
151 return featureFlags.getEnableNodeJsPunycodeModule();
152 }
153 
154 if (module.getName() == "node:cluster"_kj) {
155 return featureFlags.getEnableNodeJsClusterModule();
156 }
157 
158 if (module.getName() == "node:worker_threads"_kj) {
159 return featureFlags.getEnableNodeJsWorkerThreadsModule();
160 }
161 
162 if (module.getName() == "node:_stream_wrap"_kj) {
163 return featureFlags.getEnableNodeJsStreamWrapModule();
164 }
165 
166 if (module.getName() == "node:wasi"_kj) {
167 return featureFlags.getEnableNodeJsWasiModule();
168 }
169 
170 if (module.getName() == "node:dgram"_kj) {
171 return featureFlags.getEnableNodeJsDgramModule();
172 }
173 
174 if (module.getName() == "node:inspector"_kj ||
175 module.getName() == "node:inspector/promises"_kj) {
176 return featureFlags.getEnableNodeJsInspectorModule();
177 }
178 
179 if (module.getName() == "node:trace_events"_kj) {
180 return featureFlags.getEnableNodeJsTraceEventsModule();
181 }
182 
183 if (module.getName() == "node:readline"_kj || module.getName() == "node:readline/promises"_kj) {
184 return featureFlags.getEnableNodeJsReadlineModule();
185 }
186 
187 if (module.getName() == "node:repl"_kj) {
188 return featureFlags.getEnableNodeJsReplModule();
189 }
190 
191 if (module.getName() == "node:sqlite"_kj) {
192 return featureFlags.getEnableNodeJsSqliteModule();
193 }
194 
195 return true;
196 });
197 
198 // If the `nodejs_compat` flag is off, but the `nodejs_als` flag is on, we
199 // need to register the `node:async_hooks` module from the bundle.
200 if (!nodeJsCompatEnabled && featureFlags.getNodeJsAls()) {
201 jsg::Bundle::Reader reader = NODE_BUNDLE;
202 for (auto module: reader.getModules()) {
203 auto specifier = module.getName();
204 if (specifier == "node:async_hooks") {
205 KJ_DASSERT(module.getType() == jsg::ModuleType::BUILTIN);
206 registry.addBuiltinModule(module);
207 }
208 }
209 }
210 
211 ::workerd::rust::jsg::RustModuleRegistry r(registry);
212 ::workerd::rust::api::register_nodejs_modules(r);
213}
214 
215template <class TypeWrapper>
216kj::Own<jsg::modules::ModuleBundle> getInternalNodeJsCompatModuleBundle(auto featureFlags) {
217 jsg::modules::ModuleBundle::BuiltinBuilder builder(
218 jsg::modules::ModuleBundle::BuiltinBuilder::Type::BUILTIN_ONLY);
219#define V(M, N) \
220 static const auto k##M##Specifier = N##_url; \
221 builder.addObject<M, TypeWrapper>(k##M##Specifier);
222 NODEJS_MODULES(V)
223 if (featureFlags.getWorkerdExperimental()) {
224 NODEJS_MODULES_EXPERIMENTAL(V)
225 }
226#undef V
227 jsg::modules::ModuleBundle::getBuiltInBundleFromCapnp(builder, NODE_BUNDLE);
228 
229 // Register Rust-implemented Node.js modules using the reusable adapter
230 // that bridges Rust ModuleCallback into BuiltinBuilder::addSynthetic.
231 {
232 ::workerd::rust::jsg::RustBuiltinModuleAdapter adapter(builder);
233 ::workerd::rust::api::register_nodejs_modules(adapter);
234 }
235 
236 return builder.finish();
237}
238 
239kj::Own<jsg::modules::ModuleBundle> getExternalNodeJsCompatModuleBundle(auto featureFlags) {
240 jsg::modules::ModuleBundle::BuiltinBuilder builder(
241 jsg::modules::ModuleBundle::BuiltinBuilder::Type::BUILTIN);
242 if (isNodeJsCompatEnabled(featureFlags)) {
243 jsg::modules::ModuleBundle::getBuiltInBundleFromCapnp(
244 builder, NODE_BUNDLE, [&](jsg::Module::Reader module) -> bool {
245 if (isNodeJsCompatFsModule(module.getName())) {
246 return featureFlags.getEnableNodeJsFsModule();
247 }
248 if (isNodeHttpModule(module.getName())) {
249 return featureFlags.getEnableNodejsHttpModules();
250 }
251 if (isNodeHttpServerModule(module.getName())) {
252 return featureFlags.getEnableNodejsHttpServerModules();
253 }
254 if (isNodeOsModule(module.getName())) {
255 return featureFlags.getEnableNodeJsOsModule();
256 }
257 if (isNodeHttp2Module(module.getName())) {
258 return featureFlags.getEnableNodeJsHttp2Module();
259 }
260 if (isNodeConsoleModule(module.getName())) {
261 return featureFlags.getEnableNodeJsConsoleModule();
262 }
263 if (module.getName() == "node:vm") {
264 return featureFlags.getEnableNodeJsVmModule();
265 }
266 if (module.getName() == "node:perf_hooks") {
267 return featureFlags.getEnableNodeJsPerfHooksModule();
268 }
269 if (module.getName() == "node:domain") {
270 return featureFlags.getEnableNodeJsDomainModule();
271 }
272 if (module.getName() == "node:child_process") {
273 return featureFlags.getEnableNodeJsChildProcessModule();
274 }
275 if (module.getName() == "node:v8") {
276 return featureFlags.getEnableNodeJsV8Module();
277 }
278 if (module.getName() == "node:tty") {
279 return featureFlags.getEnableNodeJsTtyModule();
280 }
281 if (module.getName() == "node:punycode") {
282 return featureFlags.getEnableNodeJsPunycodeModule();
283 }
284 if (module.getName() == "node:cluster") {
285 return featureFlags.getEnableNodeJsClusterModule();
286 }
287 if (module.getName() == "node:worker_threads") {
288 return featureFlags.getEnableNodeJsWorkerThreadsModule();
289 }
290 if (module.getName() == "node:_stream_wrap") {
291 return featureFlags.getEnableNodeJsStreamWrapModule();
292 }
293 if (module.getName() == "node:wasi") {
294 return featureFlags.getEnableNodeJsWasiModule();
295 }
296 if (module.getName() == "node:dgram") {
297 return featureFlags.getEnableNodeJsDgramModule();
298 }
299 if (module.getName() == "node:inspector" || module.getName() == "node:inspector/promises") {
300 return featureFlags.getEnableNodeJsInspectorModule();
301 }
302 if (module.getName() == "node:trace_events") {
303 return featureFlags.getEnableNodeJsTraceEventsModule();
304 }
305 if (module.getName() == "node:readline" || module.getName() == "node:readline/promises") {
306 return featureFlags.getEnableNodeJsReadlineModule();
307 }
308 if (module.getName() == "node:repl") {
309 return featureFlags.getEnableNodeJsReplModule();
310 }
311 if (module.getName() == "node:sqlite") {
312 return featureFlags.getEnableNodeJsSqliteModule();
313 }
314 return true;
315 });
316 } else if (featureFlags.getNodeJsAls()) {
317 // The AsyncLocalStorage API can be enabled independently of the rest
318 // of the nodejs_compat layer.
319 jsg::Bundle::Reader reader = NODE_BUNDLE;
320 for (auto module: reader.getModules()) {
321 auto specifier = module.getName();
322 if (specifier == "node:async_hooks") {
323 KJ_DASSERT(module.getType() == jsg::ModuleType::BUILTIN);
324 KJ_DASSERT(module.which() == workerd::jsg::Module::SRC);
325 auto specifier = KJ_ASSERT_NONNULL(jsg::Url::tryParse(module.getName()));
326 builder.addEsm(specifier, module.getSrc().asChars());
327 }
328 }
329 }
330 return builder.finish();
331}
332 
333#undef NODEJS_MODULES
334} // namespace workerd::api::node
335 
336#define EW_NODE_ISOLATE_TYPES \
337 EW_NODE_BUFFER_ISOLATE_TYPES, EW_NODE_CRYPTO_ISOLATE_TYPES, \
338 EW_NODE_DIAGNOSTICCHANNEL_ISOLATE_TYPES, EW_NODE_ASYNCHOOKS_ISOLATE_TYPES, \
339 EW_NODE_UTIL_ISOLATE_TYPES, EW_NODE_PROCESS_ISOLATE_TYPES, EW_NODE_ZLIB_ISOLATE_TYPES, \
340 EW_NODE_URL_ISOLATE_TYPES, EW_NODE_MODULE_ISOLATE_TYPES, EW_NODE_TIMERS_ISOLATE_TYPES, \
341 EW_NODE_SQLITE_ISOLATE_TYPES