drizzle-multitenant 1.0.1 → 1.0.3
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/.claude/settings.local.json +3 -1
- package/README.md +66 -2
- package/dist/integrations/nestjs/index.d.ts +159 -3
- package/dist/integrations/nestjs/index.js +240 -17
- package/dist/integrations/nestjs/index.js.map +1 -1
- package/package.json +1 -1
- package/roadmap.md +823 -0
package/README.md
CHANGED
|
@@ -134,7 +134,7 @@ import { TenantModule, InjectTenantDb } from 'drizzle-multitenant/nestjs';
|
|
|
134
134
|
})
|
|
135
135
|
export class AppModule {}
|
|
136
136
|
|
|
137
|
-
@Injectable()
|
|
137
|
+
@Injectable({ scope: Scope.REQUEST })
|
|
138
138
|
export class UserService {
|
|
139
139
|
constructor(@InjectTenantDb() private readonly db: TenantDb) {}
|
|
140
140
|
|
|
@@ -144,6 +144,59 @@ export class UserService {
|
|
|
144
144
|
}
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
+
#### Singleton Services (Cron Jobs, Event Handlers)
|
|
148
|
+
|
|
149
|
+
Use `TenantDbFactory` when you need to access tenant databases from singleton services:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
import { TenantDbFactory, InjectTenantDbFactory } from 'drizzle-multitenant/nestjs';
|
|
153
|
+
|
|
154
|
+
@Injectable() // Singleton - no scope needed
|
|
155
|
+
export class ReportService {
|
|
156
|
+
constructor(@InjectTenantDbFactory() private dbFactory: TenantDbFactory) {}
|
|
157
|
+
|
|
158
|
+
async generateReport(tenantId: string) {
|
|
159
|
+
const db = this.dbFactory.getDb(tenantId);
|
|
160
|
+
return db.select().from(reports);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Cron job example
|
|
165
|
+
@Injectable()
|
|
166
|
+
export class DailyReportCron {
|
|
167
|
+
constructor(@InjectTenantDbFactory() private dbFactory: TenantDbFactory) {}
|
|
168
|
+
|
|
169
|
+
@Cron('0 8 * * *')
|
|
170
|
+
async run() {
|
|
171
|
+
const tenants = await this.getTenantIds();
|
|
172
|
+
for (const tenantId of tenants) {
|
|
173
|
+
const db = this.dbFactory.getDb(tenantId);
|
|
174
|
+
await this.processReports(db);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### Debugging
|
|
181
|
+
|
|
182
|
+
The injected `TenantDb` provides debug utilities:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// Console output shows useful info
|
|
186
|
+
console.log(tenantDb);
|
|
187
|
+
// [TenantDb] tenant=123 schema=empresa_123
|
|
188
|
+
|
|
189
|
+
// Access debug information
|
|
190
|
+
console.log(tenantDb.__debug);
|
|
191
|
+
// { tenantId: '123', schemaName: 'empresa_123', isProxy: true, poolCount: 5 }
|
|
192
|
+
|
|
193
|
+
// Quick access
|
|
194
|
+
console.log(tenantDb.__tenantId); // '123'
|
|
195
|
+
|
|
196
|
+
// In tests
|
|
197
|
+
expect(tenantDb.__tenantId).toBe('expected-tenant');
|
|
198
|
+
```
|
|
199
|
+
|
|
147
200
|
## CLI Commands
|
|
148
201
|
|
|
149
202
|
```bash
|
|
@@ -222,13 +275,24 @@ const result = await query
|
|
|
222
275
|
|
|
223
276
|
| Decorator | Description |
|
|
224
277
|
|-----------|-------------|
|
|
225
|
-
| `@InjectTenantDb()` | Inject tenant database |
|
|
278
|
+
| `@InjectTenantDb()` | Inject tenant database (request-scoped) |
|
|
279
|
+
| `@InjectTenantDbFactory()` | Inject factory for singleton services |
|
|
226
280
|
| `@InjectSharedDb()` | Inject shared database |
|
|
227
281
|
| `@InjectTenantContext()` | Inject tenant context |
|
|
228
282
|
| `@InjectTenantManager()` | Inject tenant manager |
|
|
229
283
|
| `@RequiresTenant()` | Mark route as requiring tenant |
|
|
230
284
|
| `@PublicRoute()` | Mark route as public |
|
|
231
285
|
|
|
286
|
+
### TenantDbFactory Methods
|
|
287
|
+
|
|
288
|
+
| Method | Description |
|
|
289
|
+
|--------|-------------|
|
|
290
|
+
| `getDb(tenantId)` | Get Drizzle instance for tenant |
|
|
291
|
+
| `getSharedDb()` | Get shared database instance |
|
|
292
|
+
| `getSchemaName(tenantId)` | Get schema name for tenant |
|
|
293
|
+
| `getDebugInfo(tenantId)` | Get debug info (tenantId, schema, pool stats) |
|
|
294
|
+
| `getManager()` | Get underlying TenantManager |
|
|
295
|
+
|
|
232
296
|
## Requirements
|
|
233
297
|
|
|
234
298
|
- Node.js 18+
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import * as _nestjs_common from '@nestjs/common';
|
|
2
2
|
import { ModuleMetadata, Type, InjectionToken, DynamicModule, CanActivate, ExecutionContext, NestInterceptor, CallHandler, Provider } from '@nestjs/common';
|
|
3
3
|
import { Request } from 'express';
|
|
4
|
-
import { C as Config, T as TenantManager } from '../../types-DKVaTaIb.js';
|
|
5
|
-
export { S as SharedDb, a as TenantDb } from '../../types-DKVaTaIb.js';
|
|
4
|
+
import { C as Config, a as TenantDb, T as TenantManager, S as SharedDb } from '../../types-DKVaTaIb.js';
|
|
6
5
|
import { Reflector } from '@nestjs/core';
|
|
7
6
|
import { Observable } from 'rxjs';
|
|
8
7
|
import 'pg';
|
|
@@ -70,6 +69,32 @@ interface TenantRequest extends Request {
|
|
|
70
69
|
tenantContext?: NestTenantContext;
|
|
71
70
|
tenantId?: string;
|
|
72
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Debug information available on TenantDb proxy
|
|
74
|
+
*/
|
|
75
|
+
interface TenantDbDebugInfo$1 {
|
|
76
|
+
/** Current tenant ID (null if not resolved) */
|
|
77
|
+
tenantId: string | null;
|
|
78
|
+
/** Schema name for the tenant (null if not resolved) */
|
|
79
|
+
schemaName: string | null;
|
|
80
|
+
/** Whether this is a proxy object */
|
|
81
|
+
isProxy: boolean;
|
|
82
|
+
/** Number of active connection pools */
|
|
83
|
+
poolCount: number;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Extended TenantDb interface with debug utilities
|
|
87
|
+
*
|
|
88
|
+
* Available when using @InjectTenantDb() - provides debugging helpers
|
|
89
|
+
*/
|
|
90
|
+
interface TenantDbWithDebug<T extends Record<string, unknown> = Record<string, unknown>> extends TenantDb<T> {
|
|
91
|
+
/** Debug information about the current tenant connection */
|
|
92
|
+
__debug: TenantDbDebugInfo$1;
|
|
93
|
+
/** Current tenant ID (null if not resolved) */
|
|
94
|
+
__tenantId: string | null;
|
|
95
|
+
/** Whether this is a proxy object */
|
|
96
|
+
__isProxy: true;
|
|
97
|
+
}
|
|
73
98
|
|
|
74
99
|
/**
|
|
75
100
|
* NestJS module for multi-tenant support
|
|
@@ -127,6 +152,95 @@ declare class TenantModule {
|
|
|
127
152
|
*/
|
|
128
153
|
declare const DrizzleMultitenantModule: typeof TenantModule;
|
|
129
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Debug information for tenant database connections
|
|
157
|
+
*/
|
|
158
|
+
interface TenantDbDebugInfo {
|
|
159
|
+
tenantId: string;
|
|
160
|
+
schemaName: string;
|
|
161
|
+
isProxy: boolean;
|
|
162
|
+
poolCount: number;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Factory for creating tenant database connections
|
|
166
|
+
*
|
|
167
|
+
* Use this when you need to access tenant databases in singleton services
|
|
168
|
+
* (cron jobs, event handlers, background workers, etc.)
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```typescript
|
|
172
|
+
* // Service stays singleton - no scope change needed
|
|
173
|
+
* @Injectable()
|
|
174
|
+
* export class ReportService {
|
|
175
|
+
* constructor(private dbFactory: TenantDbFactory) {}
|
|
176
|
+
*
|
|
177
|
+
* async generateReport(tenantId: string) {
|
|
178
|
+
* const db = this.dbFactory.getDb(tenantId);
|
|
179
|
+
* return db.select().from(reports);
|
|
180
|
+
* }
|
|
181
|
+
* }
|
|
182
|
+
* ```
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```typescript
|
|
186
|
+
* // Cron job usage
|
|
187
|
+
* @Injectable()
|
|
188
|
+
* export class DailyReportCron {
|
|
189
|
+
* constructor(private dbFactory: TenantDbFactory) {}
|
|
190
|
+
*
|
|
191
|
+
* @Cron('0 8 * * *')
|
|
192
|
+
* async generateDailyReports() {
|
|
193
|
+
* const tenants = await this.getTenantIds();
|
|
194
|
+
* for (const tenantId of tenants) {
|
|
195
|
+
* const db = this.dbFactory.getDb(tenantId);
|
|
196
|
+
* await this.processReports(db);
|
|
197
|
+
* }
|
|
198
|
+
* }
|
|
199
|
+
* }
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
declare class TenantDbFactory<TTenantSchema extends Record<string, unknown> = Record<string, unknown>, TSharedSchema extends Record<string, unknown> = Record<string, unknown>> {
|
|
203
|
+
private readonly manager;
|
|
204
|
+
constructor(manager: TenantManager<TTenantSchema, TSharedSchema>);
|
|
205
|
+
/**
|
|
206
|
+
* Get a tenant database connection by tenant ID
|
|
207
|
+
*
|
|
208
|
+
* @param tenantId - The tenant identifier
|
|
209
|
+
* @returns The tenant-scoped Drizzle database instance
|
|
210
|
+
*
|
|
211
|
+
* @throws Error if tenantId is empty or invalid
|
|
212
|
+
*/
|
|
213
|
+
getDb(tenantId: string): TenantDb<TTenantSchema>;
|
|
214
|
+
/**
|
|
215
|
+
* Get the shared database connection
|
|
216
|
+
*
|
|
217
|
+
* @returns The shared Drizzle database instance
|
|
218
|
+
*/
|
|
219
|
+
getSharedDb(): SharedDb<TSharedSchema>;
|
|
220
|
+
/**
|
|
221
|
+
* Get the schema name for a tenant
|
|
222
|
+
*
|
|
223
|
+
* @param tenantId - The tenant identifier
|
|
224
|
+
* @returns The schema name for the tenant
|
|
225
|
+
*/
|
|
226
|
+
getSchemaName(tenantId: string): string;
|
|
227
|
+
/**
|
|
228
|
+
* Get debug information for a tenant database
|
|
229
|
+
*
|
|
230
|
+
* @param tenantId - The tenant identifier
|
|
231
|
+
* @returns Debug information including schema name and pool stats
|
|
232
|
+
*/
|
|
233
|
+
getDebugInfo(tenantId: string): TenantDbDebugInfo;
|
|
234
|
+
/**
|
|
235
|
+
* Get the underlying TenantManager instance
|
|
236
|
+
*
|
|
237
|
+
* Use this for advanced operations like pool management
|
|
238
|
+
*
|
|
239
|
+
* @returns The TenantManager instance
|
|
240
|
+
*/
|
|
241
|
+
getManager(): TenantManager<TTenantSchema, TSharedSchema>;
|
|
242
|
+
}
|
|
243
|
+
|
|
130
244
|
/**
|
|
131
245
|
* Inject the tenant database for the current request
|
|
132
246
|
*
|
|
@@ -256,6 +370,46 @@ declare const RequiresTenant: () => ClassDecorator & MethodDecorator;
|
|
|
256
370
|
* ```
|
|
257
371
|
*/
|
|
258
372
|
declare const PublicRoute: () => MethodDecorator;
|
|
373
|
+
/**
|
|
374
|
+
* Inject the TenantDbFactory for singleton services
|
|
375
|
+
*
|
|
376
|
+
* Use this when you need to access tenant databases in singleton services
|
|
377
|
+
* (cron jobs, event handlers, background workers, etc.) without requiring
|
|
378
|
+
* request scope.
|
|
379
|
+
*
|
|
380
|
+
* @example
|
|
381
|
+
* ```typescript
|
|
382
|
+
* // Service stays singleton - no scope change needed
|
|
383
|
+
* @Injectable()
|
|
384
|
+
* export class ReportService {
|
|
385
|
+
* constructor(@InjectTenantDbFactory() private dbFactory: TenantDbFactory) {}
|
|
386
|
+
*
|
|
387
|
+
* async generateReport(tenantId: string) {
|
|
388
|
+
* const db = this.dbFactory.getDb(tenantId);
|
|
389
|
+
* return db.select().from(reports);
|
|
390
|
+
* }
|
|
391
|
+
* }
|
|
392
|
+
* ```
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* ```typescript
|
|
396
|
+
* // Cron job usage
|
|
397
|
+
* @Injectable()
|
|
398
|
+
* export class DailyReportCron {
|
|
399
|
+
* constructor(@InjectTenantDbFactory() private dbFactory: TenantDbFactory) {}
|
|
400
|
+
*
|
|
401
|
+
* @Cron('0 8 * * *')
|
|
402
|
+
* async generateDailyReports() {
|
|
403
|
+
* const tenants = await this.getTenantIds();
|
|
404
|
+
* for (const tenantId of tenants) {
|
|
405
|
+
* const db = this.dbFactory.getDb(tenantId);
|
|
406
|
+
* await this.processReports(db);
|
|
407
|
+
* }
|
|
408
|
+
* }
|
|
409
|
+
* }
|
|
410
|
+
* ```
|
|
411
|
+
*/
|
|
412
|
+
declare const InjectTenantDbFactory: () => ParameterDecorator;
|
|
259
413
|
|
|
260
414
|
/**
|
|
261
415
|
* Guard that extracts and validates tenant ID from requests
|
|
@@ -382,5 +536,7 @@ declare const TENANT_ID_EXTRACTOR: unique symbol;
|
|
|
382
536
|
declare const REQUIRES_TENANT_KEY = "requires_tenant";
|
|
383
537
|
/** Metadata key for public routes (no tenant required) */
|
|
384
538
|
declare const IS_PUBLIC_KEY = "is_public_tenant";
|
|
539
|
+
/** Token for injecting the TenantDbFactory */
|
|
540
|
+
declare const TENANT_DB_FACTORY: unique symbol;
|
|
385
541
|
|
|
386
|
-
export { Config, DrizzleMultitenantModule, IS_PUBLIC_KEY, InjectSharedDb, InjectTenantContext, InjectTenantDb, InjectTenantManager, type NestTenantContext, PublicRoute, REQUIRES_TENANT_KEY, RequireTenantGuard, RequiresTenant, SHARED_DB, TENANT_CONTEXT, TENANT_DB, TENANT_ID_EXTRACTOR, TENANT_MANAGER, TENANT_MODULE_OPTIONS, TenantContextInterceptor, TenantCtx, TenantGuard, TenantId, type TenantIdExtractor, TenantLoggingInterceptor, TenantManager, TenantModule, type TenantModuleAsyncOptions, type TenantModuleOptions, type TenantModuleOptionsFactory, type TenantRequest, type TenantValidator, createAsyncProviders, createTenantProviders };
|
|
542
|
+
export { Config, DrizzleMultitenantModule, IS_PUBLIC_KEY, InjectSharedDb, InjectTenantContext, InjectTenantDb, InjectTenantDbFactory, InjectTenantManager, type NestTenantContext, PublicRoute, REQUIRES_TENANT_KEY, RequireTenantGuard, RequiresTenant, SHARED_DB, SharedDb, TENANT_CONTEXT, TENANT_DB, TENANT_DB_FACTORY, TENANT_ID_EXTRACTOR, TENANT_MANAGER, TENANT_MODULE_OPTIONS, TenantContextInterceptor, TenantCtx, TenantDb, type TenantDbDebugInfo$1 as TenantDbDebugInfo, TenantDbFactory, type TenantDbWithDebug, TenantGuard, TenantId, type TenantIdExtractor, TenantLoggingInterceptor, TenantManager, TenantModule, type TenantModuleAsyncOptions, type TenantModuleOptions, type TenantModuleOptionsFactory, type TenantRequest, type TenantValidator, createAsyncProviders, createTenantProviders };
|
|
@@ -9212,6 +9212,7 @@ var TENANT_MODULE_OPTIONS = /* @__PURE__ */ Symbol("TENANT_MODULE_OPTIONS");
|
|
|
9212
9212
|
var TENANT_ID_EXTRACTOR = /* @__PURE__ */ Symbol("TENANT_ID_EXTRACTOR");
|
|
9213
9213
|
var REQUIRES_TENANT_KEY = "requires_tenant";
|
|
9214
9214
|
var IS_PUBLIC_KEY = "is_public_tenant";
|
|
9215
|
+
var TENANT_DB_FACTORY = /* @__PURE__ */ Symbol("TENANT_DB_FACTORY");
|
|
9215
9216
|
|
|
9216
9217
|
// src/types.ts
|
|
9217
9218
|
var DEFAULT_CONFIG = {
|
|
@@ -9478,18 +9479,85 @@ function createTenantProviders() {
|
|
|
9478
9479
|
},
|
|
9479
9480
|
inject: [TENANT_MODULE_OPTIONS]
|
|
9480
9481
|
},
|
|
9481
|
-
// TenantDb - request scoped
|
|
9482
|
+
// TenantDb - request scoped with lazy resolution via Proxy
|
|
9483
|
+
// This fixes the timing issue where TENANT_DB is resolved before TenantGuard executes
|
|
9482
9484
|
{
|
|
9483
9485
|
provide: TENANT_DB,
|
|
9484
9486
|
scope: Scope.REQUEST,
|
|
9485
|
-
useFactory: (request, manager) => {
|
|
9486
|
-
const
|
|
9487
|
-
|
|
9488
|
-
|
|
9489
|
-
|
|
9490
|
-
|
|
9487
|
+
useFactory: (request, manager, options) => {
|
|
9488
|
+
const resolveTenantId = () => {
|
|
9489
|
+
let tenantId = request.tenantContext?.tenantId ?? request.tenantId;
|
|
9490
|
+
if (!tenantId && options.extractTenantId) {
|
|
9491
|
+
const extracted = options.extractTenantId(request);
|
|
9492
|
+
if (typeof extracted === "string") {
|
|
9493
|
+
tenantId = extracted;
|
|
9494
|
+
}
|
|
9495
|
+
}
|
|
9496
|
+
return tenantId;
|
|
9497
|
+
};
|
|
9498
|
+
const inspectSymbol = /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom");
|
|
9499
|
+
const debugProps = ["__debug", "__tenantId", "__isProxy"];
|
|
9500
|
+
return new Proxy({}, {
|
|
9501
|
+
get(_target, prop) {
|
|
9502
|
+
if (prop === Symbol.toStringTag) return "TenantDb";
|
|
9503
|
+
const resolvedTenantId = resolveTenantId();
|
|
9504
|
+
if (prop === inspectSymbol || prop === "toString") {
|
|
9505
|
+
return () => {
|
|
9506
|
+
if (resolvedTenantId) {
|
|
9507
|
+
return `[TenantDb] tenant=${resolvedTenantId} schema=${manager.getSchemaName(resolvedTenantId)}`;
|
|
9508
|
+
}
|
|
9509
|
+
return "[TenantDb] (no tenant context)";
|
|
9510
|
+
};
|
|
9511
|
+
}
|
|
9512
|
+
if (prop === "__debug") {
|
|
9513
|
+
return {
|
|
9514
|
+
tenantId: resolvedTenantId ?? null,
|
|
9515
|
+
schemaName: resolvedTenantId ? manager.getSchemaName(resolvedTenantId) : null,
|
|
9516
|
+
isProxy: true,
|
|
9517
|
+
poolCount: manager.getPoolCount()
|
|
9518
|
+
};
|
|
9519
|
+
}
|
|
9520
|
+
if (prop === "__tenantId") {
|
|
9521
|
+
return resolvedTenantId ?? null;
|
|
9522
|
+
}
|
|
9523
|
+
if (prop === "__isProxy") {
|
|
9524
|
+
return true;
|
|
9525
|
+
}
|
|
9526
|
+
if (!resolvedTenantId) {
|
|
9527
|
+
throw new Error(
|
|
9528
|
+
"[drizzle-multitenant] No tenant context found. Ensure the route has a tenant ID or use @PublicRoute() decorator."
|
|
9529
|
+
);
|
|
9530
|
+
}
|
|
9531
|
+
const db = manager.getDb(resolvedTenantId);
|
|
9532
|
+
return db[prop];
|
|
9533
|
+
},
|
|
9534
|
+
has(_target, prop) {
|
|
9535
|
+
if (debugProps.includes(prop)) return true;
|
|
9536
|
+
const tenantId = request.tenantContext?.tenantId ?? request.tenantId;
|
|
9537
|
+
if (!tenantId) return false;
|
|
9538
|
+
const db = manager.getDb(tenantId);
|
|
9539
|
+
return prop in db;
|
|
9540
|
+
},
|
|
9541
|
+
ownKeys() {
|
|
9542
|
+
const tenantId = request.tenantContext?.tenantId ?? request.tenantId;
|
|
9543
|
+
if (!tenantId) {
|
|
9544
|
+
return [...debugProps];
|
|
9545
|
+
}
|
|
9546
|
+
const db = manager.getDb(tenantId);
|
|
9547
|
+
return [.../* @__PURE__ */ new Set([...debugProps, ...Reflect.ownKeys(db)])];
|
|
9548
|
+
},
|
|
9549
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
9550
|
+
if (debugProps.includes(prop)) {
|
|
9551
|
+
return { configurable: true, enumerable: true, writable: false };
|
|
9552
|
+
}
|
|
9553
|
+
const tenantId = request.tenantContext?.tenantId ?? request.tenantId;
|
|
9554
|
+
if (!tenantId) return void 0;
|
|
9555
|
+
const db = manager.getDb(tenantId);
|
|
9556
|
+
return Object.getOwnPropertyDescriptor(db, prop);
|
|
9557
|
+
}
|
|
9558
|
+
});
|
|
9491
9559
|
},
|
|
9492
|
-
inject: [REQUEST, TENANT_MANAGER]
|
|
9560
|
+
inject: [REQUEST, TENANT_MANAGER, TENANT_MODULE_OPTIONS]
|
|
9493
9561
|
},
|
|
9494
9562
|
// SharedDb - singleton (doesn't need request scope)
|
|
9495
9563
|
{
|
|
@@ -9499,14 +9567,97 @@ function createTenantProviders() {
|
|
|
9499
9567
|
},
|
|
9500
9568
|
inject: [TENANT_MANAGER]
|
|
9501
9569
|
},
|
|
9502
|
-
// TenantContext - request scoped
|
|
9570
|
+
// TenantContext - request scoped with lazy resolution via Proxy
|
|
9571
|
+
// This fixes the timing issue where TENANT_CONTEXT is resolved before TenantGuard executes
|
|
9503
9572
|
{
|
|
9504
9573
|
provide: TENANT_CONTEXT,
|
|
9505
9574
|
scope: Scope.REQUEST,
|
|
9506
|
-
useFactory: (request) => {
|
|
9507
|
-
|
|
9575
|
+
useFactory: (request, manager, options) => {
|
|
9576
|
+
const inspectSymbol = /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom");
|
|
9577
|
+
const resolveContext = () => {
|
|
9578
|
+
if (request.tenantContext) {
|
|
9579
|
+
return {
|
|
9580
|
+
tenantId: request.tenantContext.tenantId,
|
|
9581
|
+
schemaName: request.tenantContext.schemaName
|
|
9582
|
+
};
|
|
9583
|
+
}
|
|
9584
|
+
let tenantId = request.tenantId;
|
|
9585
|
+
if (!tenantId && options.extractTenantId) {
|
|
9586
|
+
const extracted = options.extractTenantId(request);
|
|
9587
|
+
if (typeof extracted === "string") {
|
|
9588
|
+
tenantId = extracted;
|
|
9589
|
+
}
|
|
9590
|
+
}
|
|
9591
|
+
if (!tenantId) return null;
|
|
9592
|
+
return {
|
|
9593
|
+
tenantId,
|
|
9594
|
+
schemaName: manager.getSchemaName(tenantId)
|
|
9595
|
+
};
|
|
9596
|
+
};
|
|
9597
|
+
return new Proxy({}, {
|
|
9598
|
+
get(_target, prop) {
|
|
9599
|
+
if (prop === Symbol.toStringTag) return "TenantContext";
|
|
9600
|
+
const ctx = resolveContext();
|
|
9601
|
+
if (prop === inspectSymbol || prop === "toString") {
|
|
9602
|
+
return () => {
|
|
9603
|
+
if (ctx) {
|
|
9604
|
+
return `[TenantContext] tenant=${ctx.tenantId} schema=${ctx.schemaName}`;
|
|
9605
|
+
}
|
|
9606
|
+
return "[TenantContext] (no tenant context)";
|
|
9607
|
+
};
|
|
9608
|
+
}
|
|
9609
|
+
if (prop === "__debug") {
|
|
9610
|
+
return {
|
|
9611
|
+
tenantId: ctx?.tenantId ?? null,
|
|
9612
|
+
schemaName: ctx?.schemaName ?? null,
|
|
9613
|
+
isProxy: true,
|
|
9614
|
+
hasContext: !!request.tenantContext
|
|
9615
|
+
};
|
|
9616
|
+
}
|
|
9617
|
+
if (prop === "__isProxy") {
|
|
9618
|
+
return true;
|
|
9619
|
+
}
|
|
9620
|
+
if (!ctx) {
|
|
9621
|
+
throw new Error(
|
|
9622
|
+
"[drizzle-multitenant] No tenant context found. Ensure the route has a tenant ID or use @PublicRoute() decorator."
|
|
9623
|
+
);
|
|
9624
|
+
}
|
|
9625
|
+
if (prop === "tenantId") return ctx.tenantId;
|
|
9626
|
+
if (prop === "schemaName") return ctx.schemaName;
|
|
9627
|
+
if (request.tenantContext) {
|
|
9628
|
+
return request.tenantContext[prop];
|
|
9629
|
+
}
|
|
9630
|
+
return void 0;
|
|
9631
|
+
},
|
|
9632
|
+
has(_target, prop) {
|
|
9633
|
+
if (prop === "__debug" || prop === "__isProxy") return true;
|
|
9634
|
+
if (request.tenantContext) {
|
|
9635
|
+
return prop in request.tenantContext;
|
|
9636
|
+
}
|
|
9637
|
+
return prop === "tenantId" || prop === "schemaName";
|
|
9638
|
+
},
|
|
9639
|
+
ownKeys() {
|
|
9640
|
+
const baseKeys = ["tenantId", "schemaName", "__debug", "__isProxy"];
|
|
9641
|
+
if (request.tenantContext) {
|
|
9642
|
+
return [.../* @__PURE__ */ new Set([...baseKeys, ...Reflect.ownKeys(request.tenantContext)])];
|
|
9643
|
+
}
|
|
9644
|
+
return baseKeys;
|
|
9645
|
+
},
|
|
9646
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
9647
|
+
if (prop === "__debug" || prop === "__isProxy") {
|
|
9648
|
+
return { configurable: true, enumerable: true, writable: false };
|
|
9649
|
+
}
|
|
9650
|
+
if (request.tenantContext) {
|
|
9651
|
+
return Object.getOwnPropertyDescriptor(request.tenantContext, prop);
|
|
9652
|
+
}
|
|
9653
|
+
if (prop === "tenantId" || prop === "schemaName") {
|
|
9654
|
+
return { configurable: true, enumerable: true, writable: true };
|
|
9655
|
+
}
|
|
9656
|
+
return void 0;
|
|
9657
|
+
}
|
|
9658
|
+
});
|
|
9508
9659
|
},
|
|
9509
|
-
inject: [REQUEST]
|
|
9660
|
+
inject: [REQUEST, TENANT_MANAGER, TENANT_MODULE_OPTIONS]
|
|
9510
9661
|
}
|
|
9511
9662
|
];
|
|
9512
9663
|
}
|
|
@@ -9713,6 +9864,73 @@ var TenantLoggingInterceptor = class {
|
|
|
9713
9864
|
TenantLoggingInterceptor = __decorateClass([
|
|
9714
9865
|
Injectable()
|
|
9715
9866
|
], TenantLoggingInterceptor);
|
|
9867
|
+
var TenantDbFactory = class {
|
|
9868
|
+
constructor(manager) {
|
|
9869
|
+
this.manager = manager;
|
|
9870
|
+
}
|
|
9871
|
+
/**
|
|
9872
|
+
* Get a tenant database connection by tenant ID
|
|
9873
|
+
*
|
|
9874
|
+
* @param tenantId - The tenant identifier
|
|
9875
|
+
* @returns The tenant-scoped Drizzle database instance
|
|
9876
|
+
*
|
|
9877
|
+
* @throws Error if tenantId is empty or invalid
|
|
9878
|
+
*/
|
|
9879
|
+
getDb(tenantId) {
|
|
9880
|
+
if (!tenantId || typeof tenantId !== "string") {
|
|
9881
|
+
throw new Error(
|
|
9882
|
+
"[drizzle-multitenant] TenantDbFactory.getDb() requires a valid tenantId string."
|
|
9883
|
+
);
|
|
9884
|
+
}
|
|
9885
|
+
return this.manager.getDb(tenantId);
|
|
9886
|
+
}
|
|
9887
|
+
/**
|
|
9888
|
+
* Get the shared database connection
|
|
9889
|
+
*
|
|
9890
|
+
* @returns The shared Drizzle database instance
|
|
9891
|
+
*/
|
|
9892
|
+
getSharedDb() {
|
|
9893
|
+
return this.manager.getSharedDb();
|
|
9894
|
+
}
|
|
9895
|
+
/**
|
|
9896
|
+
* Get the schema name for a tenant
|
|
9897
|
+
*
|
|
9898
|
+
* @param tenantId - The tenant identifier
|
|
9899
|
+
* @returns The schema name for the tenant
|
|
9900
|
+
*/
|
|
9901
|
+
getSchemaName(tenantId) {
|
|
9902
|
+
return this.manager.getSchemaName(tenantId);
|
|
9903
|
+
}
|
|
9904
|
+
/**
|
|
9905
|
+
* Get debug information for a tenant database
|
|
9906
|
+
*
|
|
9907
|
+
* @param tenantId - The tenant identifier
|
|
9908
|
+
* @returns Debug information including schema name and pool stats
|
|
9909
|
+
*/
|
|
9910
|
+
getDebugInfo(tenantId) {
|
|
9911
|
+
return {
|
|
9912
|
+
tenantId,
|
|
9913
|
+
schemaName: this.manager.getSchemaName(tenantId),
|
|
9914
|
+
isProxy: false,
|
|
9915
|
+
// Factory returns direct db, not proxy
|
|
9916
|
+
poolCount: this.manager.getPoolCount()
|
|
9917
|
+
};
|
|
9918
|
+
}
|
|
9919
|
+
/**
|
|
9920
|
+
* Get the underlying TenantManager instance
|
|
9921
|
+
*
|
|
9922
|
+
* Use this for advanced operations like pool management
|
|
9923
|
+
*
|
|
9924
|
+
* @returns The TenantManager instance
|
|
9925
|
+
*/
|
|
9926
|
+
getManager() {
|
|
9927
|
+
return this.manager;
|
|
9928
|
+
}
|
|
9929
|
+
};
|
|
9930
|
+
TenantDbFactory = __decorateClass([
|
|
9931
|
+
Injectable(),
|
|
9932
|
+
__decorateParam(0, Inject(TENANT_MANAGER))
|
|
9933
|
+
], TenantDbFactory);
|
|
9716
9934
|
|
|
9717
9935
|
// src/integrations/nestjs/tenant.module.ts
|
|
9718
9936
|
var TenantModule = class {
|
|
@@ -9728,7 +9946,8 @@ var TenantModule = class {
|
|
|
9728
9946
|
...createTenantProviders(),
|
|
9729
9947
|
Reflector,
|
|
9730
9948
|
TenantGuard,
|
|
9731
|
-
TenantContextInterceptor
|
|
9949
|
+
TenantContextInterceptor,
|
|
9950
|
+
TenantDbFactory
|
|
9732
9951
|
];
|
|
9733
9952
|
if (options.isGlobal) {
|
|
9734
9953
|
providers.push(
|
|
@@ -9749,7 +9968,8 @@ var TenantModule = class {
|
|
|
9749
9968
|
TENANT_MODULE_OPTIONS,
|
|
9750
9969
|
...createTenantProviders(),
|
|
9751
9970
|
TenantGuard,
|
|
9752
|
-
TenantContextInterceptor
|
|
9971
|
+
TenantContextInterceptor,
|
|
9972
|
+
TenantDbFactory
|
|
9753
9973
|
]
|
|
9754
9974
|
};
|
|
9755
9975
|
if (options.isGlobal) {
|
|
@@ -9770,7 +9990,8 @@ var TenantModule = class {
|
|
|
9770
9990
|
...createTenantProviders(),
|
|
9771
9991
|
Reflector,
|
|
9772
9992
|
TenantGuard,
|
|
9773
|
-
TenantContextInterceptor
|
|
9993
|
+
TenantContextInterceptor,
|
|
9994
|
+
TenantDbFactory
|
|
9774
9995
|
];
|
|
9775
9996
|
const module = {
|
|
9776
9997
|
module: TenantModule,
|
|
@@ -9780,7 +10001,8 @@ var TenantModule = class {
|
|
|
9780
10001
|
TENANT_MODULE_OPTIONS,
|
|
9781
10002
|
...createTenantProviders(),
|
|
9782
10003
|
TenantGuard,
|
|
9783
|
-
TenantContextInterceptor
|
|
10004
|
+
TenantContextInterceptor,
|
|
10005
|
+
TenantDbFactory
|
|
9784
10006
|
]
|
|
9785
10007
|
};
|
|
9786
10008
|
if (options.isGlobal) {
|
|
@@ -9822,7 +10044,8 @@ var TenantId = createParamDecorator(
|
|
|
9822
10044
|
);
|
|
9823
10045
|
var RequiresTenant = () => SetMetadata(REQUIRES_TENANT_KEY, true);
|
|
9824
10046
|
var PublicRoute = () => SetMetadata(IS_PUBLIC_KEY, true);
|
|
10047
|
+
var InjectTenantDbFactory = () => Inject(TenantDbFactory);
|
|
9825
10048
|
|
|
9826
|
-
export { DrizzleMultitenantModule, IS_PUBLIC_KEY, InjectSharedDb, InjectTenantContext, InjectTenantDb, InjectTenantManager, PublicRoute, REQUIRES_TENANT_KEY, RequireTenantGuard, RequiresTenant, SHARED_DB, TENANT_CONTEXT, TENANT_DB, TENANT_ID_EXTRACTOR, TENANT_MANAGER, TENANT_MODULE_OPTIONS, TenantContextInterceptor, TenantCtx, TenantGuard, TenantId, TenantLoggingInterceptor, TenantModule, createAsyncProviders, createTenantProviders };
|
|
10049
|
+
export { DrizzleMultitenantModule, IS_PUBLIC_KEY, InjectSharedDb, InjectTenantContext, InjectTenantDb, InjectTenantDbFactory, InjectTenantManager, PublicRoute, REQUIRES_TENANT_KEY, RequireTenantGuard, RequiresTenant, SHARED_DB, TENANT_CONTEXT, TENANT_DB, TENANT_DB_FACTORY, TENANT_ID_EXTRACTOR, TENANT_MANAGER, TENANT_MODULE_OPTIONS, TenantContextInterceptor, TenantCtx, TenantDbFactory, TenantGuard, TenantId, TenantLoggingInterceptor, TenantModule, createAsyncProviders, createTenantProviders };
|
|
9827
10050
|
//# sourceMappingURL=index.js.map
|
|
9828
10051
|
//# sourceMappingURL=index.js.map
|