Files
skills/docs/api-stack/api/api-sandbox-wasm-host-bridge/src/wasi-stdio/wasi-stdio-imports.md
2026-05-29 08:20:56 +01:00

3.8 KiB

wasi-stdio-imports

Purpose

Builds the wasi_snapshot_preview1 imports table the WASI-stdio runner threads into WebAssembly.instantiate. Centralises the WASI shim implementations so the runner stays focused on protocol orchestration; testing each WASI hook in isolation becomes a unit test against this module rather than an integration test that needs a hand-encoded fixture per hook.

The five hooks the bridge's ABI validator requires (fd_read, fd_write, environ_get, environ_sizes_get, proc_exit) are always present. Additional common WASI hooks (fd_close, clock_time_get, random_get, path_open, fd_seek, fd_fdstat_get, fd_prestat_get, fd_prestat_dir_name) are wired with safe defaults so a guest that calls them does not abort instantiation. The defaults are intentionally restrictive — path_open returns ENOENT (2), fd_prestat_get returns BADF (8) — to keep the wasi-stdio variant focused on its stdin/stdout protocol surface.

Features

  • Single-call builder; returns a frozen-shape record ready to feed into WebAssembly.Instance.
  • memoryRef thunk pattern — callers wire the imports BEFORE the instance is constructed; the thunk resolves to the bound memory once the runner has called bindMemory.
  • random_get uses the host's crypto.getRandomValues, the same source as @suaveplan/api-sandbox-wasm's default snapshot.
  • clock_time_get returns wall-clock nanoseconds (Date.now() * 1_000_000). Test consumers wanting a deterministic clock build their own imports table.

Usage: Basic

import { buildWasiStdioImports } from
    "@suaveplan/api-sandbox-wasm-host-bridge/wasi-stdio";
import { createWasiStdioStreams } from
    "@suaveplan/api-sandbox-wasm-host-bridge/wasi-stdio";

const streams = createWasiStdioStreams({ pluginName: "ex" });
let memory: WebAssembly.Memory | undefined;
const imports = buildWasiStdioImports({
    streams,
    memoryRef: () => {
        if (memory === undefined) throw new Error("memory not bound");
        return memory;
    },
});
void imports;

Usage: Advanced

import { buildWasiStdioImports } from
    "@suaveplan/api-sandbox-wasm-host-bridge/wasi-stdio";

declare const streams: import("./wasi-stdio-streams.js").WasiStdioStreams;
declare const memory: WebAssembly.Memory;

const imports = buildWasiStdioImports({
    streams,
    memoryRef: () => memory,
});

// Verify the shape — every guarded WASI hook is present.
for (const name of [
    "fd_read",
    "fd_write",
    "environ_get",
    "environ_sizes_get",
    "proc_exit",
    "fd_close",
    "clock_time_get",
    "random_get",
    "path_open",
    "fd_seek",
] as const) {
    if (typeof imports[name] !== "function") {
        throw new Error(`missing import ${name}`);
    }
}

API Reference

buildWasiStdioImports({ streams, memoryRef })

Returns a record keyed by WASI snapshot-preview1 function name. Every entry is a function with the WASI ABI signature. Pass the returned record under the wasi_snapshot_preview1 namespace when instantiating a module.

Implementation Notes

  • The (...args: never[]) => number | undefined signature is the union of proc_exit (void return) with every other entry (number return). The as never cast at each call site is structurally safe — WASI host imports are typed at the JS API by argument arity, not return shape.
  • path_open returns 2 (ENOENT) so a guest's path_open request fails synchronously instead of trapping. Operators wanting real preopens compose extraImports on the runner.
  • fd_prestat_get returns 8 (BADF) — the host has zero preopened fds beyond stdin/stdout/stderr. The matching fd_prestat_dir_name is wired for symmetry; both return BADF.
  • clock_time_get writes to the supplied timePtr then returns 0; the value is little-endian as required by the WASI ABI.