# wasm-rquickjs
## Introduction
Command line tool and library to generate a Rust crate wrapping JavaScript code into a WebAssembly Component using the
QuickJS engine.
Generated components use QuickJS-ng 0.16.2 through rquickjs 0.14.0, including
Unicode `v`-flag regular expression sets, intersections and subtraction.
The engine automatically collects unreachable reference cycles. Each runtime starts
with a 32 MiB GC threshold, which the engine then adapts to its retained heap.
Collection makes freed space reusable; WebAssembly linear memory keeps its high-water
size. Explicit `gc()` remains optional and schedules collection after the current
JavaScript execution completes.
Disposal uses the engine's well-known `Symbol.dispose` and `Symbol.asyncDispose`,
including for generated WIT resources. These symbols are not in the global symbol
registry, unlike Node.js 22.14. Their properties are read-only in both runtimes.
### Comparison with ComponentizeJS
[ComponentizeJS](https://github.com/bytecodealliance/ComponentizeJS) achieves the same goal of wrapping JavaScript code
into
a WebAssembly Component, but it does it using a modified version of the SpiderMonkey engine.
Advantages of wasm-rquickjs over ComponentizeJS:
- At the time of writing, there are known bugs in ComponentizeJS (or one of its underlying modules) that prevents it
from being used in production.
- Much simpler to develop and debug, as everything exposed for JavaScript is implemented in async Rust using
the [rquickjs library](https://github.com/DelSkayn/rquickjs)
- The WIT-JS mapping rules and the set of available JavaScript APIs are well defined
- Smaller WASM binary size
Advantages of ComponentizeJS over wasm-rquickjs:
- Faster runtime (due to the SpiderMonkey engine)
- Faster startup time (it does pre-initialization with Wizer)
- No need for the Rust toolchain for end users
### Comparison with wasmedge-quickjs
The project is similar to [wasmedge-quickjs](https://github.com/second-state/wasmedge-quickjs/) in using te QuickJS
engine compiled to WASM
to run JavaScript code, but it is different in the following ways:
- It does not provide support for using the component model from JS (defining imports and exports using WIT)
- The imports the resulting WASM component has are WasmEdge specific
## Release lines
- `main` targets upcoming Golem 2.0 and releases wasm-rquickjs **0.5.x**, starting with `v0.5.0`.
- `v0.4.x` is the maintenance branch for Golem 1.6.x bug fixes and wasm-rquickjs **0.4.x** releases.
It preserves main before the Wasmtime 50 upgrade.
Cargo manifests intentionally retain the development version `0.0.0`. Release CI replaces it
with the pushed Git tag's version when building binaries and publishing crates. Create `v0.5.x`
tags from the Golem 2.0 line and `v0.4.x` tags from the maintenance branch only. The main-line
workflow accepts only `v0.5.*` release tags; the maintenance branch retains its existing workflow.
Branch preparation does not itself create a release tag or publish a release.
## Usage
The tool can be used as a command line tool or as a library. The command line tool has two top level commands:
```
generate-wrapper-crate Generate the wrapper crate for a JavaScript module
generate-dts Generate TypeScript module definitions
```
### Generating the wrapper crate
This is the primary command that generates the Rust crate embedding the JavaScript code into a WebAssembly Component.
```
Usage: wasm-rquickjs generate-wrapper-crate --js <JS> --wit <WIT> --output <OUTPUT>
```
- The `--js` arguments is the path to the JavaScript file to be wrapped. There can be only one JavaScript file,
containing an ES6 module exporting the necessary functions and classes as described below.
- The `--wit` argument is the path to the WIT root containing a single world that describes the imports and exports of
the component
- The `--output` argument is the path to the output directory where the generated Rust crate will be created.
The output directory is going to contain a self-contained Rust crate that can be compiled into a WASM component using
`cargo build --target wasm32-wasip2`.
Every generated crate also contains a target-specific `THIRD_PARTY_COMPONENT_LICENSES.txt` for the
locked Cargo dependency union of the standard `normal` / `normal-p3` TypeScript runtime and
transform profiles. It is deliberately a superset for crates that enable fewer features.
Distributors must ship that notice with the compiled component and separately audit application
code, custom feature combinations, bundled JavaScript in the skeleton (including third-party code
adapted from Node.js, Deno, and web-streams-polyfill), toolchain and WASI sysroot components,
vendored native sources, and upstream `NOTICE` obligations. Maintainers can regenerate and verify
the retained notices with
`tools/check-component-licenses.sh --write` and `--check`, respectively, using the pinned
`cargo-about` version named by the script.
The generated crate has some **features** that control what imports the component will have beside the ones defined in
the user's WIT world:
- `logging`: enables the `wasi:logging` import to be used for the JavaScript `console` API
- `http`: enables the `wasi:http` import to be used for the JavaScript `fetch` API
- `sqlite`: enables the `node:sqlite` module with an embedded SQLite database engine
By default `logging` and `http` are enabled. The `sqlite` feature must be explicitly enabled.
#### Choosing the WASI target
The `--target` option selects which WASI generation mode is used:
- `--target wasi-p2` (**default**): the historical WASI Preview 2 mode. Component exports are
synchronous WIT functions and the full Node.js-compatible builtin set is available. This is the
stable, production path.
- `--target wasi-p3`: the opt-in WASI Preview 3 mode. WIT `async func` exports and imports use the
Component Model async ABI, while plain functions remain synchronous. WIT `future<T>` /
`stream<T>` values are supported at function boundaries, including inside composite types. See
[WASI Preview 3 target](#wasi-preview-3-target) below for the current status and limitations.
Both targets generate a crate that is still compiled with `cargo build --target wasm32-wasip2`
(a dedicated `wasm32-wasip3` rustc target does not exist yet; the Preview 3 crate uses the
[`wasip3`](https://crates.io/crates/wasip3) bindings on top of the Preview 2 target).
#### Native modules and dependencies
A host application can give JavaScript its own Rust modules without editing the generated crate:
```
wasm-rquickjs generate-wrapper-crate ... \
--native-module my-native=my_native::Module \
--dependency 'my-native={ path = "../my-native" }'
```
- `--native-module <name>=<rust path>` registers a type implementing `rquickjs::module::ModuleDef`
so that JavaScript can `import ... from '<name>'`. The name must not be a JavaScript module name,
an imported WIT interface, or in the reserved `node:` and `__wasm_rquickjs_builtin/` namespaces.
Like an embedded JavaScript module, a native module named after a schemeless builtin alias, such
as `fs`, takes precedence over the alias for the component's own modules; `node:fs` still
resolves to the builtin.
- `--dependency <key>=<TOML value>` adds a `[dependencies]` entry, a version string or an inline
table, and replaces an entry of the skeleton with the same key as a whole. A relative `path` is
resolved against the current directory and written relative to the generated crate.
With any `--dependency`, the skeleton's `Cargo.lock` is not copied: it was resolved for the
unmodified dependencies. A fresh output directory then has no lock and Cargo resolves one on the
first build; a `Cargo.lock` already in the output directory is kept.
Both options can be repeated and work with both targets. The native module's crate must use the
same `rquickjs` version as the generated crate; the crate's `[patch.crates-io]` applies to it.
### Generating TypeScript module definitions
The `generate-dts` command generates TypeScript module definitions for all the exported and imported interfaces:
```
Usage: wasm-rquickjs generate-dts --wit <WIT> --output <OUTPUT>
```
- The `--wit` argument is the path to the WIT root containing a single world that describes the imports and exports of
the component.
- The `--output` argument is the path to the output directory where the generated TypeScript module definitions (
`.d.ts`) will be created.
### Using with Golem
`wasm-rquickjs` is integrated into [Golem](https://golem.cloud)'s command line interface, so it can be directly used
using Golem app templates.
> **Note:** Golem's Preview 3 SDK/template builds pin the `wasi-p3` generator branch and pass
> `--target wasi-p3` when building their JavaScript guest images. Preview 2 remains the default for
> callers that do not select a target explicitly.
### WASI Preview 3 target
Passing `--target wasi-p3` to `generate-wrapper-crate` selects the **opt-in, asynchronous** WASI
Preview 3 generation mode. It is intended for hosts that support the Component Model async ABI.
**How it differs from Preview 2:**
- WIT `async func` exports and imports use the Component Model async ABI (`task.return`); plain
functions remain synchronous.
- The JavaScript event loop is driven by the Component Model async executor instead of Preview 2
`wasi:io/poll` pollables — there are no `wstd`/`wasip2`/pollable dependencies in the Preview 3
code path.
- WIT `future<T>` and `stream<T>` are mapped to JavaScript `Promise<T>` and `AsyncIterable<T>`
respectively, both directly and when nested in composite parameter/result types.
- `fetch` / HTTP uses the `wasi:http` Preview 3 (`wasi:http/handler`) async client.
**Building and running:**
The generated crate is still built with `cargo build --target wasm32-wasip2`. Running the resulting
component requires a host with Preview 3 and Component Model async support. For example, with the
`wasmtime` CLI (version 46.x):
```
wasmtime run -S p3=y -S http=y --wasm component-model=y --wasm component-model-async=y \
--invoke 'my-export()' path/to/component.wasm
```
The Node.js-compatible builtin set is available on Preview 3 as well. Working end-to-end examples
live under [`examples/p3`](examples/p3), covering async exports/imports, `future<T>`/`stream<T>`
values, exported resources, and HTTP `fetch`.
**Current limitations (Preview 3):**
- **Composition** (`--js-modules name=@composition`) is not supported yet.
- An exported result containing `future<T>` or `stream<T>` must come from an `async func`; a
synchronous export cannot return its reader before a backpressured writer starts.
- The error arm of an exported top-level `result<T, E>` cannot contain `future<T>` or `stream<T>`.
Wizer pre-initialization (`optimize`) supports Preview 3 components. As with Preview 2, module
initialization must become quiescent before the snapshot and cannot leave timers or tasks pending.
Runtime arguments and environment variables are restored before the first export runs; modules
must not capture their values or immutable aliases during top-level initialization.
**External I/O during pre-initialization is rejected.** Top-level filesystem
probes (including `existsSync` and module/package lookup), socket/DNS/HTTP/WebSocket
acquisition, SQLite opens (including `:memory:`), execution-job creation, and
`process.chdir()` fail the
build immediately. Catching a JavaScript exception cannot suppress this failure.
The diagnostic identifies the operation and recommends moving it into an exported
function or lazy initialization after restoration. This avoids saving unavailable
build-time capabilities or false filesystem results in the snapshot. The optimizer
also traps filesystem preopens, network acquisition and outgoing HTTP for older
templates and custom initializers. Rebuild templates to get native guards,
restore-safe pre-init stdio and guest diagnostics.
Importing these builtins, embedded/data modules and explicitly supplied module
sources remains supported. Logging and pure computation remain available.
`process.cwd()` is the logical `/` during pre-initialization and refreshes from the
runtime environment after restoration. `wasm-rquickjs:execution` may be imported,
but `runJavaScript()` and `startJavaScript()` are runtime-only, including for pure
computation. Create jobs from an exported function or lazy runtime initialization.
On cold start and after
restoration, the main runtime honors `wasi:cli/environment`'s initial cwd. Hosts
must implement this standard WASI import; `none` retains `/`. A supplied cwd must
be an accessible directory, or runtime initialization fails. Owned jobs retain
their separate cwd.
Supplied CommonJS reexports that require filesystem analysis are still rejected
during pre-initialization, even if a loader could supply the child at runtime.
Defer those chains to runtime until loader-aware reexport analysis is supported.
Defer SQLite connections with `new DatabaseSync(path, { open: false })`, then call `.open()` after restoration.
Applications that require top-level external I/O should skip `optimize` and
initialize at runtime instead.
Host exception diagnostics recognize native Errors and legacy Error-prototype
objects and use bounded native stack
frames without invoking application getters, coercion, `toJSON`, or
`Error.prepareStackTrace`. Ordinary records and arrays show own data with a
two-level depth limit, a shared 16-property budget and a 4096-byte summary limit;
accessors, cycles and omitted entries are marked explicitly. Proxies and exotic
objects remain opaque. Array inspection reads only a bounded prefix, while native
record key discovery allocates a native table per visited record and may sort
integer keys. Its cost grows with record size, including repeated visits to shared
records; allocation failure falls back to an unavailable marker. Oversized strings receive
bounded descriptions. When the engine supplies no usable position, the
diagnostic explicitly says that the source location is unavailable. Existing
source maps and wrapper offsets are read through guarded data lookup; unsafe,
missing or overly expensive mappings fall back to generated code locations.
Previously prepared stack text is reused within the same bounds without running
its hook again. Readable bundles and source map configuration remain the
responsibility of the build tooling. Public
JavaScript `.stack` and `prepareStackTrace` behavior is unchanged.
**Deterministic pre-initialization:** newly generated P2 and P3 templates export
`wasm-rquickjs-wizer-restore-v1`. When optimizing their `wizer-initialize` export,
the optimizer supplies fixed-seed entropy, Unix-epoch wall time, and a zero
monotonic clock, with empty arguments/environment. These services exist only in
the optimizer store. Before any restored application JS runs, the guest reseeds
its crypto RNG from runtime host entropy (failure traps) and refreshes its
performance/uptime origin. Captured `Math.random` functions, including functions
from `node:vm` contexts, use that reseeded RNG. Runtime clock reads still use the
deployment host. Determinism is for identical input with the same optimizer and
dependencies, not a cross-version artifact format guarantee. Initialization must
also avoid external I/O and application-owned sources of nondeterminism; the
optimizer does not make arbitrary application code reproducible.
**Application initialization is still snapshotted.** Values already computed at
module top level—timestamps, random IDs, keys, library-owned PRNG state, performance
marks, and copies of environment values—remain frozen. Internal-service refresh
cannot undo those computations. Generate secrets and per-instance IDs after
restoration, inside an exported function; do not generate them at module top level.
Initialization must not depend on clocks advancing. Applications that need live
top-level entropy/time should skip `optimize` and initialize at runtime instead.
Old/unmarked components and other initializer names retain legacy real entropy
and clocks, with a warning: output is not guaranteed reproducible, and cached RNG
state may still be shared by restored instances. **Update both the optimizer and
rebuild guest templates** to enable the deterministic path safely. Updating the
optimizer alone does not repair a published template or existing snapshots. The
capability export is a template producer's declaration of this restore contract,
not a guarantee that arbitrary application state or composed code is snapshot-safe.
Library callers using the `optimize` feature can configure the guest-to-host copy budget for
initialization and Wizer snapshot memory extraction:
```rust,no_run
use wasm_rquickjs::{OptimizeOptions, optimize_component_with_options};
# async fn example(input: &camino::Utf8Path, output: &camino::Utf8Path) -> anyhow::Result<()> {
optimize_component_with_options(
input,
output,
"wizer-initialize",
&OptimizeOptions {
hostcall_fuel: Some(512 * 1024 * 1024), // 512 MiB per hostcall
},
).await?;
# Ok(())
# }
```
An explicit budget requires `use-golem-wasmtime` **and** Cargo patches selecting Golem's
compatible Wasmtime 46.0.1 fork (see the commented patches in `Cargo.toml`); the feature alone
does not select the fork. Stock Wasmtime does not expose this setting, and an explicit budget
without the feature returns an error. `optimize_component` and `OptimizeOptions::default()`
leave the engine default unchanged (128 MiB in Golem's fork). This limits bytes copied per
hostcall, not Wasm instruction fuel or the total snapshot size. Raising it allows larger
copies and increases potential host memory use; insufficient fuel can still trigger Wizer's
existing snapshot-extraction panic.
Preview 3 is opt-in and evolving; Preview 2 (`--target wasi-p2`) remains the default and stable
path.
## Mappings
### Exports
#### Top level exported functions
The following WIT code:
```wit
package demo:pkg;
world example {
export hello: func() -> string;
}
```
must be implemented in JavaScript as:
```javascript
export const hello = () => {
return "Hello, world!";
};
```
The `this` is bound to the module object. The JS function name is always in camelCase.
#### Exported interfaces
Exported interfaces has to be exported from JavaScript as objects:
The following WIT example:
```wit
package demo:pkg;
interface sample-api {
get-string-length: func(value: string) -> u64;
}
world example {
export sample-api;
}
```
has to be implemented in JavaScript as:
```javascript
export const sampleApi = {
getStringLength: (value) => {
return value.length;
}
};
```
All names are converted to camelCase. The JavaScript `this` is bound to object representing the exporter interface, in
the above example it is `sampleApi`.
If two exported interfaces in the same world map to the same JavaScript object name, the colliding interfaces are
exported with a qualified name derived from their full WIT interface id. For example, `test:a/guest@1.0.0` and
`test:b/guest@1.0.0` both have the short interface name `guest`, so they must be exported as:
```javascript
export const testA100Guest = {
invoke: (a, b, c) => `${a}:${b}:${c}`,
};
export const testB100Guest = {
invoke: (a, b, c, d, e) => `${a}:${b}:${c}:${d}:${e}`,
};
```
Interfaces whose JavaScript object names do not collide keep the short camelCase export shape.
#### Exported resources
Exported resources are implemented as **classes** in JS:
The following WIT example:
```wit
package demo:pkg;
interface iface {
resource example-resource {
constructor(name: string);
get-name: func() -> string;
compare: static func(h1: borrow<example-resource>, h2: borrow<example-resource>) -> s32;
merge: static func(h1: own<example-resource>, h2: own<example-resource>) -> hello;
}
}
world example {
export iface;
}
```
Must be exported from JavaScript in the following way:
```js
class Hello {
constructor(name) {
this.name = name;
}
// async to demonstrate it is possible
async getName() {
return this.name;
}
static compare(h1, h2) {
if (h1.name === h2.name) {
return 0;
} else if h1.name < h2.name) {
return -1;
} else {
return 1;
}
}
static merge(h1, h2) {
return new Hello(`${h1.name} & ${h2.name}`);
}
}
export const iface = {
Hello: Hello,
};
```
The classes have a UpperCamelCase name and their methods are in camelCase. All methods and static methods can be either
sync or async.
### Types
| Name | WIT | JS | Notes |
|-------------------------|---------------------|---------------------------------------------------|-------------------------------------------------------------------------------|
| Character | `char` | `string` | - |
| String | `string` | `string` | - |
| Signed 8-bit integer | `s8` | `number` | - |
| Unsigned 8-bit integer | `u8` | `number` | - |
| Signed 16-bit integer | `s16` | `number` | - |
| Unsigned 16-bit integer | `u16` | `number` | - |
| Signed 32-bit integer | `s32` | `number` | - |
| Unsigned 32-bit integer | `u32` | `number` | - |
| Signed 64-bit integer | `s64` | `bigint` | - |
| Unsigned 64-bit integer | `u64` | `bigint` | - |
| 32-bit float | `f32` | `number` | - |
| 64-bit float | `f64` | `number` | - |
| Optional type | `option<T>` | `T \| undefined` | Nested options are encoded differently |
| List | `list<T>` | `T[]` | - |
| Result | `result<T, E>` | `{ tag: "ok": val: T } \| { tag: "err", val: E }` | - |
| Tuple | `tuple<A, B, C>` | Array | - |
| Enum | `enum { a, b, c}` | `"a" \| "b" \| "c"` | The strings match the WIT enum cases |
| Flags | `flags { a, b, c }` | `{ a: boolean, b: boolean, c: boolean }` | The object keys are camelCase |
| Record | `record { .. }` | Object | Field names are camelCase |
| Variant | `variant { .. }` | `{ tag: "x", val: X }` | Tag names match the WIT variant case names; `val` is undefined for unit cases |
| Future | `future<T>` | `Promise<T>` | WASI Preview 3 (`--target wasi-p3`) only |
| Stream | `stream<T>` | `AsyncIterable<T>` | WASI Preview 3 (`--target wasi-p3`) only |
### Imported result errors
An imported function that returns `result<T, E>` returns `T` on success and throws
`E` on failure. Async imports reject with the same error value.
Structured error values retain their WIT representation. `String(error)`, template
interpolation, and `new Error(error).message` render the payload as JSON instead of
`[object Object]`. For example, a permission error renders as
`{"tag":"permission-denied"}`, and a backend error renders as
`{"tag":"backend-error","val":"service unavailable"}`. Bigint fields render as
decimal strings without changing the original values.
The coercion methods are non-enumerable. The original fields, object identity,
enumerable keys, and typed catch-and-rethrow behavior remain unchanged. Primitive
errors and successful values are not decorated. This rendering applies to error
string coercion; it does not replace the runtime's uncaught-exception formatter.
### Limitations
- Maximum number of function parameters is 26
- Anonymous interface exports/imports are not supported
- Imported individual functions into the world are not supported (only whole interfaces)
- Preview 3 synchronous exports cannot return `future<T>` or `stream<T>`, directly or nested; use
`async func`
- Preview 3 exported top-level `result<T, E>` error arms cannot contain `future<T>` or `stream<T>`
## Available JavaScript APIs
### APIs
#### Web Platform APIs
<details>
<summary><strong>Console</strong></summary>
If the `logging` feature flag is enabled in the generated crate, it depends on `wasi:logging`, otherwise just on the core WASI interfaces.
- `assert`, `clear`, `count`, `countReset`, `debug`, `dir`, `dirXml`, `error`, `group`, `groupCollapsed`, `groupEnd`, `info`, `log`, `table`, `time`, `timeEnd`, `timeLog`, `trace`, `warn`
</details>
<details>
<summary><strong>HTTP (fetch)</strong></summary>
Only if the `http` feature flag is enabled in the generated crate. It depends on `wasi:http`.
- `fetch`, `Headers`, `Request`, `Response`, `FormData`, `Blob`, `File`
</details>
<details>
<summary><strong>URL</strong></summary>
- `URL` (`createObjectURL`, `revokeObjectURL`)
- `URLSearchParams`
</details>
<details>
<summary><strong>Streams</strong></summary>
Implemented by https://github.com/MattiasBuelens/web-streams-polyfill
- `ByteLengthQueuingStrategy`, `CountQueuingStrategy`
- `ReadableStream`, `ReadableStreamDefaultReader`, `ReadableStreamBYOBReader`, `ReadableStreamDefaultController`, `ReadableByteStreamController`, `ReadableStreamBYOBRequest`
- `WritableStream`, `WritableStreamDefaultController`
- `TransformStream`, `TransformStreamDefaultController`
</details>
<details>
<summary><strong>Timers</strong></summary>
- `setTimeout`, `clearTimeout`, `setInterval`, `clearInterval`, `setImmediate`
</details>
<details>
<summary><strong>Abort Controller</strong></summary>
- `AbortController`, `AbortSignal`, `DOMException`
</details>
<details>
<summary><strong>Encoding</strong></summary>
- `TextEncoder`, `TextDecoder`, `TextDecoderStream`, `TextEncoderStream`
</details>
<details>
<summary><strong>Messaging</strong></summary>
- `MessageChannel`, `MessagePort`
</details>
<details>
<summary><strong>Events</strong></summary>
- `Event`, `EventTarget`, `CustomEvent`
</details>
<details>
<summary><strong>Intl (Internationalization)</strong></summary>
Minimal en-US implementation for library compatibility. All locale inputs are accepted but resolved to `en-US`.
- `Intl.DateTimeFormat` — `format()`, `formatToParts()`, `resolvedOptions()`, `supportedLocalesOf()`
- `Intl.NumberFormat` — `format()`, `formatToParts()`, `resolvedOptions()`, `supportedLocalesOf()`
- `Intl.Collator` — `compare()`, `resolvedOptions()`, `supportedLocalesOf()`
- `Intl.PluralRules` — `select()`, `selectRange()`, `resolvedOptions()`, `supportedLocalesOf()`
- `Intl.getCanonicalLocales()`, `Intl.supportedValuesOf()`
When the `timezone` feature is enabled (default), `DateTimeFormat` supports all ~590 IANA timezones via `chrono-tz`.
</details>
<details>
<summary><strong>Crypto (global)</strong></summary>
- `crypto.randomUUID`, `crypto.getRandomValues`
</details>
<details>
<summary><strong>Performance (global)</strong></summary>
- `performance.now()` uses the WASI monotonic clock and is not affected by wall-clock adjustments.
- `performance.timeOrigin` is the deployment wall-clock origin paired with that monotonic timeline.
- The global object is shared with `node:perf_hooks`.
</details>
<details>
<summary><strong>Structured Clone</strong></summary>
`structuredClone` preserves object graphs, ArrayBuffer/view aliases and genuine in-memory Blob data.
The runtime-owned `process.env` clones into an ordinary, independent environment snapshot; application-created proxies remain uncloneable.
CryptoKey metadata participates in the same graph, including aliases and cycles.
Transfer buffers detach only after the complete graph and host metadata validate; unlike Node.js,
a rejected CryptoKey metadata graph leaves these buffers attached.
ArrayBuffer transfer and runtime-local transferable AbortSignals are supported. Transferred signal reasons
are copied before local abort listeners run; target abort delivery is queued. Clone/transfer markers
are enforced by the same private policy used by MessageChannel; nonstandard `json`/`lossy` options
cannot disable rejection. Blob clones own independent byte data. File-backed/foreign Blob backing,
SharedArrayBuffer or immutable ArrayBuffer cloning, resizable-ArrayBuffer-backed views, and stream/MessagePort/FileHandle transfer are unsupported and rejected.
FileHandle and web-stream payloads also reject without a transfer list; their ownership transfer is unavailable.
Execution-job results reject Blob and other host values rather than transporting opaque resources.
The serializer is adapted from https://github.com/ungap/structured-clone.
</details>
#### Node.js-Compatible Modules
<details>
<summary><strong><code>node:assert</code></strong></summary>
- `ok`, `equal`, `notEqual`, `strictEqual`, `notStrictEqual`
- `deepEqual`, `notDeepEqual`, `deepStrictEqual`, `notDeepStrictEqual`, `partialDeepStrictEqual`
- `throws`, `doesNotThrow`, `rejects`, `doesNotReject`
- `ifError`, `match`, `doesNotMatch`, `fail`
- `AssertionError`, `CallTracker`
- `strict` — strict mode variant (re-maps `equal` → `strictEqual`, `deepEqual` → `deepStrictEqual`, etc.)
</details>
<details>
<summary><strong><code>node:async_hooks</code></strong></summary>
- `AsyncLocalStorage` — `run`, `exit`, `getStore`, `enterWith`, `disable`, `snapshot`, `bind`
- `AsyncResource` — `runInAsyncScope`, `asyncId`, `triggerAsyncId`, `bind`
- `createHook` — stub (returns enable/disable no-ops)
- `executionAsyncId`, `triggerAsyncId`, `executionAsyncResource` — stubs
Context propagation works through `Promise.prototype.then/catch/finally` and `setTimeout`/`setInterval`. **Limitation:** QuickJS `await` uses internal C-level `perform_promise_then` which bypasses JS-visible `Promise.prototype.then`, so context is **not** propagated across `await` boundaries.
</details>
<details>
<summary><strong><code>node:buffer</code></strong></summary>
- `Buffer`, `Blob`, `File`, `SlowBuffer`
- `resolveObjectURL`, `isAscii`, `isUtf8`
- `INSPECT_MAX_BYTES`, `kMaxLength`, `kStringMaxLength`, `constants`
</details>
<details>
<summary><strong><code>node:child_process</code></strong></summary>
WASI cannot create host processes. JavaScript commands targeting `process.execPath` and executable
Node shebang files run in the component. On Preview 3, each local child has a fresh runtime,
argv, environment, cwd and module cache. Synchronous calls pause parent JavaScript while the child
drains its referenced work. Local `spawn` and `execFile` expose live output and let the parent run
while the child waits for timers or I/O. JavaScript execution is cooperative on the shared WASM
thread: a CPU-bound child needs its own timeout; parent timers cannot preempt it to call `kill()`.
Nested children inherit the outer child's deadline. The constrained local `exec` and `fork`
adapters still execute synchronously before delivering their events.
Preview 2 retains inline execution and does not join asynchronous local-child work.
The constrained compatibility adapter also recognizes npm's literal `sh -c` form for plain Node
commands. Unsupported shells, shell syntax, external executables and unsupported shebangs fail
explicitly with `ENOSYS`, unless an embedding handles them through a registered backend.
- `ChildProcess`, `exec`, `execFile`, `fork`, `spawn`, `execSync`, `execFileSync`, `spawnSync`
An embedding may register command fallbacks through `wasm-rquickjs:child-process` (below).
Without a backend, the unsupported-command behavior is unchanged.
`exec` and `execFile` collect the asynchronous backend child's output, preserve its
error or exit status, and enforce their output limits and timeout. They do not use
the synchronous backend when an asynchronous command is accepted.
Promisified `exec` and `execFile` return both output channels and expose their child
through the promise's `child` property.
Preview 3 local children capture exit status and uncaught failures, preserve piped bytes, and
support timeout, cancellation and bounded output. Fixed input is available through `process.stdin`,
`fs.readFile(0)` / `fs.readFileSync(0)` and `/dev/stdin`. Live writes to `child.stdin`, interactive
stdin, extra file descriptors and IPC are not provided; unsupported stdio requests fail with `ENOSYS`.
Inherited output passes through the parent's stream adapter and has a 64 MiB per-channel transfer
limit. Fresh children do not provide hard JavaScript termination after `process.exit()`.
Native embedding modules must compose their child work explicitly rather than spawning unrelated
wit-bindgen tasks across the synchronous child boundary. Runtime-init hooks must stay lazy.
</details>
<details>
<summary><strong><code>node:cluster</code></strong> (stub)</summary>
Compatibility stubs — clustering is not supported in WASM.
- `isPrimary`, `isMaster`, `isWorker`, `fork`, `disconnect`, `setupPrimary`
</details>
<details>
<summary><strong><code>node:constants</code></strong></summary>
Merged constants from `os.constants`, `fs.constants`, and `crypto.constants` (signal numbers, errno values, file flags, etc.)
</details>
<details>
<summary><strong><code>node:crypto</code></strong></summary>
- **Hashing:** `createHash`, `createHmac`, `hash`, `getHashes`
- **Ciphers:** `createCipheriv`, `createDecipheriv`, `getCiphers`
- Supported: aes-128/256-cbc, aes-128/256-ctr, aes-128/256-gcm, aes-128/192/256-wrap, id-aes128/192/256-wrap, id-aes128/192/256-wrap-pad, des-ede3-cbc, des3-wrap, chacha20-poly1305
- **Signing:** `createSign`, `createVerify` (Ed25519, ECDSA with P-256, P-384, secp256k1)
- **Keys:** `createPublicKey`, `createPrivateKey`, `createSecretKey`, `KeyObject`, `generateKeyPairSync`, `generateKeyPair`
- **ECDH:** `createECDH`, `ECDH`, `diffieHellman`, `getCurves`
- **Certificate:** `Certificate` (`verifySpkac`, `exportPublicKey`, `exportChallenge`)
- **Random:** `randomBytes`, `randomFillSync`, `randomFill`, `randomInt`, `randomUUID`, `pseudoRandomBytes`, `prng`, `rng`
- **KDF:** `pbkdf2`, `pbkdf2Sync`, `scrypt`, `scryptSync`, `hkdf`, `hkdfSync`
- **Primes:** `generatePrime`, `generatePrimeSync`, `checkPrime`, `checkPrimeSync`
- **Other:** `timingSafeEqual`, `getFips`, `setFips`, `secureHeapUsed`
</details>
<details>
<summary><strong><code>node:dgram</code></strong></summary>
UDP datagram sockets built on WASI sockets.
- `dgram.createSocket(type[, listener])` — create a UDP socket (`udp4` or `udp6`)
- `socket.bind`, `socket.send`, `socket.sendto`, `socket.connect`, `socket.disconnect`, `socket.close`
- `socket.address()`, `socket.remoteAddress()`
- `socket.setTTL`, `socket.setRecvBufferSize` / `getRecvBufferSize`, `socket.setSendBufferSize` / `getSendBufferSize`
- `socket.ref()` / `socket.unref()` — no-op
- Events: `message`, `listening`, `close`, `error`, `connect`
**Not supported:** multicast operations (`setBroadcast`, `setMulticastTTL`, `addMembership`, etc.) throw `ENOSYS`.
</details>
<details>
<summary><strong><code>node:diagnostics_channel</code></strong></summary>
Publish/subscribe diagnostic messaging and tracing.
Subscriber and store-transform errors are delivered through `uncaughtException` on the next tick, without interrupting other subscribers.
- `channel(name)`, `subscribe(name, onMessage)`, `unsubscribe(name, onMessage)`, `hasSubscribers(name)`, `tracingChannel(name)`
- `Channel` — `subscribe`, `unsubscribe`, `publish`, `bindStore`, `unbindStore`, `runStores`, `hasSubscribers`, `name`
- `TracingChannel` — `subscribe`, `unsubscribe`, `hasSubscribers`, `traceSync`, `tracePromise`, `traceCallback`
Built-in HTTP diagnostics channels: `http.client.request.created`, `http.client.request.start`, `http.client.request.error`, `http.client.response.finish`.
</details>
<details>
<summary><strong><code>node:dns</code></strong></summary>
DNS resolution via WASI sockets.
- `lookup`, `lookupService`, `resolve`, `resolve4`, `resolve6`
- `resolveAny`, `resolveCname`, `resolveCaa`, `resolveMx`, `resolveNaptr`, `resolveNs`, `resolvePtr`, `resolveSoa`, `resolveSrv`, `resolveTxt`, `resolveTlsa`
- `reverse`, `setServers`, `getServers`, `setDefaultResultOrder`, `getDefaultResultOrder`
- `Resolver` class
- `promises` — promise-based API
- Error constants: `NODATA`, `FORMERR`, `SERVFAIL`, `NOTFOUND`, `NOTIMP`, `REFUSED`, `BADQUERY`, `BADNAME`, `BADFAMILY`, `BADRESP`, `CONNREFUSED`, `TIMEOUT`, `EOF`, `FILE`, `NOMEM`, `DESTRUCTION`, `BADSTR`, `BADFLAGS`, `NONAME`, `BADHINTS`, `NOTINITIALIZED`, `LOADIPHLPAPI`, `ADDRGETNETWORKPARAMS`, `CANCELLED`, `ADDRCONFIG`, `V4MAPPED`, `ALL`
</details>
<details>
<summary><strong><code>node:domain</code></strong></summary>
Deprecated error-handling domains (sync-only).
- `domain.create()` / `domain.createDomain()`, `domain.active`, `domain._stack`
- `Domain` class (extends `EventEmitter`): `run`, `add`, `remove`, `bind`, `intercept`, `enter`, `exit`, `dispose`, `members`, `parent`
- `process.domain` — reflects active domain
- Error decoration: `error.domain`, `error.domainEmitter`, `error.domainBound`, `error.domainThrown`
- EventEmitter integration and async propagation via `process.nextTick`, `setTimeout`, `setInterval`
**Limitation:** No async context propagation via `async_hooks` — only synchronous errors and explicitly bound callbacks are captured.
</details>
<details>
<summary><strong><code>node:events</code></strong></summary>
- `EventEmitter` — `on`, `once`, `off`, `addListener`, `removeListener`, `removeAllListeners`, `emit`, `listenerCount`, `eventNames`, `listeners`, `rawListeners`, `prependListener`, `prependOnceListener`, `setMaxListeners`, `getMaxListeners`
- Static: `once`, `on`, `getEventListeners`, `getMaxListeners`, `setMaxListeners`, `addAbortListener`, `errorMonitor`, `captureRejections`
- `Event`, `EventTarget`, `CustomEvent`
</details>
<details>
<summary><strong><code>node:fs</code></strong></summary>
Comprehensive filesystem API built on WASI filesystem.
Symbolic links are persistent WASI filesystem objects, so linked package trees
remain visible to later fresh execution runtimes. Link targets must be relative:
Rooted targets cannot be resolved within a WASI preopen, so `symlink` fails with
`EINVAL` without creating an object when given one. Production Golem and
macOS/Linux local development support these links. Windows local development
currently requires Developer Mode or an equivalent symlink privilege; non-link
filesystem and npm workflows are unaffected.
- **Sync:** `readFileSync`, `writeFileSync`, `appendFileSync`, `openSync`, `closeSync`, `readSync`, `writeSync`, `ftruncateSync`, `fsyncSync`, `fdatasyncSync`, `statSync`, `lstatSync`, `fstatSync`, `statfsSync`, `readdirSync`, `accessSync`, `existsSync`, `realpathSync`, `truncateSync`, `copyFileSync`, `linkSync`, `symlinkSync`, `readlinkSync`, `chmodSync`, `fchmodSync`, `lchmodSync`, `chownSync`, `fchownSync`, `lchownSync`, `utimesSync`, `futimesSync`, `lutimesSync`, `unlinkSync`, `renameSync`, `mkdirSync`, `rmdirSync`, `rmSync`, `mkdtempSync`, `opendirSync`, `readvSync`, `writevSync`, `cpSync`
- **Async (callback):** `readFile`, `writeFile`, `appendFile`, `open`, `close`, `read`, `write`, `stat`, `lstat`, `fstat`, `statfs`, `ftruncate`, `fsync`, `fdatasync`, `readdir`, `access`, `exists`, `realpath`, `truncate`, `copyFile`, `link`, `symlink`, `readlink`, `chmod`, `fchmod`, `lchmod`, `chown`, `fchown`, `lchown`, `utimes`, `futimes`, `lutimes`, `unlink`, `rename`, `mkdir`, `rmdir`, `rm`, `mkdtemp`, `opendir`, `watch`, `watchFile`, `unwatchFile`, `readv`, `writev`, `cp`, `openAsBlob`
- **Streams:** `createReadStream`, `createWriteStream`
- **Classes:** `Stats`, `Dirent`, `Dir`, `FSWatcher`, `StatWatcher`, `ReadStream`, `WriteStream`
- **Constants:** `F_OK`, `R_OK`, `W_OK`, `X_OK`, `O_RDONLY`, `O_WRONLY`, `O_RDWR`, `O_CREAT`, `O_EXCL`, `O_TRUNC`, `O_APPEND`, etc.
</details>
<details>
<summary><strong><code>node:fs/promises</code></strong></summary>
Promise-based filesystem API.
- `FileHandle`, `open`, `readFile`, `writeFile`, `appendFile`, `unlink`, `rename`, `mkdir`, `rmdir`, `rm`, `stat`, `lstat`, `readdir`, `opendir`, `access`, `realpath`, `truncate`, `copyFile`, `link`, `symlink`, `readlink`, `chmod`, `lchmod`, `chown`, `lchown`, `utimes`, `lutimes`, `mkdtemp`, `cp`, `watch`, `statfs`, `constants`
</details>
<details>
<summary><strong><code>node:http</code> / <code>node:https</code></strong></summary>
Requires the `http` feature flag. Client requests use `wasi:http` (TLS handled transparently). Server support uses `wasi:sockets` for TCP-level HTTP/1.1 serving.
- `http.request(url|options[, callback])`, `http.get(url|options[, callback])`
- `http.METHODS`, `http.STATUS_CODES`, `http.maxHeaderSize`
- `http.validateHeaderName`, `http.validateHeaderValue`
- `http.Agent`, `http.globalAgent`
- `http.OutgoingMessage` — base class for writable HTTP message objects
- `http.ClientRequest` — `write`, `end`, `setHeader`, `getHeader`, `removeHeader`, `hasHeader`, `getHeaderNames`, `getHeaders`, `getRawHeaderNames`, `flushHeaders`, `setNoDelay`, `setSocketKeepAlive`, `abort`, `destroy`, `setTimeout`
- `http.IncomingMessage` — `statusCode`, `statusMessage`, `headers`, `rawHeaders`, `httpVersion`
- `https.request` / `https.get` — delegates to `http` (WASI-HTTP handles TLS transparently)
- `http.createServer([options][, requestListener])` — create an HTTP/1.1 server (requires `wasi:sockets`)
- `http.Server` (extends `net.Server`): `listen`, `close`, `closeAllConnections`, `closeIdleConnections`, `setTimeout`, `maxRequestsPerSocket`; emits `dropRequest` when the per-socket limit is exceeded
- `http.ServerResponse` (extends `EventEmitter`): `writeHead`, `setHeader`, `getHeader`, `removeHeader`, `hasHeader`, `getHeaders`, `getHeaderNames`, `getRawHeaderNames`, `write`, `end`, `flushHeaders`, `writeContinue`, `writeProcessing`, `writeEarlyHints`, `cork`, `uncork`
- Server-side `IncomingMessage` (extends `stream.Readable`): `method`, `url`, `headers`, `headersDistinct`, `rawHeaders`, `httpVersion`, `socket`, `client`, `complete`, `aborted`, `trailers`, `trailersDistinct`, `rawTrailers`
- `node:_http_common` — `_checkIsHttpToken`, `_checkInvalidHeaderChar`
- Supported features: keep-alive connections, chunked transfer encoding, content-length bodies, ordered pipelined responses, server-side 100/102/103 informational responses, per-socket request limits, idle connection cleanup
**Not yet supported:** HTTP Upgrade and CONNECT tunnels, client-side 1xx informational events
through `wasi:http`, server-side timeout enforcement, `https.createServer()` / HTTPS server, client
`lookup` / `autoSelectFamily` options, and custom per-request `createConnection` transports. Custom
Agent `createConnection` hooks are ignored with a warning so Agent metadata and scheduling remain
usable, but they cannot route or isolate traffic; every outbound request still uses `wasi:http`.
</details>
<details>
<summary><strong><code>node:http2</code></strong> (stub)</summary>
Compatibility stubs — HTTP/2 is not supported.
- `connect`, `createServer`, `createSecureServer`, `Http2ServerRequest`, `Http2ServerResponse`
</details>
<details>
<summary><strong><code>wasm-rquickjs:child-process</code></strong></summary>
`setProcessBackend({ spawn?, spawnSync? })` installs runtime-local command fallbacks; pass `null`
or `undefined` to clear them. Methods receive `(command, args, options)` and return `undefined`
when they do not handle a command. Local Node and executable Node-shebang targets retain priority.
The optional-argv forms `spawn(command, options)` and `spawnSync(command, options)` are
normalized before dispatch, so adapters always receive an argv array and a separate options object.
`spawn` returns a ChildProcess-compatible object. `spawnSync` returns a synchronous Node-style
result with byte stdout/stderr, or `null` for unpiped channels; the wrapper applies the requested output encoding. A Promise
from `spawnSync` is rejected. `execFileSync` uses that fallback; `execSync` delegates with the
shell option set. This hook does not itself execute host processes or implement a
tool registry, permissions, or shell policy.
Embeddings can supply an additional JS module named `wasm-rquickjs-runtime-init` using
`--js-modules`. It is imported after builtin wiring and before entry code in both the main
runtime and every fresh execution job. Use it to register adapters independently in each runtime.
It also runs during Wizer initialization: registration must not discover tools, capture invocation
authority, perform host I/O, or start background work. Do that lazily in backend methods instead.
This configuration hook is not a security boundary; host capability checks remain authoritative.
</details>
<details>
<summary><strong><code>wasm-rquickjs:execution</code></strong></summary>
Runs JavaScript asynchronously in a fresh QuickJS runtime. `startJavaScript(options)` returns live
`stdout`/`stderr` readable streams, a `result` promise resolving to `{ value, overflowed }`, and
`cancel()`. Jobs are runtime-only; importing this module during pre-initialization
is safe. `runJavaScript(options)` collects the streams and resolves to
`{ value, overflowed, stdout, stderr }`. `value` crosses the runtime boundary through structured
clone.
Options accept exactly one of `entry: string` or inline `source: string`, plus `cwd`, `argv`,
`env`, `timeoutMs`, `maxBytes` (1 MiB per stream by default, 64 MiB maximum), and `overflow`
(`terminate` by default or `truncate`). `language: "javascript" | "typescript"` applies only to
inline `source` and defaults to JavaScript. With the generated crate's `typescript-runtime`
feature enabled, TypeScript entry files are selected by their `.ts`, `.mts`, or `.cts` extension,
and inline jobs can select `language: "typescript"`.
Output and completion wake the parent without periodic polling. Timeout budgets start
after child-runtime initialization. An async wait observes deadlines while jobs yield; for
non-yielding JavaScript, the QuickJS interrupt handler samples the clock at an adaptive stride
targeting one check per 10% of `timeoutMs` during steady CPU work. This is a best-effort budget,
not an exact cutoff or a maximum-overrun guarantee. A job that finishes between checks can
succeed after its literal deadline; no extra clock check occurs at completion. Entry modules run
for their side effects; an exported
`default` function (or `run` function when there is no default function) is invoked and awaited.
Relative entry paths and imports resolve from `cwd`. Inline `source` is an async function body:
top-level `await` and `return` are supported, while static `import`/`export` declarations are not.
`env` is an explicit allowlist and defaults to an empty environment; jobs never merge or inherit
the enclosing component's environment variables. When `argv` is omitted, entry jobs receive
`["wasm-rquickjs-execution", resolvedEntryPath]` and inline jobs receive
`["wasm-rquickjs-execution"]`.
With `overflow: "terminate"`, exceeding either stream's bound rejects the job. With
`overflow: "truncate"`, execution continues, captured output is bounded, and `overflowed` is
`true` when either stream exceeded the bound.
Cancellation is cooperative for queued or yielding code and cannot preempt a tight loop already
running on the same thread; a positive `timeoutMs` is the best-effort escape mechanism for such a
loop. Cancellation remains a guest-local flag check on every QuickJS interrupt. A job that
never settles must be cancelled or given a timeout before the enclosing component invocation can
finish. A runtime accepts at
most eight active jobs, and child executions cannot recursively create more execution jobs.
Fresh-runtime execution means fresh JavaScript globals, process state, timers, module caches, and
other mutable QuickJS runtime state. Results cross the runtime boundary through structured clone.
It is not a security sandbox: execution code inherits the component's filesystem, network, clocks,
randomness, and other available host capabilities, and `cwd` is only a resolution base—not a
filesystem boundary. Failed jobs are
currently returned as parent-realm `Error` messages rather than structured-cloned thrown values.
This API is distinct from `node:vm`. The `node:vm` module provides synchronous Node-compatible
contexts within one QuickJS runtime, including live-object behavior required by its API.
`wasm-rquickjs:execution` provides asynchronous jobs in fresh runtimes with structured-clone
results. Neither API is a security sandbox, and both share the enclosing component's capabilities
and filesystem access.
</details>
<details>
<summary><strong><code>node:inspector</code></strong> (stub)</summary>
Compatibility stubs — no V8 inspector in WASM.
- `Session`, `open`, `close`, `url`, `waitForDebugger`, `console`, `Network`
</details>
<details>
<summary><strong><code>node:module</code></strong></summary>
Builtin ESM export names are fixed by the runtime, including aliases. Changing builtin properties before the first ESM import does not add or remove named exports; `syncBuiltinESMExports()` updates their values.
- `require`, `require.resolve`, `createRequire`, `builtinModules`, `isBuiltin`, `runMain`, `_nodeModulePaths`
- `stripTypeScriptTypes(code[, options])` with Node-compatible strip and transform modes when the
generated crate enables `typescript-runtime`
- Package resolution supports `package.json` `main`, `exports` root/subpath maps, wildcard `exports` patterns, `imports` maps, and wildcard `imports` patterns. CJS resolution recognizes `golem`, `node`, `require`, `module-sync`, and `default` conditions; ESM resolution recognizes `golem`, `node`, `import`, `module-sync`, and `default`. Package `imports` can target relative files, external packages, and `node:` builtins.
- With `typescript-runtime`, `.mts` is always ESM, `.cts` is always CommonJS, and `.ts` follows the
nearest package type and Node-style syntax classification. ESM imports require explicit file
extensions. Type stripping inside `node_modules` is rejected, matching Node's built-in support.
The runtime does not type-check, read `tsconfig.json`, or support `.tsx`; enable
`typescript-transform-runtime` instead of `typescript-runtime` for enums, runtime namespaces,
and other transform-required syntax in module files or inline `language: "typescript"`
execution source.
</details>
<details>
<summary><strong><code>node:net</code></strong></summary>
TCP sockets and servers built on WASI sockets.
- `net.createServer([options][, connectionListener])`, `net.createConnection(options[, connectListener])`, `net.connect(...)`
- `net.isIP(input)` / `net.isIPv4(input)` / `net.isIPv6(input)`
- `net.getDefaultAutoSelectFamily()` / `net.setDefaultAutoSelectFamily(value)` — stubs
- `net.Socket` (extends `stream.Duplex`): `connect`, `write`, `end`, `destroy`, `resetAndDestroy`, `setTimeout`, `setNoDelay`, `setKeepAlive`, `address`, `ref`, `unref`
- Properties: `remoteAddress`, `remotePort`, `remoteFamily`, `localAddress`, `localPort`, `localFamily`, `bytesRead`, `bytesWritten`, `connecting`, `pending`, `readyState`
- Events: `connect`, `ready`, `data`, `end`, `close`, `error`, `timeout`, `drain`, `lookup`
- `net.Server` (extends `EventEmitter`): `listen`, `close`, `address`, `getConnections`, `ref`, `unref`
- Properties: `listening`, `maxConnections`
- Events: `listening`, `connection`, `close`, `error`, `drop`
- `net.BlockList`, `net.SocketAddress`, `net.Stream`
**Not supported:** IPC/Unix domain sockets, Happy Eyeballs/`autoSelectFamily` (stubbed), `cluster` integration.
</details>
<details>
<summary><strong><code>node:os</code></strong></summary>
- `arch`, `platform`, `type`, `release`, `version`, `machine`, `hostname`, `homedir`, `tmpdir`, `endianness`
- `cpus`, `loadavg`, `freemem`, `totalmem`, `uptime`, `availableParallelism`
- `networkInterfaces`, `userInfo`, `getPriority`, `setPriority`
- `EOL`, `devNull`, `constants` (signals, errno, priority)
</details>
<details>
<summary><strong><code>node:path</code></strong></summary>
- `sep`, `delimiter`, `basename`, `dirname`, `extname`, `isAbsolute`, `join`, `normalize`, `relative`, `resolve`, `parse`, `format`, `matchesGlob`, `toNamespacedPath`, `posix`
</details>
<details>
<summary><strong><code>node:perf_hooks</code></strong></summary>
- `performance` — monotonic `now()`, deployment `timeOrigin`, `mark()`, `measure()`, `getEntries()`, `getEntriesByName()`, `getEntriesByType()`, `clearMarks()`, `clearMeasures()`, `toJSON()`
- `PerformanceEntry`, `PerformanceObserver`
- `monitorEventLoopDelay`, `createHistogram`, `constants`
</details>
<details>
<summary><strong><code>node:process</code></strong></summary>
- **Properties:** `argv`, `argv0`, `env`, `pid`, `ppid`, `platform` (`'wasi'`), `arch` (`'wasm32'`), `version`, `versions`, `config`, `features`, `execArgv`, `execPath`, `exitCode`, `stdout`, `stderr`
- **Methods:** `exit(code)`, `cwd()`, `nextTick(callback, ...args)`, `getBuiltinModule(id)`, `hrtime()`, `hrtime.bigint()`, `uptime()`, `cpuUsage()`, `memoryUsage()`, `kill(pid, signal)`, `abort()`, `emitWarning()`
- `getBuiltinModule(id)` returns the existing object for builtins registered by this runtime, and `undefined` for unavailable IDs. The supported set is smaller than Node.js: `node:sea`, `node:test/reporters`, `node:inspector/promises`, `node:wasi`, `_stream_wrap`, and unimplemented `_http_*`/`_tls_*` wrappers are unavailable.
- **User/Group:** `getuid`, `getgid`, `geteuid`, `getegid`, `getgroups`, `setuid`, `setgid`
- Inherits from `EventEmitter` — supports `on('uncaughtException')`, `on('unhandledRejection')`, etc.
</details>
<details>
<summary><strong><code>node:punycode</code></strong></summary>
- `decode`, `encode`, `toASCII`, `toUnicode`, `ucs2`, `version`
</details>
<details>
<summary><strong><code>node:querystring</code></strong></summary>
- `stringify` / `encode`, `parse` / `decode`, `escape`, `unescape`, `unescapeBuffer`
</details>
<details>
<summary><strong><code>node:readline</code></strong></summary>
- `createInterface`, `Interface`, `clearLine`, `clearScreenDown`, `cursorTo`, `moveCursor`, `emitKeypressEvents`
- `readline/promises` — promise-based API
</details>
<details>
<summary><strong><code>node:repl</code></strong> (stub)</summary>
- `start`, `REPLServer`, `Recoverable`, `builtinModules`
</details>
<details>
<summary><strong><code>node:sqlite</code></strong></summary>
Requires the `sqlite` feature flag. Provides a synchronous SQLite database API with an embedded SQLite engine.
- `DatabaseSync` — `prepare`, `exec`, `close`, `open`, `isOpen`, `isTransaction`, `createSession`, `applyChangeset`, `enableLoadExtension`, `function`, `aggregate`, `backup`
- `StatementSync` — `run`, `get`, `all`, `iterate`, `columns`, `setReadBigInts`, `setAllowBareNamedParameters`, `sourceSQL`, `expandedSQL`
- `Session` — `changeset`, `patchset`, `close`
- Constants: `SQLITE_CHANGESET_OMIT`, `SQLITE_CHANGESET_REPLACE`, `SQLITE_CHANGESET_ABORT`
**Not supported:** `loadExtension()` (throws — native extensions cannot be loaded in WASM).
</details>
<details>
<summary><strong><code>node:stream</code></strong></summary>
- Legacy `_stream_readable`, `_stream_writable`, `_stream_duplex`, `_stream_transform` and `_stream_passthrough` aliases support CommonJS and ESM, with the same constructor defaults.
- `Readable` (with `from`, `fromWeb`, `toWeb`, `wrap`, and functional methods: `map`, `filter`, `flatMap`, `take`, `drop`, `toArray`, `forEach`, `reduce`, `some`, `every`, `find`)
- `Writable`, `Duplex`, `Transform`, `PassThrough`, `Stream`
- `pipeline`, `finished`, `compose`, `duplexPair`, `addAbortSignal`
- `getDefaultHighWaterMark`, `setDefaultHighWaterMark`, `isDisturbed`, `destroy`
- `stream/consumers` (`arrayBuffer`, `blob`, `buffer`, `json`, `text`)
- `stream/promises` — promise-based `pipeline` and `finished`
</details>
<details>
<summary><strong><code>node:string_decoder</code></strong></summary>
- `StringDecoder`
</details>
<details>
<summary><strong><code>node:test</code></strong></summary>
Built-in test runner.
- `test`, `describe` / `suite`, `it`, `before`, `after`, `beforeEach`, `afterEach`
- `mock` — function mocking utilities
- `run` — programmatic test execution
</details>
<details>
<summary><strong><code>node:timers</code></strong></summary>
- `setTimeout`, `setInterval`, `setImmediate`, `clearTimeout`, `clearInterval`
- `timers/promises` — promise-based `setTimeout`, `setInterval`, `setImmediate`
</details>
<details>
<summary><strong><code>node:tls</code></strong> (stub)</summary>
Compatibility stubs — TLS is handled transparently by the WASI-HTTP layer.
- `SecureContext`, `TLSSocket`, `Server`, `connect`, `createServer`, `createSecureContext`, `checkServerIdentity`, `getCiphers`, `rootCertificates`
- `DEFAULT_MIN_VERSION`, `DEFAULT_MAX_VERSION`, `DEFAULT_ECDH_CURVE`
</details>
<details>
<summary><strong><code>node:trace_events</code></strong></summary>
Partial compatibility API to unblock modules that inspect tracing state.
- `createTracing({ categories: string[] })` — creates a `Tracing` handle
- `Tracing#enable()` / `Tracing#disable()` — toggles local tracing state
- `Tracing#enabled` / `Tracing#categories` — read tracing configuration
- `getEnabledCategories()` — returns currently enabled categories as a comma-separated string
**Limitation:** No native trace sink; API-surface compatibility only.
</details>
<details>
<summary><strong><code>node:tty</code></strong></summary>
- `isatty`, `ReadStream`, `WriteStream`
</details>
<details>
<summary><strong><code>node:url</code></strong></summary>
- `URL`, `URLSearchParams`
- `parse`, `resolve`, `format` (legacy API)
- `domainToASCII`, `domainToUnicode`
- `fileURLToPath`, `pathToFileURL`, `urlToHttpOptions`
</details>
<details>
<summary><strong><code>node:util</code></strong></summary>
- `sys` / `node:sys` aliases share the util object and support CommonJS and ESM.
- `format`, `inspect`, `deprecate`, `debugLog`, `log`
- Type checks: `isArray`, `isBoolean`, `isNull`, `isNullOrUndefined`, `isNumber`, `isString`, `isSymbol`, `isUndefined`, `isRegExp`, `isObject`, `isDate`, `isError`, `isFunction`, `isPrimitive`, `isBuffer`
- `promisify`, `callbackify`
- `parseEnv`, `styleText`, `getCallSite`, `getCallSites`, `toUSVString`, `_extend`
- `TextEncoder`, `TextDecoder`
</details>
<details>
<summary><strong><code>node:v8</code></strong></summary>
- `getHeapStatistics`, `getHeapSpaceStatistics`, `getHeapSnapshot`, `getHeapCodeStatistics`
- `setFlagsFromString`, `writeHeapSnapshot`, `takeCoverage`, `stopCoverage`
- `serialize`, `deserialize`, `Serializer`, `Deserializer`, `DefaultSerializer`, `DefaultDeserializer`
</details>
<details>
<summary><strong><code>node:vm</code></strong></summary>
- `runInNewContext`, `runInContext`, `runInThisContext`, `createContext`, `isContext`, `compileFunction`
- `Script`, `createScript`
- `SourceTextModule` (experimental, limited `export const`/`export let`/`export var` support)
These contexts are synchronous compatibility contexts within one QuickJS runtime. For asynchronous
jobs in fresh runtimes with structured-clone results, use `wasm-rquickjs:execution`. Neither API is
a security sandbox; both share the enclosing component's capabilities and filesystem access.
</details>
<details>
<summary><strong><code>node:worker_threads</code></strong> (runtime-local messaging)</summary>
MessageChannel and MessagePort work within one runtime. Worker and BroadcastChannel construction
throws immediately. Environment-data APIs are unavailable.
- `isMainThread`, `parentPort`, `workerData`, `threadId`, `resourceLimits`
- `MessagePort`, `MessageChannel`, `receiveMessageOnPort`
- `markAsUncloneable`, `markAsUntransferable`, `isMarkedAsUntransferable`
- `moveMessagePortToContext` (limited compatibility API; no external execution context)
- Unsupported compatibility exports: `Worker`, `BroadcastChannel`,
`getEnvironmentData`, `setEnvironmentData`
</details>
<details>
<summary><strong><code>node:zlib</code></strong></summary>
- **Classes:** `Deflate`, `Inflate`, `Gzip`, `Gunzip`, `DeflateRaw`, `InflateRaw`, `Unzip`, `BrotliCompress`, `BrotliDecompress`
- **Factory:** `createGzip`, `createGunzip`, `createDeflate`, `createInflate`, `createDeflateRaw`, `createInflateRaw`, `createUnzip`, `createBrotliCompress`, `createBrotliDecompress`
- **Async:** `gzip`, `gunzip`, `deflate`, `inflate`, `deflateRaw`, `inflateRaw`, `unzip`, `brotliCompress`, `brotliDecompress`
- **Sync:** `gzipSync`, `gunzipSync`, `deflateSync`, `inflateSync`, `deflateRawSync`, `inflateRawSync`, `unzipSync`, `brotliCompressSync`, `brotliDecompressSync`
- `crc32`, `constants`, `codes`
</details>
#### Internal / Compatibility Modules
<details>
<summary><strong>Additional internal modules</strong></summary>
- `internal/url` — `isURL`
- `internal/http` — `kOutHeaders` symbol
- `base64-js` — `byteLength`, `toByteArray`, `fromByteArray`
- `ieee754` — `read`, `write`
</details>
### Compatibility Reports
- [NPM Library Compatibility Tracker](tests/libraries/libraries.md) — test results for popular npm packages
- [Node.js v22 Compatibility Report](tests/node_compat/report.md) — per-test results for vendored Node.js test suite
### Coming from QuickJS
- Global:
- `parseInt`
- `parseFloat`
- `isNaN`
- `isFinite`
- `quickMicrotask`
- `decodeURI`
- `decodeURIComponent`
- `encodeURI`
- `encodeURIComponent`
- `escape`
- `unescape`
- `Infinity`
- `NaN`
- `undefined`
- `[Symbol.toStringTag]`
- `Object`
- static methods and properties:
- `create`
- `getPrototypeOf`
- `setPrototypeOf`
- `defineProperty`
- `defineProperties`
- `getOwnPropertyNames`
- `getOwnPropertySymbols`
- `groupBy`
- `keys`
- `values`
- `entries`
- `isExtensible`
- `preventExtensions`
- `getOwnPropertyDescriptor`
- `getOwnPropertyDescriptors`
- `is`
- `assign`
- `seal`
- `freeze`
- `isSealed`
- `isFrozen`
- `fromEntries`
- `hasOwn`
- methods and properties:
- `toString`
- `toLocaleString`
- `valueOf`
- `hasOwnProperty`
- `isPrototypeOf`
- `propertyIsEnumerable`
- `__proto__`
- `__defineGetter__`
- `__defineSetter__`
- `__lookupGetter__`
- `__lookupSetter__`
- `Function`
- methods and properties:
- `call`
- `apply`
- `bind`
- `toString`
- `[Symbol.hasInstance]`
- `fileName`
- `lineNumber`
- `columnNumber`
- `Error`
- methods and properties:
- `name`
- `message`
- `toString`
- static methods and properties:
- `isError`
- `captureStackTrace`
- `stackTraceLimit`
- `prepareStackTrace`
- `Generator`
- methods and properties:
- `next`
- `return`
- `throw`
- `[Symbol.toStringTag]`
- static methods and properties:
- `from`
- `Iterator`
- static methods and properties:
- `from`
- methods and properties:
- `drop`
`filter`
`flatMap`
`map`
`take`
`every`
`find`
`forEach`
`some`
`reduce`
`toArray`
`[Symbol.iterator]`
`[Symbol.toStringTag]`
- `Array`
- static methods and properties:
- `isArray`
- `from`
- `of`
- `[Symbol.species]`
- methods and properties:
- `at`
- `with`
- `concat`
- `every`
- `some`
- `forEach`
- `map`
- `filter`
- `reduce`
- `reduceRight`
- `fill`
- `find`
- `findIndex`
- `findLast`
- `findLastIndex`
- `indexOf`
- `lastIndexOf`
- `includes`
- `join`
- `toString`
- `toLocaleString`
- `pop`
- `push`
- `shift`
- `unshift`
- `reverse`
- `toReversed`
- `sort`
- `toSorted`
- `slice`
- `splice`
- `toSpliced`
- `copyWithin`
- `flatMap`
- `flat`
- `values`
- `[Symbol.iterator]`
- `keys`
- `entries`
- `Number`
- static methods and properties:
- `parseInt`
- `parseFloat`
- `isNaN`
- `isFinite`
- `isInteger`
- `isSafeInteger`
- `MAX_VALUE`
- `MIN_VALUE`
- `NaN`
- `NEGATIVE_INFINITY`
- `POSITIVE_INFINITY`
- `EPSILON`
- `MAX_SAFE_INTEGER`
- `MIN_SAFE_INTEGER`
- methods and properties:
- `toExponential`
- `toFixed`
- `toPrecision`
- `toString`
- `toLocaleString`
- `valueOf`
- `Boolean`
- methods and properties:
- `toString`
- `valueOf`
- `String`
- static methods and properties:
- `fromCharCode`
- `fromCodePoint`
- `raw`
- methods and properties:
- `length`
- `at`
- `charCodeAt`
- `charAt`
- `concat`
- `codePointAt`
- `isWellFormed`
- `toWellFormed`
- `indexOf`
- `lastIndexOf`
- `includes`
- `endsWith`
- `startsWith`
- `match`
- `matchAll`
- `search`
- `split`
- `substring`
- `substr`
- `slice`
- `repeat`
- `replace`
- `replaceAll`
- `padEnd`
- `padStart`
- `trim`
- `trimEnd`
- `trimRight`
- `trimStart`
- `trimLeft`
- `toString`
- `valueOf`
- `localeCompare`
- `normalize`
- `toLowerCase`
- `toUpperCase`
- `toLocaleLowerCase`
- `toLocaleUpperCase`
- `[Symbol.iterator]`
- `anchor`
- `big`
- `blink`
- `bold`
- `fixed`
- `fontcolor`
- `fontsize`
- `italics`
- `link`
- `small`
- `strike`
- `sub`
- `sup`
- `Symbol`
- static methods and properties:
- `for`
- `keyFor`
- methods and properties:
- `toString`
- `valueOf`
- `description`
- `[Symbol.toPrimitive]`
- `[Symbol.toStringTag]`
- `Map`
- static methods and properties:
- `groupBy`
- `[Symbol.species]`
- methods and properties:
- `set`
- `get`
- `has`
- `delete`
- `clear`
- `size`
- `forEach`
- `values`
- `keys`
- `entries`
- `[Symbol.iterator]`
- `[Symbol.toStringTag]`
- `Set`
- static methods and properties:
- `[Symbol.species]`
- methods and properties:
- `add`
- `has`
- `delete`
- `clear`
- `size`
- `forEach`
- `isDisjointFrom`
- `isSubsetOf`
- `isSupersetOf`
- `intersection`
- `difference`
- `symmetricDifference`
- `union`
- `values`
- `keys`
- `[Symbol.iterator]`
- `entries`
- `[Symbol.toStringTag]`
- `WeakMap`
- methods and properties:
- `set`
- `get`
- `has`
- `delete`
- `[Symbol.toStringTag]`
- `WeakSet`
- methods and properties:
- `add`
- `has`
- `delete`
- `[Symbol.toStringTag]`
- `GeneratorFunction`
- methods and properties:
- `[Symbol.toStringTag]`
- `Math`
- static methods and properties:
- `min`
- `max`
- `abs`
- `floor`
- `ceil`
- `round`
- `sqrt`
- `acos`
- `asin`
- `atan`
- `atan2`
- `cos`
- `exp`
- `log`
- `pow`
- `sin`
- `tan`
- `trunc`
- `sign`
- `cosh`
- `sinh`
- `tanh`
- `acosh`
- `asinh`
- `atanh`
- `expm1`
- `log1p`
- `log2`
- `log10`
- `cbrt`
- `hypot`
- `random`
- `f16round`
- `fround`
- `imul`
- `clz32`
- `sumPrecise`
- `[Symbol.toStringTag]`
- `E`
- `LN10`
- `LN2`
- `LOG2E`
- `LOG10E`
- `PI`
- `SQRT1_2`
- `SQRT2`
- `Reflect`
- static methods and properties:
- `apply`
- `construct`
- `defineProperty`
- `deleteProperty`
- `get`
- `getOwnPropertyDescriptor`
- `getPrototypeOf`
- `has`
- `isExtensible`
- `ownKeys`
- `preventExtensions`
- `set`
- `setPrototypeOf`
- `[Symbol.toStringTag]`
- `RegExp`
- static methods and properties:
- `escape`
- `[Symbol.species]`
- methods and properties:
- `flags`
- `source`
- `global`
- `ignoreCase`
- `multiline`
- `dotAll`
- `unicode`
- `unicodeSets`
- `sticky`
- `hasIndices`
- `exec`
- `compile`
- `test`
- `toString`
- `[Symbol.replace]`
- `[Symbol.match]`
- `[Symbol.matchAll]`
- `[Symbol.search]`
- `[Symbol.split]`
- `JSON`
- static methods and properties:
- `parse`
- `stringify`
- `[Symbol.toStringTag]`
- `Promise`
- static methods and properties:
- `resolve`
- `reject`
- `all`
- `allSettled`
- `any`
- `try`
- `race`
- `withResolvers`
- `[Symbol.species]`
- methods and properties:
- `then`
- `catch`
- `finally`
- `[Symbol.toStringTag]`
- `AsyncFunction`
- methods and properties:
- `[Symbol.toStringTag]`
- `AsyncIterator`
- methods and properties:
- `next`
- `return`
- `throw`
- `AsyncGeneratorFunction`
- methods and properties:
- `[Symbol.toStringTag]`
- `AsyncGenerator`
- methods and properties:
- `next`
- `return`
- `throw`
- `[Symbol.toStringTag]`
- `Date`
- static methods and properties:
- `now`
- `parse`
- `UTC`
- methods and properties:
- `valueOf`
- `toString`
- `[Symbol.toPrimitive]`
- `toUTCString`
- `toGMTString`
- `toISOString`
- `toDateString`
- `toTimeString`
- `toLocaleString`
- `toLocaleDateString`
- `toLocaleTimeString`
- `getTimezoneOffset`
- `getTime`
- `getYear`
- `getFullYear`
- `getUTCFullYear`
- `getMonth`
- `getUTCMonth`
- `getDate`
- `getUTCDate`
- `getHours`
- `getUTCHours`
- `getMinutes`
- `getUTCMinutes`
- `getSeconds`
- `getUTCSeconds`
- `getMilliseconds`
- `getUTCMilliseconds`
- `getDay`
- `getUTCDay`
- `setTime`
- `setMilliseconds`
- `setUTCMilliseconds`
- `setSeconds`
- `setUTCSeconds`
- `setMinutes`
- `setUTCMinutes`
- `setHours`
- `setUTCHours`
- `setDate`
- `setUTCDate`
- `setMonth`
- `setUTCMonth`
- `setYear`
- `setFullYear`
- `setUTCFullYear`
- `toJSON`
- `BigInt`
- static methods and properties:
- `asIntN`
- `asUintN`
- methods and properties:
- `toString`
- `valueOf`
- `[Symbol.toStringTag]`
- `ArrayBuffer`
- static methods and properties:
- `isView`
- `[Symbol.species`
- methods and properties:
- `byteLength`
- `maxByteLength`
- `resizeable`
- `detached`
- `resize`
- `slice`
- `transfer`
- `transferToFixedLength`
- `[Symbol.toStringTag]`
- `SharedArrayBuffer`
- static methods and properties:
- `[Symbol.species]`
- methods and properties:
- `byteLength`
- `maxByteLength`
- `growable`
- `grow`
- `slice`
- `[Symbol.toStringTag]`
- Typed arrays (`Int8Array`, `Uint8Array`, `Int16Array`, `Uint16Array`, `Int32Array`, `Uint32Array`, `BigInt64Array`,
`BigUint64Array`, `Float32Array`, `Float64Array`, `Float16Array`)
- static methods and properties:
- `from`
- `of`
- `[Symbol.species]`
- methods and properties:
- `length`
- `at`
- `with`
- `buffer`
- `byteLength`
- `set`
- `byteOffset`
- `values`
- `[Symbol.iterator]`
- `keys`
- `entries`
- `[Symbol.toStringTag]`
- `copyWithin`
- `every`
- `some`
- `forEach`
- `map`
- `filter`
- `reduce`
- `reduceRight`
- `fill`
- `find`
- `findIndex`
- `findLast`
- `findLastIndex`
- `reverse`
- `toReversed`
- `slice`
- `subarray`
- `sort`
- `toSorted`
- `join`
- `toLocaleString`
- `indexOf`
- `lastIndexOf`
- `includes`
- `DataView`
- methods and properties:
- `buffer`
- `byteLength`
- `byteOffset`
- `getInt8`
- `getUint8`
- `getInt16`
- `getUint16`
- `getInt32`
- `getUint32`
- `getBigInt64`
- `getBigUint64`
- `getFloat16`
- `getFloat32`
- `getFloat64`
- `setInt8`
- `setUint8`
- `setInt16`
- `setUint16`
- `setInt32`
- `setUint32`
- `setBigInt64`
- `setBigUint64`
- `setFloat16`
- `setFloat32`
- `setFloat64`
- `[Symbol.toStringTag]`
- `Atomics`
- static methods and properties:
- `add`
- `and`
- `or`
- `sub`
- `xor`
- `exchange`
- `compareExchange`
- `load`
- `store`
- `isLockFree`
- `pause`
- `wait`
- `notify`
- `[Symbol.toStringTag]`
- `Performance`
- methods and properties:
- `now`
- `WeakRef`
- methods and properties:
- `deref`
- `[Symbol.toStringTag]`
- `FinalizationRegistry`
- methods and properties:
- `register`
- `unregister`
- `[Symbol.toStringTag]`
- `Callsite`
- methods and properties:
-`isNative`
- `getFileName`
- `getFunction`
- `getFunctionName`
- `getColumnNumber`
- `getLineNumber`
- `[Symbol.toStringTag]`
- `Proxy`
## Developing
There are a few important things to keep in mind when working on the project:
- The `skeleton` crate can be opened and compiled separately when working on the APIs provided for JavaScript.
Unfortunately we cannot use the `Cargo.toml` file name in it because that breaks Rust packaging - so before working on
it, it has to be renamed to `Cargo.toml`
and before committing back it has to be renamed back to `Cargo.toml_`.
- If the `skeleton` crate was compiled for testing, and then `wasm-rquickjs` is compiled, the `include_dir!` macro is
embedding everything from the `skeleton` directory **including** the `target` directory, resulting in slow compilation
times and huge resulting binaries. Use the `cleanup-skeleton.sh` script to quickly remove the `target` directory from
the `skeleton` crate.
- Run `tools/check-skeleton-clippy.sh` to lint the supported Preview 2 and Preview 3 skeleton feature matrix. The helper
temporarily symlinks `Cargo.toml` to the checked-in `Cargo.toml_`, directs output to the workspace-root `target/` cache,
preserves that cache, and removes the symlink on exit. If a skeleton-local `target/` already exists, continue to use
`cleanup-skeleton.sh` before embedding the skeleton.
- Runtime and node compatibility tests support opt-in caches for faster local iteration. Select Node with
`nvm use 22.14.0`, then use the artifact and Wasmtime caches explicitly:
```sh
WASM_RQUICKJS_TEST_ARTIFACT_CACHE=1 \
WASM_RQUICKJS_TEST_WASMTIME_CACHE=1 \
cargo test --test runtime --features use-golem-wasmtime -- module_resolution --report-time
```
These caches may reuse generated wrapper, optimized component, and compiled Wasmtime artifacts. Every test still
creates fresh wasm memory, a fresh Wasmtime `Store` and component instance, a fresh WASI context and temporary
directory, and fresh QuickJS runtime state. Do not group compatibility cases into one wasm instance.
`WASM_RQUICKJS_TEST_DROP_CACHE=1` forces generated artifacts to be rebuilt and bypasses Wasmtime's filesystem cache.
`WASM_RQUICKJS_TEST_UNOPTIMIZED=1` skips Wizer pre-initialization for a shorter compile/test loop.
`WASM_RQUICKJS_TEST_PREPARED_COMPONENT_CACHE=1` reuses immutable prepared `Engine`/`Linker`/`Component` state on the
normal `TestInstance::new` path.
For skeleton development, select a local performance profile:
```sh
tools/dev-test.sh p2 fast-start runtime <exact_test_filter>
tools/dev-test.sh p2 fast-run node_compat <test_filter>
tools/dev-test.sh p2 fast-run node_compat <test_filter> --test-threads 4
tools/dev-test.sh p3 standard runtime ':tag:group3'
```
`fast-start` skips Wizer and uses one worker to minimize latency to the first result. `fast-run` runs Wizer,
precompiles a changed component once before parallel workers start, reuses prepared components, and uses eight
workers. Both accelerated profiles read the skeleton externally, reuse generated artifacts and Wasmtime's filesystem
cache, and use locked Cargo builds. `standard` uses the embedded skeleton with optional caches and parent
precompilation disabled, normal Wizer optimization, and default test-runner behavior.
`fast-run` defaults to eight workers and `fast-start` to one; pass test-r's `--test-threads N` after the filter to
override either default. The eight-worker default was measured on a 14-core Apple M3 Max MacBook Pro (10 performance
cores, 4 efficiency cores, 36 GB RAM, macOS 26.5.1). Performance comparisons must report preparation time, test
execution time, and total wall time separately: precompilation makes the execution phase much faster but adds
preparation that is only recovered by a sufficiently large or repeated filter.
## Acknowledgements
The builtin JS modules are based on the work of several other projects:
- [abortcontroller-polyfill](https://github.com/mo/abortcontroller-polyfill)
- [base64-js](https://github.com/beatgammit/base64-js)
- [buffer](https://github.com/feross/buffer)
- [consoleTable](https://github.com/ronnyKJ/consoleTable)
- [eventemitter3](https://github.com/primus/eventemitter3)
- [fetch-blob](https://github.com/node-fetch/fetch-blob)
- [fetch.js](https://github.com/JakeChampion/fetch/blob/main/fetch.js)
- [FormData](https://github.com/jimmywarting/FormData)
- [ieee754](https://github.com/feross/ieee754)
- [url-search-params-polyfill](https://github.com/jerrybendy/url-search-params-polyfill)
- [structured-clone](https://github.com/ungap/structured-clone)
- [wasmedge-quickjs](https://github.com/second-state/wasmedge-quickjs)
- [web-streams-polyfill](https://github.com/MattiasBuelens/web-streams-polyfill)