File
Blob: src/workerd/api/node/node.h
| 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 | |
| 26 | namespace 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 | |
| 46 | bool isNodeJsCompatEnabled(auto featureFlags) { |
| 47 | return featureFlags.getNodeJsCompat() || featureFlags.getNodeJsCompatV2(); |
| 48 | } |
| 49 | |
| 50 | constexpr bool isNodeJsCompatFsModule(kj::StringPtr name) { |
| 51 | return name == "node:fs"_kj; |
| 52 | } |
| 53 | |
| 54 | constexpr 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 | |
| 60 | constexpr bool isNodeHttpServerModule(kj::StringPtr name) { |
| 61 | return name == "node:_http_server"_kj; |
| 62 | } |
| 63 | |
| 64 | constexpr bool isNodeOsModule(kj::StringPtr name) { |
| 65 | return name == "node:os"_kj; |
| 66 | } |
| 67 | |
| 68 | constexpr bool isNodeHttp2Module(kj::StringPtr name) { |
| 69 | return name == "node:http2"_kj; |
| 70 | } |
| 71 | |
| 72 | constexpr bool isNodeConsoleModule(kj::StringPtr name) { |
| 73 | return name == "node:console"_kj; |
| 74 | } |
| 75 | |
| 76 | template <class Registry> |
| 77 | void 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 | |
| 215 | template <class TypeWrapper> |
| 216 | kj::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 | |
| 239 | kj::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 |