@veloxts/orm 0.6.27 → 0.6.29
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.
- package/CHANGELOG.md +16 -0
- package/GUIDE.md +102 -1
- package/dist/tenant/client-pool.d.ts +39 -0
- package/dist/tenant/client-pool.d.ts.map +1 -0
- package/dist/tenant/client-pool.js +234 -0
- package/dist/tenant/client-pool.js.map +1 -0
- package/dist/tenant/errors.d.ts +142 -0
- package/dist/tenant/errors.d.ts.map +1 -0
- package/dist/tenant/errors.js +221 -0
- package/dist/tenant/errors.js.map +1 -0
- package/dist/tenant/index.d.ts +59 -0
- package/dist/tenant/index.d.ts.map +1 -0
- package/dist/tenant/index.js +86 -0
- package/dist/tenant/index.js.map +1 -0
- package/dist/tenant/middleware.d.ts +82 -0
- package/dist/tenant/middleware.d.ts.map +1 -0
- package/dist/tenant/middleware.js +163 -0
- package/dist/tenant/middleware.js.map +1 -0
- package/dist/tenant/schema/manager.d.ts +38 -0
- package/dist/tenant/schema/manager.d.ts.map +1 -0
- package/dist/tenant/schema/manager.js +335 -0
- package/dist/tenant/schema/manager.js.map +1 -0
- package/dist/tenant/schema/provisioner.d.ts +33 -0
- package/dist/tenant/schema/provisioner.d.ts.map +1 -0
- package/dist/tenant/schema/provisioner.js +282 -0
- package/dist/tenant/schema/provisioner.js.map +1 -0
- package/dist/tenant/types.d.ts +432 -0
- package/dist/tenant/types.d.ts.map +1 -0
- package/dist/tenant/types.js +36 -0
- package/dist/tenant/types.js.map +1 -0
- package/package.json +10 -2
|
@@ -0,0 +1,221 @@
|
|
|
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
|
+
}
|
|
221
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/tenant/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
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';
|
|
59
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tenant/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAMH,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EAEnB,MAAM,EACN,gBAAgB,EAEhB,sBAAsB,EACtB,aAAa,EACb,kBAAkB,EAElB,oBAAoB,EAEpB,sBAAsB,EACtB,eAAe,EACf,iBAAiB,EAEjB,uBAAuB,EACvB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EAEnB,yBAAyB,EACzB,YAAY,GACb,MAAM,YAAY,CAAC;AAMpB,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EAErB,wBAAwB,EACxB,gBAAgB,EAChB,oBAAoB,EAEpB,gBAAgB,EAEhB,aAAa,EAEb,cAAc,EACd,wBAAwB,EAExB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EAEnB,uBAAuB,EAEvB,WAAW,EACX,KAAK,eAAe,EACpB,oBAAoB,EACpB,oBAAoB,EAEpB,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAMrB,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAM1D,OAAO,EACL,YAAY,EACZ,sBAAsB,EACtB,gBAAgB,EAChB,SAAS,EACT,KAAK,kBAAkB,EACvB,KAAK,eAAe,GACrB,MAAM,iBAAiB,CAAC;AAMzB,OAAO,EACL,yBAAyB,EACzB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
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';
|
|
86
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tenant/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAiCH,+EAA+E;AAC/E,SAAS;AACT,+EAA+E;AAE/E,OAAO,EACL,iBAAiB,EACjB,qBAAqB;AACrB,qBAAqB;AACrB,wBAAwB,EACxB,gBAAgB,EAChB,oBAAoB;AACpB,oBAAoB;AACpB,gBAAgB;AAChB,YAAY;AACZ,aAAa;AACb,sBAAsB;AACtB,cAAc,EACd,wBAAwB;AACxB,gBAAgB;AAChB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,mBAAmB;AACnB,sBAAsB;AACtB,uBAAuB;AACvB,aAAa;AACb,WAAW,EAEX,oBAAoB,EACpB,oBAAoB;AACpB,gBAAgB;AAChB,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAErB,+EAA+E;AAC/E,cAAc;AACd,+EAA+E;AAE/E,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,+EAA+E;AAC/E,aAAa;AACb,+EAA+E;AAE/E,OAAO,EACL,YAAY,EACZ,sBAAsB,EACtB,gBAAgB,EAChB,SAAS,GAGV,MAAM,iBAAiB,CAAC;AAEzB,+EAA+E;AAC/E,oBAAoB;AACpB,+EAA+E;AAE/E,OAAO,EACL,yBAAyB,EACzB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
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>;
|
|
82
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/tenant/middleware.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAOlD,OAAO,KAAK,EACV,MAAM,EACN,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,MAAM,MAAM,kBAAkB,CAAC,MAAM,EAAE,OAAO,IAAI,CAAC,IAAI,EAAE;IACvD,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,CAAC,IAAI,EAAE;QAAE,GAAG,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACpD,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAEvB;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,SAAS,cAAc,EACnE,MAAM,EAAE,sBAAsB,CAAC,OAAO,CAAC,GACtC,kBAAkB,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC,CAkErF;AASD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,OAAO,SAAS,cAAc,EACzD,MAAM,EAAE,sBAAsB,CAAC,OAAO,CAAC,GACtC,eAAe,CAAC,OAAO,CAAC,CAuC1B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe,CAAC,OAAO,SAAS,cAAc;IAC7D,UAAU,EAAE,MAAM,kBAAkB,CAClC,kBAAkB,EAClB,kBAAkB,GAAG,aAAa,CAAC,OAAO,CAAC,CAC5C,CAAC;IACF,kBAAkB,EAAE,MAAM,kBAAkB,CAC1C,kBAAkB,EAClB,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CACrD,CAAC;IACF,aAAa,EAAE,MAAM,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC/C,eAAe,EAAE,MAAM,OAAO,GAAG,SAAS,CAAC;IAC3C,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzD,SAAS,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACrD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,SAAS,cAAc,EACtD,GAAG,EAAE,kBAAkB,GACtB,GAAG,IAAI,kBAAkB,GAAG,aAAa,CAAC,OAAO,CAAC,CAUpD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,SAAS,cAAc,EAC7D,GAAG,EAAE,kBAAkB,GACtB,aAAa,CAAC,OAAO,CAAC,CAKxB"}
|
|
@@ -0,0 +1,163 @@
|
|
|
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
|
+
}
|
|
163
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/tenant/middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
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;
|
|
38
|
+
//# sourceMappingURL=manager.d.ts.map
|