Files
2026-05-27 23:29:53 +01:00

5.7 KiB

@suaveplan/api-db-worker-proxy - Worker-side proxy for the api db capability. Marshals AdminDatabaseLike / TenantScopedDatabaseLike calls — including transactions and cursor streams — over @suaveplan/api-worker-rpc MessagePort frames. Tenant scoping is enforced main-side; this package is the transport.

Worker-side proxy for the api db capability. Marshals AdminDatabaseLike and TenantScopedDatabaseLike calls — including transactions and cursor streams — over @suaveplan/api-worker-rpc MessagePort frames.

The main side mounts a real DB adapter and exposes it over a port; the worker side imports a proxy whose API surface is identical to a direct adapter. Worker plugin code never touches a real connection and never sees plaintext credentials.

Tenant scoping is enforced on the main side, NOT in this package. wrapTenantScoped(adminProxy, tenantId) is purely a type-level facade that prevents an admin reference from being passed where a tenant reference is required by the type system.

Documentation tree

  • error-codes — This module owns the DB_PROXY.* stable error-code registry for @suaveplan/api-db-worker-proxy. Every failure that c…
    • error-codes — This module owns the DB_PROXY.* stable error-code registry for @suaveplan/api-db-worker-proxy. Every failure that c…
  • main-side — This module exposes createDbProxyServer(port, db) — the trusted-side endpoint of the worker DB proxy. It mounts an `A…
    • main-side — This module exposes createDbProxyServer(port, db) — the trusted-side endpoint of the worker DB proxy. It mounts an `A…
  • tenant-scoped — This module exposes wrapTenantScoped(adminProxy, tenantId) — a worker-side type-level facade that re-presents an admi…
    • tenant-scoped — This module exposes wrapTenantScoped(adminProxy, tenantId) — a worker-side type-level facade that re-presents an admi…
  • testing — This module provides the in-memory test harness for @suaveplan/api-db-worker-proxy. It exposes `createInMemoryAdminDb…
    • testing — This module provides the in-memory test harness for @suaveplan/api-db-worker-proxy. It exposes `createInMemoryAdminDb…
  • worker-side — This module exposes createDbProxyClient(port, { engine }) — the untrusted-side endpoint of the worker DB proxy. It re…
    • worker-side — This module exposes createDbProxyClient(port, { engine }) — the untrusted-side endpoint of the worker DB proxy. It re…

Why

Per the api-plugin foundation design (§5.1, §5.2a, §6) every capability ships in two surfaces: a direct in-process binding for trusted plugins and a MessagePort-marshalled binding for isolated / sandboxed plugins. The db capability is special because

  1. its operations return result sets that can be huge (favouring streaming over buffering),
  2. its operations are transactional (transaction handles MUST be bound to a single connection lifetime), and
  3. tenant-scoping is a security boundary the worker MUST NOT be able to circumvent.

This package is Stage-3 worker-isolation infrastructure: every Stage-4 DB adapter (api-db-adapter, api-db-sqlite-memory, …) MUST pass conformance under BOTH the direct surface AND this proxy surface before can be declared complete.

Install

bun add @suaveplan/api-db-worker-proxy

Quick start

// main side
import { createDbProxyServer } from "@suaveplan/api-db-worker-proxy/main-side";
import { MessageChannel, Worker } from "node:worker_threads";

const channel = new MessageChannel();
createDbProxyServer(channel.port1, realAdminAdapter);

const worker = new Worker("./plugin-worker.js");
worker.postMessage({ port: channel.port2, engine: "sqlite" }, [channel.port2]);
// worker side
import { parentPort } from "node:worker_threads";
import { createDbProxyClient } from "@suaveplan/api-db-worker-proxy/worker-side";
import { wrapTenantScoped } from "@suaveplan/api-db-worker-proxy/tenant-scoped";
import type { TenantId } from "@suaveplan/api-types/tenant";

parentPort?.once("message", (boot) => {
    const admin = createDbProxyClient(boot.port, { engine: boot.engine });

    // request scope
    const tenantScoped = wrapTenantScoped(admin, "acme" as TenantId);
    void runHandler(tenantScoped);
});

Surface

  • ./main-side — createDbProxyServer(port, db): serves an AdminDatabaseLike over @suaveplan/api-worker-rpc. Returns a DbProxyServer with close(), activeTransactionCount(), activePreparedCount().
  • ./worker-side — createDbProxyClient(port, { engine }): returns an AdminDatabaseLike-shaped object plus cursor(sql, params) for streaming row iteration and dispose() for abrupt teardown.
  • ./tenant-scoped — wrapTenantScoped(adminProxy, tenantId): type-level facade. Tenant scoping is enforced main-side.
  • ./error-codes — DB_PROXY.* codes: UNSAFE_NOT_PERMITTED, TRANSACTION_LOST, STREAM_INTERRUPTED, PROXY_DISPOSED.
  • ./testing — createInMemoryAdminDb + createDbProxyHarness for downstream conformance suites.

Co-located docs