@happyvertical/smrt-tenancy
v0.36.0
Published
Production-ready multi-tenancy framework for SMRT with automatic tenant isolation and enforcement
Maintainers
Readme
@happyvertical/smrt-tenancy
Multi-tenancy for SMRT with AsyncLocalStorage context propagation, automatic query filtering, and tenant ID population.
Installation
pnpm install @happyvertical/smrt-tenancyUsage
import { enableTenancy, TenantScoped, tenantId, withTenant } from '@happyvertical/smrt-tenancy';
import { smrt, SmrtObject } from '@happyvertical/smrt-core';
// 1. Enable tenancy globally (once at app startup)
enableTenancy();
// 2. Mark classes as tenant-scoped
@smrt()
@TenantScoped({ mode: 'optional' })
class Document extends SmrtObject {
@tenantId({ nullable: true })
tenantId: string | null = null;
title: string = '';
}
// 3. Wrap operations in tenant context
await withTenant({ tenantId: 'tenant-123' }, async () => {
const docs = await collection.list({ where: { status: 'active' } });
// Executes: WHERE tenant_id = 'tenant-123' AND status = 'active'
});API
Decorators
| Export | Description |
|--------|-------------|
| TenantScoped(options?) | Class decorator. Modes: 'required' (default) or 'optional' |
| tenantId(options?) | Property decorator for the tenant ID field |
Context Runners
| Export | Description |
|--------|-------------|
| withTenant(ctx, fn) | Run code scoped to a tenant |
| withTenantSync(ctx, fn) | Synchronous variant |
| withSystemContext(fn) | Bypass all tenant checks (admin/migrations) |
| withSuperAdminBypass(fn) | Keep tenant context but disable auto-filtering |
| enterTenantContext(ctx) | Enter context without callback (for middleware) |
Context Accessors
| Export | Description |
|--------|-------------|
| getCurrentTenant() | Get current tenant context (may be undefined) |
| getTenantId() | Get tenant ID string (may be undefined) |
| requireTenant() | Get tenant context or throw |
| requireTenantId() | Get tenant ID or throw |
| hasTenantContext() | Check if in tenant context |
| isSystemContext() | Check if in system context |
| isSuperAdminBypass() | Check if super admin bypass is active |
| TenantContext | AsyncLocalStorage instance (advanced use) |
Errors
TenantContextError (missing required context), TenantIsolationError (tenant mismatch).
Interceptor
| Export | Description |
|--------|-------------|
| enableTenancy() | Register tenant interceptor globally |
| disableTenancy() | Remove tenant interceptor |
| isTenancyEnabled() | Check if tenancy is active |
| createTenantInterceptor(options?) | Create interceptor manually |
Framework Adapters
| Export | Description |
|--------|-------------|
| createSvelteKitHandle(options) | SvelteKit hooks.server.ts handler |
| createExpressMiddleware(options) | Express middleware |
| createCliContext(options) | CLI context with run(), runWithTenant(), runAsSystem() |
Registry (Advanced)
| Export | Description |
|--------|-------------|
| isTenantScopedClass(name) | Check if a class is tenant-scoped |
| getTenantScopedConfig(name) | Get tenant config for a class |
| getAllTenantScopedClasses() | List all registered tenant-scoped classes |
| registerTenantScopedClass() | Register a class programmatically |
| unregisterTenantScopedClass() | Remove a class from registry |
| clearTenantScopedRegistry() | Clear all registrations |
Testing
| Export | Description |
|--------|-------------|
| setupTestTenancy(options?) | Enable tenancy for tests |
| resetTenancy() | Clean up tenancy state between tests |
| createTestTenantContext(ctx, fn) | Run test code in tenant context |
| testTenantIsolation(tenantIds, fn) | Verify isolation between tenants |
| assertTenantContextRequired(fn) | Assert operation requires context |
| assertTenantIsolationViolation(fn) | Assert operation violates isolation |
Dependencies
@happyvertical/smrt-core-- SmrtObject, SmrtCollection, GlobalInterceptors@happyvertical/sql-- database operations@happyvertical/utils-- utility functions
Optional peers: svelte, @happyvertical/smrt-users, @happyvertical/smrt-svelte
License
MIT
