Skip to main content

nodejs/stdlib/
mod.rs

1//! Node.js core modules implemented natively for node-js.
2//!
3//! A `require(spec)` (see `builtins::call_builtin_function`) resolves a supported
4//! module to a `JsObj::Builtin("<module>")` namespace value — exactly the shape
5//! of the built-in `console`/`Math` namespaces — so `mod.method(...)` dispatches
6//! through `host::call_method` → `builtins::call_builtin_function("<module>.<method>")`
7//! → `stdlib::call`, and `const { method } = require('mod')` reads the method as a
8//! first-class `Builtin("mod.method")` via `namespace_property`.
9//!
10//! Every stdlib function is free-standing and acquires the thread-local `JsHost`
11//! through `with_host` only around allocations (and releases it before any
12//! re-entrant `host::invoke`), so callbacks (`fs` async, `EventEmitter.emit`,
13//! `assert.throws`) never double-borrow the host. Stateful instances (`Buffer`,
14//! crypto `Hash`, `EventEmitter`, `URL`) are plain objects carrying a hidden
15//! `@@native` tag (filtered from enumeration/display like `@@iterator`); their
16//! methods route through `instance_call` from `host::call_method`.
17
18use crate::host::{with_host, JsObj};
19use fusevm::Value;
20
21pub mod assert;
22pub mod async_hooks;
23pub mod buffer;
24pub mod child_process;
25pub mod cluster;
26pub mod console;
27pub mod crypto;
28pub mod date;
29pub mod dgram;
30pub mod diagnostics_channel;
31pub mod dns;
32pub mod domain;
33pub mod events;
34pub mod fs;
35pub mod fs_promises;
36pub mod http;
37pub mod http2;
38pub mod https;
39pub mod net;
40pub mod node_module;
41pub mod os;
42pub mod path;
43pub mod perf_hooks;
44pub mod process;
45pub mod punycode;
46pub mod querystring;
47pub mod readline;
48pub mod repl;
49pub mod stream;
50pub mod stream_consumers;
51pub mod stream_promises;
52pub mod stream_web;
53pub mod string_decoder;
54pub mod timers;
55pub mod tls;
56pub mod trace_events;
57pub mod tty;
58pub mod typedarray;
59pub mod url;
60pub mod util;
61pub mod util_types;
62pub mod v8;
63pub mod vm;
64pub mod worker_threads;
65pub mod zlib;
66
67/// Native-heavy core modules that node-js does not yet implement (TLS handshakes,
68/// HTTP/2 framing, OS worker threads sharing the thread-local heap, UDP sockets,
69/// V8 inspector, etc.). `require`ing them succeeds and yields a namespace so that
70/// programs which import-then-conditionally-use them still load; ACTUALLY calling
71/// a method throws `Error: <mod>.<method> is not implemented in node-js`. This is
72/// an honest not-yet-built surface, never a silent fake.
73pub const UNIMPLEMENTED_MODULES: &[&str] = &["inspector", "wasi"];
74
75/// True if `ns` is a known-but-unimplemented core module (see `UNIMPLEMENTED_MODULES`).
76pub fn is_unimplemented(ns: &str) -> bool {
77    UNIMPLEMENTED_MODULES.contains(&ns)
78}
79
80/// Canonical namespace name a `require(spec)` resolves to (after stripping an
81/// optional `node:` prefix), or `None` for an unsupported module.
82pub fn resolve(spec: &str) -> Option<&'static str> {
83    match spec.strip_prefix("node:").unwrap_or(spec) {
84        "fs" => Some("fs"),
85        "path" => Some("path"),
86        "os" => Some("os"),
87        "util" => Some("util"),
88        "assert" => Some("assert"),
89        "crypto" => Some("crypto"),
90        "buffer" => Some("buffer"),
91        "url" => Some("url"),
92        "process" => Some("process"),
93        "net" => Some("net"),
94        "http" => Some("http"),
95        "stream" => Some("stream"),
96        "tty" => Some("tty"),
97        // The `events` module's export IS the EventEmitter constructor, so
98        // `require('events')` yields the ctor namespace directly.
99        "events" => Some("EventEmitter"),
100        "string_decoder" => Some("string_decoder"),
101        "zlib" => Some("zlib"),
102        "querystring" => Some("querystring"),
103        "console" => Some("console"),
104        // `path/posix` is exactly our POSIX `path`; `assert/strict` is `assert`
105        // (our assert is already strict-equality based).
106        "path/posix" => Some("path"),
107        "path/win32" => Some("path"),
108        // `sys` is the long-deprecated alias for `util`.
109        "sys" => Some("util"),
110        "assert/strict" => Some("assert"),
111        "child_process" => Some("child_process"),
112        "dns" => Some("dns"),
113        "punycode" => Some("punycode"),
114        "timers" => Some("timers"),
115        "timers/promises" => Some("timers/promises"),
116        "perf_hooks" => Some("perf_hooks"),
117        "async_hooks" => Some("async_hooks"),
118        "util/types" => Some("util/types"),
119        "diagnostics_channel" => Some("diagnostics_channel"),
120        "v8" => Some("v8"),
121        "readline" => Some("readline"),
122        "vm" => Some("vm"),
123        "fs/promises" => Some("fs/promises"),
124        "dgram" => Some("dgram"),
125        "dns/promises" => Some("dns/promises"),
126        "worker_threads" => Some("worker_threads"),
127        "tls" => Some("tls"),
128        "https" => Some("https"),
129        "repl" => Some("repl"),
130        "cluster" => Some("cluster"),
131        "domain" => Some("domain"),
132        "http2" => Some("http2"),
133        "trace_events" => Some("trace_events"),
134        "module" => Some("module"),
135        "stream/consumers" => Some("stream/consumers"),
136        "stream/promises" => Some("stream/promises"),
137        "stream/web" => Some("stream/web"),
138        other => UNIMPLEMENTED_MODULES.iter().copied().find(|&m| m == other),
139    }
140}
141
142/// True if `qualified` (`namespace.method`) is a stdlib method that
143/// `call_builtin_function` should route into `call` (extends `is_known_builtin`).
144pub fn is_method(qualified: &str) -> bool {
145    let Some((ns, m)) = qualified.split_once('.') else {
146        return qualified == "assert";
147    };
148    match ns {
149        "fs" => fs::METHODS.contains(&m),
150        "path" => path::METHODS.contains(&m),
151        "os" => os::METHODS.contains(&m),
152        "util" => util::METHODS.contains(&m),
153        "assert" => assert::METHODS.contains(&m),
154        "assertStrict" => assert::METHODS.contains(&m),
155        "crypto" => crypto::METHODS.contains(&m),
156        "Buffer" => buffer::STATIC_METHODS.contains(&m),
157        "buffer" => m == "Buffer" || buffer::MODULE_METHODS.contains(&m),
158        "Date" => date::STATIC_METHODS.contains(&m),
159        "TextEncoder" | "TextDecoder" => false,
160        n if typedarray::is_ctor(n) => typedarray::STATIC_METHODS.contains(&m),
161        "url" => url::MODULE_METHODS.contains(&m) || m == "URL",
162        "net" => net::MODULE_METHODS.contains(&m),
163        "http" => http::MODULE_METHODS.contains(&m),
164        "stream" => stream::METHODS.contains(&m),
165        "worker_threads" => worker_threads::METHODS.contains(&m),
166        "zlib" => zlib::MODULE_METHODS.contains(&m),
167        "querystring" => querystring::METHODS.contains(&m),
168        "tty" => tty::METHODS.contains(&m),
169        "process" => process::METHODS.contains(&m),
170        "EventEmitter" => m == "EventEmitter" || events::STATIC_METHODS.contains(&m),
171        "console" => console::METHODS.contains(&m),
172        "child_process" => child_process::METHODS.contains(&m),
173        "dns" => dns::METHODS.contains(&m),
174        "punycode" => punycode::METHODS.contains(&m),
175        "timers" => timers::METHODS.contains(&m),
176        "timers/promises" => timers::PROMISES_METHODS.contains(&m),
177        "perf_hooks" | "performance" => perf_hooks::METHODS.contains(&m),
178        "async_hooks" => async_hooks::METHODS.contains(&m),
179        "util/types" => util_types::METHODS.contains(&m),
180        "diagnostics_channel" => diagnostics_channel::METHODS.contains(&m),
181        "v8" => v8::METHODS.contains(&m),
182        "readline" => readline::METHODS.contains(&m),
183        "vm" => vm::METHODS.contains(&m),
184        "fs/promises" => fs_promises::METHODS.contains(&m),
185        "dgram" => dgram::MODULE_METHODS.contains(&m),
186        "dns/promises" => matches!(
187            m,
188            "lookup"
189                | "lookupService"
190                | "resolve"
191                | "resolve4"
192                | "resolve6"
193                | "resolveMx"
194                | "resolveTxt"
195                | "resolveCname"
196                | "resolveNs"
197                | "resolvePtr"
198                | "resolveSrv"
199                | "resolveSoa"
200                | "resolveNaptr"
201                | "resolveCaa"
202                | "resolveTlsa"
203                | "resolveAny"
204                | "reverse"
205                | "getServers"
206                | "setServers"
207                | "getDefaultResultOrder"
208                | "setDefaultResultOrder"
209        ),
210        "tls" => tls::MODULE_METHODS.contains(&m),
211        "https" => https::MODULE_METHODS.contains(&m),
212        "repl" => repl::METHODS.contains(&m),
213        "cluster" => cluster::METHODS.contains(&m),
214        "domain" => domain::METHODS.contains(&m),
215        "http2" => http2::METHODS.contains(&m),
216        "trace_events" => trace_events::METHODS.contains(&m),
217        "module" => node_module::METHODS.contains(&m),
218        "Module" => node_module::MODULE_STATIC_METHODS.contains(&m),
219        "stream/consumers" => stream_consumers::METHODS.contains(&m),
220        "stream/promises" => stream_promises::is_method(m),
221        // Any method on an unimplemented namespace routes to `call`, which throws
222        // an honest "not implemented" error (so `mod.foo()` fails clearly rather
223        // than silently returning undefined).
224        _ if is_unimplemented(ns) => true,
225        _ => false,
226    }
227}
228
229/// Dispatch a resolved stdlib builtin (`assert`, or `namespace.method`). Returns
230/// `None` if `name` is not a stdlib builtin (the caller falls through to the core
231/// builtin table).
232pub fn call(name: &str, args: &[Value]) -> Option<Result<Value, String>> {
233    if name == "assert" {
234        return Some(assert::assert_ok(args));
235    }
236    let (ns, m) = name.split_once('.')?;
237    Some(match ns {
238        "fs" => fs::call(m, args)?,
239        "path" => path::call(m, args)?,
240        "os" => os::call(m, args)?,
241        "util" => util::call(m, args)?,
242        "assert" => assert::call(m, args)?,
243        "assertStrict" => assert::strict_call(m, args)?,
244        "crypto" => crypto::call(m, args)?,
245        "Buffer" => buffer::static_call(m, args)?,
246        "buffer" if m == "Buffer" => Ok(with_host(|h| h.alloc(JsObj::Builtin("Buffer".into())))),
247        "buffer" => buffer::module_call(m, args)?,
248        "Date" => date::static_call(m, args)?,
249        n if typedarray::is_ctor(n) => typedarray::static_call(n, m, args)?,
250        "url" if m == "URL" => Ok(with_host(|h| h.alloc(JsObj::Builtin("URL".into())))),
251        "url" => url::call(m, args)?,
252        "net" => net::call(m, args)?,
253        "http" => http::call(m, args)?,
254        "stream" => stream::call(m, args)?,
255        "worker_threads" => worker_threads::call(m, args)?,
256        "zlib" => zlib::call(m, args)?,
257        "querystring" => querystring::call(m, args)?,
258        "tty" => tty::call(m, args)?,
259        "process" => process::call(m, args)?,
260        "EventEmitter" if m == "EventEmitter" => Ok(with_host(|h| {
261            h.alloc(JsObj::Builtin("EventEmitter".into()))
262        })),
263        "EventEmitter" => events::static_call(m, args)?,
264        "console" => console::call(m, args)?,
265        "child_process" => child_process::call(m, args)?,
266        "dns" => dns::call(m, args)?,
267        "punycode" => punycode::call(m, args)?,
268        "timers" => timers::call(m, args)?,
269        "timers/promises" => timers::promises_call(m, args)?,
270        "perf_hooks" | "performance" => perf_hooks::call(m, args)?,
271        "async_hooks" => async_hooks::call(m, args)?,
272        "util/types" => util_types::call(m, args)?,
273        "diagnostics_channel" => diagnostics_channel::call(m, args)?,
274        "v8" => v8::call(m, args)?,
275        "readline" => readline::call(m, args)?,
276        "vm" => vm::call(m, args)?,
277        "fs/promises" => fs_promises::call(m, args)?,
278        "dgram" => dgram::call(m, args)?,
279        // dns/promises: getServers/setServers/get|setDefaultResultOrder are shared
280        // sync fns; every other method maps to dns's `promise<Cap>` variant.
281        "dns/promises" => match m {
282            "getServers" | "setServers" | "getDefaultResultOrder" | "setDefaultResultOrder" => {
283                dns::call(m, args)?
284            }
285            _ => {
286                let mut pm = String::from("promise");
287                let mut cs = m.chars();
288                if let Some(c) = cs.next() {
289                    pm.extend(c.to_uppercase());
290                    pm.push_str(cs.as_str());
291                }
292                dns::call(&pm, args)?
293            }
294        },
295        "tls" => tls::call(m, args)?,
296        "https" => https::call(m, args)?,
297        "repl" => repl::call(m, args)?,
298        "cluster" => cluster::call(m, args)?,
299        "domain" => domain::call(m, args)?,
300        "http2" => http2::call(m, args)?,
301        "trace_events" => trace_events::call(m, args)?,
302        "module" => node_module::call(m, args)?,
303        "Module" => node_module::static_call(m, args)?,
304        "stream/consumers" => stream_consumers::call(m, args)?,
305        "stream/promises" => stream_promises::call(m, args)?,
306        _ if is_unimplemented(ns) => Err(format!("Error: {ns}.{m} is not implemented in node-js")),
307        _ => return None,
308    })
309}
310
311/// A non-function constant on a stdlib namespace (`path.sep`, `os.EOL`,
312/// `buffer.Buffer`, `url.URL`), reachable via `namespace_property`.
313pub fn constant(ns: &str, name: &str) -> Option<Value> {
314    match ns {
315        // `path.posix` is our POSIX path itself; expose it (and `path.win32` as a
316        // best-effort alias) as a nested namespace so `path.posix.join(...)` works.
317        "path" if name == "posix" || name == "win32" => {
318            Some(with_host(|h| h.alloc(JsObj::Builtin("path".into()))))
319        }
320        "path" => path::constant(name),
321        "os" => os::constant(name),
322        "buffer" if name == "Buffer" => {
323            Some(with_host(|h| h.alloc(JsObj::Builtin("Buffer".into()))))
324        }
325        "buffer" if matches!(name, "Blob" | "File") => {
326            Some(with_host(|h| h.alloc(JsObj::Builtin(name.into()))))
327        }
328        "url" if name == "URL" => Some(with_host(|h| h.alloc(JsObj::Builtin("URL".into())))),
329        "net" => net::constant(name),
330        "tty" => tty::constant(name),
331        "repl" => repl::constant(name),
332        "readline" => readline::constant(name),
333        "diagnostics_channel" => diagnostics_channel::constant(name),
334        "v8" => v8::constant(name),
335        "console" if name == "Console" => {
336            Some(with_host(|h| h.alloc(JsObj::Builtin("Console".into()))))
337        }
338        "assert" if name == "AssertionError" => Some(with_host(|h| {
339            h.alloc(JsObj::Builtin("AssertionError".into()))
340        })),
341        "assert" if name == "strict" => Some(with_host(|h| {
342            h.alloc(JsObj::Builtin("assertStrict".into()))
343        })),
344        "stream" => stream::constant(name),
345        "http" => http::constant(name),
346        "string_decoder" if name == "StringDecoder" => Some(with_host(|h| {
347            h.alloc(JsObj::Builtin("StringDecoder".into()))
348        })),
349        "process" => process::constant(name),
350        "EventEmitter" if name == "EventEmitter" => Some(with_host(|h| {
351            h.alloc(JsObj::Builtin("EventEmitter".into()))
352        })),
353        "perf_hooks" | "performance" => perf_hooks::constant(name),
354        "dns" => dns::constant(name),
355        "punycode" => punycode::constant(name),
356        "async_hooks" if matches!(name, "AsyncLocalStorage" | "AsyncResource") => {
357            Some(with_host(|h| h.alloc(JsObj::Builtin(name.into()))))
358        }
359        "vm" if name == "Script" => Some(with_host(|h| h.alloc(JsObj::Builtin("Script".into())))),
360        "url" if name == "URLSearchParams" => Some(with_host(|h| {
361            h.alloc(JsObj::Builtin("URLSearchParams".into()))
362        })),
363        "fs" if name == "promises" => {
364            Some(with_host(|h| h.alloc(JsObj::Builtin("fs/promises".into()))))
365        }
366        "worker_threads" => worker_threads::constant(name),
367        "https" => https::constant(name),
368        "cluster" => cluster::constant(name),
369        "domain" => domain::constant(name),
370        "http2" => http2::constant(name),
371        "module" => node_module::constant(name),
372        "Module" => node_module::static_constant(name),
373        "stream/web" => stream_web::constant(name),
374        // util.types / util.TextEncoder|TextDecoder / util.MIMEType|MIMEParams.
375        "util" => util::constant(name),
376        // crypto class-constructor exports (require('crypto').Sign etc.) — the
377        // instances are made by factory fns, but the ctor names must resolve.
378        "crypto"
379            if matches!(
380                name,
381                "Sign"
382                    | "Verify"
383                    | "KeyObject"
384                    | "DiffieHellman"
385                    | "ECDH"
386                    | "X509Certificate"
387                    | "Hash"
388                    | "Hmac"
389                    | "Cipheriv"
390                    | "Decipheriv"
391            ) =>
392        {
393            Some(with_host(|h| h.alloc(JsObj::Builtin(name.into()))))
394        }
395        _ => None,
396    }
397}
398
399/// Construct a stdlib class instance (`new URL(...)`, `new EventEmitter()`, and
400/// `new Buffer(...)` legacy), reachable from `construct_builtin`. `None` if `name`
401/// is not a stdlib constructor.
402pub fn construct(name: &str, args: &[Value]) -> Option<Result<Value, String>> {
403    match name {
404        "URL" => Some(url::construct(args)),
405        "EventEmitter" => Some(Ok(events::new_emitter())),
406        "Buffer" => Some(buffer::static_call("from", args).unwrap_or(Ok(Value::Undef))),
407        "Date" => Some(date::construct(args)),
408        "StringDecoder" => Some(string_decoder::construct(args)),
409        "WeakRef" => Some(typedarray::construct_weakref(args)),
410        "FinalizationRegistry" => Some(typedarray::construct_finalization_registry(args)),
411        "TextEncoder" => Some(typedarray::construct_text_encoder()),
412        "TextDecoder" => Some(typedarray::construct_text_decoder(args)),
413        n if typedarray::is_ctor(n) => Some(typedarray::construct(n, args)),
414        n if stream::is_class(n) => Some(Ok(stream::construct(n))),
415        "AsyncLocalStorage" | "AsyncResource" => async_hooks::construct(name, args),
416        "Script" => Some(vm::construct(args)),
417        "URLSearchParams" => Some(url::construct_search_params(args)),
418        "Worker" => Some(worker_threads::construct_worker(args)),
419        "Domain" => Some(domain::construct(args)),
420        "Tracing" => Some(trace_events::construct(args)),
421        "Blob" => Some(buffer::construct_blob(args)),
422        "File" => Some(buffer::construct_file(args)),
423        "AssertionError" => Some(Ok(assert::construct_assertion_error(args))),
424        "X509Certificate" => Some(crypto::construct_x509(args)),
425        "MIMEType" => Some(util::construct_mime_type(args)),
426        "MIMEParams" => Some(util::construct_mime_params(args)),
427        "Resolver" => Some(Ok(dns::construct_resolver(args))),
428        "ReadStream" | "WriteStream" => Some(Ok(tty::construct(name, args))),
429        "MessageChannel" => Some(worker_threads::construct_message_channel(args)),
430        "BroadcastChannel" => Some(worker_threads::construct_broadcast_channel(args)),
431        "PerformanceObserver" => Some(perf_hooks::construct(name, args)),
432        "REPLServer" | "Recoverable" => Some(repl::construct(name, args)),
433        "Interface" => Some(readline::construct(args)),
434        "Console" => Some(console::construct(args)),
435        "Serializer" | "DefaultSerializer" | "Deserializer" | "DefaultDeserializer" => {
436            Some(v8::construct(name, args))
437        }
438        // net/http constructors: their `construct` already returns Option<Result>.
439        "Socket" | "Stream" | "Server" | "SocketAddress" | "BlockList" => {
440            net::construct(name, args)
441        }
442        "Agent" | "http.Server" => http::construct(name, args),
443        // stream/web WHATWG classes (its `construct` returns Option<Result>).
444        n if stream_web::is_class(n) => stream_web::construct(n, args),
445        _ => None,
446    }
447}
448
449/// The hidden `@@native` instance tag of `recv` (`"Buffer"`/`"Hash"`/
450/// `"EventEmitter"`/`"URL"`), or `None` for a non-native object.
451pub fn native_tag(recv: &Value) -> Option<String> {
452    with_host(|h| match h.get(recv) {
453        Some(JsObj::Object(p)) => p.get("@@native").map(|v| h.str_of(v)),
454        _ => None,
455    })
456}
457
458/// Whether `name` is a method of a native instance tagged `tag`. Used by
459/// `get_property` so a method *read* (`server.listen.apply(...)`, the express
460/// listen path) yields a bound method rather than `undefined` — the method is
461/// still dispatched through `instance_call` when the bound method is invoked.
462pub fn instance_has_method(tag: &str, name: &str) -> bool {
463    // Shared EventEmitter surface for the emitter-backed instances.
464    const EMITTER: &[&str] = &[
465        "on",
466        "once",
467        "emit",
468        "addListener",
469        "prependListener",
470        "prependOnceListener",
471        "removeListener",
472        "off",
473        "removeAllListeners",
474        "listeners",
475        "listenerCount",
476        "eventNames",
477        "setMaxListeners",
478        "getMaxListeners",
479    ];
480    let base: &[&str] = match tag {
481        "Server" => &["listen", "close", "address"],
482        "Socket" => &[
483            "write",
484            "end",
485            "destroy",
486            "pause",
487            "resume",
488            "setEncoding",
489            "setKeepAlive",
490            "setNoDelay",
491            "setTimeout",
492            "ref",
493            "unref",
494            "connect",
495        ],
496        "ServerResponse" => &[
497            "writeHead",
498            "setHeader",
499            "getHeader",
500            "getHeaderNames",
501            "getHeaders",
502            "hasHeader",
503            "removeHeader",
504            "write",
505            "end",
506            "flushHeaders",
507        ],
508        "IncomingMessage" => &["pause", "resume", "setEncoding", "destroy"],
509        "Buffer" => &[
510            "toString",
511            "toJSON",
512            "equals",
513            "slice",
514            "subarray",
515            "readUInt8",
516            "includes",
517            "indexOf",
518            "lastIndexOf",
519            "write",
520            "copy",
521            "fill",
522            "compare",
523            "readUInt16BE",
524            "readUInt16LE",
525            "writeUInt8",
526            "writeUInt16BE",
527            "writeUInt16LE",
528        ],
529        "Readable" | "Writable" | "Duplex" | "Transform" | "PassThrough" | "Stream" => &[
530            "read",
531            "write",
532            "end",
533            "pipe",
534            "pause",
535            "resume",
536            "setEncoding",
537            "destroy",
538            "push",
539        ],
540        "URL" => &["toString", "toJSON"],
541        "AsyncLocalStorage" => async_hooks::ALS_METHODS,
542        "AsyncHook" => async_hooks::HOOK_METHODS,
543        "Channel" => &["subscribe", "unsubscribe", "publish"],
544        "WriteStream" => &[
545            "write",
546            "end",
547            "on",
548            "once",
549            "removeListener",
550            "cork",
551            "uncork",
552            "setEncoding",
553        ],
554        "Hmac" => &["update", "digest"],
555        "Interface" => readline::INTERFACE_METHODS,
556        "Script" => vm::SCRIPT_METHODS,
557        "URLSearchParams" => url::SEARCH_PARAMS_METHODS,
558        "UdpSocket" => dgram::SOCKET_METHODS,
559        "Worker" => worker_threads::WORKER_METHODS,
560        "MessagePort" => worker_threads::PORT_METHODS,
561        "TLSServer" => tls::SERVER_METHODS,
562        "TLSSocket" => tls::SOCKET_METHODS,
563        "HTTPSServerResponse" => https::RESPONSE_METHODS,
564        "HTTPSClientRequest" => https::CLIENT_REQUEST_METHODS,
565        "REPLServer" => repl::REPLSERVER_METHODS,
566        "ClusterWorker" => cluster::WORKER_METHODS,
567        "Domain" => domain::DOMAIN_METHODS,
568        "Tracing" => trace_events::TRACING_METHODS,
569        "Http2Server" => http2::SERVER_METHODS,
570        "Http2Stream" => http2::STREAM_METHODS,
571        "Http2Session" => http2::SESSION_METHODS,
572        "Cipheriv" | "Decipheriv" => &["update", "final", "setAutoPadding"],
573        "BlockList" => net::BLOCKLIST_METHODS,
574        "ClientRequest" => http::CLIENT_REQUEST_METHODS,
575        "Agent" => &["destroy", "getName"],
576        "Blob" | "File" => buffer::BLOB_METHODS,
577        "ReadStream" => tty::READ_STREAM_METHODS,
578        "Dirent" => fs::DIRENT_METHODS,
579        "Dir" => fs::DIR_METHODS,
580        "FSReadStream" => fs::READ_STREAM_METHODS,
581        "FSWriteStream" => fs::WRITE_STREAM_METHODS,
582        "Resolver" => dns::RESOLVER_METHODS,
583        "Histogram" => perf_hooks::HISTOGRAM_METHODS,
584        "PerformanceObserver" => perf_hooks::PERFORMANCE_OBSERVER_METHODS,
585        "PerformanceObserverEntryList" => perf_hooks::OBSERVER_ENTRY_LIST_METHODS,
586        "BroadcastChannel" => worker_threads::BROADCAST_CHANNEL_METHODS,
587        "TracingChannel" => diagnostics_channel::TRACING_CHANNEL_METHODS,
588        "Serializer" => v8::SERIALIZER_METHODS,
589        "Deserializer" => v8::DESERIALIZER_METHODS,
590        "Console" => console::CONSOLE_METHODS,
591        "ChildProcess" => child_process::CHILD_PROCESS_METHODS,
592        "Sign" => &["update", "sign"],
593        "Verify" => &["update", "verify"],
594        "KeyObject" => &["export", "equals"],
595        "DiffieHellman" => &[
596            "generateKeys",
597            "computeSecret",
598            "getPrime",
599            "getGenerator",
600            "getPublicKey",
601            "getPrivateKey",
602            "setPublicKey",
603            "setPrivateKey",
604        ],
605        "ECDH" => &[
606            "generateKeys",
607            "computeSecret",
608            "getPublicKey",
609            "getPrivateKey",
610            "setPrivateKey",
611        ],
612        "X509Certificate" => &["toString"],
613        "FinalizationRegistry" => &["register", "unregister"],
614        "MIMEType" => util::MIME_TYPE_METHODS,
615        "MIMEParams" => util::MIME_PARAMS_METHODS,
616        t if stream_web::is_class(t) => stream_web::methods_for(t),
617        _ => &[],
618    };
619    let is_emitter = matches!(
620        tag,
621        "Server"
622            | "Socket"
623            | "ServerResponse"
624            | "IncomingMessage"
625            | "EventEmitter"
626            | "Readable"
627            | "Writable"
628            | "Duplex"
629            | "Transform"
630            | "PassThrough"
631            | "Stream"
632            | "UdpSocket"
633            | "Worker"
634            | "MessagePort"
635            | "TLSServer"
636            | "TLSSocket"
637            | "HTTPSServerResponse"
638            | "HTTPSClientRequest"
639            | "ClusterWorker"
640            | "Domain"
641            | "Http2Server"
642            | "Http2Stream"
643            | "Http2Session"
644            | "ClientRequest"
645            | "FSReadStream"
646            | "FSWriteStream"
647            | "ChildProcess"
648    );
649    base.contains(&name) || (is_emitter && EMITTER.contains(&name))
650}
651
652/// Dispatch a method call on a native stdlib instance (`recv` carries a
653/// `@@native` tag). Called from `host::call_method` before the generic object
654/// method resolution.
655pub fn instance_call(
656    tag: &str,
657    recv: &Value,
658    method: &str,
659    args: Vec<Value>,
660) -> Result<Value, String> {
661    match tag {
662        "Buffer" => buffer::instance_call(recv, method, &args),
663        "Date" => date::instance_call(recv, method, &args),
664        "StringDecoder" => string_decoder::instance_call(recv, method, &args),
665        "WeakRef" => typedarray::weakref_call(recv, method),
666        "FinalizationRegistry" => typedarray::finalization_registry_call(recv, method, &args),
667        "TextEncoder" => typedarray::text_encoder_call(recv, method, &args),
668        "TextDecoder" => typedarray::text_decoder_call(recv, method, &args),
669        "TypedArray" => typedarray::instance_call(recv, method, &args),
670        "Hash" => crypto::instance_call(recv, method, &args),
671        "Hmac" => crypto::hmac_instance_call(recv, method, &args),
672        "Interface" => readline::instance_call(recv, method, args),
673        "Script" => vm::instance_call(recv, method, args),
674        "URLSearchParams" => url::search_params_call(recv, method, &args),
675        "UdpSocket" => dgram::instance_call(recv, method, args),
676        "Worker" | "MessagePort" | "BroadcastChannel" => {
677            worker_threads::instance_call(tag, recv, method, args)
678        }
679        "TLSServer" | "TLSSocket" => tls::instance_call(tag, recv, method, args),
680        "HTTPSServerResponse" | "HTTPSClientRequest" => {
681            https::instance_call(tag, recv, method, args)
682        }
683        "REPLServer" => repl::instance_call(recv, method, args),
684        "ClusterWorker" => cluster::instance_call(recv, method, args),
685        "Domain" => domain::instance_call(recv, method, args),
686        "Tracing" => trace_events::instance_call(recv, method, args),
687        "Http2Server" | "Http2Stream" | "Http2Session" => {
688            http2::instance_call(tag, recv, method, args)
689        }
690        "EventEmitter" => events::instance_call(recv, method, args),
691        "URL" => url::instance_call(recv, method, &args),
692        "Stats" => fs::stats_call(recv, method),
693        "Dirent" => fs::dirent_call(recv, method),
694        "Dir" => fs::dir_call(recv, method, args),
695        "FSReadStream" => fs::read_stream_call(recv, method, args),
696        "FSWriteStream" => fs::write_stream_call(recv, method, args),
697        "Server" | "Socket" | "BlockList" => net::instance_call(tag, recv, method, args),
698        "IncomingMessage" | "ServerResponse" | "ClientRequest" | "Agent" => {
699            http::instance_call(tag, recv, method, args)
700        }
701        "Readable" | "Writable" | "Duplex" | "Transform" | "PassThrough" | "Stream" => {
702            stream::instance_call(tag, recv, method, args)
703        }
704        "Cipheriv" | "Decipheriv" => crypto::cipher_instance_call(tag, recv, method, &args),
705        "Sign" | "Verify" => crypto::sign_verify_instance_call(tag, recv, method, &args),
706        "KeyObject" => crypto::key_object_instance_call(recv, method, &args),
707        "DiffieHellman" => crypto::dh_instance_call(recv, method, &args),
708        "ECDH" => crypto::ecdh_instance_call(recv, method, &args),
709        "X509Certificate" => crypto::x509_instance_call(recv, method, &args),
710        "MIMEType" => util::mime_type_instance_call(recv, method, &args),
711        "MIMEParams" => util::mime_params_instance_call(recv, method, &args),
712        "Blob" | "File" => buffer::blob_call(recv, method, &args),
713        "ReadStream" => tty::instance_call(recv, method, &args),
714        "Resolver" => dns::resolver_instance_call(recv, method, args),
715        "Histogram" => perf_hooks::histogram_instance_call(recv, method, &args),
716        "PerformanceObserver" => perf_hooks::observer_instance_call(recv, method, &args),
717        "PerformanceObserverEntryList" => perf_hooks::entry_list_instance_call(recv, method, &args),
718        "TracingChannel" => diagnostics_channel::tracing_instance_call(recv, method, &args),
719        "Serializer" | "Deserializer" => v8::instance_call(tag, recv, method, args),
720        "Console" => console::instance_call(recv, method, args),
721        "ChildProcess" => child_process::instance_call(recv, method, args),
722        t if stream_web::is_class(t) => stream_web::instance_call(t, recv, method, args),
723        "AsyncLocalStorage" | "AsyncHook" => async_hooks::instance_call(tag, recv, method, args),
724        "Channel" => diagnostics_channel::instance_call(recv, method, &args),
725        "WriteStream" => process::stream_instance_call(recv, method, &args),
726        _ => Err(crate::host::type_error(&format!(
727            "{method} is not a function"
728        ))),
729    }
730}
731
732// ── shared helpers ──────────────────────────────────────────────────────────
733
734/// ToString of `args[i]` (empty string if absent).
735pub(crate) fn arg_str(args: &[Value], i: usize) -> String {
736    with_host(|h| args.get(i).map(|v| h.str_of(v)).unwrap_or_default())
737}
738
739/// ToNumber of `args[i]` (`NaN` if absent).
740pub(crate) fn arg_num(args: &[Value], i: usize) -> f64 {
741    with_host(|h| args.get(i).map(|v| h.to_number(v)).unwrap_or(f64::NAN))
742}
743
744/// Lowercase hex encoding of `bytes`.
745pub(crate) fn to_hex(bytes: &[u8]) -> String {
746    let mut s = String::with_capacity(bytes.len() * 2);
747    for b in bytes {
748        s.push(char::from_digit((b >> 4) as u32, 16).unwrap());
749        s.push(char::from_digit((b & 0xf) as u32, 16).unwrap());
750    }
751    s
752}
753
754/// Decode a hex string to bytes (ignoring a trailing odd nibble, like Node).
755pub(crate) fn from_hex(s: &str) -> Vec<u8> {
756    let digits: Vec<u8> = s
757        .bytes()
758        .filter_map(|c| (c as char).to_digit(16).map(|d| d as u8))
759        .collect();
760    digits
761        .chunks(2)
762        .filter(|c| c.len() == 2)
763        .map(|c| (c[0] << 4) | c[1])
764        .collect()
765}
766
767const B64: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
768
769/// Standard base64 encoding (with `=` padding) of `bytes`.
770pub(crate) fn to_base64(bytes: &[u8]) -> String {
771    let mut out = String::new();
772    for chunk in bytes.chunks(3) {
773        let b = [
774            chunk[0],
775            *chunk.get(1).unwrap_or(&0),
776            *chunk.get(2).unwrap_or(&0),
777        ];
778        let n = ((b[0] as u32) << 16) | ((b[1] as u32) << 8) | (b[2] as u32);
779        out.push(B64[((n >> 18) & 63) as usize] as char);
780        out.push(B64[((n >> 12) & 63) as usize] as char);
781        out.push(if chunk.len() > 1 {
782            B64[((n >> 6) & 63) as usize] as char
783        } else {
784            '='
785        });
786        out.push(if chunk.len() > 2 {
787            B64[(n & 63) as usize] as char
788        } else {
789            '='
790        });
791    }
792    out
793}
794
795/// Decode a standard base64 string to bytes (ignores whitespace and padding).
796pub(crate) fn from_base64(s: &str) -> Vec<u8> {
797    let rev = |c: u8| -> Option<u32> { B64.iter().position(|&x| x == c).map(|p| p as u32) };
798    let vals: Vec<u32> = s.bytes().filter_map(rev).collect();
799    let mut out = Vec::new();
800    for chunk in vals.chunks(4) {
801        if chunk.len() < 2 {
802            break;
803        }
804        let n = (chunk[0] << 18)
805            | (chunk[1] << 12)
806            | (chunk.get(2).copied().unwrap_or(0) << 6)
807            | chunk.get(3).copied().unwrap_or(0);
808        out.push((n >> 16) as u8);
809        if chunk.len() > 2 {
810            out.push((n >> 8) as u8);
811        }
812        if chunk.len() > 3 {
813            out.push(n as u8);
814        }
815    }
816    out
817}