3.6 KiB
Capability Spec Delta: api-db-worker-proxy (api-db-worker-proxy)
Package: @suaveplan/api-db-worker-proxy
Status: PROPOSED
Every requirement below carries a
**Verified by:**line at authoring time.
ADDED Requirements
Requirement: Admin and tenant-scoped surfaces are type-distinct proxy clients
The package SHALL expose createDbProxyClient(port, { surface }) whose return type is WorkerProxyOf<AdminDatabaseLike> when surface === "admin" and WorkerProxyOf<TenantScopedDatabaseLike> when surface === "tenant-scoped". The two return types MUST NOT be mutually assignable: a tenant-scoped proxy MUST NOT satisfy an admin parameter and vice versa.
Scenario: surfaces are not mutually assignable
- WHEN
expectTypeOf<Admin>().not.toEqualTypeOf<TenantScoped>()is checked - THEN TypeScript flags the surfaces as distinct
Verified by: src/client/client.spec-conformance.test.ts::"admin and tenant-scoped surfaces are distinct"
Requirement: Cursors stream results without buffering
The package SHALL marshal DB cursors as ReadableStream<Row> via api-worker-rpc's streaming primitive. for await (const row of cursor) MUST yield rows incrementally; the main-side cursor MUST NOT be drained eagerly. Consumer cancellation MUST propagate so the main-side cursor closes.
Scenario: 10k-row cursor yields incrementally
- WHEN a worker iterates a 10k-row cursor
- THEN the first row is observed BEFORE the last row has been produced main-side (verified via timing)
Scenario: cursor break propagates close
- WHEN the worker breaks out of
for awaitearly - THEN the main-side cursor is closed AND no further rows are produced
Verified by: src/cursor/cursor.test.ts::"cursor yields incrementally" AND src/cursor/cursor.test.ts::"break propagates cursor close"
Requirement: Transaction handle is scoped to callback lifetime
The package SHALL provide client.transaction(async (tx) => { ... }) where tx is only valid inside the callback. A call on tx after the callback resolves or rejects MUST throw DB_PROXY_TX_OUT_OF_SCOPE. The transaction MUST be carried by a transaction-scoped sub-port so the main side can correlate every call to the same connection.
Scenario: out-of-scope tx call throws
- WHEN the callback captures
txAND a method is called on it after the callback returns - THEN
DB_PROXY_TX_OUT_OF_SCOPEis thrown
Scenario: nested tx is rejected
- WHEN
tx.transaction(...)is invoked inside the callback - THEN
DB_PROXY_TX_OUT_OF_SCOPEis thrown (savepoints are out of scope for Stage 3)
Verified by: src/transaction/transaction.test.ts::"out-of-scope tx call throws" AND src/transaction/transaction.test.ts::"nested tx is rejected"
Requirement: Conformance suite passes against this proxy
The package SHALL be runnable as a surface argument to runDbAdapterConformance from @suaveplan/api-db/testing. Every conformance scenario (CRUD, transactions, cursors, schema introspection) MUST pass identically to a direct in-process adapter. This is the Stage-4-precondition gate: every Stage-4 DB adapter (api-db-adapter, api-db-sqlite-memory) MUST pass conformance under BOTH the direct surface AND this worker-proxy surface before Stage 4 can be declared complete.
Scenario: runDbAdapterConformance passes
- WHEN
runDbAdapterConformance("worker-proxy", makeFactory)runs against this proxy + stub server - THEN every conformance scenario passes
Verified by: tests/conformance/run-db-adapter-conformance.test.ts::"runDbAdapterConformance passes against proxy"