# UseBetter Tenant > Open-source TypeScript library for adding multi-tenancy to Postgres applications using Row-Level Security (RLS). ## What it does UseBetter Tenant enforces tenant isolation at the database level via Postgres RLS policies. Instead of adding `WHERE tenant_id = ?` to every query, the library sets a session variable via `set_config('app.current_tenant', '', true)` inside a transaction, and RLS policies filter rows automatically. ## Key concepts - **Tenant resolver**: Extracts tenant identity from incoming requests (header, subdomain, path, JWT, or custom function). Slugs are automatically resolved to UUIDs via the tenants table. - **Adapter**: Wraps your ORM (Drizzle or Prisma) to open a transaction and set the tenant session variable. - **Framework middleware**: Drop-in middleware for Hono, Express, and Next.js App Router that resolves the tenant and scopes the request. - **CLI**: Generates RLS migrations, verifies setup, and seeds tenants (`@usebetterdev/tenant-cli`). - **Admin operations**: `runAs(tenantId)` impersonates a tenant for background jobs. `runAsSystem()` bypasses RLS for cross-tenant admin work. - **Tenant API**: CRUD operations on the tenants table via `tenant.api` (runs with RLS bypass). - **Context**: `tenant.getContext()` and `tenant.getDatabase()` access the current tenant anywhere in the call tree via AsyncLocalStorage. ## Installation ``` npm install @usebetterdev/tenant ``` Subpath exports: `@usebetterdev/tenant/drizzle`, `@usebetterdev/tenant/prisma`, `@usebetterdev/tenant/hono`, `@usebetterdev/tenant/express`, `@usebetterdev/tenant/next`. ## Setup (Drizzle) Use `drizzleDatabase()` to create the database provider. This is the recommended API — it bundles the adapter and tenant repository into a single config value: ```ts import { betterTenant } from "@usebetterdev/tenant"; import { drizzleDatabase } from "@usebetterdev/tenant/drizzle"; const tenant = betterTenant({ database: drizzleDatabase(db), // db = your Drizzle instance tenantResolver: { header: "x-tenant-id" }, }); ``` For a custom tenants table, pass it as an option: `drizzleDatabase(db, { table: myTable })`. ## Setup (Prisma) Use `prismaDatabase()` — same pattern as Drizzle: ```ts import { betterTenant } from "@usebetterdev/tenant"; import { prismaDatabase } from "@usebetterdev/tenant/prisma"; const tenant = betterTenant({ database: prismaDatabase(prisma), // prisma = your PrismaClient instance tenantResolver: { header: "x-tenant-id" }, }); ``` For a custom table name, pass it as an option: `prismaDatabase(prisma, { tableName: "my_tenants" })`. ## Requirements - Node.js 22+ (also Bun and Deno) - PostgreSQL 13+ - TypeScript 5+ (recommended) - The DATABASE_URL must connect as a **non-superuser** PostgreSQL role. Superusers bypass ALL RLS policies, even with FORCE ROW LEVEL SECURITY. Create a regular application role with SELECT/INSERT/UPDATE/DELETE privileges. ## CLI The CLI package is `@usebetterdev/tenant-cli`. Always use the full scoped name: ``` npx @usebetterdev/tenant-cli init --database-url $DATABASE_URL npx @usebetterdev/tenant-cli migrate --dry-run npx @usebetterdev/tenant-cli migrate -o ./rls npx @usebetterdev/tenant-cli add-table -o ./rls npx @usebetterdev/tenant-cli check --database-url $DATABASE_URL npx @usebetterdev/tenant-cli seed --name "Acme Corp" --database-url $DATABASE_URL ``` The CLI generates **only** RLS policies, triggers, and the `set_tenant_id()` function. It does NOT create the `tenants` table or add `tenant_id` columns — your ORM schema handles that. The config file is `better-tenant.config.json` but the CLI package name is `@usebetterdev/tenant-cli` (NOT `better-tenant`). ### Non-interactive init (for CI/CD and LLM agents) Pass `-n` (or `--non-interactive`) to disable all prompts. Required flags: `--tables` and `--orm`. The CLI fails fast with a clear error if anything is missing. ``` npx @usebetterdev/tenant-cli init -n --tables "projects,tasks,users" --orm drizzle npx @usebetterdev/tenant-cli init -n --tables "projects,tasks" --orm prisma --overwrite ``` Flags: `-n` / `--non-interactive` (disable all prompts), `--tables ` (required with -n), `--orm drizzle|prisma` (required with -n), `--overwrite` (overwrite existing config). No database connection is needed in non-interactive mode. ### Drizzle workflow 1. Re-export `tenantsTable` from `@usebetterdev/tenant/drizzle` in your schema 2. Add `tenantId: uuid("tenant_id").notNull().references(() => tenantsTable.id).default(sql\`(current_setting('app.current_tenant', true))::uuid\`)` to each tenant-scoped table 3. `npx drizzle-kit generate && npx drizzle-kit migrate` (schema migration) 4. `npx drizzle-kit generate --custom --name=better_tenant_rls --prefix=none` (empty custom migration) 5. `npx @usebetterdev/tenant-cli migrate -o drizzle/_better_tenant_rls.sql` (fill with RLS SQL) 6. `npx drizzle-kit migrate` (apply RLS) 7. `npx @usebetterdev/tenant-cli check --database-url $DATABASE_URL` (verify) ### Prisma workflow 1. Add `Tenant` model to `schema.prisma` with `@@map("tenants")` 2. Add `tenantId String @map("tenant_id") @db.Uuid` to each tenant-scoped model 3. `npx prisma migrate dev --name setup` (schema migration) 4. `npx prisma migrate dev --create-only --name better_tenant_rls` (draft migration — creates file without applying) 5. `npx @usebetterdev/tenant-cli migrate -o prisma/migrations/*_better_tenant_rls/migration.sql` (fill with RLS SQL) 6. `npx prisma migrate dev` (apply RLS) 7. `npx @usebetterdev/tenant-cli check --database-url $DATABASE_URL` (verify) ## Unique constraints When adding `tenant_id` to tables with existing UNIQUE constraints (e.g., `email`, `slug`, `name`), decide whether uniqueness should be global or per-tenant. Most apps want per-tenant: convert `UNIQUE(email)` to `UNIQUE(tenant_id, email)`. The library does not modify unique constraints automatically. In Drizzle: `(table) => [unique().on(table.tenantId, table.email)]`. In Prisma: `@@unique([tenantId, email])`. ## Full documentation (single fetch) For AI agents: [llms-full.txt](/llms-full.txt) contains the complete documentation in one file. Use it when you need all details without multiple fetches. ## Documentation (per-page) - Getting Started: https://docs.usebetter.dev/tenant/getting-started/ - Installation: https://docs.usebetter.dev/tenant/installation/ - Quick Start: https://docs.usebetter.dev/tenant/quick-start/ - Configuration (resolvers, tenant API, admin ops): https://docs.usebetter.dev/tenant/configuration/ - Framework Adapters: https://docs.usebetter.dev/tenant/adapters/ - Prisma Guide (schema, typing, migration, gotchas): https://docs.usebetter.dev/tenant/prisma/ - CLI & Migrations: https://docs.usebetter.dev/tenant/cli/ - Troubleshooting: https://docs.usebetter.dev/tenant/troubleshooting/ - Architecture Internals: https://docs.usebetter.dev/tenant/architecture/ --- # UseBetter Audit > Type-safe audit logging that auto-captures every database mutation via your ORM. Framework-agnostic, database-agnostic, plugin-driven. ## What it does UseBetter Audit is a library, not a service. It auto-captures every INSERT, UPDATE, and DELETE via your ORM, enriches mutations with human-readable labels and compliance tags, and stores everything in your own database. Supports Postgres, MySQL, and SQLite. ## Key concepts - **ORM adapter**: Connects audit to your database (`drizzleAuditAdapter`, `prismaAuditAdapter`) and writes entries to the `audit_logs` table. - **ORM proxy/extension**: Transparently intercepts mutations. Drizzle uses `withAuditProxy(db, audit.captureLog)`, Prisma uses `withAuditExtension(prisma, audit.captureLog)`. - **Framework middleware**: Extracts actor identity from HTTP requests (JWT, header, or cookie) via `AsyncLocalStorage`. Drop-in for Hono, Express, and Next.js. - **Enrichment**: Add human-readable labels, severity levels, compliance tags (`gdpr`, `soc2`, `hipaa`, `pci`), and field redaction to audit entries via `audit.enrich()`. - **Query builder**: Filter audit logs by actor, resource, operation, or time range via `audit.query()`. - **Retention**: Configure automatic purge of old entries with `retention: { days: 365 }` and `npx @usebetterdev/audit-cli purge`. - **CLI**: Generate migrations, check setup, view stats, export logs, and purge old entries (`@usebetterdev/audit-cli`). ## Installation ``` npm install @usebetterdev/audit ``` Subpath exports: `@usebetterdev/audit/drizzle`, `@usebetterdev/audit/prisma`, `@usebetterdev/audit/hono`, `@usebetterdev/audit/express`, `@usebetterdev/audit/next`. ## Setup (Drizzle + Hono) ```ts import { betterAudit } from "@usebetterdev/audit"; import { drizzleAuditAdapter, withAuditProxy } from "@usebetterdev/audit/drizzle"; import { betterAuditHono } from "@usebetterdev/audit/hono"; const audit = betterAudit({ database: drizzleAuditAdapter(db), auditTables: ["users", "orders"], }); const auditedDb = withAuditProxy(db, audit.captureLog); app.use("*", betterAuditHono()); // Use auditedDb instead of db — mutations are automatically captured ``` ## Setup (Prisma + Express) ```ts import { betterAudit } from "@usebetterdev/audit"; import { prismaAuditAdapter, withAuditExtension } from "@usebetterdev/audit/prisma"; import { betterAuditExpress } from "@usebetterdev/audit/express"; const audit = betterAudit({ database: prismaAuditAdapter(prisma), auditTables: ["users", "orders"], }); const auditedPrisma = withAuditExtension(prisma, audit.captureLog); app.use(betterAuditExpress()); // Use auditedPrisma instead of prisma — mutations are automatically captured ``` ## Enrichment ```ts audit.enrich("users", "DELETE", { label: "User account deleted", severity: "critical", compliance: ["gdpr", "soc2"], redact: ["password", "ssn"], }); ``` Enrichment options: `label`, `description` (dynamic function), `severity` (`low`/`medium`/`high`/`critical`), `compliance` (string array), `redact` (blocklist), `include` (allowlist), `notify`. ## Requirements - Node.js 22+ (also Bun and Deno) - PostgreSQL 13+, MySQL, or SQLite - TypeScript 5+ (recommended) ## CLI The CLI package is `@usebetterdev/audit-cli`. Always use the full scoped name: ``` npx @usebetterdev/audit-cli migrate --dry-run npx @usebetterdev/audit-cli migrate -o drizzle/_audit_logs.sql npx @usebetterdev/audit-cli check --database-url $DATABASE_URL npx @usebetterdev/audit-cli stats --database-url $DATABASE_URL npx @usebetterdev/audit-cli export --since 1h --format json npx @usebetterdev/audit-cli purge --dry-run ``` ## Full documentation (single fetch) For AI agents: [llms-full.txt](/llms-full.txt) contains the complete documentation in one file. Use it when you need all details without multiple fetches. ## Documentation (per-page) - Introduction: https://docs.usebetter.dev/audit/introduction/ - Installation: https://docs.usebetter.dev/audit/installation/ - Quick Start: https://docs.usebetter.dev/audit/quick-start/ - Configuration (options, enrichment, retention, hooks): https://docs.usebetter.dev/audit/configuration/ - Actor Context: https://docs.usebetter.dev/audit/actor-context/ - Enrichment: https://docs.usebetter.dev/audit/enrichment/ - Adapters (Drizzle, Prisma, Hono, Express, Next.js): https://docs.usebetter.dev/audit/adapters/ --- # UseBetter Console > Optional self-hosted admin dashboard backend for UseBetterDev products. No data leaves your server. ## What it does UseBetter Console provides a backend that connects your app to the Console UI at `console.usebetter.dev`. The hosted UI runs in your browser and makes API calls directly to YOUR server via `/.well-known/better/*` endpoints. No data leaves your infrastructure. ## Key concepts - **Optional**: Not required for any UseBetterDev product. Add it when you want a visual admin dashboard. - **Connection token**: SHA-256 hashed secret stored in your environment. Used to sign/verify session JWTs. Generated by `npx @usebetterdev/console-cli init`. - **Auto-approve**: Development-only auth. Issues a stateless JWT immediately — no database needed. - **Magic link**: Production auth. 3-step handshake (init → verify → claim) with email verification, brute-force protection, and 10-minute code expiry. - **Product registration**: Products register typed endpoints via `registerProduct()`. The Console UI discovers them via the `/capabilities` endpoint. - **Permissions**: Three levels (`read` < `write` < `admin`) with hierarchical access. ## Installation ``` npm install @usebetterdev/console ``` Subpath exports: `@usebetterdev/console/drizzle`, `@usebetterdev/console/hono`. CLI: `npx @usebetterdev/console-cli ` (no install needed). ## Setup (development) ```ts import { betterConsole } from "@usebetterdev/console"; import { createConsoleMiddleware } from "@usebetterdev/console/hono"; const consoleInstance = betterConsole({ connectionTokenHash: process.env.BETTER_CONSOLE_TOKEN_HASH!, sessions: { autoApprove: process.env.NODE_ENV === "development" }, }); app.use("*", createConsoleMiddleware(consoleInstance)); ``` ## Setup (production with magic link) ```ts import { betterConsole } from "@usebetterdev/console"; import { drizzleConsoleAdapter } from "@usebetterdev/console/drizzle"; import { createConsoleMiddleware } from "@usebetterdev/console/hono"; const consoleInstance = betterConsole({ adapter: drizzleConsoleAdapter(db), connectionTokenHash: process.env.BETTER_CONSOLE_TOKEN_HASH!, sessions: { magicLink: { allowedEmails: process.env.BETTER_CONSOLE_ALLOWED_EMAILS!, }, }, }); app.use("*", createConsoleMiddleware(consoleInstance)); ``` ## Requirements - Node.js 22+ (also Bun and Deno) - PostgreSQL 13+ (only for magic link auth) - TypeScript 5+ (recommended) - Hono (required for middleware) ## CLI ``` npx @usebetterdev/console-cli init --email admin@myapp.com npx @usebetterdev/console-cli migrate --dry-run npx @usebetterdev/console-cli token generate npx @usebetterdev/console-cli token rotate npx @usebetterdev/console-cli check --database-url $DATABASE_URL ``` ## Documentation (per-page) - Introduction: https://docs.usebetter.dev/console/getting-started/ - Installation: https://docs.usebetter.dev/console/installation/ - Quick Start: https://docs.usebetter.dev/console/quick-start/ - Configuration: https://docs.usebetter.dev/console/configuration/ - Authentication: https://docs.usebetter.dev/console/authentication/ - Product Registration: https://docs.usebetter.dev/console/product-registration/ - CLI: https://docs.usebetter.dev/console/cli/ - Troubleshooting: https://docs.usebetter.dev/console/troubleshooting/ - Architecture: https://docs.usebetter.dev/console/architecture/