4.4 KiB
Bridge factory
(no submodules)
Purpose
createSandboxedWasmPluginModule(options) is the public entry point of @suaveplan/api-sandbox-wasm-host-bridge. It dispatches on the supplied abiVariant: the default ("direct-i32-i64") routes to buildDirectInstall in bridge-direct.ts, which validates the six __suaveplan_* ABI exports + memory and bridges requests through linear memory; the additive variant ("wasi-stdio") routes to buildWasiStdioInstall in bridge-wasi-stdio.ts, which validates the WASI snapshot-preview1 import surface + _start + memory and bridges requests through length-prefixed JSON envelopes on fd 0 / fd 1. Both paths return the same SandboxedWasmPluginInstall shape so the host's installPlugin call is variant-agnostic. The returned bundle carries the module, the bridged PipelineRoute[], the idempotent lifecycle, and the underlying sandbox handle.
Features
- Single bridge-init validation: the ABI is checked, routes are discovered, and instruments are wired up in one synchronous block; failures surface BEFORE the host sees the install bundle.
- Per-route closures own the full linear-memory round-trip: alloc → write → handle_request → unpack → read → free → JSON-decode → host
Response. - Trap classification:
__suaveplan_handle_requestfaults surface asSAND_WASM_BRIDGE.HANDLER_TRAPPEDwithcontext.trapKindderived from the runtime error message. - Empty-response detection: a
(0, 0)packed return value from the guest is treated as a marshal failure (reason: "empty_response") rather than as a silent success. - Capability denial passthrough: a
SerializedResponse.errorCode === "SAND_PERM.DENIED"is re-thrown as the canonicalSandboxPermissionError; other error codes surface asSAND_WASM_BRIDGE.CAPABILITY_DENIEDcarrying the sandbox's original code incontext.sandboxErrorCode. - Lifecycle stop chains through to
WasmSandboxHandle.dispose()so ahost.uninstallPluginalways tears down the underlying sandbox.
Usage: Basic
import { createWasmSandbox } from "@suaveplan/api-sandbox-wasm";
import { createSandboxedWasmPluginModule } from "@suaveplan/api-sandbox-wasm-host-bridge";
const sandbox = await createWasmSandbox({
pluginName: "example",
policy,
capabilities,
checker,
wasmBytes,
});
const install = createSandboxedWasmPluginModule({ sandbox, manifest });
await host.installPlugin({
module: install.module,
manifest: install.manifest,
manifestHash,
specifier: "example@0.1.0",
routes: install.routes,
});
Usage: Advanced
import { createSandboxWasmBridgeInstruments } from "@suaveplan/api-sandbox-wasm-host-bridge/instruments";
import { createSandboxedWasmPluginModule } from "@suaveplan/api-sandbox-wasm-host-bridge/bridge";
const instruments = createSandboxWasmBridgeInstruments({
pluginName: manifest.name,
meterProvider: customProvider,
});
const install = createSandboxedWasmPluginModule({
sandbox,
manifest,
instruments,
});
install.module.start();
try {
await runRequests(install.routes);
} finally {
install.module.stop();
}
API Reference
createSandboxedWasmPluginModule(options): SandboxedWasmPluginInstall— throwsSAND_WASM_BRIDGE.INVALID_PLUGIN_SURFACEon ABI mismatch.CreateSandboxedWasmPluginModuleOptions—{ sandbox, manifest, instruments? }.SandboxedWasmPluginInstall—{ module, manifest, routes, lifecycle, sandbox }.SandboxedWasmPluginModule— plugin-module facade withname,version,requires,isolation: "sandboxed",trustLevel,start(),stop().
Implementation Notes
The per-route closure is async because reading the host's Request.body requires awaiting the arrayBuffer() promise; the guest call itself is synchronous (WASM exports are synchronous unless the module uses the JS Promise Integration proposal, which this bridge does not target). Base64-url-no-pad encoding/decoding is inlined rather than delegated to a foundation utility because the bridge must boot before any optional foundation polyfill has a chance to load — keeping the codec self-contained avoids a circular load order with @suaveplan/api-host. Object.freeze is applied to the routes array AND each route's options / params / statusMap records so the host's downstream code cannot mutate the bridge's view by accident.