
# 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](/nstdlib/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:

```js
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 gone
```

Errors 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](/nstdlib/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:

```js
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() returns
```

`nstdlib/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:

```js
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:

```js
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](/nstdlib/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:

```js
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](/nstdlib/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:

```js
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.
