# bridge _(no submodules)_ ## Purpose This module is the package's flagship factory: `createSandboxedVmPluginModule({ sandbox, manifest, registry })` → `BridgedPlugin` — a frozen object whose `module` is a `PluginModule`-shaped lifecycle bundle and whose `routes` is a materialised `PipelineRoute[]` ready to hand to `host.installPlugin({ module, manifest, routes, ... })`. The factory drives the underlying sandbox's `start()` through the idempotent lifecycle forwarder, asserts the sandbox registered at least one route via the `bridge.registerRoute` capability, and wraps every registered handler in a marshal-invoke-unmarshal closure so the host's request pipeline can dispatch into the sandboxed plugin as if it were a regular trusted plugin. ## Features - Lifecycle delegation — `module.start()`, `module.stop()`, `module.uninstall()` thread through the lifecycle forwarder so the host's lifecycle driver sees consistent idempotent semantics. - Route materialisation — every sandbox registration becomes a frozen `PipelineRoute` with the plugin id, method (already upper-cased), path, options (defaulted, overridable), wrapped handler, empty params, and forwarded status map. - Marshal closure per route — host `Request` → `SerializedRequest` → sandbox handler → `SerializedResponse` → host `Response`. - Empty-registry guard — sandboxes that registered no routes raise `INVALID_PLUGIN_SURFACE` so the host never mounts an empty plugin. - OTel attribution — each step (marshal, handler invocation, unmarshal) increments the relevant counter on the bridge's OTel bundle. - Cause-chain preservation — sandbox throws surface as `HANDLER_REJECTED` with the original error preserved as `cause`. ## Usage: Basic ```ts import { createSandboxedVmPluginModule, createBridgeRouteRegistry } from "@suaveplan/api-sandbox-vm-host-bridge"; import { createVmSandbox } from "@suaveplan/api-sandbox-vm"; const registry = createBridgeRouteRegistry(); const sandbox = createVmSandbox({ pluginId: "demo", pluginModule: { kind: "source", identifier: "demo.mjs", source }, checker, proxies: { fs, fetch, capabilities: { bridge: registry.capability } }, logger, }); const bridged = await createSandboxedVmPluginModule({ sandbox, manifest, registry, }); await host.installPlugin({ module: bridged.module, manifest, manifestHash, specifier, routes: bridged.routes, }); ``` ## Usage: Advanced Supply per-route option overrides keyed by `"METHOD /path"`: ```ts const bridged = await createSandboxedVmPluginModule({ sandbox, manifest, registry, routeOptions: { "POST /upload": { streaming: true, auth: "tenant.write" }, "GET /healthz": { auth: null, csrf: false }, }, statusMap: { 200: 201 }, }); ``` ## API Reference - `interface BridgedPluginModule` — frozen lifecycle bundle. - `interface BridgedPlugin` — `module`, `routes`, `dispose`, `lifecycle`. - `interface CreateSandboxedVmPluginModuleOptions` — `sandbox`, `manifest`, `registry`, `routeOptions?`, `statusMap?`. - `createSandboxedVmPluginModule(options): Promise>` — async factory; runs `lifecycle.start()` synchronously before returning. ## Implementation Notes The factory deliberately freezes both the returned `BridgedPlugin` and its nested `module` / `routes` so the host's lifecycle driver cannot patch a route's handler after install. The wrapped handler captures the OTel duration histogram across the whole marshal-invoke-unmarshal cycle — this surfaces back-pressure under load as a single metric the operator can alert on without instrumenting each sub-step individually. The `requires` generic on `BridgedPluginModule` is allowed to be empty because the bridge owns the only capability the sandbox needs (`bridge`); plugins that declare other capability requirements (db, secrets) thread those through the sandbox via the host's existing capability map — the bridge itself is unaware of them.