@usebetterdev/tenant-core
v0.2.1-beta.1
Published
Core library for request-scoped multi-tenancy with Postgres RLS. Provides tenant context (AsyncLocalStorage), resolver strategies, adapter contract, and tenant API. No database driver dependency — use with the Drizzle adapter or a custom adapter.
Downloads
880
Readme
@usebetterdev/tenant-core
Core library for request-scoped multi-tenancy with Postgres RLS. Provides tenant context (AsyncLocalStorage), resolver strategies, adapter contract, and tenant API. No database driver dependency — use with the Drizzle adapter or a custom adapter.
Install
pnpm add @usebetterdev/tenantQuick start
import { betterTenant } from "@usebetterdev/tenant";
import { drizzleDatabase } from "@usebetterdev/tenant/drizzle"; // or prismaDatabase
const tenant = betterTenant({
database: drizzleDatabase(db),
tenantResolver: { header: "x-tenant-id" },
});
// In handlers:
const context = tenant.getContext(); // { tenantId, tenant?, database? }
const database = tenant.getDatabase(); // tenant-scoped DB handle (use only this for tenant tables)API
| API | Description |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| betterTenant(config) | Create instance with database provider and resolver. |
| getContext() | Current TenantContext \| undefined (tenantId, tenant?, database?). |
| tenant.getDatabase() | Tenant-scoped database handle, or undefined outside request scope. Use only this for tenant-scoped tables. |
| runWithTenant(tenantId, fn) | Run fn with context only (no DB). For tests or when adapter not used. |
| tenant.runWithTenantAndDatabase(tenantId, fn) | Set context and run fn with adapter's tenant-scoped DB. Call from middleware. |
| resolveTenant(request, config) | Resolve tenant id from request (header, path, subdomain, jwt, custom). Returns Promise<string \| undefined>. |
| tenant.runAs(tenantId, fn) | Same as request flow; use for cron or per-tenant jobs. |
| runAsSystem(fn) | Run with RLS bypass (adapter must implement runAsSystem). |
| tenant.api | createTenant, updateTenant, listTenants, deleteTenant. |
| TenantRepository.getBySlug(slug) | Look up a tenant by slug. Used internally for slug-to-UUID auto-resolution. Returns Tenant \| null. |
Adapter contract
Adapters implement TenantAdapter:
- runWithTenant(tenantId, fn) — Begin transaction →
SET LOCAL app.current_tenant = '<uuid>'→ callfn(database)→ commit. Core does not run SQL. - runAsSystem?(fn) — Optional; run
fnwith a DB handle that has RLS bypass (e.g.SET LOCAL app.bypass_rls = true).
Types: TenantScopedDatabase (opaque handle), SystemDatabase (opaque handle for system operations).
Resolver
Resolution order: header → path → subdomain → jwt → custom. Configure tenantResolver with e.g. header: "x-tenant-id", path: "/t/:tenantId", subdomain: true, jwt: { claim: "tenant_id" }, or custom: (req) => ....
Slug-to-UUID resolution
After the resolver extracts an identifier, it is automatically normalized to a UUID:
- UUID — passes through unchanged.
- Slug (e.g.
"acme") — looked up in the tenants table viagetBySlug. - Custom transform — if
resolveToIdis set, it is called instead of the above (takes full precedence).
This means subdomain resolution ("acme") or header-based slugs automatically resolve to UUIDs.
resolveToId
Optional transform added to TenantResolverConfig:
resolveToId?: (identifier: string) => string | Promise<string>;Use this to map custom domains, external IDs, or any non-slug identifier to a tenant UUID.
Telemetry
Anonymous telemetry is on by default to help improve the library. Data is sent to https://telemetry.usebetter.dev and includes runtime, framework, and redacted config (no PII or secrets). Telemetry is disabled in NODE_ENV=test.
Opt out: Set telemetry: { enabled: false } in your config or BETTER_TENANT_TELEMETRY=0.
Debug mode: Set telemetry: { debug: true } or BETTER_TENANT_TELEMETRY_DEBUG=1 to log payloads to the console without sending.
Documentation
- Under the Hood — RLS, SET LOCAL, runAsSystem
- Product plan — Roadmap, architecture
