Node compatibility

How far Cruft's node:* built-in modules go in matching Node's published API, how the parallel cruft:* modules expose the same host capabilities directly, and where Cruft follows the ECMAScript spec instead of Node. A method that is not implemented throws naming what is missing, rather than returning a fake value.

Cruft runs the Node package ecosystem on its own JavaScript engine. It is a separate runtime that implements Node's built-in module APIs. This page describes how far each built-in module goes, how Cruft handles methods it has not implemented, and the specific places where Cruft behaves differently from Node.

Two families of built-in modules

Cruft ships its built-in modules under two namespaces:

  • cruft:* are Cruft's own modules. They expose host capabilities directly: reading bytes, resolving a path, opening a socket, serving HTTP. Their API is defined by the operation itself, not by Node's history.
  • node:* (and the bare aliases fs, path, http, and the rest) are the Node-compatibility modules. They implement Node's published API: the same function names, callback signatures, and .code error strings your packages already expect.

Both families are built on the same underlying host operations. The node:* modules add Node's API shape on top of those operations; the cruft:* modules expose the same capabilities without it. A Node-specific behavior that exists only for compatibility lives in the node:* module and does not change the shared host layer beneath it.

Unimplemented methods throw

One risk with partial compatibility is the silent partial implementation: a module is present, a method is called, and it returns a plausible value without performing the operation. Code that depends on the side effect breaks later, far from the cause.

Cruft avoids this. A built-in module that is present but has not implemented a given method throws an error at the moment it is called, and the error names what is missing. There is no stand-in return value for an operation that did not happen. This applies to every built-in: a module either does what its method says or throws saying it cannot.

Module support levels

The built-in resolver maps the full family of Node specifiers. Built-in modules fall into two groups by how much of their surface is implemented.

The first group has substantive implementations. fs goes the deepest, with sync, callback, and promise forms, glob, watch, and file-descriptor I/O.

Substantive
fspathosprocesschild_processhttphttpsnettlsdnsdgramstreameventsbufferutilzlibcryptovmassertquerystringtimers
Present but limited
ttyreadlineasync_hookscluster

The second group exists so that feature-detection and import-time code succeed: the module imports cleanly, but calling a method it has not implemented throws as described above.

Where Cruft intentionally differs from Node

A few behaviors differ from Node on purpose. Where Node's behavior contradicts the ECMAScript specification, Cruft follows the specification by default and provides Node's behavior as an opt-in for packages that need it.

  • Function.prototype.caller and arguments: V8 exposes legacy accessors on functions that the specification does not define. Cruft follows the specification by default and offers the legacy accessors as a named opt-in.
  • Module resolution: Node's package-resolution algorithm is implemented as a defined compatibility layer over Cruft's own resolver, rather than built into the engine.

In both cases the default matches the specification, and Node's behavior is available when a package asks for it.

The Node surface

Every module below imports under both the node: prefix and the bare name, with its listed API present, on cruft 0.0.10. Depth is one of three levels: substantive is a full implementation, partial carries the common paths with named gaps, and minimal is a narrow slice. The module reference lists each module's exact gaps.

ModuleDepthSurface
node:fs, node:fs/promisessubstantivesync / callback / promise forms of read, write, stat, readdir, glob, watch, fd-level I/O; real Uint8Array reads; Node .code errors
node:pathsubstantivejoin, resolve, and the path algebra
node:ossubstantiveplatform, cpus, host identity
node:processsubstantiveargv/env/exit/cwd/platform, nextTick, lifecycle events
node:http, node:httpssubstantive (client + server)createServer with streaming IncomingMessage/ServerResponse; request/get client
node:netsubstantiveserver + client socket lifecycle
node:tlssubstantive client, partial serverTLS 1.3 client handshake; createServer is a partial surface
node:dns, node:dns/promisessubstantivelookup plus the common resolve family
node:dgramsubstantiveUDP sockets with message events
node:http2minimalsingle-stream client session
node:child_processsubstantivespawn/exec and the synchronous lanes
node:buffersubstantiveBuffer
node:utilsubstantiveformat, promisify, the inspect family
node:querystringsubstantiveparse, stringify
node:zlibsubstantivegzip / deflate codecs
node:cryptosubstantivehashes, HMAC, randomBytes/randomUUID, sign/verify, key derivation (PBKDF2/scrypt), DH/ECDH, X509
node:assertsubstantivethe assertion family
node:streamsubstantiveReadable.from, pipeline, finished, async iteration
node:eventssubstantiveEventEmitter
node:timerspresentsetTimeout/setInterval/setImmediate

Modules not listed (tty, readline, async_hooks, cluster, and others) import so that feature-detection succeeds, and throw at the method boundary when you call something they have not implemented.