82 lines
2.8 KiB
Markdown
82 lines
2.8 KiB
Markdown
# lifecycle
|
|
|
|
<!-- begin:auto-docs -->
|
|
_(no submodules)_
|
|
<!-- end:auto-docs -->
|
|
|
|
## Purpose
|
|
|
|
This module owns the idempotency wrapper around a `VmSandboxHandle`'s
|
|
`start` / `dispose` lifecycle. The host's lifecycle driver may invoke
|
|
these hooks multiple times (e.g. a hot-reload's stop+start pair, or a
|
|
test harness's deterministic shutdown sequence); the wrapper makes the
|
|
double-invocation case a silent no-op rather than a throw, and
|
|
reflects under-the-hood sandbox throws via the bridge's
|
|
`LIFECYCLE_FAILED` code so the host's error boundary sees a uniform
|
|
shape.
|
|
|
|
Splitting the wrapper into its own module keeps the bridge factory
|
|
small (no inline state machine) and gives the conformance harness a
|
|
direct seam for asserting idempotency without standing up the full
|
|
bridge.
|
|
|
|
## Features
|
|
|
|
- Idempotent `start` — second call returns without re-invoking
|
|
`sandbox.start()`.
|
|
- Idempotent `dispose` — second call returns without re-invoking
|
|
`sandbox.dispose()`.
|
|
- Forbidden start-after-dispose — calling `start()` after `dispose()`
|
|
raises `LIFECYCLE_FAILED` so the host can fail the hot-reload
|
|
cleanly rather than silently swallowing the request.
|
|
- Error wrapping — underlying sandbox throws surface as
|
|
`SandboxVmBridgeError` with `code: LIFECYCLE_FAILED` and the
|
|
original error preserved as `cause`.
|
|
- Inspection helpers — `hasStarted()` and `isDisposed()` for the
|
|
bridge's internal request-pipeline guard.
|
|
|
|
## Usage: Basic
|
|
|
|
```ts
|
|
import { createSandboxLifecycleForwarder } from "@suaveplan/api-sandbox-vm-host-bridge/lifecycle/lifecycle";
|
|
|
|
const forwarder = createSandboxLifecycleForwarder(sandbox);
|
|
await forwarder.start();
|
|
forwarder.dispose();
|
|
forwarder.dispose(); // no-op
|
|
```
|
|
|
|
## Usage: Advanced
|
|
|
|
```ts
|
|
import { createSandboxLifecycleForwarder } from "@suaveplan/api-sandbox-vm-host-bridge/lifecycle/lifecycle";
|
|
|
|
const forwarder = createSandboxLifecycleForwarder(sandbox);
|
|
try {
|
|
await forwarder.start();
|
|
} catch (err) {
|
|
// err.code === "SAND_VM_BRIDGE.LIFECYCLE_FAILED"
|
|
// err.cause is the original SandboxVmError
|
|
rollbackJournal.markFailed(err);
|
|
}
|
|
```
|
|
|
|
## API Reference
|
|
|
|
- `interface SandboxLifecycleForwarder` — `start()`, `dispose()`,
|
|
`hasStarted()`, `isDisposed()`.
|
|
- `createSandboxLifecycleForwarder(sandbox: VmSandboxHandle):
|
|
SandboxLifecycleForwarder` — factory; returns a frozen object.
|
|
|
|
## Implementation Notes
|
|
|
|
The internal `started` / `disposed` flags are kept in a closure rather
|
|
than on the returned object so the sandbox cannot mutate them via a
|
|
property-set on the forwarder. The forwarder itself is `Object.freeze`d
|
|
so consumers cannot patch `dispose` to skip the cleanup. Errors raised
|
|
inside `dispose()` are caught and rewrapped — the alternative (letting
|
|
them propagate raw) would mean the host's error boundary sees a mixed
|
|
shape (sometimes `SandboxVmError`, sometimes
|
|
`SandboxVmBridgeError`); the wrap keeps the boundary's branch arm
|
|
tight.
|