Blob: src/node/AGENTS.md
src/node/ — Node.js Compatibility (TypeScript)
OVERVIEW
TypeScript and JavaScript layer implementing Node.js compatible built-in modules for Workers.
It is split across multiple layers:
- An internal layer consisting of:
- Non-user-importable TypeScript and JavaScript files in
internal/that implement core logic, utilities, and C++ JSG module declarations. - C++ JSG modules (
src/workerd/api/node/) expose native ops vianode-internal:*specifiers - Some native internal modules may be implemented in Rust
- Non-user-importable TypeScript and JavaScript files in
- A public layer of TypeScript files at the top-level that are user-importable.
It is common, but not required, for top-level .ts files to re-export from internal/ via node-internal: specifiers. This allows for a clean separation between public API surface and internal implementation details.
See README.md for 12 policy rules governing compat scope and philosophy.
MODULE GATING
node:* modules are gated behind the nodejs_compat compatibility flag.
The node:async_hooks module can be enabled individually via the nodejs_als compatibility flag.
Runtime compat flags checked via Cloudflare.compatibilityFlags['flag_name']:
| Flag | Guards |
|---|---|
enable_nodejs_http_server_modules |
http Server/createServer, https Server |
nodejs_zlib |
zlib streaming classes (Deflate, Gzip, etc.) |
enable_nodejs_process_v2 |
Extended process/events functionality |
remove_nodejs_compat_eol_v22/v23/v24 |
EOL deprecation of specific API surfaces (crypto, util, tls, process) |
Most modules require nodejs_compat + nodejs_compat_v2 flags (enforced by C++ side, not visible here).
ADDING NEW INTERNAL MODULES
New .ts and .js files in internal/ are auto-discovered by the build system via
glob(["internal/*.ts", "internal/*.js"]) in BUILD.bazel. No explicit registration is needed —
just create the file and it becomes importable via its node-internal: specifier.
For example, creating internal/internal_fs_glob.ts immediately makes
node-internal:internal_fs_glob available as an import specifier. No changes to BUILD.bazel or
any registration macro are required.
What does require explicit wiring:
- New top-level
node:*public modules need a corresponding.tsfile insrc/node/and must be registered in the C++ module registry (src/workerd/api/node/node.h, theNODEJS_MODULESmacro). - New C++ JSG native modules require a
.d.tsdeclaration file ininternal/to declare the shape of the native module.
CONVENTIONS
internal/*.js(not.ts) = upstream Node.js ports (streams_readable.js, streams_writable.js, etc.); paired with.d.tstype declarationsinternal/*.d.tswithout matching.ts= declares C++ JSG module shape (crypto.d.ts →node-internal:crypto)internal/*.d.tswith matching.js= types for ported JS (streams_readable.d.ts → streams_readable.js)- Top-level files are thin: import from
node-internal:*, re-export withexport { ... }/export * from - Feature-gated exports use
if (!flag) { throw ... }or conditional class assignment patterns - Shared validators in
internal/validators.ts; shared errors ininternal/internal_errors.ts _prefix files (e.g.,_http_agent.ts,_stream_readable.ts) = Node.js legacy internal module aliases- Some Node.js compat APIs are non-functional stubs that are either non-ops or throw when called