@vritti/api-sdk 0.0.2 → 0.0.5

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/dist/index.js CHANGED
@@ -1,14 +1,18 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
 
4
- // src/database/database.module.ts
5
- import { Global, Module } from "@nestjs/common";
4
+ // src/auth/auth-config.module.ts
5
+ import { Global as Global2, Module as Module2 } from "@nestjs/common";
6
+ import { ConfigModule, ConfigService as ConfigService2 } from "@nestjs/config";
7
+ import { APP_GUARD } from "@nestjs/core";
8
+ import { JwtModule } from "@nestjs/jwt";
6
9
 
7
- // src/database/constants.ts
8
- var DATABASE_MODULE_OPTIONS = Symbol("DATABASE_MODULE_OPTIONS");
10
+ // src/request/request.module.ts
11
+ import { Global, Module } from "@nestjs/common";
9
12
 
10
- // src/database/services/primary-database.service.ts
11
- import { Inject, Injectable, InternalServerErrorException, Logger } from "@nestjs/common";
13
+ // src/request/services/request.service.ts
14
+ import { Inject, Injectable, Scope } from "@nestjs/common";
15
+ import { REQUEST } from "@nestjs/core";
12
16
  function _ts_decorate(decorators, target, key, desc) {
13
17
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
14
18
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -26,6 +30,141 @@ function _ts_param(paramIndex, decorator) {
26
30
  };
27
31
  }
28
32
  __name(_ts_param, "_ts_param");
33
+ var RequestService = class {
34
+ static {
35
+ __name(this, "RequestService");
36
+ }
37
+ request;
38
+ constructor(request) {
39
+ this.request = request;
40
+ }
41
+ /**
42
+ * Extract tenant identifier from request headers
43
+ * Priority: x-tenant-id > x-subdomain
44
+ * @returns Tenant identifier or null if not found
45
+ */
46
+ getTenantIdentifier() {
47
+ const getHeader = /* @__PURE__ */ __name((key) => {
48
+ const value = this.request.headers?.[key];
49
+ return Array.isArray(value) ? value[0] : value;
50
+ }, "getHeader");
51
+ return getHeader("x-tenant-id") || getHeader("x-subdomain") || null;
52
+ }
53
+ /**
54
+ * Extract access token from Authorization header
55
+ * Format: "Bearer <token>"
56
+ * @returns Access token or null if not found
57
+ */
58
+ getAccessToken() {
59
+ const authHeader = this.request.headers?.authorization;
60
+ if (!authHeader) {
61
+ return null;
62
+ }
63
+ const [type, token] = authHeader.split(" ") ?? [];
64
+ return type === "Bearer" && token ? token : null;
65
+ }
66
+ /**
67
+ * Extract refresh token from session-id cookie
68
+ * Cookie name: session-id
69
+ * @returns Refresh token or null if not found
70
+ */
71
+ getRefreshToken() {
72
+ try {
73
+ const cookies = this.request.cookies;
74
+ if (cookies && typeof cookies === "object") {
75
+ const sessionId = cookies["session-id"];
76
+ if (sessionId) {
77
+ return sessionId;
78
+ }
79
+ }
80
+ return null;
81
+ } catch (error) {
82
+ return null;
83
+ }
84
+ }
85
+ /**
86
+ * Get a specific header value
87
+ * @param key Header key
88
+ * @returns Header value (string, array, or undefined)
89
+ */
90
+ getHeader(key) {
91
+ return this.request.headers?.[key];
92
+ }
93
+ /**
94
+ * Get all headers
95
+ * @returns Record of all headers
96
+ */
97
+ getAllHeaders() {
98
+ return this.request.headers || {};
99
+ }
100
+ };
101
+ RequestService = _ts_decorate([
102
+ Injectable({
103
+ scope: Scope.REQUEST
104
+ }),
105
+ _ts_param(0, Inject(REQUEST)),
106
+ _ts_metadata("design:type", Function),
107
+ _ts_metadata("design:paramtypes", [
108
+ typeof FastifyRequest === "undefined" ? Object : FastifyRequest
109
+ ])
110
+ ], RequestService);
111
+
112
+ // src/request/request.module.ts
113
+ function _ts_decorate2(decorators, target, key, desc) {
114
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
115
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
116
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
117
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
118
+ }
119
+ __name(_ts_decorate2, "_ts_decorate");
120
+ var RequestModule = class {
121
+ static {
122
+ __name(this, "RequestModule");
123
+ }
124
+ };
125
+ RequestModule = _ts_decorate2([
126
+ Global(),
127
+ Module({
128
+ providers: [
129
+ RequestService
130
+ ],
131
+ exports: [
132
+ RequestService
133
+ ]
134
+ })
135
+ ], RequestModule);
136
+
137
+ // src/auth/guards/vritti-auth.guard.ts
138
+ import { Injectable as Injectable3, Logger as Logger2, Scope as Scope2, UnauthorizedException } from "@nestjs/common";
139
+ import { ConfigService } from "@nestjs/config";
140
+ import { Reflector } from "@nestjs/core";
141
+ import { JwtService } from "@nestjs/jwt";
142
+ import * as jwt from "jsonwebtoken";
143
+
144
+ // src/database/services/primary-database.service.ts
145
+ import { Inject as Inject2, Injectable as Injectable2, InternalServerErrorException, Logger } from "@nestjs/common";
146
+
147
+ // src/database/constants.ts
148
+ var DATABASE_MODULE_OPTIONS = Symbol("DATABASE_MODULE_OPTIONS");
149
+
150
+ // src/database/services/primary-database.service.ts
151
+ function _ts_decorate3(decorators, target, key, desc) {
152
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
153
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
154
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
155
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
156
+ }
157
+ __name(_ts_decorate3, "_ts_decorate");
158
+ function _ts_metadata2(k, v) {
159
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
160
+ }
161
+ __name(_ts_metadata2, "_ts_metadata");
162
+ function _ts_param2(paramIndex, decorator) {
163
+ return function(target, key) {
164
+ decorator(target, key, paramIndex);
165
+ };
166
+ }
167
+ __name(_ts_param2, "_ts_param");
29
168
  var PrimaryDatabaseService = class _PrimaryDatabaseService {
30
169
  static {
31
170
  __name(this, "PrimaryDatabaseService");
@@ -123,10 +262,13 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
123
262
  id: tenantIdentifier
124
263
  },
125
264
  {
126
- subDomain: tenantIdentifier
265
+ subdomain: tenantIdentifier
127
266
  }
128
267
  ],
129
268
  status: "ACTIVE"
269
+ },
270
+ include: {
271
+ databaseConfig: true
130
272
  }
131
273
  });
132
274
  if (!tenant) {
@@ -136,16 +278,18 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
136
278
  const info = {
137
279
  id: tenant.id,
138
280
  subdomain: tenant.subdomain,
139
- type: tenant.type,
281
+ type: tenant.dbType,
140
282
  status: tenant.status,
141
- schemaName: tenant.schemaName || void 0,
142
- databaseName: tenant.databaseName || void 0,
143
- databaseHost: tenant.databaseHost || void 0,
144
- databasePort: tenant.databasePort || void 0,
145
- databaseUsername: tenant.databaseUsername ? this.decrypt(tenant.databaseUsername) : void 0,
146
- databasePassword: tenant.databasePassword ? this.decrypt(tenant.databasePassword) : void 0,
147
- databaseSslMode: tenant.databaseSslMode || void 0,
148
- connectionPoolSize: tenant.connectionPoolSize || void 0
283
+ // For SHARED tenants: schema name
284
+ schemaName: tenant.databaseConfig?.dbSchema || void 0,
285
+ // For DEDICATED tenants: database configuration from TenantDatabaseConfig table
286
+ databaseName: tenant.databaseConfig?.dbName || void 0,
287
+ databaseHost: tenant.databaseConfig?.dbHost || void 0,
288
+ databasePort: tenant.databaseConfig?.dbPort || void 0,
289
+ databaseUsername: tenant.databaseConfig?.dbUsername ? this.decrypt(tenant.databaseConfig.dbUsername) : void 0,
290
+ databasePassword: tenant.databaseConfig?.dbPassword ? this.decrypt(tenant.databaseConfig.dbPassword) : void 0,
291
+ databaseSslMode: tenant.databaseConfig?.dbSslMode || void 0,
292
+ connectionPoolSize: tenant.databaseConfig?.connectionPoolSize || void 0
149
293
  };
150
294
  this.cacheInfo(info);
151
295
  return info;
@@ -190,16 +334,15 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
190
334
  this.logger.log(`Cleared ${size} cached tenant configs`);
191
335
  }
192
336
  /**
193
- * Get primary database client for direct database access
194
- *
195
- * This is useful for platform admin operations (creating tenants, billing, etc.)
337
+ * Get the Prisma client for the primary database.
338
+ * This is a synchronous property that returns the initialized Prisma client.
196
339
  *
197
340
  * @returns Primary database client instance
198
341
  * @throws Error if primary database client is not initialized
199
342
  */
200
- getPrimaryDbClient() {
343
+ get prismaClient() {
201
344
  if (!this.primaryDbClient) {
202
- throw new Error("Primary database client not initialized. Are you in gateway mode?");
345
+ throw new Error("Primary database client not initialized");
203
346
  }
204
347
  return this.primaryDbClient;
205
348
  }
@@ -221,24 +364,312 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
221
364
  }
222
365
  }
223
366
  };
224
- PrimaryDatabaseService = _ts_decorate([
225
- Injectable(),
226
- _ts_param(0, Inject(DATABASE_MODULE_OPTIONS)),
227
- _ts_metadata("design:type", Function),
228
- _ts_metadata("design:paramtypes", [
367
+ PrimaryDatabaseService = _ts_decorate3([
368
+ Injectable2(),
369
+ _ts_param2(0, Inject2(DATABASE_MODULE_OPTIONS)),
370
+ _ts_metadata2("design:type", Function),
371
+ _ts_metadata2("design:paramtypes", [
229
372
  typeof DatabaseModuleOptions === "undefined" ? Object : DatabaseModuleOptions
230
373
  ])
231
374
  ], PrimaryDatabaseService);
232
375
 
376
+ // src/auth/guards/vritti-auth.guard.ts
377
+ function _ts_decorate4(decorators, target, key, desc) {
378
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
379
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
380
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
381
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
382
+ }
383
+ __name(_ts_decorate4, "_ts_decorate");
384
+ function _ts_metadata3(k, v) {
385
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
386
+ }
387
+ __name(_ts_metadata3, "_ts_metadata");
388
+ var VrittiAuthGuard = class _VrittiAuthGuard {
389
+ static {
390
+ __name(this, "VrittiAuthGuard");
391
+ }
392
+ reflector;
393
+ configService;
394
+ jwtService;
395
+ primaryDatabase;
396
+ requestService;
397
+ logger = new Logger2(_VrittiAuthGuard.name);
398
+ constructor(reflector, configService, jwtService, primaryDatabase, requestService) {
399
+ this.reflector = reflector;
400
+ this.configService = configService;
401
+ this.jwtService = jwtService;
402
+ this.primaryDatabase = primaryDatabase;
403
+ this.requestService = requestService;
404
+ }
405
+ async canActivate(context) {
406
+ const request = context.switchToHttp().getRequest();
407
+ const isPublic = this.reflector.getAllAndOverride("isPublic", [
408
+ context.getHandler(),
409
+ context.getClass()
410
+ ]);
411
+ if (isPublic) {
412
+ this.logger.debug("Public endpoint detected, skipping authentication");
413
+ return true;
414
+ }
415
+ const isOnboarding = this.reflector.getAllAndOverride("isOnboarding", [
416
+ context.getHandler(),
417
+ context.getClass()
418
+ ]);
419
+ try {
420
+ const accessToken = this.requestService.getAccessToken();
421
+ if (!accessToken) {
422
+ this.logger.warn("Access token not found in Authorization header");
423
+ throw new UnauthorizedException("Access token not found");
424
+ }
425
+ const decodedToken = this.jwtService.decode(accessToken);
426
+ if (!decodedToken) {
427
+ this.logger.warn("Failed to decode access token");
428
+ throw new UnauthorizedException("Invalid token format");
429
+ }
430
+ if (isOnboarding) {
431
+ if (decodedToken.type !== "onboarding") {
432
+ this.logger.warn("Onboarding endpoint requires onboarding token");
433
+ throw new UnauthorizedException("This endpoint requires an onboarding token");
434
+ }
435
+ const validatedToken2 = this.validateAccessToken(accessToken);
436
+ this.logger.debug("Onboarding token validated successfully");
437
+ const userId2 = validatedToken2.userId;
438
+ request.user = {
439
+ id: userId2
440
+ };
441
+ return true;
442
+ }
443
+ if (decodedToken.type === "onboarding") {
444
+ this.logger.warn("Regular endpoint accessed with onboarding token");
445
+ throw new UnauthorizedException("Onboarding tokens cannot access this endpoint");
446
+ }
447
+ const validatedToken = this.validateAccessToken(accessToken);
448
+ this.logger.debug("Access token validated successfully");
449
+ const refreshToken = this.requestService.getRefreshToken();
450
+ if (!refreshToken) {
451
+ this.logger.warn("Refresh token (session-id) not found in cookies");
452
+ throw new UnauthorizedException("Refresh token not found");
453
+ }
454
+ this.validateRefreshToken(refreshToken);
455
+ this.logger.debug("Refresh token validated successfully");
456
+ const userId = validatedToken.userId;
457
+ request.user = {
458
+ id: userId
459
+ };
460
+ const tenantIdentifier = this.requestService.getTenantIdentifier();
461
+ if (!tenantIdentifier) {
462
+ this.logger.warn("Tenant identifier not found in request");
463
+ throw new UnauthorizedException("Tenant identifier not found");
464
+ }
465
+ this.logger.debug(`Tenant identifier extracted: ${tenantIdentifier}`);
466
+ if (tenantIdentifier === "cloud") {
467
+ this.logger.debug("Platform admin access detected, skipping tenant database validation");
468
+ return true;
469
+ }
470
+ const tenantInfo = await this.primaryDatabase.getTenantInfo(tenantIdentifier);
471
+ if (!tenantInfo) {
472
+ this.logger.warn(`Invalid tenant: ${tenantIdentifier}`);
473
+ throw new UnauthorizedException("Invalid tenant");
474
+ }
475
+ if (tenantInfo.status !== "ACTIVE") {
476
+ this.logger.warn(`Tenant ${tenantIdentifier} has status: ${tenantInfo.status}`);
477
+ throw new UnauthorizedException(`Tenant is ${tenantInfo.status}`);
478
+ }
479
+ this.logger.debug(`Tenant validated: ${tenantInfo.subdomain} (${tenantInfo.type})`);
480
+ return true;
481
+ } catch (error) {
482
+ if (error instanceof UnauthorizedException) {
483
+ throw error;
484
+ }
485
+ this.logger.error("Unexpected error in auth guard", error);
486
+ throw new UnauthorizedException("Authentication failed");
487
+ }
488
+ }
489
+ /**
490
+ * Validate access token with proper expiry checks
491
+ * Throws UnauthorizedException if token is invalid or expired
492
+ */
493
+ validateAccessToken(token) {
494
+ try {
495
+ const decoded = this.jwtService.verify(token);
496
+ this.logger.debug(`Access token decoded for user: ${decoded.userId}`);
497
+ if (decoded.exp) {
498
+ const expiryTime = decoded.exp * 1e3;
499
+ const currentTime = Date.now();
500
+ const timeRemaining = expiryTime - currentTime;
501
+ this.logger.debug(`Access token valid for ${Math.floor(timeRemaining / 1e3)} more seconds`);
502
+ }
503
+ return decoded;
504
+ } catch (error) {
505
+ if (error instanceof UnauthorizedException) {
506
+ throw error;
507
+ }
508
+ const jwtError = error;
509
+ if (jwtError?.name === "TokenExpiredError") {
510
+ this.logger.warn(`Access token expired at: ${jwtError?.expiredAt}`);
511
+ throw new UnauthorizedException("Access token has expired");
512
+ }
513
+ if (jwtError?.name === "JsonWebTokenError") {
514
+ this.logger.warn(`Access token verification failed: ${jwtError?.message}`);
515
+ throw new UnauthorizedException("Invalid access token");
516
+ }
517
+ if (jwtError?.name === "NotBeforeError") {
518
+ this.logger.warn("Access token used before valid (nbf claim)");
519
+ throw new UnauthorizedException("Access token not yet valid");
520
+ }
521
+ this.logger.error("Unexpected error validating access token", error);
522
+ throw new UnauthorizedException("Access token validation failed");
523
+ }
524
+ }
525
+ /**
526
+ * Validate refresh token with proper expiry checks
527
+ * Throws UnauthorizedException if token is invalid or expired
528
+ */
529
+ validateRefreshToken(token) {
530
+ const jwtSecret = this.configService.get("JWT_REFRESH_SECRET") || this.configService.get("JWT_SECRET");
531
+ this.validateRefreshTokenWithSecret(token, jwtSecret);
532
+ }
533
+ /**
534
+ * Helper to validate refresh token with specific secret
535
+ */
536
+ validateRefreshTokenWithSecret(token, secret) {
537
+ if (!secret) {
538
+ this.logger.error("JWT secret not configured for refresh token validation");
539
+ throw new UnauthorizedException("Server configuration error");
540
+ }
541
+ try {
542
+ const decoded = jwt.verify(token, secret, {
543
+ algorithms: [
544
+ "HS256",
545
+ "HS512",
546
+ "RS256"
547
+ ]
548
+ });
549
+ this.logger.debug(`Refresh token decoded for user: ${decoded.userId}`);
550
+ if (decoded.exp) {
551
+ const expiryTime = decoded.exp * 1e3;
552
+ const currentTime = Date.now();
553
+ if (currentTime > expiryTime) {
554
+ this.logger.warn("Refresh token has expired");
555
+ throw new UnauthorizedException("Refresh token has expired. Please login again");
556
+ }
557
+ const timeRemaining = expiryTime - currentTime;
558
+ this.logger.debug(`Refresh token valid for ${Math.floor(timeRemaining / 1e3)} more seconds`);
559
+ }
560
+ } catch (error) {
561
+ if (error instanceof UnauthorizedException) {
562
+ throw error;
563
+ }
564
+ const jwtError = error;
565
+ if (jwtError?.name === "TokenExpiredError") {
566
+ this.logger.warn(`Refresh token expired at: ${jwtError?.expiredAt}`);
567
+ throw new UnauthorizedException("Refresh token has expired. Please login again");
568
+ }
569
+ if (jwtError?.name === "JsonWebTokenError") {
570
+ this.logger.warn(`Refresh token verification failed: ${jwtError?.message}`);
571
+ throw new UnauthorizedException("Invalid refresh token");
572
+ }
573
+ if (jwtError?.name === "NotBeforeError") {
574
+ this.logger.warn("Refresh token used before valid (nbf claim)");
575
+ throw new UnauthorizedException("Refresh token not yet valid");
576
+ }
577
+ this.logger.error("Unexpected error validating refresh token", error);
578
+ throw new UnauthorizedException("Refresh token validation failed");
579
+ }
580
+ }
581
+ };
582
+ VrittiAuthGuard = _ts_decorate4([
583
+ Injectable3({
584
+ scope: Scope2.REQUEST
585
+ }),
586
+ _ts_metadata3("design:type", Function),
587
+ _ts_metadata3("design:paramtypes", [
588
+ typeof Reflector === "undefined" ? Object : Reflector,
589
+ typeof ConfigService === "undefined" ? Object : ConfigService,
590
+ typeof JwtService === "undefined" ? Object : JwtService,
591
+ typeof PrimaryDatabaseService === "undefined" ? Object : PrimaryDatabaseService,
592
+ typeof RequestService === "undefined" ? Object : RequestService
593
+ ])
594
+ ], VrittiAuthGuard);
595
+
596
+ // src/auth/auth-config.module.ts
597
+ function _ts_decorate5(decorators, target, key, desc) {
598
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
599
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
600
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
601
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
602
+ }
603
+ __name(_ts_decorate5, "_ts_decorate");
604
+ var AuthConfigModule = class _AuthConfigModule {
605
+ static {
606
+ __name(this, "AuthConfigModule");
607
+ }
608
+ /**
609
+ * Register the auth module with async configuration
610
+ *
611
+ * This method:
612
+ * 1. Configures JwtModule with JWT_SECRET from ConfigService
613
+ * 2. Provides VrittiAuthGuard globally (applies to all routes)
614
+ * 3. Exports JwtModule for use in other modules (e.g., for signing tokens)
615
+ *
616
+ * @returns Dynamic module configuration
617
+ */
618
+ static forRootAsync() {
619
+ return {
620
+ module: _AuthConfigModule,
621
+ imports: [
622
+ ConfigModule,
623
+ RequestModule,
624
+ JwtModule.registerAsync({
625
+ imports: [
626
+ ConfigModule
627
+ ],
628
+ inject: [
629
+ ConfigService2
630
+ ],
631
+ useFactory: /* @__PURE__ */ __name((config) => ({
632
+ secret: config.get("JWT_SECRET"),
633
+ signOptions: {
634
+ algorithm: "HS256"
635
+ }
636
+ }), "useFactory")
637
+ })
638
+ ],
639
+ providers: [
640
+ {
641
+ provide: APP_GUARD,
642
+ useClass: VrittiAuthGuard
643
+ }
644
+ ],
645
+ exports: [
646
+ JwtModule
647
+ ]
648
+ };
649
+ }
650
+ };
651
+ AuthConfigModule = _ts_decorate5([
652
+ Global2(),
653
+ Module2({})
654
+ ], AuthConfigModule);
655
+
656
+ // src/database/database.module.ts
657
+ import { Global as Global3, Module as Module3 } from "@nestjs/common";
658
+ import { APP_INTERCEPTOR } from "@nestjs/core";
659
+
660
+ // src/database/interceptors/message-tenant-context.interceptor.ts
661
+ import { Injectable as Injectable5, Logger as Logger3, Scope as Scope4 } from "@nestjs/common";
662
+ import { tap } from "rxjs/operators";
663
+
233
664
  // src/database/services/tenant-context.service.ts
234
- import { Injectable as Injectable2, Scope, UnauthorizedException } from "@nestjs/common";
235
- function _ts_decorate2(decorators, target, key, desc) {
665
+ import { Injectable as Injectable4, Scope as Scope3, UnauthorizedException as UnauthorizedException2 } from "@nestjs/common";
666
+ function _ts_decorate6(decorators, target, key, desc) {
236
667
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
237
668
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
238
669
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
239
670
  return c > 3 && r && Object.defineProperty(target, key, r), r;
240
671
  }
241
- __name(_ts_decorate2, "_ts_decorate");
672
+ __name(_ts_decorate6, "_ts_decorate");
242
673
  var TenantContextService = class {
243
674
  static {
244
675
  __name(this, "TenantContextService");
@@ -269,7 +700,7 @@ var TenantContextService = class {
269
700
  */
270
701
  getTenant() {
271
702
  if (!this.tenantInfo) {
272
- throw new UnauthorizedException("Tenant context not set");
703
+ throw new UnauthorizedException2("Tenant context not set");
273
704
  }
274
705
  return this.tenantInfo;
275
706
  }
@@ -310,70 +741,224 @@ var TenantContextService = class {
310
741
  return this.tenantInfo?.subdomain ?? null;
311
742
  }
312
743
  };
313
- TenantContextService = _ts_decorate2([
314
- Injectable2({
315
- scope: Scope.REQUEST
744
+ TenantContextService = _ts_decorate6([
745
+ Injectable4({
746
+ scope: Scope3.REQUEST
316
747
  })
317
748
  ], TenantContextService);
318
749
 
319
- // src/database/services/tenant-database.service.ts
320
- import { Inject as Inject2, Injectable as Injectable3, InternalServerErrorException as InternalServerErrorException2, Logger as Logger2 } from "@nestjs/common";
321
- function _ts_decorate3(decorators, target, key, desc) {
750
+ // src/database/interceptors/message-tenant-context.interceptor.ts
751
+ function _ts_decorate7(decorators, target, key, desc) {
322
752
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
323
753
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
324
754
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
325
755
  return c > 3 && r && Object.defineProperty(target, key, r), r;
326
756
  }
327
- __name(_ts_decorate3, "_ts_decorate");
328
- function _ts_metadata2(k, v) {
757
+ __name(_ts_decorate7, "_ts_decorate");
758
+ function _ts_metadata4(k, v) {
329
759
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
330
760
  }
331
- __name(_ts_metadata2, "_ts_metadata");
332
- function _ts_param2(paramIndex, decorator) {
333
- return function(target, key) {
334
- decorator(target, key, paramIndex);
335
- };
336
- }
337
- __name(_ts_param2, "_ts_param");
338
- var TenantDatabaseService = class _TenantDatabaseService {
761
+ __name(_ts_metadata4, "_ts_metadata");
762
+ var MessageTenantContextInterceptor = class _MessageTenantContextInterceptor {
339
763
  static {
340
- __name(this, "TenantDatabaseService");
764
+ __name(this, "MessageTenantContextInterceptor");
341
765
  }
342
- options;
343
766
  tenantContext;
344
- logger = new Logger2(_TenantDatabaseService.name);
345
- /** Connection pool: Map<cacheKey, DbClient> */
346
- clients = /* @__PURE__ */ new Map();
347
- /** Track last usage time for idle connection cleanup */
348
- clientLastUsed = /* @__PURE__ */ new Map();
349
- /** Cleanup interval timer */
350
- cleanupInterval;
351
- constructor(options, tenantContext) {
352
- this.options = options;
767
+ logger = new Logger3(_MessageTenantContextInterceptor.name);
768
+ constructor(tenantContext) {
353
769
  this.tenantContext = tenantContext;
354
- this.startConnectionCleaner();
355
770
  }
356
- /**
357
- * Get tenant-scoped database client for the current request/message
358
- *
359
- * This method:
360
- * 1. Gets tenant info from TenantContextService
361
- * 2. Builds a connection URL based on tenant type
362
- * 3. Returns cached client if exists, otherwise creates new one
363
- *
364
- * @returns Promise<Database client instance>
365
- * @throws UnauthorizedException if tenant context not set
366
- * @throws InternalServerErrorException if connection fails
367
- *
368
- * @example
369
- * const dbClient = await tenantDatabase.getDbClient<PrismaClient>();
370
- * const users = await dbClient.user.findMany();
371
- */
372
- async getDbClient() {
373
- const tenant = this.tenantContext.getTenant();
374
- const cacheKey = this.buildCacheKey(tenant);
375
- if (this.clients.has(cacheKey)) {
376
- this.clientLastUsed.set(cacheKey, Date.now());
771
+ intercept(context, next) {
772
+ const contextType = context.getType();
773
+ if (contextType === "rpc") {
774
+ const rpcContext = context.switchToRpc();
775
+ const payload = rpcContext.getData();
776
+ if (payload && payload.tenant) {
777
+ const tenant = payload.tenant;
778
+ this.logger.debug(`Setting tenant context from message: ${tenant.subdomain}`);
779
+ try {
780
+ this.tenantContext.setTenant(tenant);
781
+ this.logger.log(`Tenant context set: ${tenant.subdomain} (${tenant.type})`);
782
+ } catch (error) {
783
+ this.logger.error("Failed to set tenant context from message", error);
784
+ }
785
+ } else {
786
+ this.logger.warn("Message payload missing tenant information");
787
+ }
788
+ }
789
+ return next.handle().pipe(tap({
790
+ next: /* @__PURE__ */ __name(() => {
791
+ this.cleanupContext();
792
+ }, "next"),
793
+ error: /* @__PURE__ */ __name(() => {
794
+ this.cleanupContext();
795
+ }, "error"),
796
+ complete: /* @__PURE__ */ __name(() => {
797
+ this.cleanupContext();
798
+ }, "complete")
799
+ }));
800
+ }
801
+ /**
802
+ * Clean up tenant context after message is processed
803
+ */
804
+ cleanupContext() {
805
+ if (this.tenantContext.hasTenant()) {
806
+ const tenant = this.tenantContext.getTenantIdSafe();
807
+ this.tenantContext.clearTenant();
808
+ this.logger.debug(`Cleaned up tenant context: ${tenant}`);
809
+ }
810
+ }
811
+ };
812
+ MessageTenantContextInterceptor = _ts_decorate7([
813
+ Injectable5({
814
+ scope: Scope4.REQUEST
815
+ }),
816
+ _ts_metadata4("design:type", Function),
817
+ _ts_metadata4("design:paramtypes", [
818
+ typeof TenantContextService === "undefined" ? Object : TenantContextService
819
+ ])
820
+ ], MessageTenantContextInterceptor);
821
+
822
+ // src/database/interceptors/tenant-context.interceptor.ts
823
+ import { Injectable as Injectable6, Logger as Logger4, Scope as Scope5, UnauthorizedException as UnauthorizedException3 } from "@nestjs/common";
824
+ function _ts_decorate8(decorators, target, key, desc) {
825
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
826
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
827
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
828
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
829
+ }
830
+ __name(_ts_decorate8, "_ts_decorate");
831
+ function _ts_metadata5(k, v) {
832
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
833
+ }
834
+ __name(_ts_metadata5, "_ts_metadata");
835
+ var TenantContextInterceptor = class _TenantContextInterceptor {
836
+ static {
837
+ __name(this, "TenantContextInterceptor");
838
+ }
839
+ tenantContext;
840
+ primaryDatabase;
841
+ requestService;
842
+ logger = new Logger4(_TenantContextInterceptor.name);
843
+ constructor(tenantContext, primaryDatabase, requestService) {
844
+ this.tenantContext = tenantContext;
845
+ this.primaryDatabase = primaryDatabase;
846
+ this.requestService = requestService;
847
+ }
848
+ async intercept(context, next) {
849
+ const request = context.switchToHttp().getRequest();
850
+ this.logger.debug(`Processing request: ${request.method} ${request.url}`);
851
+ try {
852
+ const tenantIdentifier = this.requestService.getTenantIdentifier();
853
+ if (!tenantIdentifier) {
854
+ throw new UnauthorizedException3("Tenant identifier not found in request");
855
+ }
856
+ this.logger.debug(`Tenant identifier extracted: ${tenantIdentifier}`);
857
+ if (tenantIdentifier === "cloud") {
858
+ this.logger.log("Cloud platform access detected, skipping tenant context setup");
859
+ return next.handle();
860
+ }
861
+ const tenantInfo = await this.primaryDatabase.getTenantInfo(tenantIdentifier);
862
+ if (!tenantInfo) {
863
+ this.logger.warn(`Invalid tenant: ${tenantIdentifier}`);
864
+ throw new UnauthorizedException3("Invalid tenant");
865
+ }
866
+ if (tenantInfo.status !== "ACTIVE") {
867
+ this.logger.warn(`Tenant ${tenantIdentifier} has status: ${tenantInfo.status}`);
868
+ throw new UnauthorizedException3(`Tenant is ${tenantInfo.status}`);
869
+ }
870
+ this.logger.debug(`Tenant config loaded: ${tenantInfo.subdomain} (${tenantInfo.type})`);
871
+ this.tenantContext.setTenant(tenantInfo);
872
+ request.tenant = tenantInfo;
873
+ this.logger.log(`Tenant context set: ${tenantInfo.subdomain}`);
874
+ } catch (error) {
875
+ this.logger.error("Failed to set tenant context", error);
876
+ throw error;
877
+ }
878
+ return next.handle();
879
+ }
880
+ };
881
+ TenantContextInterceptor = _ts_decorate8([
882
+ Injectable6({
883
+ scope: Scope5.REQUEST
884
+ }),
885
+ _ts_metadata5("design:type", Function),
886
+ _ts_metadata5("design:paramtypes", [
887
+ typeof TenantContextService === "undefined" ? Object : TenantContextService,
888
+ typeof PrimaryDatabaseService === "undefined" ? Object : PrimaryDatabaseService,
889
+ typeof RequestService === "undefined" ? Object : RequestService
890
+ ])
891
+ ], TenantContextInterceptor);
892
+
893
+ // src/database/services/tenant-database.service.ts
894
+ import { Inject as Inject3, Injectable as Injectable7, InternalServerErrorException as InternalServerErrorException2, Logger as Logger5 } from "@nestjs/common";
895
+ function _ts_decorate9(decorators, target, key, desc) {
896
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
897
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
898
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
899
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
900
+ }
901
+ __name(_ts_decorate9, "_ts_decorate");
902
+ function _ts_metadata6(k, v) {
903
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
904
+ }
905
+ __name(_ts_metadata6, "_ts_metadata");
906
+ function _ts_param3(paramIndex, decorator) {
907
+ return function(target, key) {
908
+ decorator(target, key, paramIndex);
909
+ };
910
+ }
911
+ __name(_ts_param3, "_ts_param");
912
+ var TenantDatabaseService = class _TenantDatabaseService {
913
+ static {
914
+ __name(this, "TenantDatabaseService");
915
+ }
916
+ options;
917
+ tenantContext;
918
+ logger = new Logger5(_TenantDatabaseService.name);
919
+ /** Connection pool: Map<cacheKey, DbClient> */
920
+ clients = /* @__PURE__ */ new Map();
921
+ /** Track last usage time for idle connection cleanup */
922
+ clientLastUsed = /* @__PURE__ */ new Map();
923
+ /** Cleanup interval timer */
924
+ cleanupInterval;
925
+ constructor(options, tenantContext) {
926
+ this.options = options;
927
+ this.tenantContext = tenantContext;
928
+ this.startConnectionCleaner();
929
+ }
930
+ /**
931
+ * Get the Prisma client for the current tenant's database.
932
+ * This returns the tenant-scoped database client.
933
+ *
934
+ * @returns Tenant-scoped database client instance
935
+ * @throws UnauthorizedException if tenant context not set
936
+ * @throws InternalServerErrorException if connection fails
937
+ */
938
+ get prismaClient() {
939
+ return this.getDbClient();
940
+ }
941
+ /**
942
+ * Get tenant-scoped database client for the current request/message
943
+ *
944
+ * This method:
945
+ * 1. Gets tenant info from TenantContextService
946
+ * 2. Builds a connection URL based on tenant type
947
+ * 3. Returns cached client if exists, otherwise creates new one
948
+ *
949
+ * @returns Promise<Database client instance>
950
+ * @throws UnauthorizedException if tenant context not set
951
+ * @throws InternalServerErrorException if connection fails
952
+ *
953
+ * @example
954
+ * const dbClient = await tenantDatabase.getDbClient<PrismaClient>();
955
+ * const users = await dbClient.user.findMany();
956
+ */
957
+ async getDbClient() {
958
+ const tenant = this.tenantContext.getTenant();
959
+ const cacheKey = this.buildCacheKey(tenant);
960
+ if (this.clients.has(cacheKey)) {
961
+ this.clientLastUsed.set(cacheKey, Date.now());
377
962
  this.logger.debug(`Reusing cached connection: ${cacheKey}`);
378
963
  return this.clients.get(cacheKey);
379
964
  }
@@ -497,87 +1082,116 @@ var TenantDatabaseService = class _TenantDatabaseService {
497
1082
  this.logger.log("All database connections closed");
498
1083
  }
499
1084
  };
500
- TenantDatabaseService = _ts_decorate3([
501
- Injectable3(),
502
- _ts_param2(0, Inject2(DATABASE_MODULE_OPTIONS)),
503
- _ts_metadata2("design:type", Function),
504
- _ts_metadata2("design:paramtypes", [
1085
+ TenantDatabaseService = _ts_decorate9([
1086
+ Injectable7(),
1087
+ _ts_param3(0, Inject3(DATABASE_MODULE_OPTIONS)),
1088
+ _ts_metadata6("design:type", Function),
1089
+ _ts_metadata6("design:paramtypes", [
505
1090
  typeof DatabaseModuleOptions === "undefined" ? Object : DatabaseModuleOptions,
506
1091
  typeof TenantContextService === "undefined" ? Object : TenantContextService
507
1092
  ])
508
1093
  ], TenantDatabaseService);
509
1094
 
510
1095
  // src/database/database.module.ts
511
- function _ts_decorate4(decorators, target, key, desc) {
1096
+ function _ts_decorate10(decorators, target, key, desc) {
512
1097
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
513
1098
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
514
1099
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
515
1100
  return c > 3 && r && Object.defineProperty(target, key, r), r;
516
1101
  }
517
- __name(_ts_decorate4, "_ts_decorate");
1102
+ __name(_ts_decorate10, "_ts_decorate");
518
1103
  var DatabaseModule = class _DatabaseModule {
519
1104
  static {
520
1105
  __name(this, "DatabaseModule");
521
1106
  }
522
1107
  /**
523
- * Synchronous configuration
1108
+ * Configure DatabaseModule for Gateway/HTTP mode (multi-tenant web servers)
524
1109
  *
525
- * @param options Module configuration options
526
- * @returns Dynamic module configuration
1110
+ * This mode is for API Gateways that handle HTTP requests:
1111
+ * - Automatically registers TenantContextInterceptor
1112
+ * - Extracts tenant from subdomain or x-tenant-id header
1113
+ * - Queries primary database for tenant configuration
1114
+ * - Provides PrimaryDatabaseService for tenant lookup
1115
+ *
1116
+ * @param options Async configuration options
1117
+ * @returns Dynamic module configuration with HTTP interceptor
1118
+ *
1119
+ * @example
1120
+ * DatabaseModule.forServer({
1121
+ * inject: [ConfigService],
1122
+ * useFactory: (config: ConfigService) => ({
1123
+ * primaryDb: {
1124
+ * host: config.get('PRIMARY_DB_HOST'),
1125
+ * port: config.get('PRIMARY_DB_PORT'),
1126
+ * username: config.get('PRIMARY_DB_USERNAME'),
1127
+ * password: config.get('PRIMARY_DB_PASSWORD'),
1128
+ * database: config.get('PRIMARY_DB_DATABASE'),
1129
+ * },
1130
+ * prismaClientConstructor: PrismaClient,
1131
+ * }),
1132
+ * })
527
1133
  */
528
- static forRoot(options) {
529
- const providers = [
530
- {
531
- provide: DATABASE_MODULE_OPTIONS,
532
- useValue: options
533
- },
534
- TenantDatabaseService,
535
- TenantContextService,
536
- PrimaryDatabaseService
537
- ];
538
- return {
539
- module: _DatabaseModule,
540
- providers,
541
- exports: [
542
- TenantDatabaseService,
543
- TenantContextService,
544
- PrimaryDatabaseService
545
- ]
546
- };
1134
+ static forServer(options) {
1135
+ return this.createDynamicModule(options, "server");
547
1136
  }
548
1137
  /**
549
- * Asynchronous configuration (recommended)
1138
+ * Configure DatabaseModule for Microservice/Messaging mode (RabbitMQ workers)
550
1139
  *
551
- * Allows injecting ConfigService or other dependencies
1140
+ * This mode is for microservices that process messages from queues:
1141
+ * - Automatically registers MessageTenantContextInterceptor
1142
+ * - Extracts tenant from RabbitMQ message patterns
1143
+ * - No primary database needed (tenant comes from message context)
552
1144
  *
553
1145
  * @param options Async configuration options
554
- * @returns Dynamic module configuration
1146
+ * @returns Dynamic module configuration with message interceptor
555
1147
  *
556
1148
  * @example
557
- * DatabaseModule.forRootAsync({
558
- * imports: [ConfigModule],
559
- * useFactory: async (config: ConfigService) => ({
560
- * cloudDatabaseUrl: config.get('CLOUD_DATABASE_URL'),
1149
+ * DatabaseModule.forMicroservice({
1150
+ * inject: [ConfigService],
1151
+ * useFactory: (config: ConfigService) => ({
561
1152
  * prismaClientConstructor: PrismaClient,
562
- * tenantResolver: 'subdomain',
563
1153
  * }),
564
- * inject: [ConfigService],
565
1154
  * })
566
1155
  */
567
- static forRootAsync(options) {
1156
+ static forMicroservice(options) {
1157
+ return this.createDynamicModule(options, "microservice");
1158
+ }
1159
+ /**
1160
+ * Internal helper to create dynamic module with conditional interceptor registration
1161
+ *
1162
+ * @param options Configuration options
1163
+ * @param mode Mode of operation (gateway or microservice)
1164
+ * @returns Dynamic module configuration
1165
+ */
1166
+ static createDynamicModule(options, mode) {
568
1167
  const asyncProvider = {
569
1168
  provide: DATABASE_MODULE_OPTIONS,
570
1169
  useFactory: options.useFactory,
571
1170
  inject: options.inject || []
572
1171
  };
1172
+ const providers = [
1173
+ asyncProvider,
1174
+ TenantContextService,
1175
+ PrimaryDatabaseService,
1176
+ TenantDatabaseService
1177
+ ];
1178
+ if (mode === "server") {
1179
+ providers.push({
1180
+ provide: APP_INTERCEPTOR,
1181
+ useClass: TenantContextInterceptor
1182
+ });
1183
+ } else {
1184
+ providers.push({
1185
+ provide: APP_INTERCEPTOR,
1186
+ useClass: MessageTenantContextInterceptor
1187
+ });
1188
+ }
573
1189
  return {
574
1190
  module: _DatabaseModule,
575
- providers: [
576
- asyncProvider,
577
- TenantContextService,
578
- PrimaryDatabaseService,
579
- TenantDatabaseService
1191
+ imports: [
1192
+ RequestModule
580
1193
  ],
1194
+ providers,
581
1195
  exports: [
582
1196
  TenantDatabaseService,
583
1197
  TenantContextService,
@@ -587,216 +1201,549 @@ var DatabaseModule = class _DatabaseModule {
587
1201
  };
588
1202
  }
589
1203
  };
590
- DatabaseModule = _ts_decorate4([
591
- Global(),
592
- Module({})
1204
+ DatabaseModule = _ts_decorate10([
1205
+ Global3(),
1206
+ Module3({})
593
1207
  ], DatabaseModule);
594
1208
 
595
- // src/database/interceptors/message-tenant-context.interceptor.ts
596
- import { Injectable as Injectable4, Logger as Logger3, Scope as Scope2 } from "@nestjs/common";
597
- import { tap } from "rxjs/operators";
598
- function _ts_decorate5(decorators, target, key, desc) {
599
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
600
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
601
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
602
- return c > 3 && r && Object.defineProperty(target, key, r), r;
603
- }
604
- __name(_ts_decorate5, "_ts_decorate");
605
- function _ts_metadata3(k, v) {
606
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
607
- }
608
- __name(_ts_metadata3, "_ts_metadata");
609
- var MessageTenantContextInterceptor = class _MessageTenantContextInterceptor {
1209
+ // src/database/repositories/primary-base.repository.ts
1210
+ import { Logger as Logger6 } from "@nestjs/common";
1211
+ var PrimaryBaseRepository = class {
610
1212
  static {
611
- __name(this, "MessageTenantContextInterceptor");
612
- }
613
- tenantContext;
614
- logger = new Logger3(_MessageTenantContextInterceptor.name);
615
- constructor(tenantContext) {
616
- this.tenantContext = tenantContext;
617
- }
618
- intercept(context, next) {
619
- const contextType = context.getType();
620
- if (contextType === "rpc") {
621
- const rpcContext = context.switchToRpc();
622
- const payload = rpcContext.getData();
623
- if (payload && payload.tenant) {
624
- const tenant = payload.tenant;
625
- this.logger.debug(`Setting tenant context from message: ${tenant.subdomain}`);
626
- try {
627
- this.tenantContext.setTenant(tenant);
628
- this.logger.log(`Tenant context set: ${tenant.subdomain} (${tenant.type})`);
629
- } catch (error) {
630
- this.logger.error("Failed to set tenant context from message", error);
631
- }
632
- } else {
633
- this.logger.warn("Message payload missing tenant information");
634
- }
635
- }
636
- return next.handle().pipe(tap({
637
- next: /* @__PURE__ */ __name(() => {
638
- this.cleanupContext();
639
- }, "next"),
640
- error: /* @__PURE__ */ __name(() => {
641
- this.cleanupContext();
642
- }, "error"),
643
- complete: /* @__PURE__ */ __name(() => {
644
- this.cleanupContext();
645
- }, "complete")
646
- }));
1213
+ __name(this, "PrimaryBaseRepository");
647
1214
  }
1215
+ database;
1216
+ logger;
1217
+ modelGetter;
648
1218
  /**
649
- * Clean up tenant context after message is processed
1219
+ * Lazy getter for Prisma client.
1220
+ * Accesses the client from the database service only when needed,
1221
+ * avoiding initialization timing issues with NestJS lifecycle.
650
1222
  */
651
- cleanupContext() {
652
- if (this.tenantContext.hasTenant()) {
653
- const tenant = this.tenantContext.getTenantIdSafe();
654
- this.tenantContext.clearTenant();
655
- this.logger.debug(`Cleaned up tenant context: ${tenant}`);
656
- }
1223
+ get prisma() {
1224
+ return this.database.prismaClient;
657
1225
  }
658
- };
659
- MessageTenantContextInterceptor = _ts_decorate5([
660
- Injectable4({
661
- scope: Scope2.REQUEST
662
- }),
663
- _ts_metadata3("design:type", Function),
664
- _ts_metadata3("design:paramtypes", [
665
- typeof TenantContextService === "undefined" ? Object : TenantContextService
666
- ])
667
- ], MessageTenantContextInterceptor);
668
-
669
- // src/database/interceptors/tenant-context.interceptor.ts
670
- import { Inject as Inject3, Injectable as Injectable5, Logger as Logger4, Scope as Scope3, UnauthorizedException as UnauthorizedException2 } from "@nestjs/common";
671
-
672
- // src/database/utils/subdomain-parser.util.ts
673
- function extractSubdomain(host) {
674
- if (!host) {
675
- return null;
1226
+ /**
1227
+ * Lazy getter for the Prisma model delegate.
1228
+ * Returns the specific model (e.g., prisma.user, prisma.tenant) for this repository.
1229
+ */
1230
+ get model() {
1231
+ return this.modelGetter(this.prisma);
676
1232
  }
677
- const hostname = host.split(":")[0];
678
- if (!hostname) {
679
- return null;
1233
+ /**
1234
+ * Create a new repository instance
1235
+ *
1236
+ * @param database - The primary database service
1237
+ * @param getModel - Function that returns the Prisma model delegate from the client
1238
+ *
1239
+ * @example
1240
+ * ```typescript
1241
+ * // Standard usage with full parameter name
1242
+ * constructor(database: PrimaryDatabaseService) {
1243
+ * super(database, (prisma) => prisma.user);
1244
+ * }
1245
+ *
1246
+ * // Short syntax
1247
+ * constructor(database: PrimaryDatabaseService) {
1248
+ * super(database, (p) => p.user);
1249
+ * }
1250
+ *
1251
+ * // Complex model names
1252
+ * constructor(database: PrimaryDatabaseService) {
1253
+ * super(database, (p) => p.emailVerification);
1254
+ * }
1255
+ * ```
1256
+ */
1257
+ constructor(database, getModel) {
1258
+ this.database = database;
1259
+ this.logger = new Logger6(this.constructor.name);
1260
+ this.modelGetter = getModel;
1261
+ this.logger.debug(`Initialized ${this.constructor.name}`);
680
1262
  }
681
- const parts = hostname.split(".");
682
- if (parts.length < 3) {
683
- return null;
1263
+ /**
1264
+ * Create a new record
1265
+ *
1266
+ * @param data - The data to create the record with
1267
+ * @returns Promise resolving to the created record
1268
+ *
1269
+ * @example
1270
+ * ```typescript
1271
+ * const user = await userRepository.create({
1272
+ * email: 'user@example.com',
1273
+ * name: 'John Doe'
1274
+ * });
1275
+ * ```
1276
+ */
1277
+ async create(data) {
1278
+ this.logger.log("Creating record");
1279
+ return await this.model.create({
1280
+ data
1281
+ });
684
1282
  }
685
- return parts[0] ?? null;
686
- }
687
- __name(extractSubdomain, "extractSubdomain");
1283
+ /**
1284
+ * Find a single record by ID
1285
+ *
1286
+ * @param id - The record ID
1287
+ * @returns Promise resolving to the record or null if not found
1288
+ *
1289
+ * @example
1290
+ * ```typescript
1291
+ * const user = await userRepository.findById('user-id-123');
1292
+ * ```
1293
+ */
1294
+ async findById(id) {
1295
+ this.logger.debug(`Finding record by ID: ${id}`);
1296
+ return await this.model.findUnique({
1297
+ where: {
1298
+ id
1299
+ }
1300
+ });
1301
+ }
1302
+ /**
1303
+ * Find a single record with custom where clause
1304
+ *
1305
+ * @param where - The where clause or findUnique args
1306
+ * @returns Promise resolving to the record or null if not found
1307
+ *
1308
+ * @example
1309
+ * ```typescript
1310
+ * // Simple where clause
1311
+ * const user = await userRepository.findOne({ email: 'user@example.com' });
1312
+ *
1313
+ * // With include
1314
+ * const user = await userRepository.findOne({
1315
+ * where: { email: 'user@example.com' },
1316
+ * include: { posts: true }
1317
+ * });
1318
+ * ```
1319
+ */
1320
+ async findOne(where) {
1321
+ this.logger.debug("Finding record with custom query");
1322
+ return await this.model.findUnique(typeof where === "object" && "where" in where ? where : {
1323
+ where
1324
+ });
1325
+ }
1326
+ /**
1327
+ * Find multiple records
1328
+ *
1329
+ * @param args - Prisma findMany arguments (where, orderBy, take, skip, etc.)
1330
+ * @returns Promise resolving to an array of records
1331
+ *
1332
+ * @example
1333
+ * ```typescript
1334
+ * // Find all users
1335
+ * const users = await userRepository.findMany();
1336
+ *
1337
+ * // Find with filtering and pagination
1338
+ * const users = await userRepository.findMany({
1339
+ * where: { status: 'ACTIVE' },
1340
+ * orderBy: { createdAt: 'desc' },
1341
+ * take: 10,
1342
+ * skip: 0
1343
+ * });
1344
+ * ```
1345
+ */
1346
+ async findMany(args) {
1347
+ this.logger.debug("Finding multiple records");
1348
+ return await this.model.findMany(args);
1349
+ }
1350
+ /**
1351
+ * Update a record by ID
1352
+ *
1353
+ * @param id - The record ID
1354
+ * @param data - The data to update
1355
+ * @returns Promise resolving to the updated record
1356
+ *
1357
+ * @example
1358
+ * ```typescript
1359
+ * const user = await userRepository.update('user-id-123', {
1360
+ * name: 'Jane Doe'
1361
+ * });
1362
+ * ```
1363
+ */
1364
+ async update(id, data) {
1365
+ this.logger.log(`Updating record with ID: ${id}`);
1366
+ return await this.model.update({
1367
+ where: {
1368
+ id
1369
+ },
1370
+ data
1371
+ });
1372
+ }
1373
+ /**
1374
+ * Update multiple records
1375
+ *
1376
+ * @param where - The where clause to match records
1377
+ * @param data - The data to update
1378
+ * @returns Promise resolving to the count of updated records
1379
+ *
1380
+ * @example
1381
+ * ```typescript
1382
+ * const result = await userRepository.updateMany(
1383
+ * { status: 'PENDING' },
1384
+ * { status: 'ACTIVE' }
1385
+ * );
1386
+ * console.log(`Updated ${result.count} users`);
1387
+ * ```
1388
+ */
1389
+ async updateMany(where, data) {
1390
+ this.logger.log("Updating multiple records");
1391
+ return await this.model.updateMany({
1392
+ where,
1393
+ data
1394
+ });
1395
+ }
1396
+ /**
1397
+ * Delete a record by ID
1398
+ *
1399
+ * @param id - The record ID
1400
+ * @returns Promise resolving to the deleted record
1401
+ *
1402
+ * @example
1403
+ * ```typescript
1404
+ * const user = await userRepository.delete('user-id-123');
1405
+ * ```
1406
+ */
1407
+ async delete(id) {
1408
+ this.logger.log(`Deleting record with ID: ${id}`);
1409
+ return await this.model.delete({
1410
+ where: {
1411
+ id
1412
+ }
1413
+ });
1414
+ }
1415
+ /**
1416
+ * Delete multiple records
1417
+ *
1418
+ * @param where - The where clause to match records
1419
+ * @returns Promise resolving to the count of deleted records
1420
+ *
1421
+ * @example
1422
+ * ```typescript
1423
+ * const result = await userRepository.deleteMany({
1424
+ * status: 'INACTIVE',
1425
+ * createdAt: { lt: new Date('2020-01-01') }
1426
+ * });
1427
+ * console.log(`Deleted ${result.count} users`);
1428
+ * ```
1429
+ */
1430
+ async deleteMany(where) {
1431
+ this.logger.log("Deleting multiple records");
1432
+ return await this.model.deleteMany({
1433
+ where
1434
+ });
1435
+ }
1436
+ /**
1437
+ * Count records
1438
+ *
1439
+ * @param where - Optional where clause to filter records
1440
+ * @returns Promise resolving to the count of records
1441
+ *
1442
+ * @example
1443
+ * ```typescript
1444
+ * // Count all users
1445
+ * const total = await userRepository.count();
1446
+ *
1447
+ * // Count active users
1448
+ * const activeCount = await userRepository.count({ status: 'ACTIVE' });
1449
+ * ```
1450
+ */
1451
+ async count(where) {
1452
+ this.logger.debug("Counting records");
1453
+ return await this.model.count({
1454
+ where
1455
+ });
1456
+ }
1457
+ /**
1458
+ * Check if a record exists
1459
+ *
1460
+ * @param where - The where clause to match records
1461
+ * @returns Promise resolving to true if at least one record exists, false otherwise
1462
+ *
1463
+ * @example
1464
+ * ```typescript
1465
+ * const emailExists = await userRepository.exists({
1466
+ * email: 'user@example.com'
1467
+ * });
1468
+ * ```
1469
+ */
1470
+ async exists(where) {
1471
+ const count = await this.model.count({
1472
+ where
1473
+ });
1474
+ return count > 0;
1475
+ }
1476
+ };
688
1477
 
689
- // src/database/interceptors/tenant-context.interceptor.ts
690
- function _ts_decorate6(decorators, target, key, desc) {
691
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
692
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
693
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
694
- return c > 3 && r && Object.defineProperty(target, key, r), r;
695
- }
696
- __name(_ts_decorate6, "_ts_decorate");
697
- function _ts_metadata4(k, v) {
698
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
699
- }
700
- __name(_ts_metadata4, "_ts_metadata");
701
- function _ts_param3(paramIndex, decorator) {
702
- return function(target, key) {
703
- decorator(target, key, paramIndex);
704
- };
705
- }
706
- __name(_ts_param3, "_ts_param");
707
- var TenantContextInterceptor = class _TenantContextInterceptor {
1478
+ // src/database/repositories/tenant-base.repository.ts
1479
+ import { Logger as Logger7 } from "@nestjs/common";
1480
+ var TenantBaseRepository = class {
708
1481
  static {
709
- __name(this, "TenantContextInterceptor");
1482
+ __name(this, "TenantBaseRepository");
710
1483
  }
711
- tenantContext;
712
- primaryDatabase;
713
- options;
714
- logger = new Logger4(_TenantContextInterceptor.name);
715
- constructor(tenantContext, primaryDatabase, options) {
716
- this.tenantContext = tenantContext;
717
- this.primaryDatabase = primaryDatabase;
718
- this.options = options;
1484
+ database;
1485
+ logger;
1486
+ modelGetter;
1487
+ /**
1488
+ * Lazy getter for Prisma client.
1489
+ * Accesses the client from the database service only when needed,
1490
+ * avoiding initialization timing issues with NestJS lifecycle.
1491
+ */
1492
+ get prisma() {
1493
+ return this.database.prismaClient;
719
1494
  }
720
- async intercept(context, next) {
721
- const request = context.switchToHttp().getRequest();
722
- this.logger.debug(`Processing request: ${request.method} ${request.url}`);
723
- try {
724
- const tenantIdentifier = this.extractTenantIdentifier(request);
725
- if (!tenantIdentifier) {
726
- throw new UnauthorizedException2("Tenant identifier not found in request");
727
- }
728
- this.logger.debug(`Tenant identifier extracted: ${tenantIdentifier}`);
729
- if (tenantIdentifier === "cloud") {
730
- this.logger.log("Cloud platform access detected, skipping tenant context setup");
731
- return next.handle();
732
- }
733
- const tenantInfo = await this.primaryDatabase.getTenantInfo(tenantIdentifier);
734
- if (!tenantInfo) {
735
- this.logger.warn(`Invalid tenant: ${tenantIdentifier}`);
736
- throw new UnauthorizedException2("Invalid tenant");
737
- }
738
- if (tenantInfo.status !== "ACTIVE") {
739
- this.logger.warn(`Tenant ${tenantIdentifier} has status: ${tenantInfo.status}`);
740
- throw new UnauthorizedException2(`Tenant is ${tenantInfo.status}`);
1495
+ /**
1496
+ * Lazy getter for the Prisma model delegate.
1497
+ * Returns the specific model (e.g., prisma.product, prisma.order) for this repository.
1498
+ */
1499
+ get model() {
1500
+ return this.modelGetter(this.prisma);
1501
+ }
1502
+ /**
1503
+ * Create a new repository instance
1504
+ *
1505
+ * @param database - The tenant database service
1506
+ * @param getModel - Function that returns the Prisma model delegate from the client
1507
+ *
1508
+ * @example
1509
+ * ```typescript
1510
+ * // Standard usage with full parameter name
1511
+ * constructor(database: TenantDatabaseService) {
1512
+ * super(database, (prisma) => prisma.product);
1513
+ * }
1514
+ *
1515
+ * // Short syntax
1516
+ * constructor(database: TenantDatabaseService) {
1517
+ * super(database, (p) => p.product);
1518
+ * }
1519
+ *
1520
+ * // Complex model names
1521
+ * constructor(database: TenantDatabaseService) {
1522
+ * super(database, (p) => p.inventoryItem);
1523
+ * }
1524
+ * ```
1525
+ */
1526
+ constructor(database, getModel) {
1527
+ this.database = database;
1528
+ this.logger = new Logger7(this.constructor.name);
1529
+ this.modelGetter = getModel;
1530
+ this.logger.debug(`Initialized ${this.constructor.name}`);
1531
+ }
1532
+ /**
1533
+ * Create a new record
1534
+ *
1535
+ * @param data - The data to create the record with
1536
+ * @returns Promise resolving to the created record
1537
+ *
1538
+ * @example
1539
+ * ```typescript
1540
+ * const product = await productRepository.create({
1541
+ * name: 'Widget',
1542
+ * sku: 'WDG-001',
1543
+ * price: 9.99
1544
+ * });
1545
+ * ```
1546
+ */
1547
+ async create(data) {
1548
+ this.logger.log("Creating record");
1549
+ return await this.model.create({
1550
+ data
1551
+ });
1552
+ }
1553
+ /**
1554
+ * Find a single record by ID
1555
+ *
1556
+ * @param id - The record ID
1557
+ * @returns Promise resolving to the record or null if not found
1558
+ *
1559
+ * @example
1560
+ * ```typescript
1561
+ * const product = await productRepository.findById('product-id-123');
1562
+ * ```
1563
+ */
1564
+ async findById(id) {
1565
+ this.logger.debug(`Finding record by ID: ${id}`);
1566
+ return await this.model.findUnique({
1567
+ where: {
1568
+ id
741
1569
  }
742
- this.logger.debug(`Tenant config loaded: ${tenantInfo.subdomain} (${tenantInfo.type})`);
743
- this.tenantContext.setTenant(tenantInfo);
744
- request.tenant = tenantInfo;
745
- this.logger.log(`Tenant context set: ${tenantInfo.subdomain}`);
746
- } catch (error) {
747
- this.logger.error("Failed to set tenant context", error);
748
- throw error;
749
- }
750
- return next.handle();
1570
+ });
1571
+ }
1572
+ /**
1573
+ * Find a single record with custom where clause
1574
+ *
1575
+ * @param where - The where clause or findUnique args
1576
+ * @returns Promise resolving to the record or null if not found
1577
+ *
1578
+ * @example
1579
+ * ```typescript
1580
+ * // Simple where clause
1581
+ * const product = await productRepository.findOne({ sku: 'WDG-001' });
1582
+ *
1583
+ * // With include
1584
+ * const product = await productRepository.findOne({
1585
+ * where: { sku: 'WDG-001' },
1586
+ * include: { category: true }
1587
+ * });
1588
+ * ```
1589
+ */
1590
+ async findOne(where) {
1591
+ this.logger.debug("Finding record with custom query");
1592
+ return await this.model.findUnique(typeof where === "object" && "where" in where ? where : {
1593
+ where
1594
+ });
751
1595
  }
752
1596
  /**
753
- * Extract tenant identifier: tries subdomain first, then falls back to header
1597
+ * Find multiple records
1598
+ *
1599
+ * @param args - Prisma findMany arguments (where, orderBy, take, skip, etc.)
1600
+ * @returns Promise resolving to an array of records
1601
+ *
1602
+ * @example
1603
+ * ```typescript
1604
+ * // Find all products
1605
+ * const products = await productRepository.findMany();
1606
+ *
1607
+ * // Find with filtering and pagination
1608
+ * const products = await productRepository.findMany({
1609
+ * where: { status: 'ACTIVE' },
1610
+ * orderBy: { createdAt: 'desc' },
1611
+ * take: 10,
1612
+ * skip: 0
1613
+ * });
1614
+ * ```
754
1615
  */
755
- extractTenantIdentifier(request) {
756
- let tenantIdentifier = this.extractFromSubdomain(request);
757
- if (!tenantIdentifier) {
758
- tenantIdentifier = this.extractFromHeader(request);
759
- if (tenantIdentifier) {
760
- this.logger.debug("Using tenant from header (subdomain not found)");
1616
+ async findMany(args) {
1617
+ this.logger.debug("Finding multiple records");
1618
+ return await this.model.findMany(args);
1619
+ }
1620
+ /**
1621
+ * Update a record by ID
1622
+ *
1623
+ * @param id - The record ID
1624
+ * @param data - The data to update
1625
+ * @returns Promise resolving to the updated record
1626
+ *
1627
+ * @example
1628
+ * ```typescript
1629
+ * const product = await productRepository.update('product-id-123', {
1630
+ * price: 12.99
1631
+ * });
1632
+ * ```
1633
+ */
1634
+ async update(id, data) {
1635
+ this.logger.log(`Updating record with ID: ${id}`);
1636
+ return await this.model.update({
1637
+ where: {
1638
+ id
1639
+ },
1640
+ data
1641
+ });
1642
+ }
1643
+ /**
1644
+ * Update multiple records
1645
+ *
1646
+ * @param where - The where clause to match records
1647
+ * @param data - The data to update
1648
+ * @returns Promise resolving to the count of updated records
1649
+ *
1650
+ * @example
1651
+ * ```typescript
1652
+ * const result = await productRepository.updateMany(
1653
+ * { status: 'PENDING' },
1654
+ * { status: 'ACTIVE' }
1655
+ * );
1656
+ * console.log(`Updated ${result.count} products`);
1657
+ * ```
1658
+ */
1659
+ async updateMany(where, data) {
1660
+ this.logger.log("Updating multiple records");
1661
+ return await this.model.updateMany({
1662
+ where,
1663
+ data
1664
+ });
1665
+ }
1666
+ /**
1667
+ * Delete a record by ID
1668
+ *
1669
+ * @param id - The record ID
1670
+ * @returns Promise resolving to the deleted record
1671
+ *
1672
+ * @example
1673
+ * ```typescript
1674
+ * const product = await productRepository.delete('product-id-123');
1675
+ * ```
1676
+ */
1677
+ async delete(id) {
1678
+ this.logger.log(`Deleting record with ID: ${id}`);
1679
+ return await this.model.delete({
1680
+ where: {
1681
+ id
761
1682
  }
762
- }
763
- return tenantIdentifier;
1683
+ });
764
1684
  }
765
1685
  /**
766
- * Extract tenant from subdomain
767
- * @example acme.vritti.com → 'acme'
1686
+ * Delete multiple records
1687
+ *
1688
+ * @param where - The where clause to match records
1689
+ * @returns Promise resolving to the count of deleted records
1690
+ *
1691
+ * @example
1692
+ * ```typescript
1693
+ * const result = await productRepository.deleteMany({
1694
+ * status: 'INACTIVE',
1695
+ * createdAt: { lt: new Date('2020-01-01') }
1696
+ * });
1697
+ * console.log(`Deleted ${result.count} products`);
1698
+ * ```
768
1699
  */
769
- extractFromSubdomain(request) {
770
- if (request.subdomain) {
771
- return request.subdomain;
772
- }
773
- const host = request.headers?.host || request.hostname;
774
- return extractSubdomain(host);
1700
+ async deleteMany(where) {
1701
+ this.logger.log("Deleting multiple records");
1702
+ return await this.model.deleteMany({
1703
+ where
1704
+ });
775
1705
  }
776
1706
  /**
777
- * Extract tenant from HTTP headers
778
- * Checks x-tenant-id and x-subdomain headers
1707
+ * Count records
1708
+ *
1709
+ * @param where - Optional where clause to filter records
1710
+ * @returns Promise resolving to the count of records
1711
+ *
1712
+ * @example
1713
+ * ```typescript
1714
+ * // Count all products
1715
+ * const total = await productRepository.count();
1716
+ *
1717
+ * // Count active products
1718
+ * const activeCount = await productRepository.count({ status: 'ACTIVE' });
1719
+ * ```
779
1720
  */
780
- extractFromHeader(request) {
781
- const getHeader = /* @__PURE__ */ __name((key) => {
782
- const value = request.headers?.[key];
783
- return Array.isArray(value) ? value[0] : value;
784
- }, "getHeader");
785
- return getHeader("x-tenant-id") || getHeader("x-subdomain") || null;
1721
+ async count(where) {
1722
+ this.logger.debug("Counting records");
1723
+ return await this.model.count({
1724
+ where
1725
+ });
1726
+ }
1727
+ /**
1728
+ * Check if a record exists
1729
+ *
1730
+ * @param where - The where clause to match records
1731
+ * @returns Promise resolving to true if at least one record exists, false otherwise
1732
+ *
1733
+ * @example
1734
+ * ```typescript
1735
+ * const skuExists = await productRepository.exists({
1736
+ * sku: 'WDG-001'
1737
+ * });
1738
+ * ```
1739
+ */
1740
+ async exists(where) {
1741
+ const count = await this.model.count({
1742
+ where
1743
+ });
1744
+ return count > 0;
786
1745
  }
787
1746
  };
788
- TenantContextInterceptor = _ts_decorate6([
789
- Injectable5({
790
- scope: Scope3.REQUEST
791
- }),
792
- _ts_param3(2, Inject3(DATABASE_MODULE_OPTIONS)),
793
- _ts_metadata4("design:type", Function),
794
- _ts_metadata4("design:paramtypes", [
795
- typeof TenantContextService === "undefined" ? Object : TenantContextService,
796
- typeof PrimaryDatabaseService === "undefined" ? Object : PrimaryDatabaseService,
797
- typeof DatabaseModuleOptions === "undefined" ? Object : DatabaseModuleOptions
798
- ])
799
- ], TenantContextInterceptor);
800
1747
 
801
1748
  // src/database/decorators/tenant.decorator.ts
802
1749
  import { createParamDecorator } from "@nestjs/common";
@@ -808,14 +1755,242 @@ var Tenant = createParamDecorator((data, ctx) => {
808
1755
  }
809
1756
  return tenantContext.getTenant();
810
1757
  });
1758
+
1759
+ // src/auth/decorators/onboarding.decorator.ts
1760
+ import { SetMetadata } from "@nestjs/common";
1761
+ var Onboarding = /* @__PURE__ */ __name(() => SetMetadata("isOnboarding", true), "Onboarding");
1762
+
1763
+ // src/auth/decorators/public.decorator.ts
1764
+ import { SetMetadata as SetMetadata2 } from "@nestjs/common";
1765
+ var Public = /* @__PURE__ */ __name(() => SetMetadata2("isPublic", true), "Public");
1766
+
1767
+ // src/http/http.module.ts
1768
+ import { Module as Module4 } from "@nestjs/common";
1769
+
1770
+ // src/http/guards/csrf.guard.ts
1771
+ import { ForbiddenException, Injectable as Injectable8, Logger as Logger8 } from "@nestjs/common";
1772
+ import { Reflector as Reflector2 } from "@nestjs/core";
1773
+ function _ts_decorate11(decorators, target, key, desc) {
1774
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1775
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1776
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1777
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1778
+ }
1779
+ __name(_ts_decorate11, "_ts_decorate");
1780
+ function _ts_metadata7(k, v) {
1781
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1782
+ }
1783
+ __name(_ts_metadata7, "_ts_metadata");
1784
+ var CsrfGuard = class _CsrfGuard {
1785
+ static {
1786
+ __name(this, "CsrfGuard");
1787
+ }
1788
+ reflector;
1789
+ logger = new Logger8(_CsrfGuard.name);
1790
+ constructor(reflector) {
1791
+ this.reflector = reflector;
1792
+ }
1793
+ async canActivate(context) {
1794
+ const request = context.switchToHttp().getRequest();
1795
+ const reply = context.switchToHttp().getResponse();
1796
+ const safeMethods = [
1797
+ "GET",
1798
+ "HEAD",
1799
+ "OPTIONS"
1800
+ ];
1801
+ if (safeMethods.includes(request.method)) {
1802
+ return true;
1803
+ }
1804
+ try {
1805
+ const fastifyInstance = request.server;
1806
+ if (!fastifyInstance.csrfProtection) {
1807
+ this.logger.error("CSRF protection plugin not found. Ensure @fastify/csrf-protection is registered.");
1808
+ throw new ForbiddenException("CSRF protection not configured");
1809
+ }
1810
+ await new Promise((resolve, reject) => {
1811
+ fastifyInstance.csrfProtection(request, reply, (err) => {
1812
+ if (err) {
1813
+ reject(err);
1814
+ } else {
1815
+ resolve();
1816
+ }
1817
+ });
1818
+ });
1819
+ this.logger.debug(`CSRF validation successful for ${request.method} ${request.url}`);
1820
+ return true;
1821
+ } catch (error) {
1822
+ this.logger.warn(`CSRF validation failed for ${request.method} ${request.url}: ${error instanceof Error ? error.message : "Unknown error"}`);
1823
+ throw new ForbiddenException({
1824
+ errors: [
1825
+ {
1826
+ field: "csrf",
1827
+ message: "Invalid or missing CSRF token"
1828
+ }
1829
+ ],
1830
+ message: "CSRF validation failed"
1831
+ });
1832
+ }
1833
+ }
1834
+ };
1835
+ CsrfGuard = _ts_decorate11([
1836
+ Injectable8(),
1837
+ _ts_metadata7("design:type", Function),
1838
+ _ts_metadata7("design:paramtypes", [
1839
+ typeof Reflector2 === "undefined" ? Object : Reflector2
1840
+ ])
1841
+ ], CsrfGuard);
1842
+
1843
+ // src/http/http.module.ts
1844
+ function _ts_decorate12(decorators, target, key, desc) {
1845
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1846
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1847
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1848
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1849
+ }
1850
+ __name(_ts_decorate12, "_ts_decorate");
1851
+ var HttpModule = class {
1852
+ static {
1853
+ __name(this, "HttpModule");
1854
+ }
1855
+ };
1856
+ HttpModule = _ts_decorate12([
1857
+ Module4({
1858
+ providers: [
1859
+ CsrfGuard
1860
+ ],
1861
+ exports: [
1862
+ CsrfGuard
1863
+ ]
1864
+ })
1865
+ ], HttpModule);
1866
+
1867
+ // src/http/filters/http-exception.filter.ts
1868
+ import { Catch, HttpException, HttpStatus, Logger as Logger9 } from "@nestjs/common";
1869
+ function _ts_decorate13(decorators, target, key, desc) {
1870
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1871
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1872
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1873
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1874
+ }
1875
+ __name(_ts_decorate13, "_ts_decorate");
1876
+ var HttpExceptionFilter = class _HttpExceptionFilter {
1877
+ static {
1878
+ __name(this, "HttpExceptionFilter");
1879
+ }
1880
+ logger = new Logger9(_HttpExceptionFilter.name);
1881
+ catch(exception, host) {
1882
+ const ctx = host.switchToHttp();
1883
+ const reply = ctx.getResponse();
1884
+ const request = ctx.getRequest();
1885
+ let status = HttpStatus.INTERNAL_SERVER_ERROR;
1886
+ let errors = [];
1887
+ let message;
1888
+ if (exception instanceof HttpException) {
1889
+ status = exception.getStatus();
1890
+ const exceptionResponse = exception.getResponse();
1891
+ if (typeof exceptionResponse === "object" && exceptionResponse !== null) {
1892
+ const responseObj = exceptionResponse;
1893
+ if (Array.isArray(responseObj.message)) {
1894
+ errors = this.parseValidationErrors(responseObj.message);
1895
+ message = "Validation failed";
1896
+ } else if (responseObj.message) {
1897
+ errors = [
1898
+ {
1899
+ field: "general",
1900
+ message: responseObj.message
1901
+ }
1902
+ ];
1903
+ message = responseObj.message;
1904
+ }
1905
+ } else if (typeof exceptionResponse === "string") {
1906
+ errors = [
1907
+ {
1908
+ field: "general",
1909
+ message: exceptionResponse
1910
+ }
1911
+ ];
1912
+ message = exceptionResponse;
1913
+ }
1914
+ } else if (exception instanceof Error) {
1915
+ this.logger.error(`Unhandled error: ${exception.message}`, exception.stack);
1916
+ errors = [
1917
+ {
1918
+ field: "general",
1919
+ message: "Internal server error"
1920
+ }
1921
+ ];
1922
+ message = "An unexpected error occurred";
1923
+ } else {
1924
+ this.logger.error("Unknown exception type", exception);
1925
+ errors = [
1926
+ {
1927
+ field: "general",
1928
+ message: "Internal server error"
1929
+ }
1930
+ ];
1931
+ message = "An unexpected error occurred";
1932
+ }
1933
+ const errorResponse = {
1934
+ errors,
1935
+ message,
1936
+ statusCode: status,
1937
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1938
+ path: request.url
1939
+ };
1940
+ if (status >= 500) {
1941
+ this.logger.error(`HTTP ${status} Error: ${JSON.stringify(errorResponse)}`, exception instanceof Error ? exception.stack : void 0);
1942
+ } else {
1943
+ this.logger.warn(`HTTP ${status} Error: ${JSON.stringify(errorResponse)}`);
1944
+ }
1945
+ reply.status(status).send(errorResponse);
1946
+ }
1947
+ /**
1948
+ * Parse class-validator error messages into field-specific errors
1949
+ */
1950
+ parseValidationErrors(messages) {
1951
+ const errors = [];
1952
+ for (const msg of messages) {
1953
+ if (typeof msg === "string") {
1954
+ errors.push({
1955
+ field: "general",
1956
+ message: msg
1957
+ });
1958
+ } else if (typeof msg === "object" && msg.property && msg.constraints) {
1959
+ const field = msg.property;
1960
+ const constraintMessages = Object.values(msg.constraints);
1961
+ for (const constraintMsg of constraintMessages) {
1962
+ errors.push({
1963
+ field,
1964
+ message: constraintMsg
1965
+ });
1966
+ }
1967
+ }
1968
+ }
1969
+ return errors.length > 0 ? errors : [
1970
+ {
1971
+ field: "general",
1972
+ message: "Validation failed"
1973
+ }
1974
+ ];
1975
+ }
1976
+ };
1977
+ HttpExceptionFilter = _ts_decorate13([
1978
+ Catch()
1979
+ ], HttpExceptionFilter);
811
1980
  export {
1981
+ AuthConfigModule,
1982
+ CsrfGuard,
812
1983
  DatabaseModule,
813
- MessageTenantContextInterceptor,
1984
+ HttpExceptionFilter,
1985
+ HttpModule,
1986
+ Onboarding,
1987
+ PrimaryBaseRepository,
814
1988
  PrimaryDatabaseService,
1989
+ Public,
815
1990
  Tenant,
816
- TenantContextInterceptor,
1991
+ TenantBaseRepository,
817
1992
  TenantContextService,
818
1993
  TenantDatabaseService,
819
- extractSubdomain
1994
+ VrittiAuthGuard
820
1995
  };
821
1996
  //# sourceMappingURL=index.js.map