Initial Commit
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
# `main-side`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
This module exposes `createDbProxyServer(port, db)` — the trusted-side
|
||||
endpoint of the worker DB proxy. It mounts an `AdminDatabaseLike`
|
||||
adapter on a `MessagePort` and turns every worker-issued RPC frame
|
||||
into a real call on the wrapped adapter. The main side is the only
|
||||
side that touches the underlying DB driver; the worker code that
|
||||
imported `@suaveplan/api-db-worker-proxy/worker-side` never sees a
|
||||
real connection, never sees plaintext credentials, and cannot
|
||||
fabricate a tenant identity.
|
||||
|
||||
The server keeps a registry of live transaction handles and prepared
|
||||
statements keyed by id so the worker can drive them across multiple
|
||||
round-trips without leaking driver handles into the worker's address
|
||||
space.
|
||||
|
||||
## Features
|
||||
|
||||
- Marshals `query`, `execute`, `prepare`, `transaction`, `cursor`,
|
||||
`engine`, and `close` over `@suaveplan/api-worker-rpc`.
|
||||
- Streams cursor rows as `stream-chunk` frames; worker-side break
|
||||
triggers `cancel` which the handler observes via its `AbortSignal`.
|
||||
- Bounds transaction lifetimes by port: closing the server (or
|
||||
receiving `goodbye`) raises `TRANSACTION_LOST` on every dangling
|
||||
handle rather than leaking driver state.
|
||||
- Exposes diagnostic counters `activeTransactionCount` and
|
||||
`activePreparedCount`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { createDbProxyServer } from "@suaveplan/api-db-worker-proxy/main-side";
|
||||
import { Worker, MessageChannel } from "node:worker_threads";
|
||||
|
||||
const channel = new MessageChannel();
|
||||
const server = createDbProxyServer(channel.port1, realAdapter);
|
||||
|
||||
const worker = new Worker("./worker.js");
|
||||
worker.postMessage({ port: channel.port2 }, [channel.port2]);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
When the host needs to scope the adapter per request, pass an
|
||||
already-tenant-scoped adapter to a per-request server. The proxy
|
||||
itself does NOT rewrite SQL — that responsibility lives in the
|
||||
adapter the host hands in here. `wrapTenantScoped` on the worker
|
||||
side is purely a type-level facade.
|
||||
|
||||
```ts
|
||||
const scoped = realAdapter.scopedFor(tenantId); // host responsibility
|
||||
const server = createDbProxyServer(port, scoped);
|
||||
// shut down at request end:
|
||||
server.close("request finished");
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `createDbProxyServer(port: PortLike, db: AdminDatabaseLike): DbProxyServer`.
|
||||
- `DbProxyServer.close(reason?: string): void` — closes the server,
|
||||
rolling back live transactions and finalising prepared statements.
|
||||
- `DbProxyServer.activeTransactionCount(): number`.
|
||||
- `DbProxyServer.activePreparedCount(): number`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The cursor handler returns an `AsyncGenerator`; `serveWorkerRpc`'s
|
||||
streaming path interprets the return as `AsyncIterable<unknown>` and
|
||||
emits one `stream-chunk` per yield, then `stream-end ok`. Cancellation
|
||||
from the worker comes in as an `AbortSignal.aborted = true` on the
|
||||
handler's `signal`; the generator throws `STREAM_INTERRUPTED` which
|
||||
the server marshals as `stream-end ok=false` carrying the
|
||||
`DB_PROXY.STREAM_INTERRUPTED` code.
|
||||
|
||||
Transactions are implemented by holding the adapter's `transaction()`
|
||||
promise open until either `tx.commit` or `tx.rollback` fires from the
|
||||
worker. Rolling back is signalled by throwing inside the adapter
|
||||
callback, which mirrors the adapter's own rollback semantics without
|
||||
adding a parallel state machine.
|
||||
Reference in New Issue
Block a user