Initial Commit
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
# bridge
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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<Caps>` — frozen lifecycle bundle.
|
||||
- `interface BridgedPlugin<Caps>` — `module`, `routes`, `dispose`,
|
||||
`lifecycle`.
|
||||
- `interface CreateSandboxedVmPluginModuleOptions` — `sandbox`,
|
||||
`manifest`, `registry`, `routeOptions?`, `statusMap?`.
|
||||
- `createSandboxedVmPluginModule<Caps>(options):
|
||||
Promise<BridgedPlugin<Caps>>` — 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.
|
||||
Reference in New Issue
Block a user