Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

node:wasi

ImportsImplementation
node:wasi@bytecodealliance/jco-std/wasi/0.2.x/node/24.x.x/wasi

node:wasi runs a WASI preview1 module: the caller compiles it with the WebAssembly API, instantiates it against wasi.getImportObject(), and hands the instance to wasi.start() or wasi.initialize(), whose syscalls then read and write the instance’s linear memory. None of that is possible from inside a Jco component. Neither guest engine exposes a WebAssembly global, so a component cannot instantiate a nested module, and a linear memory cannot cross the component boundary, so no host adapter can run the module either. This is a property of the component model, not a missing shim, and it is why a Node passthrough for node:wasi does not exist.

node:wasi therefore resolves so that such source bundles and fails clearly rather than leaving an unresolved import. The module ports Node v24.19.0’s lib/wasi.js: the WASI class, its exact option validation, the 46-entry wasiImport table with Node’s names and arities, getImportObject(), and the instance checks of start(), initialize() and finalizeBindings(). When bundled source imports node:wasi, Jco adds jco:node/wasi@0.1.0 to the selected world and installs wasi.wit and the shared types.wit under deps/jco-node-0.1.0.

That capability carries the one part of the API a host can honour: the constructor’s uvwasi_init step, which opens every preopen and checks the standard descriptors. It is denied by default, so new WASI() throws ERR_JCO_WASI_ADAPTER_REQUIRED after validating its options; the message also explains that running a module is unsupported, so the limitation is visible from the first call. jco-std ships a provider for Node that runs the real node:wasi constructor and discards the result:

jco transpile component.wasm \
  --map 'jco:node/wasi@0.1.0=@bytecodealliance/jco-std/wasi/0.2.x/node/24.x.x/wasi/host/node'

With it, a missing preopen fails with Node’s own UVWASI_ENOENT (errno, code and syscall: 'uvwasi_init'), a preopen that is a file with UVWASI_ENOTDIR, and a closed descriptor with UVWASI_EBADF, in Node’s order relative to the JavaScript validation (returnOnExit is checked after initialisation, as in Node). Importing the provider emits Node’s ExperimentalWarning once in the host process.

import { WASI } from "node:wasi";

export function prepare(sandbox) {
  const wasi = new WASI({ version: "preview1", args: ["app"], preopens: { "/sandbox": sandbox } });
  return Object.keys(wasi.getImportObject()); // ["wasi_snapshot_preview1"]
}

Boundaries

SurfaceBehavior
start(), initialize(), finalizeBindings()Validate instance and instance.exports as Node does, then throw ERR_JCO_UNSUPPORTED_NODE_API explaining that a component cannot instantiate a nested module and pointing at composition (wac, wasm-tools compose) or the host.
instance.exports.memoryWhere a WebAssembly global exists, a value that is not a WebAssembly.Memory still gets Node’s ERR_INVALID_ARG_TYPE; in a guest there is no such global, so every value is refused with the explanation above.
wasiImportNode’s 46 syscalls with Node’s bound names and arities. Before start(), which never completes, each answers UVWASI_EINVAL to a call with the wrong argument count or types and throws ERR_WASI_NOT_STARTED otherwise.
proc_exitWith returnOnExit (the default) records the exit code and throws Node’s kExitCode symbol, as in Node; otherwise behaves like the other syscalls.
Experimental warningNode warns once on require('node:wasi'); the guest module does not, since a component has no warning channel to promise.