Files
suaveplan/openspec/archive/2026-05-25-api-db-worker-proxy/proposal.md
2026-05-27 23:29:53 +01:00

54 lines
4.1 KiB
Markdown

# `@suaveplan/api-db-worker-proxy` — worker-side proxy to main-process DB adapter
**Package**: `@suaveplan/api-db-worker-proxy`
**Status**: PROPOSED
**Type**: new-package
**Stage**: 3 (Worker isolation infrastructure)
## Why
Per design doc §5.2a, every capability ships two callable surfaces. `db` is special because (a) its operations return result sets that can be huge (favoring streaming over buffering), (b) its operations are transactional (transaction handles must be tied to a single connection lifetime that the worker can't naively shuttle around), and (c) tenant-scoping is a security boundary the worker MUST NOT be able to circumvent. `api-db-worker-proxy` provides the worker-side surface of `AdminDatabaseLike` / `TenantScopedDatabaseLike` whose method calls marshal across `MessagePort` to the main-process DB adapter (which lives in `api-db-adapter`, Stage 4).
The proxy preserves the `PluginContext` / `RequestContext` `db`-slot type contract: a `PluginContext`'s `db` is `AdminDatabaseLike`; a `RequestContext`'s `db` is `TenantScopedDatabaseLike`. Tenant scoping is enforced on the MAIN side before the call is dispatched — worker code cannot smuggle an admin DB into a request handler.
## What changes
Net-new package. Worker-side only — the main-side counterpart lives in `api-db-adapter` (Stage 4).
**ADDED exports:**
- `./client` — `createDbProxyClient(port, { surface: "admin" | "tenant-scoped" })`: returns a `WorkerProxyOf<AdminDatabaseLike>` or `WorkerProxyOf<TenantScopedDatabaseLike>` typed per surface.
- `./cursor` — DB cursors marshalled as `ReadableStream<Row>` via `api-worker-rpc`'s streaming primitive. Worker-side `for await (const row of cursor)` is identical to direct usage.
- `./transaction` — transaction handle proxy: `client.transaction(async (tx) => { ... })` opens a transaction on the main side; the worker-side callback gets a `tx` whose methods route through a transaction-scoped sub-port. Calls outside the callback throw `DB_PROXY_TX_OUT_OF_SCOPE`.
- `./error-codes` — `DB_PROXY_*` codes (`DB_PROXY_PORT_CLOSED`, `DB_PROXY_TX_OUT_OF_SCOPE`, `DB_PROXY_CURSOR_AFTER_CLOSE`, `DB_PROXY_SURFACE_MISMATCH`).
- `./testing` — paired `MessageChannel` + a stub main-side server for unit tests; conformance harness `runDbProxyClientConformance` for downstream adapters.
## Out of scope
- **Main-side adapter implementation** — `api-db-adapter` (Stage 4) and `api-db-sqlite-memory` (Stage 5) implement the receiving side.
- **Tenant scoping enforcement** — enforced on the main side BEFORE marshalling. This package is the transport, not the policy.
- **Engine-specific SQL** — `api-db-worker-proxy` is engine-agnostic; the main-side adapter resolves the engine (`postgres` / `sqlite` / `surrealdb`).
- **Schema migrations** — host metadata migrations live in `api-host`; plugin migrations live in `api-db-adapter`.
## Source mapping
Genesis-original. Design doc sections: §5.1 (two-context separation of `db` slot), §5.2a (worker-RPC variants), §6 (capability scoping in step 7).
| Requirement | Design doc section |
|---|---|
| Two surfaces: `AdminDatabaseLike` vs `TenantScopedDatabaseLike` | §5.1 |
| Cursors streamed via `Transferable` | §5.2a |
| Worker-side proxy preserves direct-surface ergonomics | §5.2a |
| Tenant scoping enforced main-side, not worker-side | §6 |
## Verification approach
- `src/client/client.spec-conformance.test.ts` — `expectTypeOf` proves admin surface and tenant surface are distinct; admin surface NOT assignable to tenant-scoped.
- `src/cursor/cursor.test.ts` — `for await` over large result set; consumer cancel propagates to main side.
- `src/transaction/transaction.test.ts` — `tx` only valid inside callback; out-of-scope call throws `DB_PROXY_TX_OUT_OF_SCOPE`.
- `tests/conformance/run-db-adapter-conformance.test.ts` — `runDbAdapterConformance("worker-proxy", makeFactory)` from `@suaveplan/api-db` passes against this proxy + a stub main-side server. **Required before Stage 4 can complete** — every Stage-4 DB adapter MUST pass conformance under BOTH the direct and worker-proxy surfaces.
## Migration impact
None — additive only.