import { CollectionInterceptor, DispatchBus, InterceptorContext } from '@happyvertical/smrt-core'; import { isTenancyEnabled } from './enabled-state.js'; /** * Policy controlling what happens when raw SQL is executed against a * tenant-scoped class without an explicit bypass. * * - `'throw'` — Raises a `TenantIsolationError` (most secure; default). * - `'warn'` — Logs a `console.warn` but allows the query to proceed (useful * during migration periods). * - `'allow'` — Silently allows the query; not recommended for production. * * @see TenantInterceptorOptions.rawQueryPolicy * @see enableTenancy */ export type RawQueryPolicy = 'throw' | 'warn' | 'allow'; /** * Configuration options accepted by `createTenantInterceptor()` and * `enableTenancy()`. * * All options are optional; reasonable defaults are applied. The callback * hooks (`onRawQuery`, `onMissingContext`, `onIsolationViolation`) are useful * for logging and alerting without altering the enforcement behaviour. * * @see createTenantInterceptor * @see enableTenancy */ export interface TenantInterceptorOptions { /** * Policy for raw SQL queries on tenant-scoped classes * - 'throw': Throw error (most secure, default) * - 'warn': Log warning but allow (for migration) * - 'allow': Silently allow (not recommended for production) * @default 'throw' */ rawQueryPolicy?: RawQueryPolicy; /** * Called when a raw query is attempted on a tenant-scoped class * Useful for logging/auditing */ onRawQuery?: (className: string, sql: string, context: InterceptorContext) => void; /** * Called when tenant context is missing for a tenant-scoped operation */ onMissingContext?: (className: string, operation: string, context: InterceptorContext) => void; /** * Called when an isolation violation is detected */ onIsolationViolation?: (className: string, expectedTenantId: string, actualTenantId: string, context: InterceptorContext) => void; /** * DispatchBus instance for emitting provisioning events on lifecycle changes. * When provided along with directoryClasses, afterSave/afterDelete hooks * emit dispatches like `directory.membership.created`. */ dispatchBus?: DispatchBus; /** * Class names to emit directory dispatches for on save/delete lifecycle events. * Only classes listed here will trigger dispatch emissions. * @example ['Tenant', 'Membership', 'User'] */ directoryClasses?: string[]; } /** * Create a `CollectionInterceptor` that enforces tenant isolation on all * `SmrtCollection` operations. * * The returned interceptor hooks into the smrt-core `GlobalInterceptors` * pipeline at priority 100 (runs before all other interceptors) and * handles the following lifecycle hooks: * * | Hook | Behaviour | * |---------------|-----------| * | `beforeList` | Injects tenant filter into `WHERE`; validates explicit filters. | * | `beforeGet` | Resolves string lookups (id vs slug) and adds the tenant predicate. | * | `beforeSave` | Auto-populates `tenantId`; validates existing values. | * | `beforeDelete`| Validates the instance's `tenantId` matches context. | * | `beforeQuery` | Enforces `rawQueryPolicy` on raw SQL calls. | * | `afterSave` | Emits `directory..created/updated` via `dispatchBus`. | * | `afterDelete` | Emits `directory..deleted` via `dispatchBus`. | * * Use `enableTenancy()` to register the interceptor globally. Call this * directly only when you need multiple interceptor instances (e.g., for * isolated tests or feature flags). * * @param options - Configuration for the interceptor. * @returns A `CollectionInterceptor` ready to be registered with * `GlobalInterceptors.register()`. * * @example * ```typescript * import { createTenantInterceptor } from '@happyvertical/smrt-tenancy'; * import { GlobalInterceptors } from '@happyvertical/smrt-core'; * * const interceptor = createTenantInterceptor({ rawQueryPolicy: 'warn' }); * GlobalInterceptors.register(interceptor); * ``` * * @see enableTenancy * @see TenantInterceptorOptions */ export declare function createTenantInterceptor(options?: TenantInterceptorOptions): CollectionInterceptor; /** * Enable tenant enforcement globally * * Call this once at application startup to enable automatic tenant isolation. * * @param options - Configuration options * * @example * ```typescript * // In your app initialization * import { enableTenancy } from '@happyvertical/smrt-tenancy'; * * enableTenancy({ * rawQueryPolicy: 'throw', * onMissingContext: (className, operation) => { * console.error(`Missing tenant context for ${operation} on ${className}`); * } * }); * ``` */ export declare function enableTenancy(options?: TenantInterceptorOptions): void; /** * Disable global tenant enforcement. * * Unregisters the interceptor previously installed by `enableTenancy()` and * resets the internal enabled flag so `enableTenancy()` can be called again. * Idempotent — safe to call even when tenancy was never enabled. * * Common use-cases: * - Test teardown (via `resetTenancy()`). * - Temporarily disabling tenancy before reconfiguring with new options. * * @example * ```typescript * afterAll(() => { * disableTenancy(); * }); * ``` * * @see enableTenancy * @see isTenancyEnabled * @see resetTenancy */ export declare function disableTenancy(): void; /** * Return `true` if tenant enforcement is currently active. * * Reflects whether `enableTenancy()` has been called and the interceptor has not * yet been removed by `disableTenancy()`. Re-exported from `enabled-state.ts` * (the shared leaf module) so the public API surface is unchanged. * * @see enableTenancy * @see disableTenancy */ export { isTenancyEnabled }; //# sourceMappingURL=interceptor.d.ts.map