@usebetterdev/tenant-prisma
v0.5.4
Published
Prisma adapter for [@usebetterdev/tenant](https://github.com/usebetter-dev/usebetter). Runs each tenant scope in an interactive transaction with `SET LOCAL app.current_tenant = '<uuid>'` so Postgres RLS applies.
Downloads
1,387
Readme
@usebetterdev/tenant-prisma
Prisma adapter for @usebetterdev/tenant. Runs each tenant scope in an interactive transaction with SET LOCAL app.current_tenant = '<uuid>' so Postgres RLS applies.
Install
pnpm add @usebetterdev/tenant @prisma/client @prisma/adapter-pgUsage
import { PrismaClient } from "./generated/prisma/client.js";
import { PrismaPg } from "@prisma/adapter-pg";
import { betterTenant } from "@usebetterdev/tenant";
import { prismaDatabase } from "@usebetterdev/tenant/prisma";
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
const prisma = new PrismaClient({ adapter });
const tenant = betterTenant({
database: prismaDatabase(prisma),
tenantResolver: { header: "x-tenant-id" },
});
// tenant.api.createTenant, updateTenant, listTenants, deleteTenant now work (via runAsSystem).
// In handlers: tenant.getDatabase() returns the Prisma transaction client.prismaDatabase(prisma) bundles the adapter and tenant repository into a single database provider. If you use a custom table name, pass it via the tableName option:
import { prismaDatabase } from "@usebetterdev/tenant/prisma";
const tenant = betterTenant({
database: prismaDatabase(prisma, { tableName: "my_tenants" }),
tenantResolver: { header: "x-tenant-id" },
});The tenants table must match the CLI-generated schema: id (UUID), name, slug, created_at.
Behaviour
- runWithTenant(tenantId, fn) — Opens an interactive
$transaction, runsSELECT set_config('app.current_tenant', tenantId, true), then callsfn(tx). Thetxhandle is the Prisma transaction client; use only this for tenant-scoped tables so RLS sees the tenant. - runAsSystem(fn) — Opens an interactive
$transaction, runsSELECT set_config('app.bypass_rls', 'true', true), then callsfn(tx). Use for admin/cron only (e.g. tenant.api.*). RLS policies must allow rows whencurrent_setting('app.bypass_rls', true) = 'true'(CLI generates this bypass policy). - loadTenant — The full Tenant row is loaded by default into
getContext().tenant. SetloadTenant: falseto opt out (saves one query per request). - The handle passed to
fnis the Prisma interactive transaction client — all generated model methods (db.user.findMany(), etc.) are available on it.
Testing
Integration tests use @testcontainers/postgresql: a Postgres container is started automatically when you run tests (Docker required). If Docker is unavailable, the tests are skipped. You can instead set DATABASE_URL to run the same tests against an existing database.
Run integration tests: From the repo root, run pnpm run test:integration or pnpm run test --filter @usebetterdev/tenant-prisma.
Peer dependencies
Requires Prisma 7+:
@prisma/client(>= 7.0.0)@prisma/adapter-pg(>= 7.0.0)
Prisma 7 uses the prisma-client generator with a required output path. Adjust the PrismaClient import to match your output configuration in schema.prisma.
