@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.d.cts CHANGED
@@ -1,5 +1,83 @@
1
- import { DynamicModule, OnModuleInit, OnModuleDestroy, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
2
- import { Observable } from 'rxjs';
1
+ import * as _nestjs_common from '@nestjs/common';
2
+ import { DynamicModule, OnModuleDestroy, OnModuleInit, Logger, CanActivate, ExecutionContext, ExceptionFilter, ArgumentsHost } from '@nestjs/common';
3
+ import { ConfigService } from '@nestjs/config';
4
+ import { Reflector } from '@nestjs/core';
5
+ import { JwtService } from '@nestjs/jwt';
6
+ import { FastifyRequest } from 'fastify';
7
+
8
+ /**
9
+ * Global authentication configuration module
10
+ *
11
+ * This module provides:
12
+ * - JWT token verification (JwtModule)
13
+ * - Global authentication guard (VrittiAuthGuard)
14
+ * - Support for @Public and @Onboarding decorators
15
+ *
16
+ * ## Features:
17
+ * - Automatically applies VrittiAuthGuard to all routes
18
+ * - Configures JwtModule with JWT_SECRET from environment
19
+ * - Exports JwtModule for token generation in services
20
+ *
21
+ * ## Usage in Application:
22
+ *
23
+ * @example
24
+ * // In app.module.ts
25
+ * @Module({
26
+ * imports: [
27
+ * ConfigModule.forRoot({ isGlobal: true }),
28
+ *
29
+ * // Auth configuration (global guard + JWT)
30
+ * AuthConfigModule.forRootAsync(),
31
+ *
32
+ * // Database configuration (Gateway mode)
33
+ * DatabaseModule.forServer({
34
+ * useFactory: (config: ConfigService) => ({
35
+ * primaryDb: {
36
+ * host: config.get('PRIMARY_DB_HOST'),
37
+ * // ... other config
38
+ * },
39
+ * prismaClientConstructor: PrismaClient,
40
+ * }),
41
+ * inject: [ConfigService],
42
+ * }),
43
+ * ],
44
+ * })
45
+ * export class AppModule {}
46
+ *
47
+ * ## Environment Variables Required:
48
+ * - JWT_SECRET: Secret key to verify access tokens (required)
49
+ * - JWT_REFRESH_SECRET: Secret key for refresh tokens (optional, falls back to JWT_SECRET)
50
+ *
51
+ * ## Bypass Authentication:
52
+ *
53
+ * @example
54
+ * // Skip authentication on specific endpoints
55
+ * @Public()
56
+ * @Post('auth/login')
57
+ * async login() { ... }
58
+ *
59
+ * @example
60
+ * // Onboarding endpoints (only accept onboarding tokens)
61
+ * @Onboarding()
62
+ * @Post('onboarding/verify-email')
63
+ * async verifyEmail(@Request() req) {
64
+ * const userId = req.user.id; // Available from guard
65
+ * ...
66
+ * }
67
+ */
68
+ declare class AuthConfigModule {
69
+ /**
70
+ * Register the auth module with async configuration
71
+ *
72
+ * This method:
73
+ * 1. Configures JwtModule with JWT_SECRET from ConfigService
74
+ * 2. Provides VrittiAuthGuard globally (applies to all routes)
75
+ * 3. Exports JwtModule for use in other modules (e.g., for signing tokens)
76
+ *
77
+ * @returns Dynamic module configuration
78
+ */
79
+ static forRootAsync(): DynamicModule;
80
+ }
3
81
 
4
82
  /**
5
83
  * Primary database connection configuration
@@ -66,19 +144,22 @@ interface DatabaseModuleOptions {
66
144
  /**
67
145
  * Tenant configuration stored in cloud database
68
146
  * This is the shape of data returned from the tenant registry
147
+ *
148
+ * Note: Database configuration is now stored in a separate TenantDatabaseConfig table
149
+ * but is flattened into this interface for convenience.
69
150
  */
70
151
  interface TenantInfo {
71
152
  /** Unique tenant identifier */
72
153
  id: string;
73
154
  /** Human-readable tenant slug */
74
155
  subdomain: string;
75
- /** Tenant type */
76
- type: 'SHARED' | 'DEDIACTED';
156
+ /** Tenant type - SHARED or DEDICATED */
157
+ type: 'SHARED' | 'DEDICATED';
77
158
  /** Tenant status */
78
159
  status: string;
79
- /** For CLOUD tenants: schema name */
160
+ /** For SHARED tenants: schema name within the shared database */
80
161
  schemaName?: string;
81
- /** For ENTERPRISE tenants: database configuration */
162
+ /** For DEDICATED tenants: database configuration (from TenantDatabaseConfig table) */
82
163
  databaseName?: string;
83
164
  databaseHost?: string;
84
165
  databasePort?: number;
@@ -98,20 +179,24 @@ interface TenantInfo {
98
179
  * - Support for both gateway and microservice modes
99
180
  *
100
181
  * ## Gateway Mode (API Gateway)
182
+ * - Use DatabaseModule.forServer() method
101
183
  * - Automatically extracts tenant from subdomain, falls back to x-tenant-id header
102
184
  * - Provide primaryDb configuration and prismaClientConstructor
103
185
  * - Automatically queries primary DB for tenant config
104
- * - Attaches TenantContextInterceptor globally
186
+ * - Automatically registers TenantContextInterceptor globally
187
+ * - No manual interceptor registration needed
105
188
  *
106
- * ## Microservice Mode
189
+ * ## Microservice Mode (RabbitMQ Workers)
190
+ * - Use DatabaseModule.forMicroservice() method
107
191
  * - Only provide prismaClientConstructor
108
192
  * - Tenant context comes from RabbitMQ messages
109
- * - Use MessageTenantContextInterceptor manually
193
+ * - Automatically registers MessageTenantContextInterceptor globally
194
+ * - No manual interceptor registration needed
110
195
  *
111
196
  * @example
112
197
  * // Gateway configuration
113
- * DatabaseModule.forRootAsync({
114
- * imports: [ConfigModule],
198
+ * DatabaseModule.forServer({
199
+ * inject: [ConfigService],
115
200
  * useFactory: (config: ConfigService) => ({
116
201
  * primaryDb: {
117
202
  * host: config.get('PRIMARY_DB_HOST'),
@@ -122,127 +207,80 @@ interface TenantInfo {
122
207
  * },
123
208
  * prismaClientConstructor: PrismaClient,
124
209
  * }),
125
- * inject: [ConfigService],
126
210
  * })
127
211
  *
128
212
  * @example
129
213
  * // Microservice configuration
130
- * DatabaseModule.forRoot({
131
- * prismaClientConstructor: PrismaClient,
214
+ * DatabaseModule.forMicroservice({
215
+ * inject: [ConfigService],
216
+ * useFactory: (config: ConfigService) => ({
217
+ * prismaClientConstructor: PrismaClient,
218
+ * }),
132
219
  * })
133
220
  */
134
221
  declare class DatabaseModule {
135
222
  /**
136
- * Synchronous configuration
137
- *
138
- * @param options Module configuration options
139
- * @returns Dynamic module configuration
140
- */
141
- static forRoot(options: DatabaseModuleOptions): DynamicModule;
142
- /**
143
- * Asynchronous configuration (recommended)
223
+ * Configure DatabaseModule for Gateway/HTTP mode (multi-tenant web servers)
144
224
  *
145
- * Allows injecting ConfigService or other dependencies
225
+ * This mode is for API Gateways that handle HTTP requests:
226
+ * - Automatically registers TenantContextInterceptor
227
+ * - Extracts tenant from subdomain or x-tenant-id header
228
+ * - Queries primary database for tenant configuration
229
+ * - Provides PrimaryDatabaseService for tenant lookup
146
230
  *
147
231
  * @param options Async configuration options
148
- * @returns Dynamic module configuration
232
+ * @returns Dynamic module configuration with HTTP interceptor
149
233
  *
150
234
  * @example
151
- * DatabaseModule.forRootAsync({
152
- * imports: [ConfigModule],
153
- * useFactory: async (config: ConfigService) => ({
154
- * cloudDatabaseUrl: config.get('CLOUD_DATABASE_URL'),
235
+ * DatabaseModule.forServer({
236
+ * inject: [ConfigService],
237
+ * useFactory: (config: ConfigService) => ({
238
+ * primaryDb: {
239
+ * host: config.get('PRIMARY_DB_HOST'),
240
+ * port: config.get('PRIMARY_DB_PORT'),
241
+ * username: config.get('PRIMARY_DB_USERNAME'),
242
+ * password: config.get('PRIMARY_DB_PASSWORD'),
243
+ * database: config.get('PRIMARY_DB_DATABASE'),
244
+ * },
155
245
  * prismaClientConstructor: PrismaClient,
156
- * tenantResolver: 'subdomain',
157
246
  * }),
158
- * inject: [ConfigService],
159
247
  * })
160
248
  */
161
- static forRootAsync(options: {
249
+ static forServer(options: {
162
250
  useFactory: (...args: any[]) => Promise<DatabaseModuleOptions> | DatabaseModuleOptions;
163
251
  inject?: any[];
164
252
  }): DynamicModule;
165
- }
166
-
167
- /**
168
- * Service responsible for querying the primary database to resolve tenant configurations
169
- *
170
- * This service:
171
- * - Connects to the primary database (tenant registry)
172
- * - Queries tenant metadata (database location, credentials, etc.)
173
- * - Caches tenant configs in memory to reduce database load
174
- * - Only used in GATEWAY MODE (microservices receive tenant config from messages)
175
- *
176
- * @example
177
- * // In API Gateway
178
- * const config = await primaryDatabase.getTenantConfig('acme');
179
- * // Returns: { id, slug, type, databaseHost, databaseName, ... }
180
- */
181
- declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
182
- private readonly options;
183
- private readonly logger;
184
- /** Primary database client for querying tenant registry */
185
- private primaryDbClient;
186
- /** In-memory cache: Map<tenantIdentifier, TenantConfig> */
187
- private readonly tenantConfigCache;
188
- /** Cache TTL in milliseconds */
189
- private readonly cacheTTL;
190
- constructor(options: DatabaseModuleOptions);
191
- onModuleInit(): Promise<void>;
192
253
  /**
193
- * Initialize connection to primary database
194
- */
195
- private initializePrimaryDbClient;
196
- /**
197
- * Build connection URL from primary database properties
198
- */
199
- private buildPrimaryDbUrl;
200
- /**
201
- * Mask password in connection URL for logging
202
- */
203
- private maskPassword;
204
- /**
205
- * Get tenant configuration by identifier (ID or slug)
254
+ * Configure DatabaseModule for Microservice/Messaging mode (RabbitMQ workers)
206
255
  *
207
- * @param tenantIdentifier Tenant ID or slug
208
- * @returns Tenant configuration or null if not found
209
- */
210
- getTenantInfo(tenantIdentifier: string): Promise<TenantInfo | null>;
211
- /**
212
- * Cache tenant information with TTL
213
- */
214
- private cacheInfo;
215
- /**
216
- * Clear cached tenant information
217
- *
218
- * Useful when tenant settings are updated and cache needs to be invalidated
219
- *
220
- * @param tenantIdentifier Tenant ID or slug
221
- */
222
- clearTenantCache(tenantIdentifier: string): void;
223
- /**
224
- * Clear all cached tenant configurations
225
- */
226
- clearAllCaches(): void;
227
- /**
228
- * Get primary database client for direct database access
256
+ * This mode is for microservices that process messages from queues:
257
+ * - Automatically registers MessageTenantContextInterceptor
258
+ * - Extracts tenant from RabbitMQ message patterns
259
+ * - No primary database needed (tenant comes from message context)
229
260
  *
230
- * This is useful for platform admin operations (creating tenants, billing, etc.)
261
+ * @param options Async configuration options
262
+ * @returns Dynamic module configuration with message interceptor
231
263
  *
232
- * @returns Primary database client instance
233
- * @throws Error if primary database client is not initialized
264
+ * @example
265
+ * DatabaseModule.forMicroservice({
266
+ * inject: [ConfigService],
267
+ * useFactory: (config: ConfigService) => ({
268
+ * prismaClientConstructor: PrismaClient,
269
+ * }),
270
+ * })
234
271
  */
235
- getPrimaryDbClient<T = any>(): T;
272
+ static forMicroservice(options: {
273
+ useFactory: (...args: any[]) => Promise<DatabaseModuleOptions> | DatabaseModuleOptions;
274
+ inject?: any[];
275
+ }): DynamicModule;
236
276
  /**
237
- * Decrypt database credentials
238
- *
239
- * Override this method to implement your encryption strategy
277
+ * Internal helper to create dynamic module with conditional interceptor registration
240
278
  *
241
- * @param encrypted Encrypted value
242
- * @returns Decrypted value
279
+ * @param options Configuration options
280
+ * @param mode Mode of operation (gateway or microservice)
281
+ * @returns Dynamic module configuration
243
282
  */
244
- private decrypt;
245
- onModuleDestroy(): Promise<void>;
283
+ private static createDynamicModule;
246
284
  }
247
285
 
248
286
  /**
@@ -338,6 +376,15 @@ declare class TenantDatabaseService implements OnModuleDestroy {
338
376
  /** Cleanup interval timer */
339
377
  private cleanupInterval?;
340
378
  constructor(options: DatabaseModuleOptions, tenantContext: TenantContextService);
379
+ /**
380
+ * Get the Prisma client for the current tenant's database.
381
+ * This returns the tenant-scoped database client.
382
+ *
383
+ * @returns Tenant-scoped database client instance
384
+ * @throws UnauthorizedException if tenant context not set
385
+ * @throws InternalServerErrorException if connection fails
386
+ */
387
+ get prismaClient(): any;
341
388
  /**
342
389
  * Get tenant-scoped database client for the current request/message
343
390
  *
@@ -354,7 +401,7 @@ declare class TenantDatabaseService implements OnModuleDestroy {
354
401
  * const dbClient = await tenantDatabase.getDbClient<PrismaClient>();
355
402
  * const users = await dbClient.user.findMany();
356
403
  */
357
- getDbClient<T = any>(): Promise<T>;
404
+ private getDbClient;
358
405
  /**
359
406
  * Create a new database client for the given tenant
360
407
  */
@@ -390,83 +437,705 @@ declare class TenantDatabaseService implements OnModuleDestroy {
390
437
  }
391
438
 
392
439
  /**
393
- * Interceptor that extracts tenant context from RabbitMQ messages (Microservice Mode)
440
+ * Service responsible for querying the primary database to resolve tenant configurations
394
441
  *
395
- * This interceptor:
396
- * 1. Extracts tenant info from RabbitMQ message payload
397
- * 2. Sets it in REQUEST-SCOPED TenantContextService
398
- * 3. Cleans up after message is processed
442
+ * This service:
443
+ * - Connects to the primary database (tenant registry)
444
+ * - Queries tenant metadata (database location, credentials, etc.)
445
+ * - Caches tenant configs in memory to reduce database load
446
+ * - Only used in GATEWAY MODE (microservices receive tenant config from messages)
399
447
  *
400
- * Expected message format:
401
- * {
402
- * dto: { ... },
403
- * tenant: {
404
- * tenantId: 'abc-123',
405
- * tenantSlug: 'acme',
406
- * tenantType: 'ENTERPRISE',
407
- * databaseHost: 'enterprise-1.aws.com',
408
- * databaseName: 'acme_db',
409
- * ...
448
+ * @example
449
+ * // In API Gateway
450
+ * const config = await primaryDatabase.getTenantConfig('acme');
451
+ * // Returns: { id, slug, type, databaseHost, databaseName, ... }
452
+ */
453
+ declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
454
+ private readonly options;
455
+ private readonly logger;
456
+ /** Primary database client for querying tenant registry */
457
+ private primaryDbClient;
458
+ /** In-memory cache: Map<tenantIdentifier, TenantConfig> */
459
+ private readonly tenantConfigCache;
460
+ /** Cache TTL in milliseconds */
461
+ private readonly cacheTTL;
462
+ constructor(options: DatabaseModuleOptions);
463
+ onModuleInit(): Promise<void>;
464
+ /**
465
+ * Initialize connection to primary database
466
+ */
467
+ private initializePrimaryDbClient;
468
+ /**
469
+ * Build connection URL from primary database properties
470
+ */
471
+ private buildPrimaryDbUrl;
472
+ /**
473
+ * Mask password in connection URL for logging
474
+ */
475
+ private maskPassword;
476
+ /**
477
+ * Get tenant configuration by identifier (ID or slug)
478
+ *
479
+ * @param tenantIdentifier Tenant ID or slug
480
+ * @returns Tenant configuration or null if not found
481
+ */
482
+ getTenantInfo(tenantIdentifier: string): Promise<TenantInfo | null>;
483
+ /**
484
+ * Cache tenant information with TTL
485
+ */
486
+ private cacheInfo;
487
+ /**
488
+ * Clear cached tenant information
489
+ *
490
+ * Useful when tenant settings are updated and cache needs to be invalidated
491
+ *
492
+ * @param tenantIdentifier Tenant ID or slug
493
+ */
494
+ clearTenantCache(tenantIdentifier: string): void;
495
+ /**
496
+ * Clear all cached tenant configurations
497
+ */
498
+ clearAllCaches(): void;
499
+ /**
500
+ * Get the Prisma client for the primary database.
501
+ * This is a synchronous property that returns the initialized Prisma client.
502
+ *
503
+ * @returns Primary database client instance
504
+ * @throws Error if primary database client is not initialized
505
+ */
506
+ get prismaClient(): any;
507
+ /**
508
+ * Decrypt database credentials
509
+ *
510
+ * Override this method to implement your encryption strategy
511
+ *
512
+ * @param encrypted Encrypted value
513
+ * @returns Decrypted value
514
+ */
515
+ private decrypt;
516
+ onModuleDestroy(): Promise<void>;
517
+ }
518
+
519
+ /**
520
+ * Abstract base repository for primary database operations.
521
+ * Provides common CRUD operations with automatic logging.
522
+ *
523
+ * @template TModel - The Prisma model type
524
+ * @template TCreateDTO - DTO type for create operations
525
+ * @template TUpdateDTO - DTO type for update operations
526
+ *
527
+ * @example
528
+ * ```typescript
529
+ * // Using the model delegate pattern (RECOMMENDED)
530
+ * // Type-safe, IDE autocomplete, refactor-friendly
531
+ * @Injectable()
532
+ * export class UserRepository extends PrimaryBaseRepository<
533
+ * User,
534
+ * CreateUserDto,
535
+ * UpdateUserDto
536
+ * > {
537
+ * constructor(database: PrimaryDatabaseService) {
538
+ * super(database, (prisma) => prisma.user); // ✅ Type-safe with autocomplete!
539
+ * }
540
+ *
541
+ * // Add custom methods as needed
542
+ * async findByEmail(email: string): Promise<User | null> {
543
+ * return this.model.findUnique({ where: { email } });
410
544
  * }
411
545
  * }
412
546
  *
547
+ * // Short syntax is also supported
548
+ * @Injectable()
549
+ * export class TenantRepository extends PrimaryBaseRepository<Tenant> {
550
+ * constructor(database: PrimaryDatabaseService) {
551
+ * super(database, (p) => p.tenant); // ✅ Concise!
552
+ * }
553
+ * }
554
+ *
555
+ * // Works with complex model names
556
+ * @Injectable()
557
+ * export class EmailVerificationRepository extends PrimaryBaseRepository<EmailVerification> {
558
+ * constructor(database: PrimaryDatabaseService) {
559
+ * super(database, (p) => p.emailVerification); // ✅ Matches Prisma naming
560
+ * }
561
+ * }
562
+ * ```
563
+ */
564
+ declare abstract class PrimaryBaseRepository<TModel, TCreateDTO = any, TUpdateDTO = any> {
565
+ protected readonly database: PrimaryDatabaseService;
566
+ protected readonly logger: Logger;
567
+ private readonly modelGetter;
568
+ /**
569
+ * Lazy getter for Prisma client.
570
+ * Accesses the client from the database service only when needed,
571
+ * avoiding initialization timing issues with NestJS lifecycle.
572
+ */
573
+ protected get prisma(): any;
574
+ /**
575
+ * Lazy getter for the Prisma model delegate.
576
+ * Returns the specific model (e.g., prisma.user, prisma.tenant) for this repository.
577
+ */
578
+ protected get model(): any;
579
+ /**
580
+ * Create a new repository instance
581
+ *
582
+ * @param database - The primary database service
583
+ * @param getModel - Function that returns the Prisma model delegate from the client
584
+ *
585
+ * @example
586
+ * ```typescript
587
+ * // Standard usage with full parameter name
588
+ * constructor(database: PrimaryDatabaseService) {
589
+ * super(database, (prisma) => prisma.user);
590
+ * }
591
+ *
592
+ * // Short syntax
593
+ * constructor(database: PrimaryDatabaseService) {
594
+ * super(database, (p) => p.user);
595
+ * }
596
+ *
597
+ * // Complex model names
598
+ * constructor(database: PrimaryDatabaseService) {
599
+ * super(database, (p) => p.emailVerification);
600
+ * }
601
+ * ```
602
+ */
603
+ constructor(database: PrimaryDatabaseService, getModel: (prisma: any) => any);
604
+ /**
605
+ * Create a new record
606
+ *
607
+ * @param data - The data to create the record with
608
+ * @returns Promise resolving to the created record
609
+ *
610
+ * @example
611
+ * ```typescript
612
+ * const user = await userRepository.create({
613
+ * email: 'user@example.com',
614
+ * name: 'John Doe'
615
+ * });
616
+ * ```
617
+ */
618
+ create(data: TCreateDTO): Promise<TModel>;
619
+ /**
620
+ * Find a single record by ID
621
+ *
622
+ * @param id - The record ID
623
+ * @returns Promise resolving to the record or null if not found
624
+ *
625
+ * @example
626
+ * ```typescript
627
+ * const user = await userRepository.findById('user-id-123');
628
+ * ```
629
+ */
630
+ findById(id: string): Promise<TModel | null>;
631
+ /**
632
+ * Find a single record with custom where clause
633
+ *
634
+ * @param where - The where clause or findUnique args
635
+ * @returns Promise resolving to the record or null if not found
636
+ *
637
+ * @example
638
+ * ```typescript
639
+ * // Simple where clause
640
+ * const user = await userRepository.findOne({ email: 'user@example.com' });
641
+ *
642
+ * // With include
643
+ * const user = await userRepository.findOne({
644
+ * where: { email: 'user@example.com' },
645
+ * include: { posts: true }
646
+ * });
647
+ * ```
648
+ */
649
+ findOne(where: any): Promise<TModel | null>;
650
+ /**
651
+ * Find multiple records
652
+ *
653
+ * @param args - Prisma findMany arguments (where, orderBy, take, skip, etc.)
654
+ * @returns Promise resolving to an array of records
655
+ *
656
+ * @example
657
+ * ```typescript
658
+ * // Find all users
659
+ * const users = await userRepository.findMany();
660
+ *
661
+ * // Find with filtering and pagination
662
+ * const users = await userRepository.findMany({
663
+ * where: { status: 'ACTIVE' },
664
+ * orderBy: { createdAt: 'desc' },
665
+ * take: 10,
666
+ * skip: 0
667
+ * });
668
+ * ```
669
+ */
670
+ findMany(args?: any): Promise<TModel[]>;
671
+ /**
672
+ * Update a record by ID
673
+ *
674
+ * @param id - The record ID
675
+ * @param data - The data to update
676
+ * @returns Promise resolving to the updated record
677
+ *
678
+ * @example
679
+ * ```typescript
680
+ * const user = await userRepository.update('user-id-123', {
681
+ * name: 'Jane Doe'
682
+ * });
683
+ * ```
684
+ */
685
+ update(id: string, data: TUpdateDTO): Promise<TModel>;
686
+ /**
687
+ * Update multiple records
688
+ *
689
+ * @param where - The where clause to match records
690
+ * @param data - The data to update
691
+ * @returns Promise resolving to the count of updated records
692
+ *
693
+ * @example
694
+ * ```typescript
695
+ * const result = await userRepository.updateMany(
696
+ * { status: 'PENDING' },
697
+ * { status: 'ACTIVE' }
698
+ * );
699
+ * console.log(`Updated ${result.count} users`);
700
+ * ```
701
+ */
702
+ updateMany(where: any, data: TUpdateDTO): Promise<{
703
+ count: number;
704
+ }>;
705
+ /**
706
+ * Delete a record by ID
707
+ *
708
+ * @param id - The record ID
709
+ * @returns Promise resolving to the deleted record
710
+ *
711
+ * @example
712
+ * ```typescript
713
+ * const user = await userRepository.delete('user-id-123');
714
+ * ```
715
+ */
716
+ delete(id: string): Promise<TModel>;
717
+ /**
718
+ * Delete multiple records
719
+ *
720
+ * @param where - The where clause to match records
721
+ * @returns Promise resolving to the count of deleted records
722
+ *
723
+ * @example
724
+ * ```typescript
725
+ * const result = await userRepository.deleteMany({
726
+ * status: 'INACTIVE',
727
+ * createdAt: { lt: new Date('2020-01-01') }
728
+ * });
729
+ * console.log(`Deleted ${result.count} users`);
730
+ * ```
731
+ */
732
+ deleteMany(where: any): Promise<{
733
+ count: number;
734
+ }>;
735
+ /**
736
+ * Count records
737
+ *
738
+ * @param where - Optional where clause to filter records
739
+ * @returns Promise resolving to the count of records
740
+ *
741
+ * @example
742
+ * ```typescript
743
+ * // Count all users
744
+ * const total = await userRepository.count();
745
+ *
746
+ * // Count active users
747
+ * const activeCount = await userRepository.count({ status: 'ACTIVE' });
748
+ * ```
749
+ */
750
+ count(where?: any): Promise<number>;
751
+ /**
752
+ * Check if a record exists
753
+ *
754
+ * @param where - The where clause to match records
755
+ * @returns Promise resolving to true if at least one record exists, false otherwise
756
+ *
757
+ * @example
758
+ * ```typescript
759
+ * const emailExists = await userRepository.exists({
760
+ * email: 'user@example.com'
761
+ * });
762
+ * ```
763
+ */
764
+ exists(where: any): Promise<boolean>;
765
+ }
766
+
767
+ /**
768
+ * Abstract base repository for tenant-scoped database operations.
769
+ * All operations are automatically scoped to the current tenant.
770
+ *
771
+ * @template TModel - The Prisma model type
772
+ * @template TCreateDTO - DTO type for create operations
773
+ * @template TUpdateDTO - DTO type for update operations
774
+ *
413
775
  * @example
414
- * // In microservice module
415
- * {
416
- * provide: APP_INTERCEPTOR,
417
- * useClass: MessageTenantContextInterceptor,
776
+ * ```typescript
777
+ * // Using the model delegate pattern (RECOMMENDED)
778
+ * // Type-safe, IDE autocomplete, refactor-friendly
779
+ * @Injectable()
780
+ * export class ProductRepository extends TenantBaseRepository<
781
+ * Product,
782
+ * CreateProductDto,
783
+ * UpdateProductDto
784
+ * > {
785
+ * constructor(database: TenantDatabaseService) {
786
+ * super(database, (prisma) => prisma.product); // ✅ Type-safe with autocomplete!
787
+ * }
788
+ *
789
+ * // Add custom methods as needed
790
+ * async findBySku(sku: string): Promise<Product | null> {
791
+ * return this.model.findUnique({ where: { sku } });
792
+ * }
418
793
  * }
794
+ *
795
+ * // Short syntax is also supported
796
+ * @Injectable()
797
+ * export class OrderRepository extends TenantBaseRepository<Order> {
798
+ * constructor(database: TenantDatabaseService) {
799
+ * super(database, (p) => p.order); // ✅ Concise!
800
+ * }
801
+ * }
802
+ *
803
+ * // Works with complex model names
804
+ * @Injectable()
805
+ * export class InventoryItemRepository extends TenantBaseRepository<InventoryItem> {
806
+ * constructor(database: TenantDatabaseService) {
807
+ * super(database, (p) => p.inventoryItem); // ✅ Matches Prisma naming
808
+ * }
809
+ * }
810
+ * ```
419
811
  */
420
- declare class MessageTenantContextInterceptor implements NestInterceptor {
421
- private readonly tenantContext;
422
- private readonly logger;
423
- constructor(tenantContext: TenantContextService);
424
- intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
812
+ declare abstract class TenantBaseRepository<TModel, TCreateDTO = any, TUpdateDTO = any> {
813
+ protected readonly database: TenantDatabaseService;
814
+ protected readonly logger: Logger;
815
+ private readonly modelGetter;
816
+ /**
817
+ * Lazy getter for Prisma client.
818
+ * Accesses the client from the database service only when needed,
819
+ * avoiding initialization timing issues with NestJS lifecycle.
820
+ */
821
+ protected get prisma(): any;
822
+ /**
823
+ * Lazy getter for the Prisma model delegate.
824
+ * Returns the specific model (e.g., prisma.product, prisma.order) for this repository.
825
+ */
826
+ protected get model(): any;
827
+ /**
828
+ * Create a new repository instance
829
+ *
830
+ * @param database - The tenant database service
831
+ * @param getModel - Function that returns the Prisma model delegate from the client
832
+ *
833
+ * @example
834
+ * ```typescript
835
+ * // Standard usage with full parameter name
836
+ * constructor(database: TenantDatabaseService) {
837
+ * super(database, (prisma) => prisma.product);
838
+ * }
839
+ *
840
+ * // Short syntax
841
+ * constructor(database: TenantDatabaseService) {
842
+ * super(database, (p) => p.product);
843
+ * }
844
+ *
845
+ * // Complex model names
846
+ * constructor(database: TenantDatabaseService) {
847
+ * super(database, (p) => p.inventoryItem);
848
+ * }
849
+ * ```
850
+ */
851
+ constructor(database: TenantDatabaseService, getModel: (prisma: any) => any);
852
+ /**
853
+ * Create a new record
854
+ *
855
+ * @param data - The data to create the record with
856
+ * @returns Promise resolving to the created record
857
+ *
858
+ * @example
859
+ * ```typescript
860
+ * const product = await productRepository.create({
861
+ * name: 'Widget',
862
+ * sku: 'WDG-001',
863
+ * price: 9.99
864
+ * });
865
+ * ```
866
+ */
867
+ create(data: TCreateDTO): Promise<TModel>;
868
+ /**
869
+ * Find a single record by ID
870
+ *
871
+ * @param id - The record ID
872
+ * @returns Promise resolving to the record or null if not found
873
+ *
874
+ * @example
875
+ * ```typescript
876
+ * const product = await productRepository.findById('product-id-123');
877
+ * ```
878
+ */
879
+ findById(id: string): Promise<TModel | null>;
880
+ /**
881
+ * Find a single record with custom where clause
882
+ *
883
+ * @param where - The where clause or findUnique args
884
+ * @returns Promise resolving to the record or null if not found
885
+ *
886
+ * @example
887
+ * ```typescript
888
+ * // Simple where clause
889
+ * const product = await productRepository.findOne({ sku: 'WDG-001' });
890
+ *
891
+ * // With include
892
+ * const product = await productRepository.findOne({
893
+ * where: { sku: 'WDG-001' },
894
+ * include: { category: true }
895
+ * });
896
+ * ```
897
+ */
898
+ findOne(where: any): Promise<TModel | null>;
899
+ /**
900
+ * Find multiple records
901
+ *
902
+ * @param args - Prisma findMany arguments (where, orderBy, take, skip, etc.)
903
+ * @returns Promise resolving to an array of records
904
+ *
905
+ * @example
906
+ * ```typescript
907
+ * // Find all products
908
+ * const products = await productRepository.findMany();
909
+ *
910
+ * // Find with filtering and pagination
911
+ * const products = await productRepository.findMany({
912
+ * where: { status: 'ACTIVE' },
913
+ * orderBy: { createdAt: 'desc' },
914
+ * take: 10,
915
+ * skip: 0
916
+ * });
917
+ * ```
918
+ */
919
+ findMany(args?: any): Promise<TModel[]>;
920
+ /**
921
+ * Update a record by ID
922
+ *
923
+ * @param id - The record ID
924
+ * @param data - The data to update
925
+ * @returns Promise resolving to the updated record
926
+ *
927
+ * @example
928
+ * ```typescript
929
+ * const product = await productRepository.update('product-id-123', {
930
+ * price: 12.99
931
+ * });
932
+ * ```
933
+ */
934
+ update(id: string, data: TUpdateDTO): Promise<TModel>;
935
+ /**
936
+ * Update multiple records
937
+ *
938
+ * @param where - The where clause to match records
939
+ * @param data - The data to update
940
+ * @returns Promise resolving to the count of updated records
941
+ *
942
+ * @example
943
+ * ```typescript
944
+ * const result = await productRepository.updateMany(
945
+ * { status: 'PENDING' },
946
+ * { status: 'ACTIVE' }
947
+ * );
948
+ * console.log(`Updated ${result.count} products`);
949
+ * ```
950
+ */
951
+ updateMany(where: any, data: TUpdateDTO): Promise<{
952
+ count: number;
953
+ }>;
954
+ /**
955
+ * Delete a record by ID
956
+ *
957
+ * @param id - The record ID
958
+ * @returns Promise resolving to the deleted record
959
+ *
960
+ * @example
961
+ * ```typescript
962
+ * const product = await productRepository.delete('product-id-123');
963
+ * ```
964
+ */
965
+ delete(id: string): Promise<TModel>;
966
+ /**
967
+ * Delete multiple records
968
+ *
969
+ * @param where - The where clause to match records
970
+ * @returns Promise resolving to the count of deleted records
971
+ *
972
+ * @example
973
+ * ```typescript
974
+ * const result = await productRepository.deleteMany({
975
+ * status: 'INACTIVE',
976
+ * createdAt: { lt: new Date('2020-01-01') }
977
+ * });
978
+ * console.log(`Deleted ${result.count} products`);
979
+ * ```
980
+ */
981
+ deleteMany(where: any): Promise<{
982
+ count: number;
983
+ }>;
984
+ /**
985
+ * Count records
986
+ *
987
+ * @param where - Optional where clause to filter records
988
+ * @returns Promise resolving to the count of records
989
+ *
990
+ * @example
991
+ * ```typescript
992
+ * // Count all products
993
+ * const total = await productRepository.count();
994
+ *
995
+ * // Count active products
996
+ * const activeCount = await productRepository.count({ status: 'ACTIVE' });
997
+ * ```
998
+ */
999
+ count(where?: any): Promise<number>;
425
1000
  /**
426
- * Clean up tenant context after message is processed
1001
+ * Check if a record exists
1002
+ *
1003
+ * @param where - The where clause to match records
1004
+ * @returns Promise resolving to true if at least one record exists, false otherwise
1005
+ *
1006
+ * @example
1007
+ * ```typescript
1008
+ * const skuExists = await productRepository.exists({
1009
+ * sku: 'WDG-001'
1010
+ * });
1011
+ * ```
427
1012
  */
428
- private cleanupContext;
1013
+ exists(where: any): Promise<boolean>;
1014
+ }
1015
+
1016
+ declare class RequestService {
1017
+ private readonly request;
1018
+ constructor(request: FastifyRequest);
1019
+ /**
1020
+ * Extract tenant identifier from request headers
1021
+ * Priority: x-tenant-id > x-subdomain
1022
+ * @returns Tenant identifier or null if not found
1023
+ */
1024
+ getTenantIdentifier(): string | null;
1025
+ /**
1026
+ * Extract access token from Authorization header
1027
+ * Format: "Bearer <token>"
1028
+ * @returns Access token or null if not found
1029
+ */
1030
+ getAccessToken(): string | null;
1031
+ /**
1032
+ * Extract refresh token from session-id cookie
1033
+ * Cookie name: session-id
1034
+ * @returns Refresh token or null if not found
1035
+ */
1036
+ getRefreshToken(): string | null;
1037
+ /**
1038
+ * Get a specific header value
1039
+ * @param key Header key
1040
+ * @returns Header value (string, array, or undefined)
1041
+ */
1042
+ getHeader(key: string): string | string[] | undefined;
1043
+ /**
1044
+ * Get all headers
1045
+ * @returns Record of all headers
1046
+ */
1047
+ getAllHeaders(): FastifyRequest['headers'];
429
1048
  }
430
1049
 
431
1050
  /**
432
- * Interceptor that extracts tenant context from HTTP requests (Gateway Mode)
1051
+ * Vritti Authentication Guard - Validates JWT tokens and tenant context
1052
+ *
1053
+ * This guard performs comprehensive validation and attaches user data to request.
1054
+ *
1055
+ * Validation Flow:
1056
+ * 1. Checks if endpoint is marked with @Public() decorator → skip all validation
1057
+ * 2. Checks if endpoint is marked with @Onboarding() decorator:
1058
+ * - Requires token type='onboarding'
1059
+ * - Validates JWT signature and expiry only
1060
+ * - Skips tenant and refresh token validation
1061
+ * - Attaches user data to request.user
1062
+ * 3. For regular endpoints (no decorator):
1063
+ * - Rejects tokens with type='onboarding'
1064
+ * - Validates access token (JWT signature, expiry, nbf)
1065
+ * - Validates refresh token from session-id cookie
1066
+ * - Validates tenant exists and is ACTIVE
1067
+ * - Attaches user data to request.user
433
1068
  *
434
- * This interceptor runs BEFORE the controller and:
435
- * 1. Extracts tenant identifier from request (tries subdomain first, then falls back to header)
436
- * 2. Queries primary database for tenant configuration
437
- * 3. Stores tenant info in REQUEST-SCOPED TenantContextService
1069
+ * Token Format:
1070
+ * - Access Token: "Authorization: Bearer <jwt_token>"
1071
+ * - Refresh Token: "session-id" cookie
438
1072
  *
439
- * Tenant resolution order:
440
- * - First: Subdomain (e.g., acme.vritti.com → 'acme')
441
- * - Fallback: x-tenant-id or x-tenant-slug header
1073
+ * Token Types:
1074
+ * - type='onboarding': Limited access during registration flow (@Onboarding endpoints only)
1075
+ * - type='access': Full access to authenticated endpoints
442
1076
  *
443
- * Only used in API Gateway. Microservices use MessageTenantContextInterceptor instead.
1077
+ * Environment Variables Required:
1078
+ * - JWT_SECRET: Secret key to verify access tokens (required)
1079
+ * - JWT_REFRESH_SECRET: Secret key for refresh tokens (optional, falls back to JWT_SECRET)
1080
+ *
1081
+ * Error Responses:
1082
+ * - 401: Invalid/expired access token
1083
+ * - 401: Invalid/expired refresh token
1084
+ * - 401: Tenant not found or inactive
1085
+ * - 401: Tenant identifier not found
1086
+ * - 401: Token type mismatch (onboarding token on regular endpoint or vice versa)
1087
+ *
1088
+ * @example
1089
+ * // Automatically registered by AuthConfigModule.forRootAsync()
1090
+ * // No manual registration needed
1091
+ * //
1092
+ * // Internal registration uses useExisting pattern:
1093
+ * // providers: [
1094
+ * // VrittiAuthGuard,
1095
+ * // {
1096
+ * // provide: APP_GUARD,
1097
+ * // useExisting: VrittiAuthGuard,
1098
+ * // },
1099
+ * // ]
444
1100
  *
445
1101
  * @example
446
- * // Request: https://acme.vritti.com/api/users
447
- * // Interceptor extracts "acme" from subdomain, queries primary DB, sets context
1102
+ * // Bypass guard with @Public() decorator
1103
+ * @Public()
1104
+ * @Post('auth/login')
1105
+ * async login(@Body() dto: LoginDto) { ... }
1106
+ *
1107
+ * @example
1108
+ * // Restrict to onboarding tokens with @Onboarding() decorator
1109
+ * @Onboarding()
1110
+ * @Post('onboarding/verify-email')
1111
+ * async verifyEmail(@Request() req) {
1112
+ * const userId = req.user.id; // Available from guard
1113
+ * ...
1114
+ * }
448
1115
  */
449
- declare class TenantContextInterceptor implements NestInterceptor {
450
- private readonly tenantContext;
1116
+ declare class VrittiAuthGuard implements CanActivate {
1117
+ private readonly reflector;
1118
+ private readonly configService;
1119
+ private readonly jwtService;
451
1120
  private readonly primaryDatabase;
452
- private readonly options;
1121
+ private readonly requestService;
453
1122
  private readonly logger;
454
- constructor(tenantContext: TenantContextService, primaryDatabase: PrimaryDatabaseService, options: DatabaseModuleOptions);
455
- intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>>;
1123
+ constructor(reflector: Reflector, configService: ConfigService, jwtService: JwtService, primaryDatabase: PrimaryDatabaseService, requestService: RequestService);
1124
+ canActivate(context: ExecutionContext): Promise<boolean>;
456
1125
  /**
457
- * Extract tenant identifier: tries subdomain first, then falls back to header
1126
+ * Validate access token with proper expiry checks
1127
+ * Throws UnauthorizedException if token is invalid or expired
458
1128
  */
459
- private extractTenantIdentifier;
1129
+ private validateAccessToken;
460
1130
  /**
461
- * Extract tenant from subdomain
462
- * @example acme.vritti.com 'acme'
1131
+ * Validate refresh token with proper expiry checks
1132
+ * Throws UnauthorizedException if token is invalid or expired
463
1133
  */
464
- private extractFromSubdomain;
1134
+ private validateRefreshToken;
465
1135
  /**
466
- * Extract tenant from HTTP headers
467
- * Checks x-tenant-id and x-subdomain headers
1136
+ * Helper to validate refresh token with specific secret
468
1137
  */
469
- private extractFromHeader;
1138
+ private validateRefreshTokenWithSecret;
470
1139
  }
471
1140
 
472
1141
  /**
@@ -488,9 +1157,9 @@ declare class TenantContextInterceptor implements NestInterceptor {
488
1157
  * @Get('info')
489
1158
  * async getTenantInfo(@Tenant() tenant: TenantInfo) {
490
1159
  * return {
491
- * tenantId: tenant.tenantId,
492
- * tenantSlug: tenant.tenantSlug,
493
- * tenantType: tenant.tenantType,
1160
+ * id: tenant.id,
1161
+ * subdomain: tenant.subdomain,
1162
+ * type: tenant.type,
494
1163
  * };
495
1164
  * }
496
1165
  *
@@ -501,7 +1170,7 @@ declare class TenantContextInterceptor implements NestInterceptor {
501
1170
  * @Body() dto: CreateUserDto,
502
1171
  * @Tenant() tenant: TenantInfo,
503
1172
  * ) {
504
- * this.logger.log(`Creating user for tenant: ${tenant.tenantSlug}`);
1173
+ * this.logger.log(`Creating user for tenant: ${tenant.subdomain}`);
505
1174
  * // ...
506
1175
  * }
507
1176
  *
@@ -509,7 +1178,7 @@ declare class TenantContextInterceptor implements NestInterceptor {
509
1178
  * // Conditional business logic
510
1179
  * @Get('features')
511
1180
  * async getFeatures(@Tenant() tenant: TenantInfo) {
512
- * if (tenant.tenantType === 'ENTERPRISE') {
1181
+ * if (tenant.type === 'ENTERPRISE') {
513
1182
  * return ['feature-a', 'feature-b', 'feature-c'];
514
1183
  * }
515
1184
  * return ['feature-a'];
@@ -518,17 +1187,142 @@ declare class TenantContextInterceptor implements NestInterceptor {
518
1187
  declare const Tenant: (...dataOrPipes: unknown[]) => ParameterDecorator;
519
1188
 
520
1189
  /**
521
- * Extract subdomain from hostname
1190
+ * Onboarding Decorator - Marks endpoints that require onboarding token
1191
+ *
1192
+ * Use this decorator on controllers or route handlers that should only be
1193
+ * accessible during the onboarding flow with JWT tokens containing type='onboarding'.
522
1194
  *
523
- * @param host Full hostname (e.g., 'acme.vritti.com:3000' or 'acme.vritti.com')
524
- * @returns Subdomain or null if not found
1195
+ * These endpoints:
1196
+ * - Accept ONLY tokens with type='onboarding'
1197
+ * - Reject regular access tokens (type='access')
1198
+ * - Skip tenant validation and refresh token checks
1199
+ * - Only validate JWT signature and expiry
1200
+ *
1201
+ * Useful for:
1202
+ * - Email/phone verification during onboarding
1203
+ * - Onboarding status checks
1204
+ * - Resending OTPs during registration
1205
+ *
1206
+ * @example
1207
+ * // On a controller method
1208
+ * @Post('verify-email')
1209
+ * @Onboarding()
1210
+ * async verifyEmail(@Request() req, @Body() dto: VerifyEmailDto) {
1211
+ * const userId = req.user.id; // Available from VrittiAuthGuard
1212
+ * return this.service.verifyEmail(userId, dto.otp);
1213
+ * }
525
1214
  *
526
1215
  * @example
527
- * extractSubdomain('acme.vritti.com') // 'acme'
528
- * extractSubdomain('staging-acme.vritti.com') // 'staging-acme'
529
- * extractSubdomain('localhost') // null
530
- * extractSubdomain('vritti.com') // null
1216
+ * // Multiple onboarding endpoints
1217
+ * @Controller('onboarding')
1218
+ * export class OnboardingController {
1219
+ * @Post('verify-email')
1220
+ * @Onboarding()
1221
+ * async verifyEmail() { ... }
1222
+ *
1223
+ * @Post('resend-otp')
1224
+ * @Onboarding()
1225
+ * async resendOtp() { ... }
1226
+ * }
1227
+ */
1228
+ declare const Onboarding: () => _nestjs_common.CustomDecorator<string>;
1229
+
1230
+ /**
1231
+ * Public Decorator - Marks endpoints that don't require authentication
1232
+ *
1233
+ * Use this decorator on controllers or route handlers to bypass VrittiAuthGuard
1234
+ * tenant validation. Useful for:
1235
+ * - Login/signup endpoints
1236
+ * - Health checks
1237
+ * - Public documentation endpoints
1238
+ * - Webhook endpoints that don't require tenant context
1239
+ *
1240
+ * @example
1241
+ * // On a controller method
1242
+ * @Public()
1243
+ * @Post('auth/login')
1244
+ * async login(@Body() dto: LoginDto) {
1245
+ * return this.authService.login(dto);
1246
+ * }
1247
+ *
1248
+ * @example
1249
+ * // On an entire controller
1250
+ * @Public()
1251
+ * @Controller('health')
1252
+ * export class HealthController {
1253
+ * @Get()
1254
+ * check() {
1255
+ * return { status: 'ok' };
1256
+ * }
1257
+ * }
531
1258
  */
532
- declare function extractSubdomain(host: string): string | null;
1259
+ declare const Public: () => _nestjs_common.CustomDecorator<string>;
1260
+
1261
+ /**
1262
+ * HTTP Module
1263
+ *
1264
+ * Provides HTTP utilities including:
1265
+ * - CSRF Guard for request protection
1266
+ * - HTTP Exception Filter for standardized error responses
1267
+ *
1268
+ * Usage:
1269
+ * Import this module to access HTTP guards and filters.
1270
+ * Guards and filters are registered globally in the main application.
1271
+ */
1272
+ declare class HttpModule {
1273
+ }
1274
+
1275
+ /**
1276
+ * CSRF Guard
1277
+ *
1278
+ * Global guard that automatically protects all state-changing requests (POST, PUT, PATCH, DELETE)
1279
+ * from CSRF attacks using Fastify's csrf-protection plugin.
1280
+ *
1281
+ * Flow:
1282
+ * 1. Skip safe methods (GET, HEAD, OPTIONS)
1283
+ * 2. Skip endpoints marked with @Public()
1284
+ * 3. Validate CSRF token for all other requests
1285
+ *
1286
+ * Token Sources (in priority order by @fastify/csrf-protection):
1287
+ * 1. req.headers['csrf-token']
1288
+ * 2. req.headers['xsrf-token']
1289
+ * 3. req.headers['x-csrf-token']
1290
+ * 4. req.headers['x-xsrf-token']
1291
+ * 5. req.body._csrf
1292
+ *
1293
+ * This guard should be registered globally in main.ts after CSRF plugin registration.
1294
+ */
1295
+ declare class CsrfGuard implements CanActivate {
1296
+ private reflector;
1297
+ private readonly logger;
1298
+ constructor(reflector: Reflector);
1299
+ canActivate(context: ExecutionContext): Promise<boolean>;
1300
+ }
1301
+
1302
+ /**
1303
+ * Global HTTP Exception Filter
1304
+ *
1305
+ * Standardizes all error responses in the format:
1306
+ * {
1307
+ * errors: [{ field: string, message: string }],
1308
+ * message?: string,
1309
+ * statusCode: number,
1310
+ * timestamp: string,
1311
+ * path: string
1312
+ * }
1313
+ *
1314
+ * Handles:
1315
+ * - Validation errors (class-validator) - Converts to field-specific errors
1316
+ * - HTTP exceptions - Maps to standardized format
1317
+ * - Unknown errors - Returns generic 500 error
1318
+ */
1319
+ declare class HttpExceptionFilter implements ExceptionFilter {
1320
+ private readonly logger;
1321
+ catch(exception: unknown, host: ArgumentsHost): void;
1322
+ /**
1323
+ * Parse class-validator error messages into field-specific errors
1324
+ */
1325
+ private parseValidationErrors;
1326
+ }
533
1327
 
534
- export { DatabaseModule, type DatabaseModuleOptions, MessageTenantContextInterceptor, PrimaryDatabaseService, type PrimaryDbConfig, Tenant, TenantContextInterceptor, TenantContextService, TenantDatabaseService, type TenantInfo, extractSubdomain };
1328
+ export { AuthConfigModule, CsrfGuard, DatabaseModule, type DatabaseModuleOptions, HttpExceptionFilter, HttpModule, Onboarding, PrimaryBaseRepository, PrimaryDatabaseService, type PrimaryDbConfig, Public, Tenant, TenantBaseRepository, TenantContextService, TenantDatabaseService, type TenantInfo, VrittiAuthGuard };