@veloxts/orm 0.6.27 → 0.6.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Tenant-specific error classes for @veloxts/orm/tenant
3
+ */
4
+ /**
5
+ * Base error class for tenant-related errors
6
+ */
7
+ export class TenantError extends Error {
8
+ code;
9
+ tenantId;
10
+ schemaName;
11
+ constructor(message, code, options) {
12
+ super(message, { cause: options?.cause });
13
+ this.name = 'TenantError';
14
+ this.code = code;
15
+ this.tenantId = options?.tenantId;
16
+ this.schemaName = options?.schemaName;
17
+ // Maintains proper stack trace for where error was thrown (V8 engines)
18
+ if (Error.captureStackTrace) {
19
+ Error.captureStackTrace(this, TenantError);
20
+ }
21
+ }
22
+ }
23
+ /**
24
+ * Tenant not found in database
25
+ */
26
+ export class TenantNotFoundError extends TenantError {
27
+ constructor(tenantId) {
28
+ super(`Tenant not found: ${tenantId}`, 'TENANT_NOT_FOUND', { tenantId });
29
+ this.name = 'TenantNotFoundError';
30
+ }
31
+ }
32
+ /**
33
+ * Tenant is suspended and cannot be accessed
34
+ */
35
+ export class TenantSuspendedError extends TenantError {
36
+ constructor(tenantId) {
37
+ super(`Tenant is suspended: ${tenantId}`, 'TENANT_SUSPENDED', { tenantId });
38
+ this.name = 'TenantSuspendedError';
39
+ }
40
+ }
41
+ /**
42
+ * Tenant is pending activation
43
+ */
44
+ export class TenantPendingError extends TenantError {
45
+ constructor(tenantId) {
46
+ super(`Tenant is pending activation: ${tenantId}`, 'TENANT_PENDING', { tenantId });
47
+ this.name = 'TenantPendingError';
48
+ }
49
+ }
50
+ /**
51
+ * Tenant is currently being migrated
52
+ */
53
+ export class TenantMigratingError extends TenantError {
54
+ constructor(tenantId) {
55
+ super(`Tenant is currently migrating: ${tenantId}`, 'TENANT_MIGRATING', { tenantId });
56
+ this.name = 'TenantMigratingError';
57
+ }
58
+ }
59
+ /**
60
+ * Tenant ID missing from request context
61
+ */
62
+ export class TenantIdMissingError extends TenantError {
63
+ constructor() {
64
+ super('Tenant ID is required but was not found in request context', 'TENANT_ID_MISSING');
65
+ this.name = 'TenantIdMissingError';
66
+ }
67
+ }
68
+ /**
69
+ * User does not have access to the requested tenant
70
+ *
71
+ * SECURITY: This error is thrown when tenant access verification fails.
72
+ * It prevents tenant isolation bypass attacks where a user might try
73
+ * to access a tenant they don't belong to by manipulating JWT claims.
74
+ */
75
+ export class TenantAccessDeniedError extends TenantError {
76
+ userId;
77
+ constructor(tenantId, userId) {
78
+ super(`Access denied to tenant: ${tenantId}`, 'TENANT_ACCESS_DENIED', { tenantId });
79
+ this.name = 'TenantAccessDeniedError';
80
+ this.userId = userId;
81
+ }
82
+ }
83
+ /**
84
+ * Schema creation failed
85
+ */
86
+ export class SchemaCreateError extends TenantError {
87
+ constructor(schemaName, cause) {
88
+ super(`Failed to create schema: ${schemaName}`, 'SCHEMA_CREATE_FAILED', { schemaName, cause });
89
+ this.name = 'SchemaCreateError';
90
+ }
91
+ }
92
+ /**
93
+ * Schema deletion failed
94
+ */
95
+ export class SchemaDeleteError extends TenantError {
96
+ constructor(schemaName, cause) {
97
+ super(`Failed to delete schema: ${schemaName}`, 'SCHEMA_DELETE_FAILED', { schemaName, cause });
98
+ this.name = 'SchemaDeleteError';
99
+ }
100
+ }
101
+ /**
102
+ * Schema migration failed
103
+ */
104
+ export class SchemaMigrateError extends TenantError {
105
+ constructor(schemaName, cause) {
106
+ super(`Failed to migrate schema: ${schemaName}`, 'SCHEMA_MIGRATE_FAILED', {
107
+ schemaName,
108
+ cause,
109
+ });
110
+ this.name = 'SchemaMigrateError';
111
+ }
112
+ }
113
+ /**
114
+ * Schema not found
115
+ */
116
+ export class SchemaNotFoundError extends TenantError {
117
+ constructor(schemaName) {
118
+ super(`Schema not found: ${schemaName}`, 'SCHEMA_NOT_FOUND', { schemaName });
119
+ this.name = 'SchemaNotFoundError';
120
+ }
121
+ }
122
+ /**
123
+ * Schema list operation failed
124
+ */
125
+ export class SchemaListError extends TenantError {
126
+ constructor(cause) {
127
+ super('Failed to list schemas', 'SCHEMA_LIST_FAILED', { cause });
128
+ this.name = 'SchemaListError';
129
+ }
130
+ }
131
+ /**
132
+ * Schema already exists
133
+ */
134
+ export class SchemaAlreadyExistsError extends TenantError {
135
+ constructor(schemaName) {
136
+ super(`Schema already exists: ${schemaName}`, 'SCHEMA_ALREADY_EXISTS', { schemaName });
137
+ this.name = 'SchemaAlreadyExistsError';
138
+ }
139
+ }
140
+ /**
141
+ * Client pool has reached maximum capacity
142
+ */
143
+ export class ClientPoolExhaustedError extends TenantError {
144
+ constructor(maxClients) {
145
+ super(`Client pool exhausted: maximum ${maxClients} clients reached`, 'CLIENT_POOL_EXHAUSTED');
146
+ this.name = 'ClientPoolExhaustedError';
147
+ }
148
+ }
149
+ /**
150
+ * Failed to create database client
151
+ */
152
+ export class ClientCreateError extends TenantError {
153
+ constructor(schemaName, cause) {
154
+ super(`Failed to create client for schema: ${schemaName}`, 'CLIENT_CREATE_FAILED', {
155
+ schemaName,
156
+ cause,
157
+ });
158
+ this.name = 'ClientCreateError';
159
+ }
160
+ }
161
+ /**
162
+ * Failed to disconnect database client
163
+ */
164
+ export class ClientDisconnectError extends TenantError {
165
+ constructor(schemaName, cause) {
166
+ super(`Failed to disconnect client for schema: ${schemaName}`, 'CLIENT_DISCONNECT_FAILED', {
167
+ schemaName,
168
+ cause,
169
+ });
170
+ this.name = 'ClientDisconnectError';
171
+ }
172
+ }
173
+ /**
174
+ * Invalid tenant slug format
175
+ */
176
+ export class InvalidSlugError extends TenantError {
177
+ constructor(slug, reason) {
178
+ super(`Invalid tenant slug '${slug}': ${reason}`, 'INVALID_SLUG');
179
+ this.name = 'InvalidSlugError';
180
+ }
181
+ }
182
+ /**
183
+ * Tenant provisioning failed
184
+ */
185
+ export class ProvisionError extends TenantError {
186
+ constructor(slug, cause) {
187
+ super(`Failed to provision tenant: ${slug}`, 'PROVISION_FAILED', { cause });
188
+ this.name = 'ProvisionError';
189
+ }
190
+ }
191
+ /**
192
+ * Tenant deprovisioning failed
193
+ */
194
+ export class DeprovisionError extends TenantError {
195
+ constructor(tenantId, cause) {
196
+ super(`Failed to deprovision tenant: ${tenantId}`, 'DEPROVISION_FAILED', { tenantId, cause });
197
+ this.name = 'DeprovisionError';
198
+ }
199
+ }
200
+ /**
201
+ * Type guard to check if an error is a TenantError
202
+ */
203
+ export function isTenantError(error) {
204
+ return error instanceof TenantError;
205
+ }
206
+ /**
207
+ * Get error based on tenant status
208
+ */
209
+ export function getTenantStatusError(tenantId, status) {
210
+ switch (status) {
211
+ case 'suspended':
212
+ return new TenantSuspendedError(tenantId);
213
+ case 'pending':
214
+ return new TenantPendingError(tenantId);
215
+ case 'migrating':
216
+ return new TenantMigratingError(tenantId);
217
+ default:
218
+ return null;
219
+ }
220
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @veloxts/orm/tenant - Multi-tenancy support for VeloxTS
3
+ *
4
+ * Schema-per-tenant isolation with PostgreSQL schemas.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import {
9
+ * createTenantClientPool,
10
+ * createTenantSchemaManager,
11
+ * createTenantProvisioner,
12
+ * createTenant,
13
+ * } from '@veloxts/orm/tenant';
14
+ *
15
+ * // 1. Create schema manager
16
+ * const schemaManager = createTenantSchemaManager({
17
+ * databaseUrl: process.env.DATABASE_URL!,
18
+ * });
19
+ *
20
+ * // 2. Create client pool
21
+ * const clientPool = createTenantClientPool({
22
+ * baseDatabaseUrl: process.env.DATABASE_URL!,
23
+ * createClient: (schemaName) => {
24
+ * const url = `${process.env.DATABASE_URL}?schema=${schemaName}`;
25
+ * const adapter = new PrismaPg({ connectionString: url });
26
+ * return new PrismaClient({ adapter });
27
+ * },
28
+ * });
29
+ *
30
+ * // 3. Create provisioner
31
+ * const provisioner = createTenantProvisioner({
32
+ * schemaManager,
33
+ * publicClient: publicDb,
34
+ * clientPool,
35
+ * });
36
+ *
37
+ * // 4. Create tenant middleware
38
+ * const tenant = createTenant({
39
+ * loadTenant: (id) => publicDb.tenant.findUnique({ where: { id } }),
40
+ * clientPool,
41
+ * publicClient: publicDb,
42
+ * });
43
+ *
44
+ * // 5. Use in procedures
45
+ * const getUsers = procedure()
46
+ * .use(auth.requireAuth())
47
+ * .use(tenant.middleware())
48
+ * .query(({ ctx }) => ctx.db.user.findMany());
49
+ * ```
50
+ *
51
+ * @module @veloxts/orm/tenant
52
+ */
53
+ export type { CachedClient, SchemaCreateResult, SchemaMigrateResult, Tenant, TenantClientPool, TenantClientPoolConfig, TenantContext, TenantContextInput, TenantDatabaseClient, TenantMiddlewareConfig, TenantPoolStats, TenantProvisioner, TenantProvisionerConfig, TenantProvisionInput, TenantProvisionResult, TenantSchemaManager, TenantSchemaManagerConfig, TenantStatus, } from './types.js';
54
+ export { ClientCreateError, ClientDisconnectError, ClientPoolExhaustedError, DeprovisionError, getTenantStatusError, InvalidSlugError, isTenantError, ProvisionError, SchemaAlreadyExistsError, SchemaCreateError, SchemaDeleteError, SchemaListError, SchemaMigrateError, SchemaNotFoundError, TenantAccessDeniedError, TenantError, type TenantErrorCode, TenantIdMissingError, TenantMigratingError, TenantNotFoundError, TenantPendingError, TenantSuspendedError, } from './errors.js';
55
+ export { createTenantClientPool } from './client-pool.js';
56
+ export { createTenant, createTenantMiddleware, getTenantOrThrow, hasTenant, type MiddlewareFunction, type TenantNamespace, } from './middleware.js';
57
+ export { createTenantSchemaManager, slugToSchemaName, } from './schema/manager.js';
58
+ export { createTenantProvisioner } from './schema/provisioner.js';
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @veloxts/orm/tenant - Multi-tenancy support for VeloxTS
3
+ *
4
+ * Schema-per-tenant isolation with PostgreSQL schemas.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import {
9
+ * createTenantClientPool,
10
+ * createTenantSchemaManager,
11
+ * createTenantProvisioner,
12
+ * createTenant,
13
+ * } from '@veloxts/orm/tenant';
14
+ *
15
+ * // 1. Create schema manager
16
+ * const schemaManager = createTenantSchemaManager({
17
+ * databaseUrl: process.env.DATABASE_URL!,
18
+ * });
19
+ *
20
+ * // 2. Create client pool
21
+ * const clientPool = createTenantClientPool({
22
+ * baseDatabaseUrl: process.env.DATABASE_URL!,
23
+ * createClient: (schemaName) => {
24
+ * const url = `${process.env.DATABASE_URL}?schema=${schemaName}`;
25
+ * const adapter = new PrismaPg({ connectionString: url });
26
+ * return new PrismaClient({ adapter });
27
+ * },
28
+ * });
29
+ *
30
+ * // 3. Create provisioner
31
+ * const provisioner = createTenantProvisioner({
32
+ * schemaManager,
33
+ * publicClient: publicDb,
34
+ * clientPool,
35
+ * });
36
+ *
37
+ * // 4. Create tenant middleware
38
+ * const tenant = createTenant({
39
+ * loadTenant: (id) => publicDb.tenant.findUnique({ where: { id } }),
40
+ * clientPool,
41
+ * publicClient: publicDb,
42
+ * });
43
+ *
44
+ * // 5. Use in procedures
45
+ * const getUsers = procedure()
46
+ * .use(auth.requireAuth())
47
+ * .use(tenant.middleware())
48
+ * .query(({ ctx }) => ctx.db.user.findMany());
49
+ * ```
50
+ *
51
+ * @module @veloxts/orm/tenant
52
+ */
53
+ // ============================================================================
54
+ // Errors
55
+ // ============================================================================
56
+ export { ClientCreateError, ClientDisconnectError,
57
+ // Client pool errors
58
+ ClientPoolExhaustedError, DeprovisionError, getTenantStatusError,
59
+ // Validation errors
60
+ InvalidSlugError,
61
+ // Utilities
62
+ isTenantError,
63
+ // Provisioning errors
64
+ ProvisionError, SchemaAlreadyExistsError,
65
+ // Schema errors
66
+ SchemaCreateError, SchemaDeleteError, SchemaListError, SchemaMigrateError, SchemaNotFoundError,
67
+ // Authorization error
68
+ TenantAccessDeniedError,
69
+ // Base error
70
+ TenantError, TenantIdMissingError, TenantMigratingError,
71
+ // Tenant errors
72
+ TenantNotFoundError, TenantPendingError, TenantSuspendedError, } from './errors.js';
73
+ // ============================================================================
74
+ // Client Pool
75
+ // ============================================================================
76
+ export { createTenantClientPool } from './client-pool.js';
77
+ // ============================================================================
78
+ // Middleware
79
+ // ============================================================================
80
+ export { createTenant, createTenantMiddleware, getTenantOrThrow, hasTenant, } from './middleware.js';
81
+ // ============================================================================
82
+ // Schema Management
83
+ // ============================================================================
84
+ export { createTenantSchemaManager, slugToSchemaName, } from './schema/manager.js';
85
+ export { createTenantProvisioner } from './schema/provisioner.js';
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Tenant middleware for procedure handlers
3
+ *
4
+ * Resolves tenant from JWT claims and provides tenant-scoped database access.
5
+ */
6
+ import type { DatabaseClient } from '../types.js';
7
+ import type { Tenant, TenantClientPool, TenantContext, TenantContextInput, TenantMiddlewareConfig } from './types.js';
8
+ /**
9
+ * Middleware function type compatible with tRPC/procedure middleware
10
+ */
11
+ export type MiddlewareFunction<TInput, TOutput> = (opts: {
12
+ ctx: TInput;
13
+ next: (opts: {
14
+ ctx: TOutput;
15
+ }) => Promise<unknown>;
16
+ }) => Promise<unknown>;
17
+ /**
18
+ * Create tenant middleware for procedures
19
+ *
20
+ * This middleware:
21
+ * 1. Extracts tenantId from JWT claims (ctx.auth.token.tenantId)
22
+ * 2. Loads tenant from public schema
23
+ * 3. Validates tenant status (must be 'active')
24
+ * 4. Gets a tenant-scoped database client from the pool
25
+ * 5. Extends context with tenant info and scoped db client
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const tenantMw = createTenantMiddleware({
30
+ * loadTenant: async (tenantId) => publicDb.tenant.findUnique({ where: { id: tenantId } }),
31
+ * clientPool,
32
+ * publicClient: publicDb,
33
+ * });
34
+ *
35
+ * // Use in procedures
36
+ * const getUsers = procedure()
37
+ * .use(auth.requireAuth())
38
+ * .use(tenantMw)
39
+ * .query(({ ctx }) => ctx.db.user.findMany());
40
+ * ```
41
+ */
42
+ export declare function createTenantMiddleware<TClient extends DatabaseClient>(config: TenantMiddlewareConfig<TClient>): MiddlewareFunction<TenantContextInput, TenantContextInput & TenantContext<TClient>>;
43
+ /**
44
+ * Create a tenant namespace object with middleware and helpers
45
+ *
46
+ * Provides a cleaner API similar to auth package pattern.
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * const tenant = createTenant({
51
+ * loadTenant: async (id) => publicDb.tenant.findUnique({ where: { id } }),
52
+ * clientPool,
53
+ * publicClient: publicDb,
54
+ * });
55
+ *
56
+ * // Use the middleware
57
+ * const procedure = baseProcedure
58
+ * .use(auth.requireAuth())
59
+ * .use(tenant.middleware());
60
+ * ```
61
+ */
62
+ export declare function createTenant<TClient extends DatabaseClient>(config: TenantMiddlewareConfig<TClient>): TenantNamespace<TClient>;
63
+ /**
64
+ * Tenant namespace interface
65
+ */
66
+ export interface TenantNamespace<TClient extends DatabaseClient> {
67
+ middleware: () => MiddlewareFunction<TenantContextInput, TenantContextInput & TenantContext<TClient>>;
68
+ optionalMiddleware: () => MiddlewareFunction<TenantContextInput, TenantContextInput & Partial<TenantContext<TClient>>>;
69
+ getClientPool: () => TenantClientPool<TClient>;
70
+ getPublicClient: () => TClient | undefined;
71
+ loadTenant: (tenantId: string) => Promise<Tenant | null>;
72
+ getClient: (schemaName: string) => Promise<TClient>;
73
+ }
74
+ /**
75
+ * Type guard to check if context has tenant
76
+ */
77
+ export declare function hasTenant<TClient extends DatabaseClient>(ctx: TenantContextInput): ctx is TenantContextInput & TenantContext<TClient>;
78
+ /**
79
+ * Get tenant from context or throw
80
+ */
81
+ export declare function getTenantOrThrow<TClient extends DatabaseClient>(ctx: TenantContextInput): TenantContext<TClient>;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Tenant middleware for procedure handlers
3
+ *
4
+ * Resolves tenant from JWT claims and provides tenant-scoped database access.
5
+ */
6
+ import { getTenantStatusError, TenantAccessDeniedError, TenantIdMissingError, TenantNotFoundError, } from './errors.js';
7
+ /**
8
+ * Create tenant middleware for procedures
9
+ *
10
+ * This middleware:
11
+ * 1. Extracts tenantId from JWT claims (ctx.auth.token.tenantId)
12
+ * 2. Loads tenant from public schema
13
+ * 3. Validates tenant status (must be 'active')
14
+ * 4. Gets a tenant-scoped database client from the pool
15
+ * 5. Extends context with tenant info and scoped db client
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const tenantMw = createTenantMiddleware({
20
+ * loadTenant: async (tenantId) => publicDb.tenant.findUnique({ where: { id: tenantId } }),
21
+ * clientPool,
22
+ * publicClient: publicDb,
23
+ * });
24
+ *
25
+ * // Use in procedures
26
+ * const getUsers = procedure()
27
+ * .use(auth.requireAuth())
28
+ * .use(tenantMw)
29
+ * .query(({ ctx }) => ctx.db.user.findMany());
30
+ * ```
31
+ */
32
+ export function createTenantMiddleware(config) {
33
+ const { loadTenant, clientPool, publicClient, getTenantId = defaultGetTenantId, allowNoTenant = false, verifyTenantAccess, } = config;
34
+ return async ({ ctx, next }) => {
35
+ // Extract tenant ID from context
36
+ const tenantId = getTenantId(ctx);
37
+ if (!tenantId) {
38
+ if (allowNoTenant) {
39
+ // Continue without tenant context
40
+ return next({
41
+ ctx: ctx,
42
+ });
43
+ }
44
+ throw new TenantIdMissingError();
45
+ }
46
+ // Load tenant from database
47
+ const tenant = await loadTenant(tenantId);
48
+ if (!tenant) {
49
+ throw new TenantNotFoundError(tenantId);
50
+ }
51
+ // Validate tenant status
52
+ const statusError = getTenantStatusError(tenantId, tenant.status);
53
+ if (statusError) {
54
+ throw statusError;
55
+ }
56
+ // SECURITY: Verify user has access to this tenant
57
+ // This prevents tenant isolation bypass where a user could
58
+ // manipulate JWT claims to access another tenant's data
59
+ if (verifyTenantAccess) {
60
+ const hasAccess = await verifyTenantAccess(ctx, tenant);
61
+ if (!hasAccess) {
62
+ throw new TenantAccessDeniedError(tenantId, ctx.user?.id);
63
+ }
64
+ }
65
+ // Get tenant-scoped database client
66
+ const db = await clientPool.getClient(tenant.schemaName);
67
+ // Build extended context
68
+ const extendedCtx = {
69
+ ...ctx,
70
+ tenant,
71
+ db,
72
+ ...(publicClient ? { publicDb: publicClient } : {}),
73
+ };
74
+ try {
75
+ // Continue to next middleware/handler
76
+ return await next({ ctx: extendedCtx });
77
+ }
78
+ finally {
79
+ // Release client back to pool
80
+ clientPool.releaseClient(tenant.schemaName);
81
+ }
82
+ };
83
+ }
84
+ /**
85
+ * Default tenant ID extractor from JWT claims
86
+ */
87
+ function defaultGetTenantId(ctx) {
88
+ return ctx.auth?.token?.tenantId;
89
+ }
90
+ /**
91
+ * Create a tenant namespace object with middleware and helpers
92
+ *
93
+ * Provides a cleaner API similar to auth package pattern.
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * const tenant = createTenant({
98
+ * loadTenant: async (id) => publicDb.tenant.findUnique({ where: { id } }),
99
+ * clientPool,
100
+ * publicClient: publicDb,
101
+ * });
102
+ *
103
+ * // Use the middleware
104
+ * const procedure = baseProcedure
105
+ * .use(auth.requireAuth())
106
+ * .use(tenant.middleware());
107
+ * ```
108
+ */
109
+ export function createTenant(config) {
110
+ const middleware = createTenantMiddleware(config);
111
+ const { clientPool, publicClient, loadTenant } = config;
112
+ return {
113
+ /**
114
+ * Middleware that requires tenant context
115
+ */
116
+ middleware: () => middleware,
117
+ /**
118
+ * Middleware that makes tenant context optional
119
+ */
120
+ optionalMiddleware: () => createTenantMiddleware({
121
+ ...config,
122
+ allowNoTenant: true,
123
+ }),
124
+ /**
125
+ * Get the client pool for direct access
126
+ */
127
+ getClientPool: () => clientPool,
128
+ /**
129
+ * Get the public client
130
+ */
131
+ getPublicClient: () => publicClient,
132
+ /**
133
+ * Load a tenant by ID
134
+ */
135
+ loadTenant,
136
+ /**
137
+ * Get a tenant-scoped client directly
138
+ */
139
+ getClient: (schemaName) => clientPool.getClient(schemaName),
140
+ };
141
+ }
142
+ /**
143
+ * Type guard to check if context has tenant
144
+ */
145
+ export function hasTenant(ctx) {
146
+ return (ctx !== null &&
147
+ typeof ctx === 'object' &&
148
+ 'tenant' in ctx &&
149
+ ctx.tenant !== null &&
150
+ typeof ctx.tenant === 'object' &&
151
+ 'db' in ctx &&
152
+ ctx.db !== null);
153
+ }
154
+ /**
155
+ * Get tenant from context or throw
156
+ */
157
+ export function getTenantOrThrow(ctx) {
158
+ if (!hasTenant(ctx)) {
159
+ throw new TenantIdMissingError();
160
+ }
161
+ return ctx;
162
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Tenant schema manager for PostgreSQL schema lifecycle operations
3
+ *
4
+ * Handles:
5
+ * - Creating tenant schemas
6
+ * - Running Prisma migrations per schema
7
+ * - Listing and deleting schemas
8
+ *
9
+ * SECURITY:
10
+ * - All SQL queries use parameterized queries via pg library
11
+ * - Prisma migrations use execFile (no shell) with validated paths
12
+ * - Input validation prevents injection attacks
13
+ */
14
+ import type { TenantSchemaManager as ITenantSchemaManager, TenantSchemaManagerConfig } from '../types.js';
15
+ /**
16
+ * Create a tenant schema manager
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const schemaManager = createTenantSchemaManager({
21
+ * databaseUrl: process.env.DATABASE_URL!,
22
+ * schemaPrefix: 'tenant_',
23
+ * });
24
+ *
25
+ * // Create a new schema
26
+ * const result = await schemaManager.createSchema('acme-corp');
27
+ * // result.schemaName === 'tenant_acme_corp'
28
+ *
29
+ * // Run migrations
30
+ * await schemaManager.migrateSchema('tenant_acme_corp');
31
+ * ```
32
+ */
33
+ export declare function createTenantSchemaManager(config: TenantSchemaManagerConfig): ITenantSchemaManager;
34
+ /**
35
+ * Utility to convert a slug to a schema name without creating a manager
36
+ */
37
+ export declare function slugToSchemaName(slug: string, prefix?: string): string;