first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
# `side-effects`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Encodes every side-effect the host runtime SHALL execute on each documented
transition — `mount-routes`, `unmount-routes` with 503 + Retry-After,
`call-user-hook` with the user-facing install/start/stop/uninstall hooks,
`dispose-capabilities` in reverse-registration order, `emit-event` for the
public `plugin.*` topics, OTel-span completion, and the migration runner's
ledger writes. The table is data; Stage 2's `api-host` is the executor.
## Features
- Discriminated-union `SideEffectOp` covering all 24 distinct effect kinds
named in design §4's table — every operation is statically typed so the
Stage-2 dispatcher can exhaustively switch.
- `SIDE_EFFECTS` map keyed `"<from>|<to>"` matching the lookup form used
by the FSM factory.
- `getSideEffects(from, to)` query helper; `undefined` for unknown pairs.
- `DEFAULT_GRACEFUL_MS = 30_000` constant pinning the design-doc default
for the `stop` hook's graceful budget.
- `disposalOrder: "reverse-registration"` field surfaced on
`stopping → stopped` for direct inspection.
## Usage: Basic
```ts
import { getSideEffects } from "@suaveplan/api-lifecycle/side-effects";
const entry = getSideEffects("installing", "installed");
for (const op of entry?.ops ?? []) {
// dispatch op …
}
```
## Usage: Advanced
Dispatch table for The host driver — exhaustively handles every
`SideEffectOp` kind. The compiler reports an error if a new effect is added
to the union without a corresponding handler:
```ts
import { type SideEffectOp } from "@suaveplan/api-lifecycle/side-effects";
async function dispatch(op: SideEffectOp, ctx: HostContext): Promise<void> {
switch (op.kind) {
case "validate-manifest": return ctx.validateManifest();
case "resolve-deps": return ctx.resolveDeps();
case "acquire-db-connection": return ctx.acquireDb();
case "run-migrations": return ctx.runMigrations(op.upTo);
case "write-ledger": return ctx.writeLedger();
case "register-scopes": return ctx.registerScopes();
case "call-user-hook": return ctx.callHook(op.hook, op.gracefulMs);
case "emit-event": return ctx.emit(op.topic);
case "complete-otel-span": return ctx.endSpan();
case "mount-routes": return ctx.mountRoutes();
case "unmount-routes": return ctx.unmountRoutes(op.status, op.retryAfter);
case "subscribe-events": return ctx.subscribe();
case "unsubscribe-events": return ctx.unsubscribe();
case "register-jobs": return ctx.registerJobs({ paused: op.paused });
case "pause-jobs": return ctx.pauseJobs();
case "unpause-jobs": return ctx.unpauseJobs();
case "mark-health": return ctx.markHealth(op.state);
case "inject-capabilities": return ctx.injectCaps();
case "dispose-capabilities": return ctx.disposeCaps(op.order);
case "force-cancel-stragglers": return ctx.forceCancel();
case "apply-data-policy": return ctx.applyDataPolicy();
case "drop-scopes": return ctx.dropScopes();
case "drop-ledger": return ctx.dropLedger();
case "rollback-to-prior-module": return ctx.rollback();
}
}
```
## API Reference
- `type SideEffectOp` — 24-arm discriminated union.
- `interface SideEffectEntry` — `{ from, to, ops, disposalOrder? }`.
- `SIDE_EFFECTS` — `ReadonlyMap<"<from>|<to>", SideEffectEntry>`.
- `getSideEffects(from, to)` — query helper.
- `DEFAULT_GRACEFUL_MS` — `30_000`.
## Implementation Notes
Every entry is `Object.freeze`-d at module load so accidental mutation
throws in strict mode. The crash-recovery (`crashed → starting`) and
hot-reload (`stopped → starting`) entries reuse the same ops as the
`installed → starting` path — the design doc specifies they MUST be
indistinguishable from the operator's perspective. `started → stopping`
and `rolled_back → stopping` are similarly aliased; the test suite asserts
that the two ops sequences are equal so a future divergence is caught.