2.9 KiB
tenant-scoped
(no submodules)
Purpose
This module exposes wrapTenantScoped(adminProxy, tenantId) — a
worker-side type-level facade that re-presents an admin-surface
AdminDatabaseLike as a TenantScopedDatabaseLike. The facade
matters because the two surface types are deliberately
non-assignable in @suaveplan/api-types: a request handler declared
to accept TenantScopedDatabaseLike MUST NOT accidentally receive
an unscoped admin proxy, and vice versa.
Tenant scoping is enforced on the main side. The actual SQL rewrite / row-level security / connection routing all live in the host's adapter. This wrapper does NOT add policy; it asserts a shape so the type system can guarantee the slot in the request context has been narrowed to a tenant.
Features
- Returns a fresh facade object on every call so the same admin proxy can back many concurrent request scopes.
- Exposes
tenantIdon the returned object for audit logging. - Preserves the optional
prepare/transactioncapabilities only if the admin proxy advertised them; absent capabilities throwDB_PROXY.UNSAFE_NOT_PERMITTED. closeis intentionally omitted becauseTenantScopedDatabaseLikedoes not expose it — the admin proxy retains lifecycle ownership.
Usage: Basic
import { wrapTenantScoped } from "@suaveplan/api-db-worker-proxy/tenant-scoped";
import type { TenantId } from "@suaveplan/api-types/tenant";
const tenantId = "acme" as TenantId;
const scoped = wrapTenantScoped(adminProxy, tenantId);
const rows = await scoped.query("SELECT * FROM invoices");
Usage: Advanced
Use the facade inside a request middleware that materialises
RequestContext.db. The middleware constructs the wrapper from the
already-resolved tenantId so route handlers only ever see the
tenant-scoped type.
function attachDb(adminProxy: AdminDatabaseLike) {
return async (c: Context, next: Next): Promise<void> => {
const tenantId = c.get("tenantId") as TenantId;
const scoped = wrapTenantScoped(adminProxy, tenantId);
c.set("db", scoped);
await next();
};
}
API Reference
wrapTenantScoped(adminProxy: AdminDatabaseLike, tenantId: TenantId): TenantScopedDatabaseLike.- Throws
DB_PROXY.UNSAFE_NOT_PERMITTEDfromprepare/transactionwhen the underlying admin proxy doesn't expose that capability.
Implementation Notes
The wrapper rebinds prepare and transaction so the underlying
admin proxy's this-context survives the indirection (the methods
are looked up off the proxy each call, not captured eagerly, so
admin-side disposal is observed on the next call rather than at wrap
time).
The tenantId field is set on the returned object purely so audit
loggers and RequestContext consumers can read it directly. The
field name and type match TenantScopedDatabaseLike.tenantId
exactly; both are TenantId (a branded string).