# 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_request` faults surface as `SAND_WASM_BRIDGE.HANDLER_TRAPPED` with `context.trapKind` derived 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 canonical `SandboxPermissionError`; other error codes surface as `SAND_WASM_BRIDGE.CAPABILITY_DENIED` carrying the sandbox's original code in `context.sandboxErrorCode`. - Lifecycle stop chains through to `WasmSandboxHandle.dispose()` so a `host.uninstallPlugin` always tears down the underlying sandbox. ## Usage: Basic ```ts 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 ```ts 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` — throws `SAND_WASM_BRIDGE.INVALID_PLUGIN_SURFACE` on ABI mismatch. - `CreateSandboxedWasmPluginModuleOptions` — `{ sandbox, manifest, instruments? }`. - `SandboxedWasmPluginInstall` — `{ module, manifest, routes, lifecycle, sandbox }`. - `SandboxedWasmPluginModule` — plugin-module facade with `name`, `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.