3.3 KiB
main-side
(no submodules)
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, andcloseover@suaveplan/api-worker-rpc. - Streams cursor rows as
stream-chunkframes; worker-side break triggerscancelwhich the handler observes via itsAbortSignal. - Bounds transaction lifetimes by port: closing the server (or
receiving
goodbye) raisesTRANSACTION_LOSTon every dangling handle rather than leaking driver state. - Exposes diagnostic counters
activeTransactionCountandactivePreparedCount.
Usage: Basic
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.
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.