Data · infrastructure/db
Production-ready adapterPrisma,
with guardrails.
The classic multi-tenant bug is a query that forgets its organizationId and quietly serves another customer’s rows. UseDeploy wraps the Prisma client in a tenant guard that turns that mistake into an exception at execution time — not a data breach in production.
Two clients: guarded by default
Repositories get the guarded client. Any read or mutation on a tenant-scoped model whose where clause does not constrain organizationId throws TenantScopeViolationError. A separate, explicitly-named system client exists for the legitimate cross-tenant paths (workers, token lookups, GDPR export) so every bypass is greppable.
1 export const getPrismaClient = (): PrismaClient => {
2 if (!guardedClient) {
3 guardedClient = applyTenantGuard(getBaseClient());
4 }
5 return guardedClient;
6 };
7
8 // Unguarded, explicit, greppable — for workers / token lookups only:
9 export const getSystemPrismaClient = () => getBaseClient();
Why it matters
Fails loudly, not silently
Forgetting the org scope in new code throws on first execution instead of leaking cross-tenant rows — the failure mode you want.
Explicit model list
TENANT_SCOPED_MODELS enumerates every org-owned model. Adding a new one to the schema means adding it here — a deliberate, reviewable step.
One connection pool
The guarded and system clients share the base client’s pool, so the guard costs no extra connections.
Pooler-friendly
DIRECT_URL is supported for Supabase pgbouncer and similar poolers, where migrations need a direct connection.
Configuration reference
Environment variables
DATABASE_URLrequiredPostgres connection string
DIRECT_URLoptionaldirect connection when DATABASE_URL is a pooler
Where it lives
apps/server/src/infrastructure/db/prisma-client.tsapps/server/src/infrastructure/db/tenant-guard.ts
Multi-tenant data that fails safe.
Build on a Prisma client that turns a forgotten org scope into an exception, not a leak.