3.7 KiB
3.7 KiB
wasi-stdio-lifecycle
Purpose
Idempotent start / dispose forwarder for the WASI-stdio variant.
Each call sends one lifecycle envelope ({kind:"start"} or
{kind:"dispose"}) to the guest and verifies the reply is
{kind:"ok"}. Wraps the host's IdempotentLifecycle shape so a
caller swapping between variants sees no API divergence.
The two methods are guarded by once-flags so the host's "reload twice"
or "dispose-then-redispose" patterns never double-invoke the guest's
lifecycle hooks. dispose() ALWAYS chains through to the runner's
dispose, even when the guest's reply was unexpected — the host must
never leak the underlying instance.
Features
start()/dispose()— idempotent forwarders matching the direct variant'sIdempotentLifecycleinterface, so the bridge factory produces a single shape across variants.started()/disposed()— getters for the once-flags, used by the conformance harness invariant.- Plugin-originated
{kind:"error"}replies surface asSAND_WASM_BRIDGE.LIFECYCLE_FAILEDcarrying the plugin's code. - Unexpected reply kinds (e.g.
{kind:"response"}to astart) surface asLIFECYCLE_FAILEDwithcontext.expected = "ok"andcontext.received = "<kind>".
Usage: Basic
import {
createWasiStdioLifecycle,
createWasiStdioRunner,
} from "@suaveplan/api-sandbox-wasm-host-bridge/wasi-stdio";
declare const module: WebAssembly.Module;
const runner = createWasiStdioRunner({ pluginName: "ex", wasmModule: module });
const lifecycle = createWasiStdioLifecycle({ runner, pluginName: "ex" });
lifecycle.start();
lifecycle.dispose();
Usage: Advanced
import { createWasiStdioLifecycle } from
"@suaveplan/api-sandbox-wasm-host-bridge/wasi-stdio";
import {
SAND_WASM_BRIDGE_CODES,
SandboxWasmBridgeError,
} from "@suaveplan/api-sandbox-wasm-host-bridge/error-codes";
declare const runner: import("./wasi-stdio-runner.js").WasiStdioRunner;
const lifecycle = createWasiStdioLifecycle({
runner,
pluginName: "billing",
});
try {
lifecycle.start();
// Repeated start is a no-op — host's reload boundary safely re-calls it.
lifecycle.start();
} catch (err) {
if (
err instanceof SandboxWasmBridgeError &&
err.code === SAND_WASM_BRIDGE_CODES.LIFECYCLE_FAILED
) {
// Guest reported a fatal init failure — dispose the runner.
}
throw err;
} finally {
lifecycle.dispose();
}
API Reference
createWasiStdioLifecycle(deps): WasiStdioLifecycle
deps.runner: WasiStdioRunner— runner the lifecycle drives.deps.pluginName: string— error-context label.
Returns WasiStdioLifecycle:
start(): void— first call sends{kind:"start"}and verifies{kind:"ok"}. Subsequent calls are no-ops.dispose(): void— first call sends{kind:"dispose"}then chains torunner.dispose(). Subsequent calls are no-ops. The chain runs even if the dispose envelope reply was unexpected.started(): boolean/disposed(): boolean— once-flag readers.
Implementation Notes
- The once-flag for each method is set BEFORE the envelope is sent.
This guarantees that a trap during the envelope round-trip does not
leave the flag flipped back to
false, which would invite a second_startinvocation on the guest. dispose()always callsrunner.dispose()in itsfinallyblock. An unexpected reply during dispose is reported via the thrownSandboxWasmBridgeErrorAND the underlying instance is still torn down. This matches the direct variant's "never leak the handle" guarantee.- The plugin's own
errorCodefrom a{kind:"error"}reply becomes the thrown error'scode. Operators see the actual root cause rather than a generic bridge-namespaced label.