Host
Connect nstdlib to features supplied by your runtime, such as filesystems, DNS, modules, and globals.
nstdlib runs standard library code outside Node, including in browsers, bundler test environments, and custom JavaScript engines. Some features need services normally provided by an operating system, such as a filesystem, network connection, or source of randomness. Your runtime can provide these features through documented host integrations. This page covers the host APIs you can import and use. See Limits for features that remain unavailable.
#A filesystem: node:vfs
Most of node:fs needs real disk access, which nstdlib does not have. What it
does have is node:vfs, a filesystem that lives entirely in memory. Mount one
and the whole fs API - sync, callback, fs/promises, streams - works
against it:
import vfs from "nstdlib/vfs";
import fs from "nstdlib/fs";
import fsp from "nstdlib/fs/promises";
const mount = vfs.create(); // in-memory by default
mount.mount("/mem");
fs.writeFileSync("/mem/hello.txt", "hi");
fs.readFileSync("/mem/hello.txt", "utf8"); // 'hi'
await fsp.readdir("/mem"); // [ 'hello.txt' ]
mount.unmount(); // and the files are goneErrors carry real codes and messages (ENOENT: no such file or directory, open '/mem/nope'), and a path outside any mount still throws rather than
silently doing nothing. See Limits for the couple of fs
operations a memory mount cannot answer.
If you want a filesystem backed by something other than memory - a real disk,
an IndexedDB store, a network share - write your own provider. nstdlib/vfs
exports the class to extend; three more entries export what your provider has
to answer with, because fs compares them by identity rather than by shape:
import { VirtualFileHandle } from "nstdlib/internal/vfs/file_handle"; // what open() returns
import { createENOENT } from "nstdlib/internal/vfs/errors"; // the errno errors you throw
import "nstdlib/internal/vfs/stats"; // createFileStats() and friends, what stat() returnsnstdlib/internal/fs/utils exports vfsState.handlers, the table fs reads
a mount out of, and nstdlib/internal/fs/watchers exports the FSWatcher and
StatWatcher classes a provider hands back from watch(). These four are
lower-level than the memory provider above - reach for them only if the
built-in MemoryProvider genuinely does not fit.
#DNS over HTTPS
node:dns resolves real hostnames without a socket, by speaking DNS-over-HTTPS
over fetch. It works out of the box, but every lookup goes to a third-party
resolver (Cloudflare's, by default), which is a real change in trust worth
knowing about and worth being able to change:
import { configureDoH } from "nstdlib/stub/binding/cares_wrap";
configureDoH({ url: "https://dns.google/dns-query" }); // any RFC 8484 resolver
configureDoH({ fetch: myFetch }); // inject the transport
configureDoH({ timeout: 2000 });process.env.NSTDLIB_DOH_URL sets the endpoint without touching code, and
dns.getServers() reports where lookups actually go. If your host can resolve
names itself, it can skip DNS-over-HTTPS entirely for dns.lookup() (though
not for dns.resolve*(), which asks for DNS records rather than for a way to
reach a name) by installing globalThis.node_binding_cares_wrap_getaddrinfo
before nstdlib is imported.
#The builtin registry
require() and import() of a bare specifier like require("path") need to
know which builtins your program is willing to load - nstdlib does not decide
that for you. You register them:
import { BuiltinModule } from "nstdlib/internal/bootstrap/realm";
BuiltinModule.register("path", await import("nstdlib/path"));
BuiltinModule.registerAll({
os: await import("nstdlib/os"),
events: await import("nstdlib/events"),
});Only registered ids resolve. This keeps a program from pulling in the whole
library just because it called require("path") once, and it is what both
require() and import() read from - see Usage for the full
require() walkthrough.
#The ES module loader
import() of a CommonJS module, a JSON file or a registered builtin - over a
mount, with the real resolver - runs through a loader you call directly rather
than one that intercepts import for you:
import { getOrInitializeCascadedLoader } from "nstdlib/internal/modules/esm/loader";
const loader = getOrInitializeCascadedLoader();
const mod = await loader.import("node:path", "file:///app/index.mjs", {
__proto__: null,
});
mod.join("a", "b"); // 'a/b'A builtin still has to be registered first, the same way require() needs it.
What this loader cannot do - compile the source text of an actual .mjs file
from a mount - is covered in Limits.
#Globals for a non-Node host
Node builds process and Buffer in C++ before any of your code runs.
Nothing here does that for you, so on a fresh realm - a browser tab, most of
all - the first import "nstdlib/path" fails on process is not defined
unless you install them first:
import { installGlobals } from "nstdlib/globals";
await installGlobals(); // process + Buffer, non-enumerable, as Node's are
const { fetch } = await import("nstdlib/internal/deps/undici/undici");Install before importing anything else, and before importing undici
specifically - it reads a free Buffer while its own module body runs.
createProcess() is the lower-level export if you want the object without
installing it yourself. This is the one host integration whose source is not Node's own
code: everything else on this page is Node's standard library, rewritten;
nstdlib/globals is written by hand for the part of a realm Node normally
builds outside JavaScript entirely.