See live demo and browser demo sources
For most WASI components, instantiateWasiComponent is the simplest way:
import { instantiateWasiComponent } from '@pavelsavara/jsco';
// Works with both WASIp2 and WASIp3 components (auto-detected)
const instance = await instantiateWasiComponent('./my-component.wasm');
await instance.exports['wasi:cli/run@0.3.0'].run();For advanced use cases (custom imports, non-WASI components), use createComponent directly:
import { createComponent } from '@pavelsavara/jsco';
const component = await createComponent('./my-component.wasm');
const instance = await component.instantiate({
'my:app/logger@1.0.0': { log: console.log },
});
await instance.exports['my:app/greeter@1.0.0'].run({ name: 'World' });import { instantiateWasiComponent } from '@pavelsavara/jsco';
// Run a CLI component with real filesystem access
const instance = await instantiateWasiComponent('./my-cli-component.wasm', {
args: ['input.txt'],
env: [['HOME', '/home/user']],
mounts: [{ hostPath: './data', guestPath: '/data' }],
});
await instance.exports['wasi:cli/run@0.3.0'].run();To serve an HTTP handler component:
import { createComponent, loadWasiP3Host, loadWasiP3Serve } from '@pavelsavara/jsco';
const { createWasiP3Host } = await loadWasiP3Host();
const host = createWasiP3Host();
const component = await createComponent('./my-http-component.wasm');
const instance = await component.instantiate(host);
const handler = instance.exports['wasi:http/incoming-handler@0.2.0'];
const { serve } = await loadWasiP3Serve();
const handle = await serve(handler, { network: { httpRequestTimeoutMs: 30_000 } });
console.log(`Listening on port ${handle.port}`);createWasiP3Host() accepts an optional HostConfig:
| Option | Type | Description |
|---|---|---|
env |
[string, string][] |
Environment variables |
args |
string[] |
Command-line arguments |
cwd |
string |
Initial working directory |
stdin |
ReadableStream<Uint8Array> |
Stdin input stream |
stdout |
WritableStream<Uint8Array> |
Stdout output stream |
stderr |
WritableStream<Uint8Array> |
Stderr output stream |
fs |
Map<string, Uint8Array | string> |
In-memory VFS files |
mounts |
MountConfig[] |
Host filesystem mounts (Node.js only) |
network |
NetworkConfig |
Network limits and timeouts |
limits |
AllocationLimits |
Allocation and size limits |
enabledInterfaces |
string[] |
Whitelist of WASI interface prefixes |
Each WASI interface is implemented for one or more preview generations
(P1 = wasi_snapshot_preview1, P2 = wasi:*@0.2.x, P3 = wasi:*@0.3.x),
and may behave differently in the browser vs. Node.js.
| Interface | Browser | Node.js | P3 | P2 | P1 |
|---|---|---|---|---|---|
cli/* (environment, exit, stdio) |
✅ | ✅ | ✅ | ✅ | ✅ via adapter |
clocks/* (monotonic, system, timezone) |
✅ | ✅ | ✅ | ✅ | ✅ via adapter |
random/* (secure, insecure, seed) |
✅ | ✅ | ✅ | ✅ | ✅ via adapter |
filesystem/* (VFS, preopens) |
✅ in-memory | ✅ + real mounts | ✅ | ✅ | ✅ via adapter |
http/client (Fetch API) |
✅ | ✅ | ✅ | ✅ | — |
http/handler (server) |
❌ not-supported | ✅ via serve() |
✅ | ✅ | — |
http/handler (middleware / chain) |
❌ not-supported | ✅ via linkHandler() |
✅ | — | — |
sockets/* (TCP, UDP, DNS) |
❌ not-supported | ✅ | ✅ | ✅ | — |
P1 modules are served by the wasip1-via-wasip3 adapter (wasi_snapshot_preview1
shim layered on top of the P3 host); same configuration, same VFS/mount/network
options apply.
jsco follows a command-based CLI similar to wasmtime.
If no subcommand is provided, run is used by default.
# Run a component (default command)
jsco run ./integration-tests/hello-p2-world-wat/hello.wasm
# or without the subcommand
jsco ./integration-tests/hello-p2-world-wat/hello.wasm
# Serve an HTTP proxy component
jsco serve --addr 0.0.0.0:8080 ./my-http-component.wasm
# Show help
jsco --help
jsco run --help
jsco serve --helpWhen installed locally or via npx:
npx @pavelsavara/jsco run ./integration-tests/hello-p2-world-wat/hello.wasm| Option | Default | Description |
|---|---|---|
--dir <HOST[::GUEST[::ro]]> |
— | Mount a host directory into the guest. --dir . maps cwd, --dir /data::/mnt remaps. |
--env <NAME[=VAL]> |
— | Set (--env FOO=bar) or inherit (--env FOO) an environment variable. |
--env-inherit |
— | Inherit all host environment variables. |
--cwd <PATH> |
— | Set the working directory for the component. |
--enable <PREFIX> |
all | Enable only WASI interfaces matching prefix (e.g. --enable wasi:http). |
--use-number-for-int64 |
false |
Use number instead of bigint for i64. |
--no-jspi |
false |
Disable JSPI wrapping of exports. |
--validate-types |
true |
Validate export/import type annotations. |
| Option | Default | Description |
|---|---|---|
--addr <HOST:PORT> |
0.0.0.0:8080 |
Socket address for the HTTP server to bind to. |
DOS-mitigation budgets enforced by the runtime. Each can be tuned via CLI flag or programmatically via instantiate(..., { limits: { ... } }).
| Option | Default | Description |
|---|---|---|
--max-allocation-size <N> |
16777216 |
Max single allocation (bytes). |
--max-handles <N> |
10000 |
Max live resource handles per table. |
--max-path-length <N> |
4096 |
Max filesystem path length (bytes). |
--max-memory-bytes <N> |
268435456 |
Max WASM linear-memory size (bytes); 0 disables. |
--max-canon-ops-without-yield <N> |
1000000 |
Max canon built-in calls between JSPI yields; 0 disables. Mitigates stream.read → stream.cancel-read spin DOS. |
--max-blocking-time-ms <N> |
0 (off) |
Max ms any single JSPI suspension may block. 0 disables. Recommended for CI: 10000. Off by default so legitimately slow host I/O isn't killed. |
--max-heap-growth-per-yield <N> |
0 (off) |
Max host heap growth (bytes) between JSPI yields; 0 disables. Catches host-side state DOS that stays inside --max-canon-ops-without-yield. |
HTTP/socket budgets enforced by the host. Run jsco run --help for the full list.
| Option | Default | Description |
|---|---|---|
--max-http-body-bytes <N> |
host default | Max HTTP body size in bytes. |
--max-http-headers-bytes <N> |
host default | Max HTTP headers size in bytes. |
--socket-buffer-bytes <N> |
host default | Per-connection socket buffer in bytes. |
--max-tcp-pending <N> |
host default | Max pending TCP connections. |
--tcp-idle-timeout-ms <N> |
host default | TCP idle timeout (ms). |
--http-request-timeout-ms <N> |
host default | HTTP request timeout (ms). |
--max-udp-datagrams <N> |
host default | Max queued UDP datagrams. |
--dns-timeout-ms <N> |
host default | DNS lookup timeout (ms). |
--max-concurrent-dns <N> |
host default | Max concurrent DNS lookups. |
--max-http-connections <N> |
host default | Max concurrent HTTP server connections. |
--max-request-url-bytes <N> |
host default | Max request URL length (bytes). |
--http-headers-timeout-ms <N> |
host default | Slowloris protection: headers timeout (ms). |
--http-keep-alive-timeout-ms <N> |
host default | HTTP keep-alive timeout (ms). |
- browser polyfill for running WASM/WASI components.
- streaming parser of binary WIT
- streaming compilation of WASM core modules during .wasm file download
- in-the-browser creation of instances and necessary JavaScript interop
- keep download size small enough to be practical for browser use
- to provide host which could do the binding in the browser
- browsers currently don't implement built-in WASM component model host
- because independent implementation will help the WASM/WIT/WASI to make progress
- JCO is great alternative, really.
- But it is too large to use as dynamic host, because download size matters to browser folks.
- When you have all your components available at dev machine, JCO transpiler could be better choice.
- parser: read binary WIT to produce model of the component, it's sub components, modules and types
- compile modules via Browser API
WebAssembly.compileStreaming - resolver: resolve dependencies, create instances, bind it together.
- JS binding: for component's imports and exports
- just JS at runtime (no rust dependency)
- TypeScript, RollupJS, rust as dev time dependencies
🚧 Preview quality 🚧
The WASIp1/p2/p3 hosts are implemented and pass an extensive test suite, but the API surface is not yet stable and behavior may change between preview releases. Not recommended for production use yet. Also this is one man + 🤖 show, come and help with testing.
See ./CHANGELOG.md for release notes and ./TODO.md for the roadmap. Contributors are welcome!
See ./jspi.md for more details about JSPI - synchronous calls to JS APIs which are blocking, like I/O.
This project is licensed under the Apache License, Version 2.0, with the LLVM exception. See LICENSE for details, and THIRD-PARTY-NOTICES.TXT for attribution of code adapted from upstream projects.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.