@vritti/api-sdk 0.1.7 → 0.2.1

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.ts CHANGED
@@ -1,299 +1,21 @@
1
1
  import * as _nestjs_common from '@nestjs/common';
2
- import { DynamicModule, OnModuleInit, OnModuleDestroy, CanActivate, ExecutionContext, Logger, HttpException, HttpStatus, ExceptionFilter, ArgumentsHost, ModuleMetadata, Type, LoggerService as LoggerService$1, NestInterceptor, CallHandler, NestModule, MiddlewareConsumer, NestMiddleware } from '@nestjs/common';
2
+ import { DynamicModule, CanActivate, ExecutionContext, InjectionToken, OnModuleInit, OnModuleDestroy, Logger, HttpException, HttpStatus, ExceptionFilter, ArgumentsHost, ModuleMetadata, Type, LoggerService as LoggerService$1, NestInterceptor, CallHandler, NestModule, MiddlewareConsumer, NestMiddleware } from '@nestjs/common';
3
3
  import { ConfigService } from '@nestjs/config';
4
4
  import { Reflector } from '@nestjs/core';
5
- import { JwtService } from '@nestjs/jwt';
6
- import { NodePgDatabase } from 'drizzle-orm/node-postgres';
5
+ import { JwtService, JwtModuleOptions, JwtSignOptions } from '@nestjs/jwt';
7
6
  import { FastifyRequest, FastifyReply } from 'fastify';
7
+ import { NodePgDatabase } from 'drizzle-orm/node-postgres';
8
8
  import { InferInsertModel, InferSelectModel, SQL } from 'drizzle-orm';
9
9
  import { PgTable } from 'drizzle-orm/pg-core';
10
10
  import { Observable } from 'rxjs';
11
11
  import { AsyncLocalStorage } from 'node:async_hooks';
12
12
 
13
- /**
14
- * Global authentication configuration module
15
- *
16
- * This module provides:
17
- * - JWT token verification (JwtModule)
18
- * - Global authentication guard (VrittiAuthGuard)
19
- * - Support for @Public and @Onboarding decorators
20
- *
21
- * ## Features:
22
- * - Automatically applies VrittiAuthGuard to all routes
23
- * - Configures JwtModule with JWT_SECRET from environment
24
- * - Exports JwtModule for token generation in services
25
- *
26
- * ## Usage in Application:
27
- *
28
- * @example
29
- * // In app.module.ts
30
- * @Module({
31
- * imports: [
32
- * ConfigModule.forRoot({ isGlobal: true }),
33
- *
34
- * // Auth configuration (global guard + JWT)
35
- * AuthConfigModule.forRootAsync(),
36
- *
37
- * // Database configuration (Gateway mode)
38
- * DatabaseModule.forServer({
39
- * useFactory: (config: ConfigService) => ({
40
- * primaryDb: {
41
- * host: config.get('PRIMARY_DB_HOST'),
42
- * // ... other config
43
- * },
44
- * prismaClientConstructor: PrismaClient,
45
- * }),
46
- * inject: [ConfigService],
47
- * }),
48
- * ],
49
- * })
50
- * export class AppModule {}
51
- *
52
- * ## Environment Variables Required:
53
- * - JWT_SECRET: Secret key to verify access tokens (required)
54
- * - JWT_REFRESH_SECRET: Secret key for refresh tokens (optional, falls back to JWT_SECRET)
55
- *
56
- * ## Bypass Authentication:
57
- *
58
- * @example
59
- * // Skip authentication on specific endpoints
60
- * @Public()
61
- * @Post('auth/login')
62
- * async login() { ... }
63
- *
64
- * @example
65
- * // Onboarding endpoints (only accept onboarding tokens)
66
- * @Onboarding()
67
- * @Post('onboarding/verify-email')
68
- * async verifyEmail(@Request() req) {
69
- * const userId = req.user.id; // Available from guard
70
- * ...
71
- * }
72
- */
73
- declare class AuthConfigModule {
74
- /**
75
- * Register the auth module with async configuration
76
- *
77
- * This method:
78
- * 1. Configures JwtModule with JWT_SECRET from ConfigService
79
- * 2. Provides VrittiAuthGuard globally (applies to all routes)
80
- * 3. Exports JwtModule for use in other modules (e.g., for signing tokens)
81
- *
82
- * @returns Dynamic module configuration
83
- */
84
- static forRootAsync(): DynamicModule;
85
- }
86
-
87
- /**
88
- * Onboarding Decorator - Marks endpoints that require onboarding token
89
- *
90
- * Use this decorator on controllers or route handlers that should only be
91
- * accessible during the onboarding flow with JWT tokens containing type='onboarding'.
92
- *
93
- * These endpoints:
94
- * - Accept ONLY tokens with type='onboarding'
95
- * - Reject regular access tokens (type='access')
96
- * - Skip tenant validation and refresh token checks
97
- * - Only validate JWT signature and expiry
98
- *
99
- * Useful for:
100
- * - Email/phone verification during onboarding
101
- * - Onboarding status checks
102
- * - Resending OTPs during registration
103
- *
104
- * @example
105
- * // On a controller method
106
- * @Post('verify-email')
107
- * @Onboarding()
108
- * async verifyEmail(@Request() req, @Body() dto: VerifyEmailDto) {
109
- * const userId = req.user.id; // Available from VrittiAuthGuard
110
- * return this.service.verifyEmail(userId, dto.otp);
111
- * }
112
- *
113
- * @example
114
- * // Multiple onboarding endpoints
115
- * @Controller('onboarding')
116
- * export class OnboardingController {
117
- * @Post('verify-email')
118
- * @Onboarding()
119
- * async verifyEmail() { ... }
120
- *
121
- * @Post('resend-otp')
122
- * @Onboarding()
123
- * async resendOtp() { ... }
124
- * }
125
- */
126
- declare const Onboarding: () => _nestjs_common.CustomDecorator<string>;
127
-
128
- /**
129
- * Public Decorator - Marks endpoints that don't require authentication
130
- *
131
- * Use this decorator on controllers or route handlers to bypass VrittiAuthGuard
132
- * tenant validation. Useful for:
133
- * - Login/signup endpoints
134
- * - Health checks
135
- * - Public documentation endpoints
136
- * - Webhook endpoints that don't require tenant context
137
- *
138
- * @example
139
- * // On a controller method
140
- * @Public()
141
- * @Post('auth/login')
142
- * async login(@Body() dto: LoginDto) {
143
- * return this.authService.login(dto);
144
- * }
145
- *
146
- * @example
147
- * // On an entire controller
148
- * @Public()
149
- * @Controller('health')
150
- * export class HealthController {
151
- * @Get()
152
- * check() {
153
- * return { status: 'ok' };
154
- * }
155
- * }
156
- */
157
- declare const Public: () => _nestjs_common.CustomDecorator<string>;
158
-
159
- /**
160
- * Parameter decorator to extract user ID from authenticated request
161
- *
162
- * This decorator retrieves the user ID from the request object,
163
- * which is set by authentication guards (JwtAuthGuard, VrittiAuthGuard).
164
- *
165
- * @returns The user's ID as a string (UUID)
166
- *
167
- * @example
168
- * @Post('verify-email')
169
- * @Onboarding()
170
- * async verifyEmail(@UserId() userId: string) {
171
- * await this.service.verify(userId);
172
- * }
173
- *
174
- * @example
175
- * @Post('logout-all')
176
- * @UseGuards(JwtAuthGuard)
177
- * async logoutAll(@UserId() userId: string) {
178
- * await this.authService.logoutAll(userId);
179
- * }
180
- */
181
- declare const UserId: (...dataOrPipes: unknown[]) => ParameterDecorator;
182
-
183
- /**
184
- * Schema Registry Interface
185
- *
186
- * Projects augment this interface to register their Drizzle schema.
187
- * This enables type-safe db.query access without passing schema types everywhere.
188
- *
189
- * @example
190
- * // In your project's schema.registry.ts:
191
- * declare module '@vritti/api-sdk' {
192
- * interface SchemaRegistry {
193
- * schema: typeof import('./schema');
194
- * }
195
- * }
196
- */
197
- type SchemaRegistry = {};
198
- /**
199
- * Extracts the registered schema type.
200
- * Falls back to Record<string, unknown> if no schema is registered.
201
- */
202
- type RegisteredSchema = SchemaRegistry extends {
203
- schema: infer S;
204
- } ? S : Record<string, unknown>;
205
- /**
206
- * Type alias for the Drizzle database client with registered schema
207
- */
208
- type TypedDrizzleClient = NodePgDatabase<RegisteredSchema>;
209
-
210
- /**
211
- * Primary database connection configuration
212
- */
213
- interface PrimaryDbConfig {
214
- /** Database host */
215
- host: string;
216
- /** Database port (default: 5432) */
217
- port?: number;
218
- /** Database username */
219
- username: string;
220
- /** Database password */
221
- password: string;
222
- /** Database name */
223
- database: string;
224
- /** Default schema (default: 'public') */
225
- schema?: string;
226
- /** SSL mode: 'require' | 'prefer' | 'disable' | 'no-verify' (default: 'require') */
227
- sslMode?: 'require' | 'prefer' | 'disable' | 'no-verify';
228
- }
229
- /**
230
- * Configuration options for DatabaseModule
231
- */
232
- interface DatabaseModuleOptions {
233
- /**
234
- * Primary database configuration (for tenant registry queries)
235
- * Only required in gateway mode
236
- * @example
237
- * primaryDb: {
238
- * host: 'aws-pooler.supabase.com',
239
- * port: 5432,
240
- * username: 'postgres.xxx',
241
- * password: 'xxx',
242
- * database: 'postgres',
243
- * schema: 'public',
244
- * sslMode: 'require',
245
- * }
246
- */
247
- primaryDb: PrimaryDbConfig;
248
- /**
249
- * Drizzle schema object containing all tables
250
- * Import your schema from db/schema/index.ts and pass it here
251
- * @example import * as schema from '@/db/schema'
252
- */
253
- drizzleSchema: RegisteredSchema;
254
- /**
255
- * Drizzle relations object from defineRelations()
256
- * Required for relational queries (db.query.*.findFirst/findMany)
257
- * @example import { relations } from '@/db/schema'
258
- */
259
- drizzleRelations?: Record<string, any>;
260
- /**
261
- * Connection cache TTL in milliseconds
262
- * Idle connections will be closed after this period
263
- * @default 300000 (5 minutes)
264
- */
265
- connectionCacheTTL?: number;
266
- /**
267
- * Maximum number of concurrent connections per tenant
268
- * @default 10
269
- */
270
- maxConnections?: number;
271
- /**
272
- * Encryption key for decrypting database credentials
273
- * Required if tenant config stores encrypted passwords
274
- */
275
- encryptionKey?: string;
276
- }
277
-
278
- /**
279
- * Tenant configuration stored in cloud database
280
- * This is the shape of data returned from the tenant registry
281
- *
282
- * Note: Database configuration is now stored in a separate TenantDatabaseConfig table
283
- * but is flattened into this interface for convenience.
284
- */
285
13
  interface TenantInfo {
286
- /** Unique tenant identifier */
287
14
  id: string;
288
- /** Human-readable tenant slug */
289
15
  subdomain: string;
290
- /** Tenant type - SHARED or DEDICATED */
291
16
  type: 'SHARED' | 'DEDICATED';
292
- /** Tenant status */
293
17
  status: string;
294
- /** For SHARED tenants: schema name within the shared database */
295
18
  schemaName?: string;
296
- /** For DEDICATED tenants: database configuration (from TenantDatabaseConfig table) */
297
19
  databaseName?: string;
298
20
  databaseHost?: string;
299
21
  databasePort?: number;
@@ -303,633 +25,259 @@ interface TenantInfo {
303
25
  connectionPoolSize?: number;
304
26
  }
305
27
 
306
- /**
307
- * Service responsible for querying the primary database to resolve tenant configurations
308
- *
309
- * This service:
310
- * - Connects to the primary database (tenant registry)
311
- * - Queries tenant metadata (database location, credentials, etc.)
312
- * - Caches tenant configs in memory to reduce database load
313
- * - Only used in GATEWAY MODE (microservices receive tenant config from messages)
314
- *
315
- * @example
316
- * // In API Gateway
317
- * const config = await primaryDatabase.getTenantConfig('acme');
318
- * // Returns: { id, slug, type, databaseHost, databaseName, ... }
319
- */
320
- declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
321
- private readonly options;
322
- private readonly logger;
323
- /** PostgreSQL connection pool */
324
- private pool;
325
- /** Drizzle database instance */
326
- private db;
327
- /** In-memory cache: Map<tenantIdentifier, TenantConfig> */
328
- private readonly tenantConfigCache;
329
- /** Cache TTL in milliseconds */
330
- private readonly cacheTTL;
331
- constructor(options: DatabaseModuleOptions);
332
- onModuleInit(): Promise<void>;
333
- /**
334
- * Initialize connection to primary database using Drizzle
335
- */
336
- private initializeDrizzleClient;
337
- /**
338
- * Build connection URL from primary database properties
339
- */
340
- private buildPrimaryDbUrl;
341
- /**
342
- * Mask password in connection URL for logging
343
- */
344
- private maskPassword;
345
- /**
346
- * Get tenant configuration by identifier (ID or subdomain)
347
- *
348
- * @param tenantIdentifier Tenant ID or subdomain
349
- * @returns Tenant configuration or null if not found
350
- */
351
- getTenantInfo(tenantIdentifier: string): Promise<TenantInfo | null>;
352
- /**
353
- * Cache tenant information with TTL
354
- */
355
- private cacheInfo;
356
- /**
357
- * Clear cached tenant information
358
- *
359
- * Useful when tenant settings are updated and cache needs to be invalidated
360
- *
361
- * @param tenantIdentifier Tenant ID or subdomain
362
- */
363
- clearTenantCache(tenantIdentifier: string): void;
364
- /**
365
- * Clear all cached tenant configurations
366
- */
367
- clearAllCaches(): void;
368
- /**
369
- * Get the Drizzle database instance for the primary database.
370
- * This is a synchronous property that returns the initialized Drizzle client.
371
- *
372
- * @returns Primary database Drizzle instance
373
- * @throws Error if primary database client is not initialized
374
- */
375
- get drizzleClient(): TypedDrizzleClient;
376
- /**
377
- * Get the Drizzle schema
378
- */
379
- get schema(): typeof this$1.options.drizzleSchema;
380
- /**
381
- * Decrypt database credentials
382
- *
383
- * Override this method to implement your encryption strategy
384
- *
385
- * @param encrypted Encrypted value
386
- * @returns Decrypted value
387
- */
388
- private decrypt;
389
- onModuleDestroy(): Promise<void>;
28
+ declare module 'fastify' {
29
+ interface FastifyRequest {
30
+ sessionInfo?: {
31
+ userId: string;
32
+ sessionId: string;
33
+ sessionType: string;
34
+ };
35
+ tenant?: TenantInfo;
36
+ cookies?: Record<string, string>;
37
+ }
390
38
  }
391
39
 
40
+ declare class AuthConfigModule {
41
+ static forRootAsync(): DynamicModule;
42
+ }
43
+
44
+ declare const AccessToken: (...dataOrPipes: unknown[]) => ParameterDecorator;
45
+
46
+ declare const Onboarding: () => _nestjs_common.CustomDecorator<string>;
47
+
48
+ declare const Public: () => _nestjs_common.CustomDecorator<string>;
49
+
50
+ declare const RefreshTokenCookie: (...dataOrPipes: unknown[]) => ParameterDecorator;
51
+
52
+ declare const RESET_KEY = "isReset";
53
+ declare const Reset: () => _nestjs_common.CustomDecorator<string>;
54
+
55
+ interface SessionInfo {
56
+ userId: string;
57
+ sessionId: string;
58
+ sessionType: string;
59
+ }
60
+ declare const SessionData: (...dataOrPipes: unknown[]) => ParameterDecorator;
61
+
62
+ declare const UserId: (...dataOrPipes: unknown[]) => ParameterDecorator;
63
+
392
64
  declare class RequestService {
393
65
  private readonly request;
394
66
  constructor(request: FastifyRequest);
395
- /**
396
- * Extract tenant identifier from request headers
397
- * Priority: x-tenant-id > x-subdomain
398
- * @returns Tenant identifier or null if not found
399
- */
400
67
  getTenantIdentifier(): string | null;
401
- /**
402
- * Extract access token from Authorization header
403
- * Format: "Bearer <token>"
404
- * @returns Access token or null if not found
405
- */
406
68
  getAccessToken(): string | null;
407
- /**
408
- * Extract refresh token from httpOnly cookie
409
- * Cookie name is configurable via api-sdk config
410
- * @returns Refresh token or null if not found
411
- */
412
69
  getRefreshToken(): string | null;
413
- /**
414
- * Get a specific header value
415
- * @param key Header key
416
- * @returns Header value (string, array, or undefined)
417
- */
418
70
  getHeader(key: string): string | string[] | undefined;
419
- /**
420
- * Get all headers
421
- * @returns Record of all headers
422
- */
423
71
  getAllHeaders(): FastifyRequest['headers'];
424
72
  }
425
73
 
426
- /**
427
- * Vritti Authentication Guard - Validates JWT access tokens and tenant context
428
- *
429
- * This guard performs access token validation and attaches user data to request.
430
- * NOTE: Refresh tokens are NOT validated here - they are only validated in
431
- * /auth/token and /auth/refresh endpoints (session.service.ts).
432
- *
433
- * Validation Flow:
434
- * 1. Checks if endpoint is marked with @Public() decorator → skip all validation
435
- * 2. Checks if endpoint is marked with @Onboarding() decorator:
436
- * - Requires token type='onboarding'
437
- * - Validates JWT signature and expiry only
438
- * - Skips tenant validation
439
- * - Attaches user data to request.user
440
- * 3. For regular endpoints (no decorator):
441
- * - Rejects tokens with type='onboarding'
442
- * - Validates access token (JWT signature, expiry, nbf)
443
- * - Validates tenant exists and is ACTIVE
444
- * - Attaches user data to request.user
445
- *
446
- * Token Format:
447
- * - Access Token: "Authorization: Bearer <jwt_token>"
448
- *
449
- * Token Types:
450
- * - type='onboarding': Limited access during registration flow (@Onboarding endpoints only)
451
- * - type='access': Full access to authenticated endpoints
452
- *
453
- * Environment Variables Required:
454
- * - JWT_SECRET: Secret key to verify access tokens (required)
455
- *
456
- * Error Responses:
457
- * - 401: Invalid/expired access token
458
- * - 401: Tenant not found or inactive
459
- * - 401: Tenant identifier not found
460
- * - 401: Token type mismatch (onboarding token on regular endpoint or vice versa)
461
- *
462
- * @example
463
- * // Automatically registered by AuthConfigModule.forRootAsync()
464
- * // No manual registration needed
465
- * //
466
- * // Internal registration uses useExisting pattern:
467
- * // providers: [
468
- * // VrittiAuthGuard,
469
- * // {
470
- * // provide: APP_GUARD,
471
- * // useExisting: VrittiAuthGuard,
472
- * // },
473
- * // ]
474
- *
475
- * @example
476
- * // Bypass guard with @Public() decorator
477
- * @Public()
478
- * @Post('auth/login')
479
- * async login(@Body() dto: LoginDto) { ... }
480
- *
481
- * @example
482
- * // Restrict to onboarding tokens with @Onboarding() decorator
483
- * @Onboarding()
484
- * @Post('onboarding/verify-email')
485
- * async verifyEmail(@Request() req) {
486
- * const userId = req.user.id; // Available from guard
487
- * ...
488
- * }
489
- */
490
74
  declare class VrittiAuthGuard implements CanActivate {
491
75
  private readonly reflector;
492
76
  readonly _configService: ConfigService;
493
77
  private readonly jwtService;
494
- private readonly primaryDatabase;
495
78
  private readonly requestService;
496
79
  private readonly logger;
497
- constructor(reflector: Reflector, _configService: ConfigService, jwtService: JwtService, primaryDatabase: PrimaryDatabaseService, requestService: RequestService);
80
+ constructor(reflector: Reflector, _configService: ConfigService, jwtService: JwtService, requestService: RequestService);
498
81
  canActivate(context: ExecutionContext): Promise<boolean>;
499
- /**
500
- * Validate access token with proper expiry checks
501
- * Throws UnauthorizedException if token is invalid or expired
502
- */
503
82
  private validateAccessToken;
504
- /**
505
- * Validate that the access token is bound to the refresh token in the cookie.
506
- * This prevents token theft - a stolen access token is useless without the
507
- * corresponding refresh token cookie.
508
- *
509
- * @param context - The execution context containing the request
510
- * @param validatedToken - The decoded and validated JWT token
511
- * @throws UnauthorizedException if token binding validation fails
512
- */
513
83
  private validateRefreshTokenBinding;
514
- /**
515
- * Validate CSRF token for state-changing requests
516
- * Uses Fastify's csrf-protection plugin for token validation
517
- *
518
- * @param request - Fastify request object
519
- * @param reply - Fastify reply object
520
- * @throws ForbiddenException if CSRF validation fails
521
- */
84
+ private handleSseAuth;
522
85
  private validateCsrf;
523
86
  }
524
87
 
525
- /**
526
- * SSE Authentication Guard - For Server-Sent Events endpoints
527
- *
528
- * This guard is specifically designed for SSE endpoints where:
529
- * 1. Browser's EventSource API cannot send custom headers
530
- * 2. Token must be passed via query parameter
531
- * 3. CORS headers must be set before any response (including errors)
532
- *
533
- * Validation Flow:
534
- * 1. Set CORS headers FIRST (ensures error responses include CORS)
535
- * 2. Extract token from query param (?token=<jwt>)
536
- * 3. Validate token is type='onboarding'
537
- * 4. Attach user data to request.user
538
- *
539
- * Usage:
540
- * ```typescript
541
- * @Sse('events')
542
- * @Public() // Bypass global VrittiAuthGuard
543
- * @UseGuards(SseAuthGuard)
544
- * async subscribeToEvents(@UserId() userId: string) { ... }
545
- * ```
546
- *
547
- * Note: Must be used with @Public() to bypass the global VrittiAuthGuard
548
- * since EventSource cannot send Authorization headers.
549
- */
550
- declare class SseAuthGuard implements CanActivate {
88
+ declare const jwtConfigFactory: (configService: ConfigService) => JwtModuleOptions;
89
+ type TokenExpiryString = `${number}${'s' | 'm' | 'h' | 'd' | 'w' | 'y'}`;
90
+ interface TokenExpiry {
91
+ access: TokenExpiryString;
92
+ refresh: TokenExpiryString;
93
+ }
94
+ declare const getTokenExpiry: (configService: ConfigService) => TokenExpiry;
95
+ declare enum TokenType {
96
+ ACCESS = "access",
97
+ REFRESH = "refresh"
98
+ }
99
+ interface AccessTokenPayload {
100
+ sessionType: string;
101
+ tokenType: TokenType.ACCESS;
102
+ userId: string;
103
+ sessionId: string;
104
+ refreshTokenHash: string;
105
+ }
106
+ interface RefreshTokenPayload {
107
+ sessionType: string;
108
+ tokenType: TokenType.REFRESH;
109
+ userId: string;
110
+ sessionId: string;
111
+ }
112
+
113
+ declare class JwtAuthService {
551
114
  private readonly jwtService;
115
+ readonly configService: ConfigService;
552
116
  private readonly logger;
553
- constructor(jwtService: JwtService);
554
- canActivate(context: ExecutionContext): Promise<boolean>;
555
- /**
556
- * Set CORS headers for SSE responses
557
- * Must be called before any potential exceptions
558
- */
559
- private setCorsHeaders;
117
+ private readonly tokenExpiry;
118
+ constructor(jwtService: JwtService, configService: ConfigService);
119
+ generateAccessToken(userId: string, sessionId: string, sessionType: string, refreshToken: string): string;
120
+ generateRefreshToken(userId: string, sessionId: string, sessionType: string): string;
121
+ sign(payload: object, options?: JwtSignOptions): string;
122
+ verify(token: string, expectedType: TokenType): {
123
+ userId: string;
124
+ sessionId: string;
125
+ sessionType: string;
126
+ tokenType: TokenType;
127
+ };
128
+ getExpiryTime(type: TokenType): Date;
129
+ getExpiryInSeconds(type: TokenType): number;
560
130
  }
561
131
 
562
- /**
563
- * Hash a token using SHA-256
564
- * @param token The token to hash
565
- * @returns The hex-encoded SHA-256 hash
566
- */
567
132
  declare function hashToken(token: string): string;
568
- /**
569
- * Verify a token against its expected hash using constant-time comparison
570
- * @param token The token to verify
571
- * @param expectedHash The expected SHA-256 hash
572
- * @returns true if the token matches the hash
573
- */
574
133
  declare function verifyTokenHash(token: string, expectedHash: string): boolean;
575
134
 
576
- /**
577
- * api-sdk Configuration System
578
- *
579
- * Similar to quantum-ui's config pattern - provides a type-safe configuration system
580
- *
581
- * @example
582
- * ```typescript
583
- * // In vritti-api-nexus/src/main.ts
584
- * import { configureApiSdk } from '@vritti/api-sdk';
585
- *
586
- * configureApiSdk({
587
- * cookie: {
588
- * refreshCookieName: 'vritti_refresh',
589
- * refreshCookieMaxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
590
- * },
591
- * jwt: {
592
- * accessTokenExpiry: '15m',
593
- * refreshTokenExpiry: '30d',
594
- * validateTokenBinding: true,
595
- * },
596
- * guard: {
597
- * tenantHeaderName: 'x-tenant-id',
598
- * },
599
- * });
600
- * ```
601
- */
602
- /**
603
- * Cookie configuration options
604
- */
135
+ declare const SKIP_CSRF_KEY = "skipCsrf";
136
+ declare const SkipCsrf: () => _nestjs_common.CustomDecorator<string>;
137
+
605
138
  interface CookieConfig {
606
- /**
607
- * The name of the httpOnly cookie containing the refresh token
608
- * @default 'vritti_refresh'
609
- */
610
139
  refreshCookieName: string;
611
- /**
612
- * Max age of the refresh cookie in milliseconds
613
- * @default 2592000000 (30 days)
614
- */
615
140
  refreshCookieMaxAge: number;
616
- /**
617
- * Cookie path
618
- * @default '/'
619
- */
620
141
  refreshCookiePath: string;
621
- /**
622
- * Whether the cookie is secure (HTTPS only)
623
- * @default true in production
624
- */
625
142
  refreshCookieSecure: boolean;
626
- /**
627
- * SameSite attribute for the cookie
628
- * @default 'strict'
629
- */
630
143
  refreshCookieSameSite: 'strict' | 'lax' | 'none';
631
- /**
632
- * Cookie domain (e.g., 'localhost' for dev, '.vritti.cloud' for prod)
633
- * Required for cross-subdomain auth (e.g., cloud.localhost accessing localhost API)
634
- * @default undefined (uses request domain)
635
- */
636
144
  refreshCookieDomain?: string;
637
145
  }
638
- /**
639
- * JWT token configuration options
640
- */
641
146
  interface JwtConfig {
642
- /**
643
- * Access token expiry time
644
- * @default '15m'
645
- */
646
147
  accessTokenExpiry: string;
647
- /**
648
- * Refresh token expiry time
649
- * @default '30d'
650
- */
651
148
  refreshTokenExpiry: string;
652
- /**
653
- * Onboarding token expiry time
654
- * @default '24h'
655
- */
656
149
  onboardingTokenExpiry: string;
657
- /**
658
- * Whether to validate refresh token binding (hash in access token)
659
- * @default true
660
- */
661
- validateTokenBinding: boolean;
662
- }
663
- /**
664
- * Auth guard configuration options
665
- */
150
+ }
666
151
  interface GuardConfig {
667
- /**
668
- * Header name for tenant ID
669
- * @default 'x-tenant-id'
670
- */
671
152
  tenantHeaderName: string;
672
- /**
673
- * Header name for authorization
674
- * @default 'authorization'
675
- */
676
153
  authHeaderName: string;
677
- /**
678
- * Token prefix (e.g., 'Bearer')
679
- * @default 'Bearer'
680
- */
681
154
  tokenPrefix: string;
682
155
  }
683
- /**
684
- * Complete api-sdk configuration interface
685
- */
686
156
  interface ApiSdkConfig {
687
- /**
688
- * Cookie configuration
689
- */
690
157
  cookie?: Partial<CookieConfig>;
691
- /**
692
- * JWT token configuration
693
- */
694
158
  jwt?: Partial<JwtConfig>;
695
- /**
696
- * Auth guard configuration
697
- */
698
159
  guard?: Partial<GuardConfig>;
699
160
  }
700
- /**
701
- * Full configuration type with all properties required
702
- */
703
161
  interface FullConfig {
704
162
  cookie: CookieConfig;
705
163
  jwt: JwtConfig;
706
164
  guard: GuardConfig;
707
165
  }
708
- /**
709
- * Helper function to define configuration with type safety
710
- * Similar to Tailwind's defineConfig()
711
- */
712
166
  declare function defineConfig(config: ApiSdkConfig): ApiSdkConfig;
713
- /**
714
- * Configure api-sdk with user settings
715
- * This should be called once in the application's bootstrap (main.ts)
716
- */
717
167
  declare function configureApiSdk(userConfig: ApiSdkConfig): void;
718
- /**
719
- * Get the current configuration
720
- */
721
168
  declare function getConfig(): FullConfig;
722
- /**
723
- * Reset configuration to defaults (for testing)
724
- */
725
169
  declare function resetConfig(): void;
726
- /**
727
- * Get refresh cookie options (convenience method)
728
- */
729
170
  declare function getRefreshCookieOptions(): Record<string, unknown>;
730
- /**
731
- * Get JWT expiry settings (convenience method)
732
- */
733
171
  declare function getJwtExpiry(): {
734
172
  access: string;
735
173
  refresh: string;
736
174
  onboarding: string;
737
175
  };
738
176
 
739
- /**
740
- * Dynamic module for multi-tenant database management
741
- *
742
- * This module provides:
743
- * - Tenant context management (request-scoped)
744
- * - Database connection pooling
745
- * - Dynamic schema/cluster routing
746
- * - Support for both gateway and microservice modes
747
- *
748
- * ## Gateway Mode (API Gateway)
749
- * - Use DatabaseModule.forServer() method
750
- * - Automatically extracts tenant from subdomain, falls back to x-tenant-id header
751
- * - Provide primaryDb configuration and prismaClientConstructor
752
- * - Automatically queries primary DB for tenant config
753
- * - Automatically registers TenantContextInterceptor globally
754
- * - No manual interceptor registration needed
755
- *
756
- * ## Microservice Mode (RabbitMQ Workers)
757
- * - Use DatabaseModule.forMicroservice() method
758
- * - Only provide prismaClientConstructor
759
- * - Tenant context comes from RabbitMQ messages
760
- * - Automatically registers MessageTenantContextInterceptor globally
761
- * - No manual interceptor registration needed
762
- *
763
- * @example
764
- * // Gateway configuration
765
- * DatabaseModule.forServer({
766
- * inject: [ConfigService],
767
- * useFactory: (config: ConfigService) => ({
768
- * primaryDb: {
769
- * host: config.get('PRIMARY_DB_HOST'),
770
- * port: config.get('PRIMARY_DB_PORT'),
771
- * username: config.get('PRIMARY_DB_USERNAME'),
772
- * password: config.get('PRIMARY_DB_PASSWORD'),
773
- * database: config.get('PRIMARY_DB_DATABASE'),
774
- * },
775
- * prismaClientConstructor: PrismaClient,
776
- * }),
777
- * })
778
- *
779
- * @example
780
- * // Microservice configuration
781
- * DatabaseModule.forMicroservice({
782
- * inject: [ConfigService],
783
- * useFactory: (config: ConfigService) => ({
784
- * prismaClientConstructor: PrismaClient,
785
- * }),
786
- * })
787
- */
177
+ type SchemaRegistry = {};
178
+ type RegisteredSchema = SchemaRegistry extends {
179
+ schema: infer S;
180
+ } ? S : Record<string, unknown>;
181
+ type TypedDrizzleClient = NodePgDatabase<RegisteredSchema>;
182
+
183
+ interface PrimaryDbConfig {
184
+ host: string;
185
+ port?: number;
186
+ username: string;
187
+ password: string;
188
+ database: string;
189
+ schema?: string;
190
+ sslMode?: 'require' | 'prefer' | 'disable' | 'no-verify';
191
+ }
192
+ interface DatabaseModuleOptions {
193
+ primaryDb: PrimaryDbConfig;
194
+ drizzleSchema: RegisteredSchema;
195
+ drizzleRelations?: Record<string, any>;
196
+ connectionCacheTTL?: number;
197
+ maxConnections?: number;
198
+ encryptionKey?: string;
199
+ }
200
+
788
201
  declare class DatabaseModule {
789
- /**
790
- * Configure DatabaseModule for Gateway/HTTP mode (multi-tenant web servers)
791
- *
792
- * This mode is for API Gateways that handle HTTP requests:
793
- * - Automatically registers TenantContextInterceptor
794
- * - Extracts tenant from subdomain or x-tenant-id header
795
- * - Queries primary database for tenant configuration
796
- * - Provides PrimaryDatabaseService for tenant lookup
797
- *
798
- * @param options Async configuration options
799
- * @returns Dynamic module configuration with HTTP interceptor
800
- *
801
- * @example
802
- * DatabaseModule.forServer({
803
- * inject: [ConfigService],
804
- * useFactory: (config: ConfigService) => ({
805
- * primaryDb: {
806
- * host: config.get('PRIMARY_DB_HOST'),
807
- * port: config.get('PRIMARY_DB_PORT'),
808
- * username: config.get('PRIMARY_DB_USERNAME'),
809
- * password: config.get('PRIMARY_DB_PASSWORD'),
810
- * database: config.get('PRIMARY_DB_DATABASE'),
811
- * },
812
- * prismaClientConstructor: PrismaClient,
813
- * }),
814
- * })
815
- */
816
202
  static forServer(options: {
817
- useFactory: (...args: any[]) => Promise<DatabaseModuleOptions> | DatabaseModuleOptions;
818
- inject?: any[];
203
+ useFactory: (...args: unknown[]) => Promise<DatabaseModuleOptions> | DatabaseModuleOptions;
204
+ inject?: InjectionToken[];
819
205
  }): DynamicModule;
820
- /**
821
- * Configure DatabaseModule for Microservice/Messaging mode (RabbitMQ workers)
822
- *
823
- * This mode is for microservices that process messages from queues:
824
- * - Automatically registers MessageTenantContextInterceptor
825
- * - Extracts tenant from RabbitMQ message patterns
826
- * - No primary database needed (tenant comes from message context)
827
- *
828
- * @param options Async configuration options
829
- * @returns Dynamic module configuration with message interceptor
830
- *
831
- * @example
832
- * DatabaseModule.forMicroservice({
833
- * inject: [ConfigService],
834
- * useFactory: (config: ConfigService) => ({
835
- * prismaClientConstructor: PrismaClient,
836
- * }),
837
- * })
838
- */
839
206
  static forMicroservice(options: {
840
- useFactory: (...args: any[]) => Promise<DatabaseModuleOptions> | DatabaseModuleOptions;
841
- inject?: any[];
207
+ useFactory: (...args: unknown[]) => Promise<DatabaseModuleOptions> | DatabaseModuleOptions;
208
+ inject?: InjectionToken[];
842
209
  }): DynamicModule;
843
- /**
844
- * Internal helper to create dynamic module with conditional interceptor registration
845
- *
846
- * @param options Configuration options
847
- * @param mode Mode of operation (gateway or microservice)
848
- * @returns Dynamic module configuration
849
- */
850
210
  private static createDynamicModule;
851
211
  }
852
212
 
853
- /**
854
- * Parameter decorator that injects tenant metadata into controller method
855
- *
856
- * This decorator retrieves tenant information (ID, slug, type, etc.)
857
- * from the REQUEST-SCOPED TenantContextService.
858
- *
859
- * Useful for:
860
- * - Logging tenant-specific information
861
- * - Implementing tenant-specific business logic
862
- * - Auditing and tracking
863
- * - Conditional feature flags
864
- *
865
- * @returns TenantInfo object with tenant metadata
866
- *
867
- * @example
868
- * // Access tenant metadata
869
- * @Get('info')
870
- * async getTenantInfo(@Tenant() tenant: TenantInfo) {
871
- * return {
872
- * id: tenant.id,
873
- * subdomain: tenant.subdomain,
874
- * type: tenant.type,
875
- * };
876
- * }
877
- *
878
- * @example
879
- * // Use for logging
880
- * @Post()
881
- * async createUser(
882
- * @Body() dto: CreateUserDto,
883
- * @Tenant() tenant: TenantInfo,
884
- * ) {
885
- * this.logger.log(`Creating user for tenant: ${tenant.subdomain}`);
886
- * // ...
887
- * }
888
- *
889
- * @example
890
- * // Conditional business logic
891
- * @Get('features')
892
- * async getFeatures(@Tenant() tenant: TenantInfo) {
893
- * if (tenant.type === 'ENTERPRISE') {
894
- * return ['feature-a', 'feature-b', 'feature-c'];
895
- * }
896
- * return ['feature-a'];
897
- * }
898
- */
899
213
  declare const Tenant: (...dataOrPipes: unknown[]) => ParameterDecorator;
900
214
 
901
- /**
902
- * Drizzle ORM v2 object-based where filter type.
903
- * Supports simple equality, operators, AND/OR/NOT, and RAW SQL.
904
- *
905
- * @example
906
- * ```typescript
907
- * // Simple equality
908
- * { email: 'user@example.com' }
909
- *
910
- * // With operators
911
- * { age: { gt: 18, lt: 65 } }
912
- *
913
- * // AND/OR combinations
914
- * { AND: [{ status: 'ACTIVE' }, { age: { gte: 18 } }] }
915
- *
916
- * // RAW SQL expression
917
- * { RAW: (table) => sql`${table.email} ILIKE '%@gmail.com'` }
918
- * ```
919
- */
920
- type RelationsWhereFilter = Record<string, any>;
921
- /**
922
- * Type-safe wrapper for Drizzle's RelationalQueryBuilder (v2 API).
923
- * This interface matches the method signatures of RelationalQueryBuilder
924
- * but properly binds the TSelect generic for type safety.
925
- *
926
- * We use this instead of RelationalQueryBuilder directly because
927
- * TypeScript cannot infer TSelect from the generic base repository context.
928
- *
929
- * @remarks
930
- * Drizzle ORM v2 uses object-based `where` filters instead of SQL expressions.
931
- * See: https://orm.drizzle.team/docs/relations-v1-v2
932
- */
215
+ declare class SelectOptionsQueryDto {
216
+ search?: string;
217
+ limit?: number;
218
+ offset?: number;
219
+ values?: string;
220
+ excludeIds?: string;
221
+ valueKey?: string;
222
+ labelKey?: string;
223
+ groupIdKey?: string;
224
+ }
225
+
226
+ declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
227
+ private readonly options;
228
+ private readonly logger;
229
+ private pool;
230
+ private db;
231
+ private readonly tenantConfigCache;
232
+ private readonly cacheTTL;
233
+ constructor(options: DatabaseModuleOptions);
234
+ onModuleInit(): Promise<void>;
235
+ private initializeDrizzleClient;
236
+ private buildPrimaryDbUrl;
237
+ private maskPassword;
238
+ getTenantInfo(tenantIdentifier: string): Promise<TenantInfo | null>;
239
+ private cacheInfo;
240
+ clearTenantCache(tenantIdentifier: string): void;
241
+ clearAllCaches(): void;
242
+ get drizzleClient(): TypedDrizzleClient;
243
+ get schema(): typeof this$1.options.drizzleSchema;
244
+ private decrypt;
245
+ onModuleDestroy(): Promise<void>;
246
+ }
247
+
248
+ interface SelectQueryOption {
249
+ value: string | number | boolean;
250
+ label: string;
251
+ groupId?: string | number;
252
+ }
253
+ interface SelectQueryGroup {
254
+ id: string | number;
255
+ name: string;
256
+ }
257
+ interface SelectQueryResult {
258
+ options: SelectQueryOption[];
259
+ groups?: SelectQueryGroup[];
260
+ hasMore: boolean;
261
+ totalCount?: number;
262
+ }
263
+ interface FindForSelectConfig {
264
+ value: string;
265
+ label: string;
266
+ groupId?: string;
267
+ search?: string;
268
+ limit?: number;
269
+ offset?: number;
270
+ where?: Record<string, unknown>;
271
+ orderBy?: Record<string, 'asc' | 'desc'>;
272
+ groups?: SelectQueryGroup[];
273
+ values?: string | (string | number | boolean)[];
274
+ excludeIds?: string | (string | number | boolean)[];
275
+ groupTable?: PgTable;
276
+ groupLabelKey?: string;
277
+ groupIdKey?: string;
278
+ }
279
+
280
+ type RelationsWhereFilter = Record<string, unknown>;
933
281
  interface TypedRelationalQueryBuilder<TSelect> {
934
282
  findFirst(config?: {
935
283
  where?: RelationsWhereFilter;
@@ -945,1240 +293,225 @@ interface TypedRelationalQueryBuilder<TSelect> {
945
293
  columns?: Record<string, boolean>;
946
294
  }): Promise<TSelect[]>;
947
295
  }
948
- /**
949
- * Abstract base repository for primary database operations using Drizzle ORM.
950
- * Provides common CRUD operations with automatic logging.
951
- *
952
- * @template TTable - The Drizzle table type (must be registered in SchemaRegistry)
953
- * @template TInsert - Type for insert operations (inferred from table.$inferInsert)
954
- * @template TSelect - Type for select operations (inferred from table.$inferSelect)
955
- *
956
- * @remarks
957
- * **Type Assertion Pattern:** This repository uses `as any` casts when passing
958
- * the generic table to Drizzle methods. This is necessary because TypeScript
959
- * cannot prove that a generic `TTable extends PgTable` satisfies Drizzle's
960
- * stricter internal type requirements for `insert()`, `update()`, and `delete()`.
961
- *
962
- * The public API maintains full type safety:
963
- * - Input parameters are typed as `TInsert` (inferred from table)
964
- * - Return values are typed as `TSelect` (inferred from table)
965
- * - The casts are implementation details that don't leak to consumers
966
- *
967
- * @example
968
- * ```typescript
969
- * import { users } from '@/db/schema';
970
- *
971
- * type User = typeof users.$inferSelect;
972
- * type NewUser = typeof users.$inferInsert;
973
- *
974
- * @Injectable()
975
- * export class UserRepository extends PrimaryBaseRepository<typeof users> {
976
- * constructor(database: PrimaryDatabaseService) {
977
- * super(database, users);
978
- * }
979
- *
980
- * // Use Drizzle v2 object-based where syntax (recommended)
981
- * async findByEmail(email: string): Promise<User | undefined> {
982
- * return this.model.findFirst({
983
- * where: { email },
984
- * });
985
- * }
986
- *
987
- * // With relations
988
- * async findWithRelations(id: string): Promise<User | undefined> {
989
- * return this.model.findFirst({
990
- * where: { id },
991
- * with: { posts: true, profile: true }
992
- * });
993
- * }
994
- * }
995
- * ```
996
- */
997
296
  declare abstract class PrimaryBaseRepository<TTable extends PgTable, TInsert = InferInsertModel<TTable>, TSelect = InferSelectModel<TTable>> {
998
297
  protected readonly database: PrimaryDatabaseService;
999
298
  protected readonly table: TTable;
1000
299
  protected readonly logger: Logger;
1001
- /**
1002
- * The table name extracted from the Drizzle table at runtime.
1003
- * Stored in camelCase to match Drizzle's query object keys.
1004
- * Example: 'email_verifications' -> 'emailVerifications'
1005
- */
1006
300
  private readonly tableName;
1007
- /**
1008
- * Lazy getter for Drizzle client.
1009
- * Accesses the client from the database service only when needed,
1010
- * avoiding initialization timing issues with NestJS lifecycle.
1011
- */
1012
301
  protected get db(): TypedDrizzleClient;
1013
- /**
1014
- * Model query API for THIS repository's table (Drizzle v2 relational queries)
1015
- * Scoped to only the table this repository manages.
1016
- * Returns a type-safe wrapper around Drizzle's RelationalQueryBuilder.
1017
- *
1018
- * @example
1019
- * ```typescript
1020
- * // Use relational queries with v2 object-based where syntax
1021
- * const user = await this.model.findFirst({
1022
- * where: { id },
1023
- * with: { posts: true, profile: true }
1024
- * });
1025
- * ```
1026
- */
1027
302
  protected get model(): TypedRelationalQueryBuilder<TSelect>;
1028
- /**
1029
- * Create a new repository instance
1030
- *
1031
- * @param database - The primary database service
1032
- * @param table - The Drizzle table schema object
1033
- *
1034
- * @example
1035
- * ```typescript
1036
- * import { users } from '@/db/schema';
1037
- *
1038
- * constructor(database: PrimaryDatabaseService) {
1039
- * super(database, users);
1040
- * }
1041
- * ```
1042
- */
1043
303
  constructor(database: PrimaryDatabaseService, table: TTable);
1044
- /**
1045
- * Create a new record
1046
- *
1047
- * @param data - The data to create the record with
1048
- * @returns Promise resolving to the created record
1049
- *
1050
- * @example
1051
- * ```typescript
1052
- * const user = await userRepository.create({
1053
- * email: 'user@example.com',
1054
- * firstName: 'John'
1055
- * });
1056
- * ```
1057
- */
1058
304
  create(data: TInsert): Promise<TSelect>;
1059
- /**
1060
- * Find a single record by ID
1061
- *
1062
- * @param id - The record ID
1063
- * @returns Promise resolving to the record or undefined if not found
1064
- *
1065
- * @example
1066
- * ```typescript
1067
- * const user = await userRepository.findById('user-id-123');
1068
- * ```
1069
- */
1070
305
  findById(id: string): Promise<TSelect | undefined>;
1071
- /**
1072
- * Find a single record with custom where clause (Drizzle v2 object-based syntax)
1073
- *
1074
- * @param where - Object-based filter condition
1075
- * @returns Promise resolving to the record or undefined if not found
1076
- *
1077
- * @example
1078
- * ```typescript
1079
- * // Simple equality
1080
- * const user = await userRepository.findOne({ email: 'user@example.com' });
1081
- *
1082
- * // With operators
1083
- * const user = await userRepository.findOne({ age: { gte: 18 } });
1084
- *
1085
- * // Multiple conditions (AND)
1086
- * const user = await userRepository.findOne({
1087
- * email: 'user@example.com',
1088
- * status: 'ACTIVE'
1089
- * });
1090
- * ```
1091
- */
1092
306
  findOne(where: RelationsWhereFilter): Promise<TSelect | undefined>;
1093
- /**
1094
- * Find multiple records (Drizzle v2 object-based syntax)
1095
- *
1096
- * @param options - Query options (where, orderBy, limit, offset)
1097
- * @returns Promise resolving to an array of records
1098
- *
1099
- * @example
1100
- * ```typescript
1101
- * // Find all users
1102
- * const users = await userRepository.findMany();
1103
- *
1104
- * // Find with filtering and pagination (v2 object syntax)
1105
- * const users = await userRepository.findMany({
1106
- * where: { accountStatus: 'ACTIVE' },
1107
- * orderBy: { createdAt: 'desc' },
1108
- * limit: 10,
1109
- * offset: 0
1110
- * });
1111
- *
1112
- * // Multiple conditions
1113
- * const users = await userRepository.findMany({
1114
- * where: {
1115
- * AND: [
1116
- * { status: 'ACTIVE' },
1117
- * { age: { gte: 18 } }
1118
- * ]
1119
- * }
1120
- * });
1121
- * ```
1122
- */
1123
307
  findMany(options?: {
1124
308
  where?: RelationsWhereFilter;
1125
309
  orderBy?: Record<string, 'asc' | 'desc'>;
1126
310
  limit?: number;
1127
311
  offset?: number;
1128
312
  }): Promise<TSelect[]>;
1129
- /**
1130
- * Update a record by ID
1131
- *
1132
- * @param id - The record ID
1133
- * @param data - The data to update
1134
- * @returns Promise resolving to the updated record
1135
- *
1136
- * @example
1137
- * ```typescript
1138
- * const user = await userRepository.update('user-id-123', {
1139
- * firstName: 'Jane'
1140
- * });
1141
- * ```
1142
- */
1143
313
  update(id: string, data: Partial<TInsert>): Promise<TSelect>;
1144
- /**
1145
- * Update multiple records
1146
- *
1147
- * @param where - SQL condition to match records
1148
- * @param data - The data to update
1149
- * @returns Promise resolving to the count of updated records
1150
- *
1151
- * @example
1152
- * ```typescript
1153
- * import { eq } from 'drizzle-orm';
1154
- *
1155
- * const result = await userRepository.updateMany(
1156
- * eq(users.accountStatus, 'PENDING'),
1157
- * { accountStatus: 'ACTIVE' }
1158
- * );
1159
- * console.log(`Updated ${result.count} users`);
1160
- * ```
1161
- */
1162
314
  updateMany(where: SQL, data: Partial<TInsert>): Promise<{
1163
315
  count: number;
1164
316
  }>;
1165
- /**
1166
- * Delete a record by ID
1167
- *
1168
- * @param id - The record ID
1169
- * @returns Promise resolving to the deleted record
1170
- *
1171
- * @example
1172
- * ```typescript
1173
- * const user = await userRepository.delete('user-id-123');
1174
- * ```
1175
- */
1176
317
  delete(id: string): Promise<TSelect>;
1177
- /**
1178
- * Delete multiple records
1179
- *
1180
- * @param where - SQL condition to match records
1181
- * @returns Promise resolving to the count of deleted records
1182
- *
1183
- * @example
1184
- * ```typescript
1185
- * import { lt } from 'drizzle-orm';
1186
- *
1187
- * const result = await userRepository.deleteMany(
1188
- * lt(users.createdAt, new Date('2020-01-01'))
1189
- * );
1190
- * console.log(`Deleted ${result.count} users`);
1191
- * ```
1192
- */
1193
318
  deleteMany(where: SQL): Promise<{
1194
319
  count: number;
1195
320
  }>;
1196
- /**
1197
- * Count records
1198
- *
1199
- * @param where - Optional SQL condition to filter records
1200
- * @returns Promise resolving to the count of records
1201
- *
1202
- * @example
1203
- * ```typescript
1204
- * import { eq } from 'drizzle-orm';
1205
- *
1206
- * // Count all users
1207
- * const total = await userRepository.count();
1208
- *
1209
- * // Count active users
1210
- * const activeCount = await userRepository.count(
1211
- * eq(users.accountStatus, 'ACTIVE')
1212
- * );
1213
- * ```
1214
- */
1215
321
  count(where?: SQL): Promise<number>;
1216
- /**
1217
- * Check if a record exists
1218
- *
1219
- * @param where - SQL condition to match records
1220
- * @returns Promise resolving to true if at least one record exists, false otherwise
1221
- *
1222
- * @example
1223
- * ```typescript
1224
- * import { eq } from 'drizzle-orm';
1225
- *
1226
- * const emailExists = await userRepository.exists(
1227
- * eq(users.email, 'user@example.com')
1228
- * );
1229
- * ```
1230
- */
1231
322
  exists(where: SQL): Promise<boolean>;
323
+ findForSelect(config: FindForSelectConfig): Promise<SelectQueryResult>;
1232
324
  }
1233
325
 
1234
- /**
1235
- * Request-scoped service that holds tenant context for the current request or RabbitMQ message
1236
- *
1237
- * IMPORTANT: This service is REQUEST-SCOPED, meaning NestJS creates a new instance
1238
- * for each HTTP request or RabbitMQ message. This ensures tenant isolation and
1239
- * prevents cross-tenant data leaks in concurrent scenarios.
1240
- *
1241
- * @example
1242
- * // In a controller or service
1243
- * constructor(private readonly tenantContext: TenantContextService) {}
1244
- *
1245
- * async handleRequest() {
1246
- * const tenant = this.tenantContext.getTenant();
1247
- * console.log(`Processing request for tenant: ${tenant.tenantSlug}`);
1248
- * }
1249
- */
1250
326
  declare class TenantContextService {
1251
327
  private tenantInfo;
1252
- /**
1253
- * Set tenant information for this request/message
1254
- *
1255
- * This is typically called by:
1256
- * - TenantContextInterceptor (for HTTP requests in gateway)
1257
- * - MessageTenantContextInterceptor (for RabbitMQ messages in microservices)
1258
- * - Manual context setup in message handlers
1259
- *
1260
- * @param tenantInfo Complete tenant information
1261
- * @throws Error if tenant context is already set (prevents accidental overwrites)
1262
- */
1263
328
  setTenant(tenantInfo: TenantInfo): void;
1264
- /**
1265
- * Get tenant information for this request/message
1266
- *
1267
- * @returns Tenant information
1268
- * @throws UnauthorizedException if tenant context hasn't been set
1269
- */
1270
329
  getTenant(): TenantInfo;
1271
- /**
1272
- * Check if tenant context has been set
1273
- *
1274
- * @returns true if tenant context is available
1275
- */
1276
330
  hasTenant(): boolean;
1277
- /**
1278
- * Clear tenant context
1279
- *
1280
- * This is useful for cleanup in RabbitMQ message handlers
1281
- * after the message has been processed.
1282
- *
1283
- * HTTP requests don't need manual cleanup as the service
1284
- * instance is destroyed when the request ends.
1285
- */
1286
331
  clearTenant(): void;
1287
- /**
1288
- * Get tenant ID safely (returns null if not set)
1289
- *
1290
- * @returns Tenant ID or null
1291
- */
1292
332
  getTenantIdSafe(): string | null;
1293
- /**
1294
- * Get tenant subdomain safely (returns null if not set)
1295
- *
1296
- * @returns Tenant subdomain or null
1297
- */
1298
333
  getTenantSubdomainSafe(): string | null;
1299
334
  }
1300
335
 
1301
- /**
1302
- * Service responsible for managing tenant-scoped database connections
1303
- *
1304
- * This service:
1305
- * - Maintains a connection pool (Map<cacheKey, TenantConnection>)
1306
- * - Creates new connections dynamically based on tenant context
1307
- * - Reuses existing connections for the same tenant
1308
- * - Supports both cloud schemas and enterprise databases
1309
- * - Automatically cleans up idle connections
1310
- *
1311
- * @example
1312
- * // In a controller or service
1313
- * const db = this.tenantDatabase.drizzleClient;
1314
- * const users = await db.select().from(usersTable);
1315
- */
1316
336
  declare class TenantDatabaseService implements OnModuleDestroy {
1317
337
  private readonly options;
1318
338
  private readonly tenantContext;
1319
339
  private readonly logger;
1320
- /** Connection pool: Map<cacheKey, TenantConnection> */
1321
340
  private readonly clients;
1322
- /** Track last usage time for idle connection cleanup */
1323
341
  private readonly clientLastUsed;
1324
- /** Cleanup interval timer */
1325
342
  private cleanupInterval?;
1326
343
  constructor(options: DatabaseModuleOptions, tenantContext: TenantContextService);
1327
- /**
1328
- * Get the Drizzle client for the current tenant's database.
1329
- * This returns the tenant-scoped database client.
1330
- *
1331
- * @returns Tenant-scoped Drizzle database instance
1332
- * @throws UnauthorizedException if tenant context not set
1333
- * @throws InternalServerErrorException if connection fails
1334
- */
1335
344
  get drizzleClient(): TypedDrizzleClient;
1336
- /**
1337
- * Get the Drizzle schema
1338
- */
1339
345
  get schema(): Record<string, unknown>;
1340
- /**
1341
- * Get tenant-scoped database client for the current request/message
1342
- *
1343
- * This method:
1344
- * 1. Gets tenant info from TenantContextService
1345
- * 2. Builds a connection URL based on tenant type
1346
- * 3. Returns cached client if exists, otherwise creates new one
1347
- *
1348
- * @returns Drizzle database instance
1349
- * @throws UnauthorizedException if tenant context not set
1350
- * @throws InternalServerErrorException if connection fails
1351
- */
1352
346
  private getDbClient;
1353
- /**
1354
- * Create a new database client for the given tenant (synchronous)
1355
- */
1356
347
  private createDbClientSync;
1357
- /**
1358
- * Build connection URL for tenant (dedicated database)
1359
- */
1360
348
  private buildTenantDbUrl;
1361
- /**
1362
- * Build cache key for connection pooling
1363
- */
1364
349
  private buildCacheKey;
1365
- /**
1366
- * Start periodic cleanup of idle connections
1367
- */
1368
350
  private startConnectionCleaner;
1369
- /**
1370
- * Clean up idle connections that haven't been used recently
1371
- */
1372
351
  private cleanupIdleConnections;
1373
- /**
1374
- * Get current connection pool statistics
1375
- */
1376
352
  getPoolStats(): {
1377
353
  activeConnections: number;
1378
354
  tenants: string[];
1379
355
  };
1380
- /**
1381
- * Mask password in connection URL for logging
1382
- */
1383
356
  private maskPassword;
1384
357
  onModuleDestroy(): Promise<void>;
1385
358
  }
1386
359
 
1387
- /**
1388
- * Type helper to extract table name from Drizzle table.
1389
- * TTable['_']['name'] gives us the string literal type (e.g., 'products')
1390
- */
1391
360
  type ExtractTableName<TTable extends PgTable> = TTable['_']['name'];
1392
- /**
1393
- * Abstract base repository for tenant-scoped database operations using Drizzle ORM.
1394
- * All operations are automatically scoped to the current tenant.
1395
- *
1396
- * @template TTable - The Drizzle table type (must be registered in SchemaRegistry)
1397
- * @template TInsert - Type for insert operations (inferred from table.$inferInsert)
1398
- * @template TSelect - Type for select operations (inferred from table.$inferSelect)
1399
- *
1400
- * @remarks
1401
- * **Type Assertion Pattern:** This repository uses `as any` casts when passing
1402
- * the generic table to Drizzle methods. This is necessary because TypeScript
1403
- * cannot prove that a generic `TTable extends PgTable` satisfies Drizzle's
1404
- * stricter internal type requirements for `insert()`, `update()`, and `delete()`.
1405
- *
1406
- * The public API maintains full type safety:
1407
- * - Input parameters are typed as `TInsert` (inferred from table)
1408
- * - Return values are typed as `TSelect` (inferred from table)
1409
- * - The casts are implementation details that don't leak to consumers
1410
- *
1411
- * @example
1412
- * ```typescript
1413
- * import { products } from '@/db/schema';
1414
- *
1415
- * type Product = typeof products.$inferSelect;
1416
- * type NewProduct = typeof products.$inferInsert;
1417
- *
1418
- * @Injectable()
1419
- * export class ProductRepository extends TenantBaseRepository<typeof products> {
1420
- * constructor(database: TenantDatabaseService) {
1421
- * super(database, products);
1422
- * }
1423
- *
1424
- * // Use SQL-builder syntax
1425
- * async findBySku(sku: string): Promise<Product | null> {
1426
- * const [result] = await this.db
1427
- * .select()
1428
- * .from(this.table)
1429
- * .where(eq(products.sku, sku))
1430
- * .limit(1);
1431
- * return result ?? null;
1432
- * }
1433
- *
1434
- * // Use Prisma-like relational query syntax
1435
- * async findWithRelations(id: string): Promise<Product | null> {
1436
- * return await this.model.findFirst({
1437
- * where: eq(products.id, id),
1438
- * with: { category: true, variants: true }
1439
- * });
1440
- * }
1441
- * }
1442
- * ```
1443
- */
1444
361
  declare abstract class TenantBaseRepository<TTable extends PgTable, TInsert = InferInsertModel<TTable>, TSelect = InferSelectModel<TTable>> {
1445
362
  protected readonly database: TenantDatabaseService;
1446
363
  protected readonly table: TTable;
1447
364
  protected readonly logger: Logger;
1448
- /**
1449
- * The table name extracted from the Drizzle table at runtime.
1450
- * Used to access the query API for this repository's table.
1451
- */
1452
365
  private readonly tableName;
1453
- /**
1454
- * Lazy getter for Drizzle client.
1455
- * Accesses the client from the database service only when needed,
1456
- * avoiding initialization timing issues with NestJS lifecycle.
1457
- */
1458
366
  protected get db(): TypedDrizzleClient;
1459
- /**
1460
- * Model query API for THIS repository's table (Prisma-like syntax)
1461
- * Scoped to only the table this repository manages
1462
- *
1463
- * @example
1464
- * ```typescript
1465
- * // Use relational queries with type safety
1466
- * const product = await this.model.findFirst({
1467
- * where: eq(products.id, id),
1468
- * with: { category: true, variants: true }
1469
- * });
1470
- * ```
1471
- */
1472
367
  protected get model(): TypedDrizzleClient['query'][ExtractTableName<TTable> & keyof TypedDrizzleClient['query']];
1473
- /**
1474
- * Create a new repository instance
1475
- *
1476
- * @param database - The tenant database service
1477
- * @param table - The Drizzle table schema object
1478
- *
1479
- * @example
1480
- * ```typescript
1481
- * import { products } from '@/db/schema';
1482
- *
1483
- * constructor(database: TenantDatabaseService) {
1484
- * super(database, products);
1485
- * }
1486
- * ```
1487
- */
1488
368
  constructor(database: TenantDatabaseService, table: TTable);
1489
- /**
1490
- * Create a new record
1491
- *
1492
- * @param data - The data to create the record with
1493
- * @returns Promise resolving to the created record
1494
- *
1495
- * @example
1496
- * ```typescript
1497
- * const product = await productRepository.create({
1498
- * name: 'Widget',
1499
- * sku: 'WDG-001',
1500
- * price: 9.99
1501
- * });
1502
- * ```
1503
- */
1504
369
  create(data: TInsert): Promise<TSelect>;
1505
- /**
1506
- * Find a single record by ID
1507
- *
1508
- * @param id - The record ID
1509
- * @returns Promise resolving to the record or null if not found
1510
- *
1511
- * @example
1512
- * ```typescript
1513
- * const product = await productRepository.findById('product-id-123');
1514
- * ```
1515
- */
1516
370
  findById(id: string): Promise<TSelect | null>;
1517
- /**
1518
- * Find a single record with custom where clause
1519
- *
1520
- * @param where - SQL condition
1521
- * @returns Promise resolving to the record or null if not found
1522
- *
1523
- * @example
1524
- * ```typescript
1525
- * import { eq } from 'drizzle-orm';
1526
- * const product = await productRepository.findOne(eq(products.sku, 'WDG-001'));
1527
- * ```
1528
- */
1529
371
  findOne(where: SQL): Promise<TSelect | null>;
1530
- /**
1531
- * Find multiple records
1532
- *
1533
- * @param options - Query options (where, orderBy, limit, offset)
1534
- * @returns Promise resolving to an array of records
1535
- *
1536
- * @example
1537
- * ```typescript
1538
- * import { eq, desc } from 'drizzle-orm';
1539
- *
1540
- * // Find all products
1541
- * const products = await productRepository.findMany();
1542
- *
1543
- * // Find with filtering and pagination
1544
- * const products = await productRepository.findMany({
1545
- * where: eq(products.status, 'ACTIVE'),
1546
- * orderBy: desc(products.createdAt),
1547
- * limit: 10,
1548
- * offset: 0
1549
- * });
1550
- * ```
1551
- */
1552
372
  findMany(options?: {
1553
373
  where?: SQL;
1554
374
  orderBy?: SQL;
1555
375
  limit?: number;
1556
376
  offset?: number;
1557
377
  }): Promise<TSelect[]>;
1558
- /**
1559
- * Update a record by ID
1560
- *
1561
- * @param id - The record ID
1562
- * @param data - The data to update
1563
- * @returns Promise resolving to the updated record
1564
- *
1565
- * @example
1566
- * ```typescript
1567
- * const product = await productRepository.update('product-id-123', {
1568
- * price: 12.99
1569
- * });
1570
- * ```
1571
- */
1572
378
  update(id: string, data: Partial<TInsert>): Promise<TSelect>;
1573
- /**
1574
- * Update multiple records
1575
- *
1576
- * @param where - SQL condition to match records
1577
- * @param data - The data to update
1578
- * @returns Promise resolving to the count of updated records
1579
- *
1580
- * @example
1581
- * ```typescript
1582
- * import { eq } from 'drizzle-orm';
1583
- *
1584
- * const result = await productRepository.updateMany(
1585
- * eq(products.status, 'PENDING'),
1586
- * { status: 'ACTIVE' }
1587
- * );
1588
- * console.log(`Updated ${result.count} products`);
1589
- * ```
1590
- */
1591
379
  updateMany(where: SQL, data: Partial<TInsert>): Promise<{
1592
380
  count: number;
1593
381
  }>;
1594
- /**
1595
- * Delete a record by ID
1596
- *
1597
- * @param id - The record ID
1598
- * @returns Promise resolving to the deleted record
1599
- *
1600
- * @example
1601
- * ```typescript
1602
- * const product = await productRepository.delete('product-id-123');
1603
- * ```
1604
- */
1605
382
  delete(id: string): Promise<TSelect>;
1606
- /**
1607
- * Delete multiple records
1608
- *
1609
- * @param where - SQL condition to match records
1610
- * @returns Promise resolving to the count of deleted records
1611
- *
1612
- * @example
1613
- * ```typescript
1614
- * import { lt } from 'drizzle-orm';
1615
- *
1616
- * const result = await productRepository.deleteMany(
1617
- * lt(products.createdAt, new Date('2020-01-01'))
1618
- * );
1619
- * console.log(`Deleted ${result.count} products`);
1620
- * ```
1621
- */
1622
383
  deleteMany(where: SQL): Promise<{
1623
384
  count: number;
1624
385
  }>;
1625
- /**
1626
- * Count records
1627
- *
1628
- * @param where - Optional SQL condition to filter records
1629
- * @returns Promise resolving to the count of records
1630
- *
1631
- * @example
1632
- * ```typescript
1633
- * import { eq } from 'drizzle-orm';
1634
- *
1635
- * // Count all products
1636
- * const total = await productRepository.count();
1637
- *
1638
- * // Count active products
1639
- * const activeCount = await productRepository.count(
1640
- * eq(products.status, 'ACTIVE')
1641
- * );
1642
- * ```
1643
- */
1644
386
  count(where?: SQL): Promise<number>;
1645
- /**
1646
- * Check if a record exists
1647
- *
1648
- * @param where - SQL condition to match records
1649
- * @returns Promise resolving to true if at least one record exists, false otherwise
1650
- *
1651
- * @example
1652
- * ```typescript
1653
- * import { eq } from 'drizzle-orm';
1654
- *
1655
- * const skuExists = await productRepository.exists(
1656
- * eq(products.sku, 'WDG-001')
1657
- * );
1658
- * ```
1659
- */
1660
387
  exists(where: SQL): Promise<boolean>;
388
+ findForSelect(config: FindForSelectConfig): Promise<SelectQueryResult>;
389
+ }
390
+
391
+ declare class EmailModule {
392
+ }
393
+
394
+ declare class EmailService {
395
+ private readonly configService;
396
+ private readonly logger;
397
+ private readonly brevoClient;
398
+ private readonly senderEmail;
399
+ private readonly senderName;
400
+ constructor(configService: ConfigService);
401
+ sendVerificationEmail(email: string, otp: string, expiresAt: Date, displayName?: string): Promise<void>;
402
+ sendPasswordResetEmail(email: string, otp: string, expiresAt: Date, displayName?: string): Promise<void>;
403
+ sendEmailChangeNotification(oldEmail: string, newEmail: string, revertToken: string, revertExpiresAt: Date, displayName?: string): Promise<void>;
404
+ sendEmailRevertConfirmation(email: string, displayName?: string): Promise<void>;
405
+ verifyConnection(): Promise<boolean>;
406
+ private sendEmail;
1661
407
  }
1662
408
 
1663
409
  interface FieldError {
1664
- field?: string;
410
+ field: string;
1665
411
  message: string;
1666
412
  }
1667
413
  interface ProblemDetails {
414
+ type: string;
1668
415
  title: string;
1669
416
  status: number;
417
+ label?: string;
1670
418
  detail: string;
419
+ instance?: string;
1671
420
  }
1672
421
  interface ApiErrorResponse extends ProblemDetails {
1673
422
  errors: FieldError[];
1674
423
  }
1675
424
 
1676
- declare abstract class BaseFieldException extends HttpException {
1677
- constructor(statusOrMessageOrErrors: HttpStatus | string | FieldError[], messageOrStatus?: string | HttpStatus, statusOrDetail?: HttpStatus | string, detail?: string);
425
+ interface ProblemOptions {
426
+ type?: string;
427
+ label?: string;
428
+ detail?: string;
429
+ errors?: FieldError[];
430
+ }
431
+ declare abstract class HttpProblemException extends HttpException {
432
+ constructor(detailOrOptions: string | ProblemOptions, httpStatus: HttpStatus);
1678
433
  }
1679
434
 
1680
- /**
1681
- * Exception thrown when a gateway or proxy receives an invalid response (HTTP 502).
1682
- * Used when a server acting as a gateway gets an error from an upstream server.
1683
- *
1684
- * @example
1685
- * // Simple message
1686
- * throw new BadGatewayException('Bad gateway');
1687
- *
1688
- * // Field-specific error
1689
- * throw new BadGatewayException('upstream', 'Upstream service returned invalid response');
1690
- *
1691
- * // With detail
1692
- * throw new BadGatewayException('proxy', 'Gateway error', 'Payment service is not responding correctly');
1693
- *
1694
- * // Multiple field errors
1695
- * throw new BadGatewayException([
1696
- * { field: 'gateway', message: 'Invalid response from upstream server' }
1697
- * ]);
1698
- */
1699
- declare class BadGatewayException extends BaseFieldException {
1700
- constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
435
+ declare class BadGatewayException extends HttpProblemException {
436
+ constructor(detailOrOptions?: string | ProblemOptions);
1701
437
  }
1702
438
 
1703
- /**
1704
- * Exception thrown when a request is malformed or contains invalid data (HTTP 400).
1705
- *
1706
- * @example
1707
- * // Simple message
1708
- * throw new BadRequestException('Invalid request data');
1709
- *
1710
- * // Field-specific error
1711
- * throw new BadRequestException('email', 'Invalid email format');
1712
- *
1713
- * // With detail
1714
- * throw new BadRequestException('email', 'Invalid email format', 'Email must be in valid format');
1715
- *
1716
- * // Multiple field errors
1717
- * throw new BadRequestException([
1718
- * { field: 'email', message: 'Invalid email' },
1719
- * { field: 'password', message: 'Password too short' }
1720
- * ]);
1721
- */
1722
- declare class BadRequestException extends BaseFieldException {
1723
- constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
439
+ declare class BadRequestException extends HttpProblemException {
440
+ constructor(detailOrOptions?: string | ProblemOptions);
1724
441
  }
1725
442
 
1726
- /**
1727
- * Exception thrown when a request conflicts with the current state (HTTP 409).
1728
- * Commonly used for duplicate resources or concurrent modification issues.
1729
- *
1730
- * @example
1731
- * // Simple message
1732
- * throw new ConflictException('Resource already exists');
1733
- *
1734
- * // Field-specific error
1735
- * throw new ConflictException('email', 'Email already registered');
1736
- *
1737
- * // With detail
1738
- * throw new ConflictException('email', 'Email already exists', 'Try logging in instead');
1739
- *
1740
- * // Multiple field errors
1741
- * throw new ConflictException([
1742
- * { field: 'email', message: 'Email already in use' }
1743
- * ]);
1744
- */
1745
- declare class ConflictException extends BaseFieldException {
1746
- constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
443
+ declare class ConflictException extends HttpProblemException {
444
+ constructor(detailOrOptions?: string | ProblemOptions);
1747
445
  }
1748
446
 
1749
- /**
1750
- * Exception thrown when the user does not have permission to access a resource (HTTP 403).
1751
- *
1752
- * @example
1753
- * // Simple message
1754
- * throw new ForbiddenException('Access denied');
1755
- *
1756
- * // Field-specific error
1757
- * throw new ForbiddenException('resource', 'You do not have permission');
1758
- *
1759
- * // With detail
1760
- * throw new ForbiddenException('resource', 'Access denied', 'Admin role required');
1761
- *
1762
- * // Multiple field errors
1763
- * throw new ForbiddenException([
1764
- * { field: 'action', message: 'Insufficient permissions' }
1765
- * ]);
1766
- */
1767
- declare class ForbiddenException extends BaseFieldException {
1768
- constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
447
+ declare class ForbiddenException extends HttpProblemException {
448
+ constructor(detailOrOptions?: string | ProblemOptions);
1769
449
  }
1770
450
 
1771
- /**
1772
- * Exception thrown when a resource has been permanently removed (HTTP 410).
1773
- * Unlike 404, this indicates the resource existed but is intentionally gone.
1774
- *
1775
- * @example
1776
- * // Simple message
1777
- * throw new GoneException('Resource permanently deleted');
1778
- *
1779
- * // Field-specific error
1780
- * throw new GoneException('account', 'Account has been permanently deleted');
1781
- *
1782
- * // With detail
1783
- * throw new GoneException('account', 'Deleted', 'This account was removed on user request');
1784
- *
1785
- * // Multiple field errors
1786
- * throw new GoneException([
1787
- * { field: 'resource', message: 'This content has been permanently removed' }
1788
- * ]);
1789
- */
1790
- declare class GoneException extends BaseFieldException {
1791
- constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
451
+ declare class GoneException extends HttpProblemException {
452
+ constructor(detailOrOptions?: string | ProblemOptions);
1792
453
  }
1793
454
 
1794
- /**
1795
- * Exception thrown when an unexpected server error occurs (HTTP 500).
1796
- *
1797
- * @example
1798
- * // Simple message
1799
- * throw new InternalServerErrorException('An unexpected error occurred');
1800
- *
1801
- * // Field-specific error
1802
- * throw new InternalServerErrorException('database', 'Database connection failed');
1803
- *
1804
- * // With detail
1805
- * throw new InternalServerErrorException('database', 'Connection failed', 'Please try again later');
1806
- *
1807
- * // Multiple field errors
1808
- * throw new InternalServerErrorException([
1809
- * { field: 'system', message: 'Internal error' }
1810
- * ]);
1811
- */
1812
- declare class InternalServerErrorException extends BaseFieldException {
1813
- constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
455
+ declare class InternalServerErrorException extends HttpProblemException {
456
+ constructor(detailOrOptions?: string | ProblemOptions);
1814
457
  }
1815
458
 
1816
- /**
1817
- * Exception thrown when an HTTP method is not supported for the endpoint (HTTP 405).
1818
- * For example, when a POST is sent to a GET-only endpoint.
1819
- *
1820
- * @example
1821
- * // Simple message
1822
- * throw new MethodNotAllowedException('Method not allowed');
1823
- *
1824
- * // Field-specific error
1825
- * throw new MethodNotAllowedException('method', 'POST method not allowed on this endpoint');
1826
- *
1827
- * // With detail
1828
- * throw new MethodNotAllowedException('method', 'Not allowed', 'Only GET and PUT are supported');
1829
- *
1830
- * // Multiple field errors
1831
- * throw new MethodNotAllowedException([
1832
- * { field: 'method', message: 'DELETE is not allowed on this resource' }
1833
- * ]);
1834
- */
1835
- declare class MethodNotAllowedException extends BaseFieldException {
1836
- constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
459
+ declare class MethodNotAllowedException extends HttpProblemException {
460
+ constructor(detailOrOptions?: string | ProblemOptions);
1837
461
  }
1838
462
 
1839
- /**
1840
- * Exception thrown when content negotiation fails (HTTP 406).
1841
- * Used when the server cannot produce a response matching the Accept headers.
1842
- *
1843
- * @example
1844
- * // Simple message
1845
- * throw new NotAcceptableException('Requested format not available');
1846
- *
1847
- * // Field-specific error
1848
- * throw new NotAcceptableException('accept', 'Cannot produce response in requested format');
1849
- *
1850
- * // With detail
1851
- * throw new NotAcceptableException('accept', 'Format not supported', 'Only JSON is available');
1852
- *
1853
- * // Multiple field errors
1854
- * throw new NotAcceptableException([
1855
- * { field: 'contentType', message: 'XML format is not supported' }
1856
- * ]);
1857
- */
1858
- declare class NotAcceptableException extends BaseFieldException {
1859
- constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
463
+ declare class NotAcceptableException extends HttpProblemException {
464
+ constructor(detailOrOptions?: string | ProblemOptions);
1860
465
  }
1861
466
 
1862
- /**
1863
- * Exception thrown when a requested resource cannot be found (HTTP 404).
1864
- *
1865
- * @example
1866
- * // Simple message
1867
- * throw new NotFoundException('Resource not found');
1868
- *
1869
- * // Field-specific error
1870
- * throw new NotFoundException('userId', 'User not found');
1871
- *
1872
- * // With detail
1873
- * throw new NotFoundException('userId', 'User not found', 'No user exists with the provided ID');
1874
- *
1875
- * // Multiple field errors
1876
- * throw new NotFoundException([
1877
- * { field: 'userId', message: 'User does not exist' }
1878
- * ]);
1879
- */
1880
- declare class NotFoundException extends BaseFieldException {
1881
- constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
467
+ declare class NotFoundException extends HttpProblemException {
468
+ constructor(detailOrOptions?: string | ProblemOptions);
1882
469
  }
1883
470
 
1884
- /**
1885
- * Exception thrown when a feature or endpoint is not yet implemented (HTTP 501).
1886
- * Used for planned but unavailable functionality.
1887
- *
1888
- * @example
1889
- * // Simple message
1890
- * throw new NotImplementedException('Feature not yet implemented');
1891
- *
1892
- * // Field-specific error
1893
- * throw new NotImplementedException('feature', 'This feature is coming soon');
1894
- *
1895
- * // With detail
1896
- * throw new NotImplementedException('export', 'Not implemented', 'PDF export will be available in v2.0');
1897
- *
1898
- * // Multiple field errors
1899
- * throw new NotImplementedException([
1900
- * { field: 'functionality', message: 'This functionality is not available yet' }
1901
- * ]);
1902
- */
1903
- declare class NotImplementedException extends BaseFieldException {
1904
- constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
471
+ declare class NotImplementedException extends HttpProblemException {
472
+ constructor(detailOrOptions?: string | ProblemOptions);
1905
473
  }
1906
474
 
1907
- /**
1908
- * Exception thrown when request payload exceeds size limits (HTTP 413).
1909
- * Commonly used for file upload size restrictions or large request bodies.
1910
- *
1911
- * @example
1912
- * // Simple message
1913
- * throw new PayloadTooLargeException('Request payload too large');
1914
- *
1915
- * // Field-specific error
1916
- * throw new PayloadTooLargeException('file', 'File size exceeds maximum allowed');
1917
- *
1918
- * // With detail
1919
- * throw new PayloadTooLargeException('file', 'File too large', 'Maximum size is 10MB');
1920
- *
1921
- * // Multiple field errors
1922
- * throw new PayloadTooLargeException([
1923
- * { field: 'upload', message: 'File exceeds 10MB limit' }
1924
- * ]);
1925
- */
1926
- declare class PayloadTooLargeException extends BaseFieldException {
1927
- constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
475
+ declare class PayloadTooLargeException extends HttpProblemException {
476
+ constructor(detailOrOptions?: string | ProblemOptions);
1928
477
  }
1929
478
 
1930
- /**
1931
- * Exception thrown when a request takes too long to process (HTTP 408).
1932
- * Used when the client or server times out while waiting for completion.
1933
- *
1934
- * @example
1935
- * // Simple message
1936
- * throw new RequestTimeoutException('Request timeout');
1937
- *
1938
- * // Field-specific error
1939
- * throw new RequestTimeoutException('operation', 'Operation timed out');
1940
- *
1941
- * // With detail
1942
- * throw new RequestTimeoutException('query', 'Database query timeout', 'Try with fewer filters');
1943
- *
1944
- * // Multiple field errors
1945
- * throw new RequestTimeoutException([
1946
- * { field: 'processing', message: 'Request took too long to complete' }
1947
- * ]);
1948
- */
1949
- declare class RequestTimeoutException extends BaseFieldException {
1950
- constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
479
+ declare class RequestTimeoutException extends HttpProblemException {
480
+ constructor(detailOrOptions?: string | ProblemOptions);
1951
481
  }
1952
482
 
1953
- /**
1954
- * Exception thrown when the service is temporarily unavailable (HTTP 503).
1955
- * Used during maintenance, overload, or temporary outages.
1956
- *
1957
- * @example
1958
- * // Simple message
1959
- * throw new ServiceUnavailableException('Service temporarily unavailable');
1960
- *
1961
- * // Field-specific error
1962
- * throw new ServiceUnavailableException('service', 'Scheduled maintenance in progress');
1963
- *
1964
- * // With detail
1965
- * throw new ServiceUnavailableException('service', 'Maintenance', 'Service will be back at 2 PM EST');
1966
- *
1967
- * // Multiple field errors
1968
- * throw new ServiceUnavailableException([
1969
- * { field: 'database', message: 'Database is temporarily unavailable' }
1970
- * ]);
1971
- */
1972
- declare class ServiceUnavailableException extends BaseFieldException {
1973
- constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
483
+ declare class ServiceUnavailableException extends HttpProblemException {
484
+ constructor(detailOrOptions?: string | ProblemOptions);
1974
485
  }
1975
486
 
1976
- /**
1977
- * Exception thrown when rate limiting is triggered (HTTP 429).
1978
- * Used to prevent abuse and ensure fair resource usage.
1979
- *
1980
- * @example
1981
- * // Simple message
1982
- * throw new TooManyRequestsException('Too many requests');
1983
- *
1984
- * // Field-specific error
1985
- * throw new TooManyRequestsException('api', 'Rate limit exceeded');
1986
- *
1987
- * // With detail
1988
- * throw new TooManyRequestsException('api', 'Rate limit exceeded', 'Try again in 60 seconds');
1989
- *
1990
- * // Multiple field errors
1991
- * throw new TooManyRequestsException([
1992
- * { field: 'requests', message: 'Rate limit exceeded for this endpoint' }
1993
- * ]);
1994
- */
1995
- declare class TooManyRequestsException extends BaseFieldException {
1996
- constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
487
+ declare class TooManyRequestsException extends HttpProblemException {
488
+ constructor(detailOrOptions?: string | ProblemOptions);
1997
489
  }
1998
490
 
1999
- /**
2000
- * Exception thrown when authentication is required or has failed (HTTP 401).
2001
- *
2002
- * @example
2003
- * // Simple message
2004
- * throw new UnauthorizedException('Authentication required');
2005
- *
2006
- * // Field-specific error
2007
- * throw new UnauthorizedException('token', 'Invalid or expired token');
2008
- *
2009
- * // With detail
2010
- * throw new UnauthorizedException('token', 'Invalid token', 'Please login again');
2011
- *
2012
- * // Multiple field errors
2013
- * throw new UnauthorizedException([
2014
- * { field: 'token', message: 'Token expired' }
2015
- * ]);
2016
- */
2017
- declare class UnauthorizedException extends BaseFieldException {
2018
- constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
491
+ declare class UnauthorizedException extends HttpProblemException {
492
+ constructor(detailOrOptions?: string | ProblemOptions);
2019
493
  }
2020
494
 
2021
- /**
2022
- * Exception thrown when the request is well-formed but contains semantic errors (HTTP 422).
2023
- * Used for business logic validation failures that prevent processing.
2024
- *
2025
- * @example
2026
- * // Simple message
2027
- * throw new UnprocessableEntityException('Cannot process the request');
2028
- *
2029
- * // Field-specific error
2030
- * throw new UnprocessableEntityException('age', 'Age must be 18 or older');
2031
- *
2032
- * // With detail
2033
- * throw new UnprocessableEntityException('quantity', 'Insufficient stock', 'Only 5 items available');
2034
- *
2035
- * // Multiple field errors
2036
- * throw new UnprocessableEntityException([
2037
- * { field: 'startDate', message: 'Start date must be before end date' },
2038
- * { field: 'endDate', message: 'End date cannot be in the past' }
2039
- * ]);
2040
- */
2041
- declare class UnprocessableEntityException extends BaseFieldException {
2042
- constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
495
+ declare class UnprocessableEntityException extends HttpProblemException {
496
+ constructor(detailOrOptions?: string | ProblemOptions);
2043
497
  }
2044
498
 
2045
- /**
2046
- * Exception thrown when the media type of the request is not supported (HTTP 415).
2047
- * Used when the Content-Type header specifies an unsupported format.
2048
- *
2049
- * @example
2050
- * // Simple message
2051
- * throw new UnsupportedMediaTypeException('Unsupported media type');
2052
- *
2053
- * // Field-specific error
2054
- * throw new UnsupportedMediaTypeException('contentType', 'XML is not supported');
2055
- *
2056
- * // With detail
2057
- * throw new UnsupportedMediaTypeException('contentType', 'Not supported', 'Only JSON and form-data are accepted');
2058
- *
2059
- * // Multiple field errors
2060
- * throw new UnsupportedMediaTypeException([
2061
- * { field: 'contentType', message: 'application/xml is not supported' }
2062
- * ]);
2063
- */
2064
- declare class UnsupportedMediaTypeException extends BaseFieldException {
2065
- constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
499
+ declare class UnsupportedMediaTypeException extends HttpProblemException {
500
+ constructor(detailOrOptions?: string | ProblemOptions);
2066
501
  }
2067
502
 
2068
- /**
2069
- * Exception thrown when request validation fails (HTTP 400).
2070
- * Typically used for form validation or DTO validation errors.
2071
- *
2072
- * @example
2073
- * // Multiple validation errors
2074
- * throw new ValidationException([
2075
- * { field: 'email', message: 'Invalid email format' },
2076
- * { field: 'password', message: 'Password must be at least 8 characters' }
2077
- * ]);
2078
- *
2079
- * // With detail
2080
- * throw new ValidationException(
2081
- * [{ field: 'email', message: 'Invalid format' }],
2082
- * 'Please correct the errors and try again'
2083
- * );
2084
- */
2085
- declare class ValidationException extends BaseFieldException {
2086
- constructor(errors: FieldError[], detail?: string);
503
+ declare class ValidationException extends HttpProblemException {
504
+ constructor(detailOrOptions?: string | ProblemOptions);
2087
505
  }
2088
506
 
2089
- /**
2090
- * Converts an HTTP status code to its corresponding title string.
2091
- * Uses the HttpStatus enum to map status codes to human-readable titles.
2092
- *
2093
- * @param status - The HTTP status code
2094
- * @returns The human-readable title for the status code
2095
- *
2096
- * @example
2097
- * getHttpStatusTitle(400) // Returns: "Bad Request"
2098
- * getHttpStatusTitle(404) // Returns: "Not Found"
2099
- * getHttpStatusTitle(500) // Returns: "Internal Server Error"
2100
- */
2101
507
  declare function getHttpStatusTitle(status: number): string;
2102
- /**
2103
- * Global HTTP Exception Filter implementing RFC 7807 Problem Details
2104
- *
2105
- * Transforms all exceptions into a standardized RFC 7807 format:
2106
- * {
2107
- * title: string, // Human-readable status title
2108
- * status: number, // HTTP status code
2109
- * detail: string, // Detailed error description
2110
- * errors: FieldError[] // Field-specific error messages
2111
- * }
2112
- *
2113
- * Handles:
2114
- * - Custom field exceptions from @vritti/api-sdk (BaseFieldException)
2115
- * - Class-validator DTO validation errors
2116
- * - Standard NestJS HTTP exceptions
2117
- * - Unknown errors
2118
- */
2119
508
  declare class HttpExceptionFilter implements ExceptionFilter {
2120
509
  private readonly logger;
2121
510
  catch(exception: unknown, host: ArgumentsHost): void;
2122
511
  }
2123
512
 
2124
- declare const SKIP_CSRF_KEY = "skipCsrf";
2125
- /**
2126
- * Decorator to skip CSRF validation for specific routes or controllers.
2127
- * Use this for webhook endpoints that receive requests from external services
2128
- * (e.g., WhatsApp, Twilio) which cannot include CSRF tokens.
2129
- *
2130
- * @example
2131
- * // Skip CSRF for entire controller
2132
- * @Controller('webhooks')
2133
- * @SkipCsrf()
2134
- * export class WebhookController { ... }
2135
- *
2136
- * @example
2137
- * // Skip CSRF for specific route
2138
- * @Post()
2139
- * @SkipCsrf()
2140
- * async handleWebhook() { ... }
2141
- */
2142
- declare const SkipCsrf: () => _nestjs_common.CustomDecorator<string>;
2143
-
2144
- /**
2145
- * HTTP Module
2146
- *
2147
- * Provides HTTP utilities including:
2148
- * - CSRF Guard for request protection
2149
- * - HTTP Exception Filter for standardized error responses
2150
- *
2151
- * Usage:
2152
- * Import this module to access HTTP guards and filters.
2153
- * Guards and filters are registered globally in the main application.
2154
- */
2155
- declare class HttpModule {
2156
- }
2157
-
2158
- /**
2159
- * Extract ISO country code from E.164 phone number
2160
- * @param phone Phone number in E.164 format (e.g., +919876543210)
2161
- * @returns ISO 3166-1 alpha-2 country code (e.g., "IN") or undefined
2162
- */
2163
- declare function extractCountryFromPhone(phone: string): string | undefined;
2164
- /**
2165
- * Normalize phone number to E.164 format with + prefix
2166
- * @param phone Phone number (with or without + prefix)
2167
- * @returns Phone number in E.164 format
2168
- */
2169
- declare function normalizePhoneNumber(phone: string): string;
2170
-
2171
- /**
2172
- * Supported log levels for the logging system.
2173
- */
2174
513
  type LogLevel = 'error' | 'warn' | 'log' | 'debug' | 'verbose';
2175
- /**
2176
- * Supported log output formats.
2177
- */
2178
514
  type LogFormat = 'json' | 'text';
2179
- /**
2180
- * Metadata that can be attached to log entries.
2181
- */
2182
515
  interface LogMetadata {
2183
516
  correlationId?: string;
2184
517
  method?: string;
@@ -2189,9 +522,6 @@ interface LogMetadata {
2189
522
  userAgent?: string;
2190
523
  [key: string]: unknown;
2191
524
  }
2192
- /**
2193
- * Configuration options for the logger module.
2194
- */
2195
525
  interface LoggerModuleOptions {
2196
526
  provider?: 'default' | 'winston';
2197
527
  level?: LogLevel;
@@ -2206,31 +536,19 @@ interface LoggerModuleOptions {
2206
536
  environment?: string;
2207
537
  defaultMeta?: Record<string, unknown>;
2208
538
  }
2209
- /**
2210
- * Factory function for creating logger options asynchronously.
2211
- */
2212
539
  interface LoggerOptionsFactory {
2213
540
  createLoggerOptions(): Promise<LoggerModuleOptions> | LoggerModuleOptions;
2214
541
  }
2215
- /**
2216
- * Async configuration options for the logger module.
2217
- */
2218
542
  interface LoggerModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
2219
543
  useExisting?: Type<LoggerOptionsFactory>;
2220
544
  useClass?: Type<LoggerOptionsFactory>;
2221
545
  useFactory?: (...args: unknown[]) => Promise<LoggerModuleOptions> | LoggerModuleOptions;
2222
546
  inject?: unknown[];
2223
547
  }
2224
- /**
2225
- * Context object for correlation tracking across async operations.
2226
- */
2227
548
  interface CorrelationContext {
2228
549
  correlationId: string;
2229
550
  [key: string]: unknown;
2230
551
  }
2231
- /**
2232
- * Configuration options for HTTP request/response logger interceptor.
2233
- */
2234
552
  interface HttpLoggerOptions {
2235
553
  enableRequestLog?: boolean;
2236
554
  enableResponseLog?: boolean;
@@ -2242,331 +560,74 @@ interface HttpLoggerOptions {
2242
560
  maxBodySize?: number;
2243
561
  }
2244
562
 
2245
- /**
2246
- * Unified Logger Service
2247
- *
2248
- * Single service that provides both default NestJS Logger and Winston logger implementations.
2249
- * Automatically delegates to the configured provider (default or winston).
2250
- * @module logger/logger.service
2251
- */
2252
-
2253
- /**
2254
- * Unified logger service implementing NestJS LoggerService interface.
2255
- * Supports both default NestJS Logger and Winston implementations via facade pattern.
2256
- */
563
+ type LogMessage = string | Error | object;
2257
564
  declare class LoggerService implements LoggerService$1 {
2258
565
  private readonly defaultLogger?;
2259
566
  private readonly activeLogger;
2260
567
  private readonly options;
2261
568
  private context?;
2262
569
  constructor(options?: LoggerModuleOptions, defaultLogger?: Logger | undefined);
2263
- /**
2264
- * Creates a Winston logger instance with inline configuration.
2265
- * Consolidates winston-config.factory.ts logic.
2266
- */
2267
570
  private createWinstonLogger;
2268
- log(message: any, context?: string): void;
2269
- error(message: any, trace?: string, context?: string): void;
2270
- warn(message: any, context?: string): void;
2271
- debug(message: any, context?: string): void;
2272
- verbose(message: any, context?: string): void;
571
+ log(message: LogMessage, context?: string): void;
572
+ error(message: LogMessage, trace?: string, context?: string): void;
573
+ warn(message: LogMessage, context?: string): void;
574
+ debug(message: LogMessage, context?: string): void;
575
+ verbose(message: LogMessage, context?: string): void;
2273
576
  setContext(context: string): void;
2274
- /**
2275
- * Unified internal logging method that handles both Winston and NestJS Logger.
2276
- */
2277
577
  private _log;
2278
- /**
2279
- * Logs with custom metadata (Winston only).
2280
- */
2281
- logWithMetadata(level: LogLevel, message: any, metadata?: LogMetadata, context?: string): void;
578
+ logWithMetadata(level: LogLevel, message: LogMessage, metadata?: LogMetadata, context?: string): void;
2282
579
  private formatMessage;
2283
- /**
2284
- * Enriches metadata with correlation context from AsyncLocalStorage.
2285
- * Inline from winston-logger.service.ts
2286
- */
2287
580
  private enrichMetadata;
2288
581
  child(context: string): LoggerService;
2289
582
  }
2290
583
 
2291
- /**
2292
- * HTTP Logger Interceptor
2293
- *
2294
- * Automatically logs HTTP requests and responses with correlation tracking.
2295
- * @module logger/http-logger.interceptor
2296
- */
2297
-
2298
- /**
2299
- * HTTP Logger Interceptor for NestJS applications.
2300
- *
2301
- * Logs all HTTP requests and responses with metadata including
2302
- * correlation IDs, performance metrics, and error details.
2303
- */
2304
584
  declare class HttpLoggerInterceptor implements NestInterceptor {
2305
585
  private readonly logger;
2306
586
  private readonly enableRequestLog;
2307
587
  private readonly enableResponseLog;
2308
588
  private readonly slowRequestThreshold;
2309
589
  constructor(logger: LoggerService, options?: HttpLoggerOptions);
2310
- intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
590
+ intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
2311
591
  private logRequest;
2312
592
  private logResponse;
2313
593
  private logError;
2314
594
  }
2315
595
 
2316
- /**
2317
- * Logger Module
2318
- *
2319
- * Dynamic NestJS module providing unified logging infrastructure with:
2320
- * - Environment presets (development, staging, production, test)
2321
- * - Transparent switching between default NestJS Logger and Winston
2322
- * - Correlation ID tracking via middleware
2323
- * - HTTP request/response logging via interceptor
2324
- * - PII masking and file logging support
2325
- *
2326
- * @module logger/logger.module
2327
- */
2328
-
2329
- /**
2330
- * Dependency injection token for logger module options
2331
- */
2332
596
  declare const LOGGER_MODULE_OPTIONS: unique symbol;
2333
- /**
2334
- * Global logger module providing unified logging infrastructure.
2335
- *
2336
- * Features:
2337
- * - Environment presets (development, staging, production, test)
2338
- * - Single `LoggerService` interface for all logging needs
2339
- * - Transparent provider switching (default ↔ Winston)
2340
- * - Correlation ID tracking across async operations
2341
- * - HTTP request/response logging
2342
- * - PII masking for GDPR compliance
2343
- * - File-based logging with rotation
2344
- *
2345
- * @example
2346
- * ```typescript
2347
- * // Production environment with explicit config
2348
- * @Module({
2349
- * imports: [
2350
- * LoggerModule.forRoot({
2351
- * environment: 'production',
2352
- * appName: 'my-service'
2353
- * })
2354
- * ],
2355
- * })
2356
- * export class AppModule {}
2357
- *
2358
- * // Development environment with custom override
2359
- * @Module({
2360
- * imports: [
2361
- * LoggerModule.forRoot({
2362
- * environment: 'development',
2363
- * level: 'verbose' // Override preset's debug
2364
- * })
2365
- * ],
2366
- * })
2367
- * export class AppModule {}
2368
- *
2369
- * // Use default NestJS logger
2370
- * @Module({
2371
- * imports: [
2372
- * LoggerModule.forRoot({
2373
- * provider: 'default',
2374
- * environment: 'development'
2375
- * })
2376
- * ],
2377
- * })
2378
- * export class AppModule {}
2379
- *
2380
- * // Dynamic configuration with ConfigService
2381
- * @Module({
2382
- * imports: [
2383
- * LoggerModule.forRootAsync({
2384
- * imports: [ConfigModule],
2385
- * useFactory: (config: ConfigService) => ({
2386
- * environment: config.get('NODE_ENV', 'development'),
2387
- * provider: config.get('LOG_PROVIDER', 'winston'),
2388
- * appName: config.get('APP_NAME')
2389
- * }),
2390
- * inject: [ConfigService]
2391
- * })
2392
- * ],
2393
- * })
2394
- * export class AppModule {}
2395
- * ```
2396
- */
2397
597
  declare class LoggerModule implements NestModule {
2398
- /**
2399
- * Configures the logger module with static options.
2400
- *
2401
- * Users must explicitly pass `environment` to select a preset.
2402
- * All preset values can be overridden by passing explicit options.
2403
- *
2404
- * @param options - Logger configuration options
2405
- * @returns Dynamic module configuration
2406
- *
2407
- * @example
2408
- * ```typescript
2409
- * // Production preset with app name
2410
- * LoggerModule.forRoot({
2411
- * environment: 'production',
2412
- * appName: 'my-service'
2413
- * })
2414
- *
2415
- * // Development preset with custom level
2416
- * LoggerModule.forRoot({
2417
- * environment: 'development',
2418
- * level: 'verbose',
2419
- * enableFileLogger: true
2420
- * })
2421
- *
2422
- * // Use default NestJS logger
2423
- * LoggerModule.forRoot({
2424
- * provider: 'default',
2425
- * environment: 'development'
2426
- * })
2427
- * ```
2428
- */
2429
598
  static forRoot(options?: LoggerModuleOptions): DynamicModule;
2430
- /**
2431
- * Configures the logger module with async options.
2432
- *
2433
- * Supports dynamic configuration using:
2434
- * - `useFactory`: Factory function with dependency injection
2435
- * - `useClass`: Class implementing `LoggerOptionsFactory`
2436
- * - `useExisting`: Existing provider implementing `LoggerOptionsFactory`
2437
- *
2438
- * Options from the factory/class are merged with environment preset defaults.
2439
- *
2440
- * @param options - Async configuration options
2441
- * @returns Dynamic module configuration
2442
- *
2443
- * @example
2444
- * ```typescript
2445
- * // Factory with ConfigService
2446
- * LoggerModule.forRootAsync({
2447
- * imports: [ConfigModule],
2448
- * useFactory: (config: ConfigService) => ({
2449
- * environment: config.get('NODE_ENV', 'development'),
2450
- * provider: config.get('LOG_PROVIDER', 'winston'),
2451
- * level: config.get('LOG_LEVEL'),
2452
- * appName: config.get('APP_NAME'),
2453
- * }),
2454
- * inject: [ConfigService]
2455
- * })
2456
- *
2457
- * // Factory class
2458
- * @Injectable()
2459
- * class LoggerConfigService implements LoggerOptionsFactory {
2460
- * createLoggerOptions(): LoggerModuleOptions {
2461
- * return {
2462
- * environment: 'production',
2463
- * appName: 'my-service'
2464
- * };
2465
- * }
2466
- * }
2467
- *
2468
- * LoggerModule.forRootAsync({
2469
- * useClass: LoggerConfigService
2470
- * })
2471
- * ```
2472
- */
2473
599
  static forRootAsync(options: LoggerModuleAsyncOptions): DynamicModule;
2474
- /**
2475
- * Configures middleware for the module.
2476
- * Middleware is registered globally in main.ts using Fastify hooks.
2477
- */
2478
600
  configure(_consumer: MiddlewareConsumer): void;
2479
- /**
2480
- * Creates async providers for dynamic module configuration.
2481
- */
2482
601
  private static createAsyncProviders;
2483
- /**
2484
- * Creates the async options provider.
2485
- */
2486
602
  private static createAsyncOptionsProvider;
2487
603
  }
2488
604
 
2489
- /**
2490
- * Correlation ID Middleware
2491
- *
2492
- * Generates unique correlation IDs for request tracking across async operations.
2493
- * Stores correlation ID in AsyncLocalStorage for access throughout the request lifecycle.
2494
- * @module logger/correlation-id.middleware
2495
- */
2496
-
2497
- /**
2498
- * Configuration options for the Correlation ID middleware.
2499
- */
2500
605
  interface CorrelationIdMiddlewareOptions {
2501
- /**
2502
- * If true, adds the correlation ID to response headers.
2503
- * @default true
2504
- */
2505
606
  includeInResponse?: boolean;
2506
- /**
2507
- * The header name to use when adding correlation ID to response.
2508
- * @default 'x-correlation-id'
2509
- */
2510
607
  responseHeader?: string;
2511
608
  }
2512
- /**
2513
- * Correlation ID Middleware for Fastify/NestJS applications.
2514
- *
2515
- * Generates a unique correlation ID for each request,
2516
- * stores it in AsyncLocalStorage for access throughout the request lifecycle,
2517
- * and optionally adds it to response headers.
2518
- */
2519
609
  declare class CorrelationIdMiddleware implements NestMiddleware {
2520
610
  private readonly includeInResponse;
2521
611
  private readonly responseHeader;
2522
612
  constructor(options?: CorrelationIdMiddlewareOptions);
2523
- /**
2524
- * Middleware handler for processing requests.
2525
- */
2526
613
  use(_req: FastifyRequest, reply: FastifyReply, next: () => void): void;
2527
- /**
2528
- * Fastify hook handler for onRequest.
2529
- * This is an async function that returns a Promise, ensuring the AsyncLocalStorage
2530
- * context persists throughout the entire request lifecycle.
2531
- */
2532
614
  onRequest(_req: FastifyRequest, reply: FastifyReply): Promise<void>;
2533
615
  }
2534
616
 
2535
- /**
2536
- * Logging Utilities
2537
- *
2538
- * Consolidated utilities for correlation tracking, PII masking, and async context management.
2539
- * @module logging/utils
2540
- */
2541
-
2542
- /**
2543
- * Async local storage for correlation context tracking across async operations.
2544
- */
2545
617
  declare const correlationStorage: AsyncLocalStorage<CorrelationContext>;
2546
- /**
2547
- * Gets the current correlation context from async local storage.
2548
- */
2549
618
  declare function getCorrelationContext(): CorrelationContext | undefined;
2550
- /**
2551
- * Runs a callback within a correlation context.
2552
- */
2553
619
  declare function runWithCorrelationContext<T>(context: CorrelationContext, callback: () => T): T;
2554
- /**
2555
- * Updates the current correlation context with new values.
2556
- */
2557
620
  declare function updateCorrelationContext(updates: Partial<CorrelationContext>): void;
2558
- /**
2559
- * Default header name for setting correlation ID in responses.
2560
- */
2561
621
  declare const DEFAULT_CORRELATION_HEADER = "x-correlation-id";
2562
- /**
2563
- * Generates a new correlation ID using UUID v4.
2564
- * Always creates a fresh ID for each request.
2565
- */
2566
622
  declare function generateCorrelationId(): string;
2567
- /**
2568
- * Adds correlation ID to Fastify response headers.
2569
- */
2570
623
  declare function addCorrelationIdToResponse(reply: FastifyReply, correlationId: string, headerName?: string): void;
2571
624
 
2572
- export { type ApiErrorResponse, type ApiSdkConfig, AuthConfigModule, BadGatewayException, BadRequestException, BaseFieldException, ConflictException, type CookieConfig, type CorrelationContext, CorrelationIdMiddleware, DEFAULT_CORRELATION_HEADER, DatabaseModule, type DatabaseModuleOptions, type FieldError, ForbiddenException, GoneException, type GuardConfig, HttpExceptionFilter, HttpLoggerInterceptor, type HttpLoggerOptions, HttpModule, InternalServerErrorException, type JwtConfig, LOGGER_MODULE_OPTIONS, type LogFormat, type LogLevel, type LogMetadata, LoggerModule, type LoggerModuleAsyncOptions, type LoggerModuleOptions, type LoggerOptionsFactory, LoggerService, MethodNotAllowedException, NotAcceptableException, NotFoundException, NotImplementedException, Onboarding, PayloadTooLargeException, PrimaryBaseRepository, PrimaryDatabaseService, type PrimaryDbConfig, type ProblemDetails, Public, type RegisteredSchema, RequestTimeoutException, SKIP_CSRF_KEY, ServiceUnavailableException, SkipCsrf, SseAuthGuard, Tenant, TenantBaseRepository, TenantContextService, TenantDatabaseService, type TenantInfo, TooManyRequestsException, type TypedDrizzleClient, UnauthorizedException, UnprocessableEntityException, UnsupportedMediaTypeException, UserId, ValidationException, VrittiAuthGuard, addCorrelationIdToResponse, configureApiSdk, correlationStorage, defineConfig, extractCountryFromPhone, generateCorrelationId, getConfig, getCorrelationContext, getHttpStatusTitle, getJwtExpiry, getRefreshCookieOptions, hashToken, normalizePhoneNumber, resetConfig, runWithCorrelationContext, updateCorrelationContext, verifyTokenHash };
625
+ declare class RootModule {
626
+ }
627
+
628
+ declare function extractCountryFromPhone(phone: string): string | undefined;
629
+ declare function normalizePhoneNumber(phone: string): string;
630
+
631
+ declare function parseExpiryToMs(expiry: string): number;
632
+
633
+ export { AccessToken, type AccessTokenPayload, type ApiErrorResponse, type ApiSdkConfig, AuthConfigModule, BadGatewayException, BadRequestException, ConflictException, type CookieConfig, type CorrelationContext, CorrelationIdMiddleware, DEFAULT_CORRELATION_HEADER, DatabaseModule, type DatabaseModuleOptions, EmailModule, EmailService, type FieldError, type FindForSelectConfig, ForbiddenException, GoneException, type GuardConfig, HttpExceptionFilter, HttpLoggerInterceptor, type HttpLoggerOptions, HttpProblemException, InternalServerErrorException, JwtAuthService, type JwtConfig, LOGGER_MODULE_OPTIONS, type LogFormat, type LogLevel, type LogMetadata, LoggerModule, type LoggerModuleAsyncOptions, type LoggerModuleOptions, type LoggerOptionsFactory, LoggerService, MethodNotAllowedException, NotAcceptableException, NotFoundException, NotImplementedException, Onboarding, PayloadTooLargeException, PrimaryBaseRepository, PrimaryDatabaseService, type PrimaryDbConfig, type ProblemDetails, type ProblemOptions, Public, RESET_KEY, RefreshTokenCookie, type RefreshTokenPayload, type RegisteredSchema, RequestTimeoutException, Reset, RootModule, SKIP_CSRF_KEY, SelectOptionsQueryDto, type SelectQueryGroup, type SelectQueryOption, type SelectQueryResult, ServiceUnavailableException, SessionData, type SessionInfo, SkipCsrf, Tenant, TenantBaseRepository, TenantContextService, TenantDatabaseService, type TenantInfo, type TokenExpiry, TokenType, TooManyRequestsException, type TypedDrizzleClient, UnauthorizedException, UnprocessableEntityException, UnsupportedMediaTypeException, UserId, ValidationException, VrittiAuthGuard, addCorrelationIdToResponse, configureApiSdk, correlationStorage, defineConfig, extractCountryFromPhone, generateCorrelationId, getConfig, getCorrelationContext, getHttpStatusTitle, getJwtExpiry, getRefreshCookieOptions, getTokenExpiry, hashToken, jwtConfigFactory, normalizePhoneNumber, parseExpiryToMs, resetConfig, runWithCorrelationContext, updateCorrelationContext, verifyTokenHash };