
# Limitations

> Understand which Node.js features nstdlib cannot currently provide and how unsupported calls fail.

This page lists what doesn't work in nstdlib, what only works with help from
your runtime, and why. For the status of each individual module, see the
[API reference](/api). For how to provide missing features, see
[Host Integration](/library/host-integration).

## At a glance

| Feature                        | Out of the box          | With a host                                                        |
| ------------------------------ | ----------------------- | ------------------------------------------------------------------ |
| Pure JavaScript modules        | ✅ works                | ✅ works                                                           |
| Randomness, WebCrypto, hashing | ✅ works                | ✅ works                                                           |
| DNS                            | ✅ over HTTPS           | ✅ your own resolver                                               |
| `fs`                           | ❌ throws               | ✅ [`installFileSystem()`](/library/host-integration#a-filesystem)   |
| `net`, `http`                  | ❌ throws               | ✅ [`installNetwork()`](/library/host-integration#a-network)         |
| `https`, `tls` as a client     | ❌ throws               | ✅ with a host TLS engine                                          |
| `child_process` (async)        | ❌ throws               | ✅ [`installProcesses()`](/library/host-integration#child-processes) |
| `worker_threads`               | ❌ throws               | ✅ `installWorkers()`                                              |
| `zlib`                         | ❌ throws               | ✅ with a host implementation                                      |
| `require()`                    | ✅ if `eval` is allowed | ✅ if `eval` is allowed                                            |
| `.mjs` source files            | ❌ throws               | ✅ [`nstdlib/esm`](/library/host-integration#es-modules)             |
| Ciphers, keys, signatures      | ❌ throws               | ❌ throws                                                          |
| TLS and HTTPS servers          | ❌ throws               | ❌ throws                                                          |
| `http2`, `dgram`               | ❌ throws               | ❌ throws                                                          |

## How an unsupported call fails

When nstdlib can't do something, it throws an error that says so, right where
you made the call. It never quietly returns `undefined`, because a wrong answer
that looks right is much harder to debug than an error:

```
Error: [nstdlib] internalBinding('fs').open() is not implemented: this runtime
has no such binding, and no host implementation is installed on
`globalThis.node_binding_fs_open`. To get a filesystem without one, install a
node:vfs provider under the binding: (await import("nstdlib/globals"))
.installFileSystem(globalThis, new (await import("nstdlib/node/vfs"))
.MemoryProvider()) - in memory, or your own VirtualProvider.
```

The error's `code` is `ERR_NSTDLIB_NO_BINDING`. The message names the missing
feature and the global a host could define to provide it. `fs` errors also
suggest the easier fix: most of the time you just want an in-memory
filesystem.

A few other codes cover different reasons:

| Code                                 | Meaning                                                                                                  |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------- |
| `ERR_NSTDLIB_NO_BINDING`             | The feature needs something your runtime hasn't provided.                                                |
| `ERR_NSTDLIB_NO_MODULE_COMPILER`     | An `.mjs` file needs a module compiler. See [below](#es-module-source-files).                            |
| `ERR_NSTDLIB_NO_CJS_LEXER`           | Named imports from a CommonJS module need a CommonJS lexer.                                              |
| `ERR_NSTDLIB_NO_ASYNC_CONTEXT_FRAME` | Code tried to use V8's async context frames, which no runtime here has. You shouldn't normally see this. |

## Crypto and TLS

nstdlib doesn't include OpenSSL, so anything that depends on it throws:
ciphers, key objects, signatures, Diffie-Hellman, X.509 certificates and
`scrypt`. `crypto.getCiphers()` returns `[]`.

**Hashing does work everywhere.** Hashing is just arithmetic, so nstdlib
computes it in JavaScript. MD5, SHA-1, SHA-224/256/384/512, SHA-512/224,
SHA-512/256, SHA3-224/256/384/512 and SHAKE128/256 all work with
`createHash()`, `createHmac()`, `crypto.hash()`, `pbkdf2()` and `hkdf()`, with
no host needed. `crypto.getHashes()` lists exactly these. Other algorithms,
such as RIPEMD-160, BLAKE2 or SM3, throw rather than being approximated. If a
host provides a native implementation (through
`node_binding_crypto_digest_dispatch`, as nstd does), nstdlib uses that
instead.

**Randomness works everywhere too.** `randomBytes()`, `randomUUID()` and
`crypto.subtle` use your runtime's built-in WebCrypto.

`tls` needs a TLS engine from the host. Without one, `tls.connect()` and any
HTTPS request that verifies a certificate throw. **TLS servers never work.**
`tls.createServer()` and `https.createServer()` throw in every runtime,
because only the client side of TLS is implemented.

## Networking

There are no sockets without a host. nstdlib implements almost everything in
`net` and `http` itself, including connecting, listening and accepting
connections. All it needs from a host is one function that reads and writes a
socket. Provide that, and both clients (`net.connect()`, `http.request()`) and
servers (`net.createServer().listen()`) work. Without it, you can still create
a `net.Socket`, but `connect()` and `listen()` throw.

[`installNetwork()`](/library/host-integration#a-network) provides that function anywhere,
including in a browser, but it's an in-memory network. Servers and clients in
the same page can talk to each other, and connecting to anything else fails
with `ECONNREFUSED`. It doesn't give a page a real socket, and nothing can. To
reach the outside world, you pass it a provider, such as a relay or a data
channel.

`https` uses the same connection plus a TLS layer, so `https.request()` works
wherever a host also provides TLS, as a client only.

`http2` and `dgram` (UDP) have no implementation at all, with or without a
host, so they always throw.

The HTTP message parser is not affected by any of this. It's part of nstdlib
itself and is tested against Node's own parser.

## Child processes

`child_process` works like networking: nstdlib implements everything on top of
one function per API, which a host provides (`node_binding_process_wrap_dispatch`
for `spawn()` and `node_binding_spawn_sync_dispatch` for `spawnSync()`).
Without them, both throw. With them, `spawn`, `exec`, `execFile`, `fork` and
`cluster` all work, as [nstd](/runtime) shows.

In a page or other embedding,
[`installProcesses()`](/library/host-integration#child-processes) provides the asynchronous
half on top of your own `CommandHost`. It doesn't support the synchronous
functions like `spawnSync()`, since most embedders can't give a synchronous
answer.

Passing a handle (such as a socket) to a child process over IPC isn't
supported anywhere.

## Worker threads

A worker needs a second, separate JavaScript environment running on another
thread, which JavaScript can't create by itself. Without a host,
`new Worker()` throws. A host that can run one installs it with
`installWorkers()` (see [Host Integration](/library/host-integration#more-advanced-hooks)).
[nstd](/runtime) does this, so worker threads work there.

## Compression

`zlib` also needs a host. Without one, every compression function and stream
throws. A host provides it through `node_binding_zlib_Zlib` and
`node_binding_zlib_BrotliDecoder`. [nstd](/runtime) supplies
both, so it can read `gzip` and `br` HTTP responses. No host here supports
Brotli _compression_ or Zstandard in either direction; those would each add
another library to the build.

## The filesystem

Without an installed filesystem, `fs` has nothing to read, so a call like
`fs.readFileSync("/etc/hosts")` throws. Install one and the whole `fs` API
works against it (see [Host Integration](/library/host-integration#a-filesystem)). Even then, a few
things are worth knowing:

- **`fs/promises.glob` doesn't work.** It throws, because the pattern matcher
  it needs isn't part of the code nstdlib is built from.
- **`existsSync()` outside every mount throws** instead of returning `false`.
  nstdlib can't see anything there, so "doesn't exist" would be a guess.
- **`fs.watch()` works, but may poll.** It uses your provider's `watch()` if
  it has one, and otherwise checks for changes every 100 ms. `MemoryProvider`
  polls too, so you get at most one `"change"` event per interval, and none
  for a write that leaves the file size and modification time unchanged.
  `fs.watchFile()` polls at the interval you give it.
- **`fs.statfs()` reports a 4096-byte block size and no free space**, because
  a provider has no disk blocks to count.
- **Absolute symlink targets resolve inside the provider.** Providers see
  paths relative to their mount point. At `/` that makes no difference; under
  a mount like `/mem`, use relative symlink targets.

## `os`

Most of `os` describes the actual machine, which nstdlib can't see.
`homedir()`, `uptime()`, `networkInterfaces()`, `cpus()`, `loadavg()`,
`freemem()`, `totalmem()` and `userInfo()` all throw `ERR_NSTDLIB_NO_BINDING`.
(`networkInterfaces()` throws on purpose: an empty list could be mistaken for
"no network here".)

A few functions do return a value:

| Function                                        | Returns                                                                                                                                                              |
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `hostname()`                                    | `"localhost"`                                                                                                                                                        |
| `type()`, `version()`, `release()`, `machine()` | `""`, so that importing `os` doesn't fail                                                                                                                            |
| `platform()`, `arch()`                          | `process.platform` and `process.arch`, which default to `"browser"` and `"x64"` and can be set with [`installGlobals()`](/library/host-integration#process-and-buffer) |
| `tmpdir()`                                      | `TMPDIR`, `TMP` or `TEMP` from `process.env`, or `/tmp`                                                                                                              |
| `availableParallelism()`                        | `navigator.hardwareConcurrency`, or throws if there isn't one                                                                                                        |

## `require()` needs `eval`

`require()` compiles CommonJS modules with `new Function()`. Where that's
forbidden (a page with a Content-Security-Policy that doesn't allow
`unsafe-eval`, a Cloudflare Worker, or a runtime started with
`--disallow-code-generation-from-strings`), `require()` isn't available at
all. `import()` of a CommonJS module has the same limit. Everything else in
nstdlib still works in those environments.

## ES module source files

`import()` of a builtin, a CommonJS module or a JSON file works out of the box.
Compiling an actual `.mjs` file does not, because JavaScript has no standard
way to compile a module while controlling how its imports resolve. Without a
compiler, you get:

```
Error: [nstdlib] cannot compile the ES module at file:///app/mod.mjs: the
source-text form of ModuleWrap hands source to
v8::ScriptCompiler::CompileModule(), and this runtime has no module compiler.
```

The error code is `ERR_NSTDLIB_NO_MODULE_COMPILER`. There are three ways
around it:

- Install the compiler from [`nstdlib/esm`](/library/host-integration#es-modules). It needs
  a runtime that allows `new Function()`, like `require()` does.
- Use a JavaScript engine that has its own module compiler. nstd does this
  with QuickJS-NG.
- Let something that already compiles modules do the loading, such as Node's
  `module.registerHooks()`, or a service worker that serves your files to the
  browser's own `import()`.

## Things that never work

- **`quic`.** No official Node build includes QUIC support, so there's
  nothing to build on. The `internal/quic/quic` module isn't published at
  all, because it can't even be imported without `--experimental-quic`.
- **`ffi`.** It needs a real dynamic linker.
- **`internal/webstorage`.** It needs a storage file on disk.
- **`internal/main/*`.** These are Node's process entry points. Importing one
  asks a Node process to start up, which nstdlib doesn't do.

## Where behavior differs instead of throwing

In a few places, nstdlib fills a gap with a reasonable substitute instead of
throwing. Each one is documented at the top of the file responsible for it,
under `src/node/stub/binding/` in the source.

The one most likely to affect your code: **`AsyncLocalStorage` doesn't carry
its value across an `await`**, because the V8 feature it relies on isn't
available from JavaScript. The synchronous parts of `run()` and `enterWith()`
work, and so does anything wrapped with `AsyncResource.bind()` or
`AsyncLocalStorage.snapshot()`.
