Initial Commit
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
# `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's `IdempotentLifecycle` interface, 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 as
|
||||
`SAND_WASM_BRIDGE.LIFECYCLE_FAILED` carrying the plugin's code.
|
||||
- Unexpected reply kinds (e.g. `{kind:"response"}` to a `start`)
|
||||
surface as `LIFECYCLE_FAILED` with `context.expected = "ok"` and
|
||||
`context.received = "<kind>"`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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
|
||||
to `runner.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 `_start` invocation on the guest.
|
||||
- `dispose()` always calls `runner.dispose()` in its `finally` block.
|
||||
An unexpected reply during dispose is reported via the thrown
|
||||
`SandboxWasmBridgeError` AND the underlying instance is still torn
|
||||
down. This matches the direct variant's "never leak the handle"
|
||||
guarantee.
|
||||
- The plugin's own `errorCode` from a `{kind:"error"}` reply becomes
|
||||
the thrown error's `code`. Operators see the actual root cause
|
||||
rather than a generic bridge-namespaced label.
|
||||
Reference in New Issue
Block a user