@vritti/api-sdk 0.1.8 → 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
+ }
38
+ }
39
+
40
+ declare class AuthConfigModule {
41
+ static forRootAsync(): DynamicModule;
390
42
  }
391
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,1364 +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
- /**
1664
- * RFC 9457 Problem Details field-specific error structure.
1665
- *
1666
- * Used for validation errors or other field-specific issues.
1667
- * The `field` property is required to ensure clear association.
1668
- */
1669
409
  interface FieldError {
1670
- /** The field name (e.g., 'email', 'password') - REQUIRED */
1671
410
  field: string;
1672
- /** The error message for this field */
1673
411
  message: string;
1674
412
  }
1675
- /**
1676
- * RFC 9457 Problem Details standard fields.
1677
- *
1678
- * @see https://www.rfc-editor.org/rfc/rfc9457.html
1679
- */
1680
413
  interface ProblemDetails {
1681
- /** Problem type URI (default: "about:blank") */
1682
414
  type: string;
1683
- /** HTTP status phrase (e.g., "Unauthorized", "Not Found") */
1684
415
  title: string;
1685
- /** HTTP status code */
1686
416
  status: number;
1687
- /** Root error heading (extension member, maps to AlertTitle in frontend) */
1688
417
  label?: string;
1689
- /** Detailed error description (maps to AlertDescription in frontend) */
1690
418
  detail: string;
1691
- /** Request path where the error occurred */
1692
419
  instance?: string;
1693
420
  }
1694
- /**
1695
- * Complete API error response following RFC 9457 Problem Details format.
1696
- *
1697
- * Extends ProblemDetails with field-specific errors.
1698
- */
1699
421
  interface ApiErrorResponse extends ProblemDetails {
1700
- /** Field-specific errors (field is required in each FieldError) */
1701
422
  errors: FieldError[];
1702
423
  }
1703
424
 
1704
- /**
1705
- * Options for creating RFC 9457 Problem Details exceptions.
1706
- *
1707
- * @example
1708
- * throw new UnauthorizedException({
1709
- * label: 'Invalid Credentials',
1710
- * detail: 'The email or password is incorrect',
1711
- * });
1712
- *
1713
- * @example
1714
- * throw new BadRequestException({
1715
- * detail: 'Validation failed',
1716
- * errors: [
1717
- * { field: 'email', message: 'Invalid email format' },
1718
- * { field: 'password', message: 'Password too short' },
1719
- * ],
1720
- * });
1721
- */
1722
425
  interface ProblemOptions {
1723
- /** Problem type URI (default: "about:blank") */
1724
426
  type?: string;
1725
- /** Root error heading (maps to AlertTitle in frontend) */
1726
427
  label?: string;
1727
- /** Root error description (maps to AlertDescription in frontend) */
1728
428
  detail?: string;
1729
- /** Field-specific errors only (field is required) */
1730
429
  errors?: FieldError[];
1731
430
  }
1732
- /**
1733
- * Base exception class that follows RFC 9457 Problem Details format.
1734
- *
1735
- * Provides a clean interface for creating HTTP exceptions with:
1736
- * - RFC 9457 standard fields (type, title, status, detail, instance)
1737
- * - Extension members (label for root error heading, errors for field-specific errors)
1738
- *
1739
- * The `title` field is always set to the HTTP status phrase (e.g., "Unauthorized")
1740
- * by the HttpExceptionFilter, not by this class.
1741
- */
1742
431
  declare abstract class HttpProblemException extends HttpException {
1743
432
  constructor(detailOrOptions: string | ProblemOptions, httpStatus: HttpStatus);
1744
433
  }
1745
434
 
1746
- /**
1747
- * Exception thrown when a gateway or proxy receives an invalid response (HTTP 502).
1748
- * Used when a server acting as a gateway gets an error from an upstream server.
1749
- *
1750
- * @example
1751
- * // Simple message
1752
- * throw new BadGatewayException('Upstream service returned invalid response');
1753
- *
1754
- * // With options
1755
- * throw new BadGatewayException({
1756
- * title: 'Upstream Service Error',
1757
- * detail: 'The payment service is not responding correctly',
1758
- * instance: '/api/payments/process',
1759
- * });
1760
- */
1761
435
  declare class BadGatewayException extends HttpProblemException {
1762
436
  constructor(detailOrOptions?: string | ProblemOptions);
1763
437
  }
1764
438
 
1765
- /**
1766
- * Exception thrown when a request is malformed or contains invalid data (HTTP 400).
1767
- *
1768
- * @example
1769
- * // Simple message
1770
- * throw new BadRequestException('Invalid request data');
1771
- *
1772
- * // With field errors
1773
- * throw new BadRequestException({
1774
- * detail: 'Validation failed',
1775
- * errors: [
1776
- * { field: 'email', message: 'Invalid email format' },
1777
- * { field: 'password', message: 'Password too short' }
1778
- * ]
1779
- * });
1780
- *
1781
- * // With custom label and type
1782
- * throw new BadRequestException({
1783
- * label: 'Invalid Form Data',
1784
- * detail: 'Please check your input',
1785
- * type: 'validation-error'
1786
- * });
1787
- */
1788
439
  declare class BadRequestException extends HttpProblemException {
1789
440
  constructor(detailOrOptions?: string | ProblemOptions);
1790
441
  }
1791
442
 
1792
- /**
1793
- * Exception thrown when a request conflicts with the current state (HTTP 409).
1794
- * Commonly used for duplicate resources or concurrent modification issues.
1795
- *
1796
- * @example
1797
- * // Simple detail message
1798
- * throw new ConflictException('Resource already exists');
1799
- *
1800
- * // With custom label and detail
1801
- * throw new ConflictException({
1802
- * label: 'Duplicate Entry',
1803
- * detail: 'Email already exists',
1804
- * });
1805
- *
1806
- * // With field-specific errors
1807
- * throw new ConflictException({
1808
- * detail: 'Duplicate data detected',
1809
- * errors: [
1810
- * { field: 'email', message: 'Email already registered' }
1811
- * ],
1812
- * });
1813
- *
1814
- * // With custom label and field errors
1815
- * throw new ConflictException({
1816
- * label: 'Resource Conflict',
1817
- * detail: 'Try logging in instead or use a different email',
1818
- * errors: [{ field: 'email', message: 'Email already in use' }],
1819
- * });
1820
- */
1821
443
  declare class ConflictException extends HttpProblemException {
1822
444
  constructor(detailOrOptions?: string | ProblemOptions);
1823
445
  }
1824
446
 
1825
- /**
1826
- * Exception thrown when the user does not have permission to access a resource (HTTP 403).
1827
- *
1828
- * @example
1829
- * // Simple detail message
1830
- * throw new ForbiddenException('Access denied');
1831
- *
1832
- * // With custom label
1833
- * throw new ForbiddenException({
1834
- * label: 'Access Denied',
1835
- * detail: 'You do not have permission to perform this action',
1836
- * });
1837
- *
1838
- * // With field-specific errors
1839
- * throw new ForbiddenException({
1840
- * label: 'Permission Denied',
1841
- * detail: 'Contact your administrator for access',
1842
- * errors: [{ field: 'role', message: 'Admin role required' }],
1843
- * });
1844
- */
1845
447
  declare class ForbiddenException extends HttpProblemException {
1846
448
  constructor(detailOrOptions?: string | ProblemOptions);
1847
449
  }
1848
450
 
1849
- /**
1850
- * Exception thrown when a resource has been permanently removed (HTTP 410).
1851
- * Unlike 404, this indicates the resource existed but is intentionally gone.
1852
- *
1853
- * @example
1854
- * // Simple message
1855
- * throw new GoneException('Resource permanently deleted');
1856
- *
1857
- * // With label and detail
1858
- * throw new GoneException({
1859
- * label: 'Account Deleted',
1860
- * detail: 'This account has been permanently removed',
1861
- * });
1862
- *
1863
- * // With field errors
1864
- * throw new GoneException({
1865
- * label: 'Resource Removed',
1866
- * detail: 'The resource was removed due to policy violation',
1867
- * errors: [{ field: 'resource', message: 'This content has been permanently deleted' }],
1868
- * });
1869
- */
1870
451
  declare class GoneException extends HttpProblemException {
1871
452
  constructor(detailOrOptions?: string | ProblemOptions);
1872
453
  }
1873
454
 
1874
- /**
1875
- * Exception thrown when an unexpected server error occurs (HTTP 500).
1876
- *
1877
- * @example
1878
- * // Simple message
1879
- * throw new InternalServerErrorException('An unexpected error occurred');
1880
- *
1881
- * // With options object
1882
- * throw new InternalServerErrorException({
1883
- * title: 'Server Error',
1884
- * detail: 'Something went wrong',
1885
- * instance: '/api/users',
1886
- * });
1887
- */
1888
455
  declare class InternalServerErrorException extends HttpProblemException {
1889
456
  constructor(detailOrOptions?: string | ProblemOptions);
1890
457
  }
1891
458
 
1892
- /**
1893
- * Exception thrown when an HTTP method is not supported for the endpoint (HTTP 405).
1894
- * For example, when a POST is sent to a GET-only endpoint.
1895
- *
1896
- * @example
1897
- * // Simple message
1898
- * throw new MethodNotAllowedException('Method not allowed');
1899
- *
1900
- * // With detail
1901
- * throw new MethodNotAllowedException({
1902
- * detail: 'Method not allowed',
1903
- * instance: '/api/resource/123'
1904
- * });
1905
- *
1906
- * // With custom title
1907
- * throw new MethodNotAllowedException({
1908
- * title: 'Invalid HTTP Method',
1909
- * detail: 'This endpoint only supports GET requests',
1910
- * });
1911
- *
1912
- * // With additional context
1913
- * throw new MethodNotAllowedException({
1914
- * title: 'Unsupported Operation',
1915
- * detail: 'PATCH is not supported for this resource',
1916
- * instance: '/api/users/456',
1917
- * extensions: { allowedMethods: ['GET', 'PUT', 'DELETE'] }
1918
- * });
1919
- */
1920
459
  declare class MethodNotAllowedException extends HttpProblemException {
1921
460
  constructor(detailOrOptions?: string | ProblemOptions);
1922
461
  }
1923
462
 
1924
- /**
1925
- * Exception thrown when content negotiation fails (HTTP 406).
1926
- * Used when the server cannot produce a response matching the Accept headers.
1927
- *
1928
- * @example
1929
- * // Simple detail message
1930
- * throw new NotAcceptableException('Requested format not available');
1931
- *
1932
- * // With custom label
1933
- * throw new NotAcceptableException({
1934
- * label: 'Content Negotiation Failed',
1935
- * detail: 'Cannot produce response in the requested format',
1936
- * });
1937
- *
1938
- * // With field-specific errors
1939
- * throw new NotAcceptableException({
1940
- * detail: 'Requested format is not supported',
1941
- * errors: [
1942
- * { field: 'accept', message: 'XML format is not available' },
1943
- * { field: 'contentType', message: 'Only JSON is supported' },
1944
- * ],
1945
- * });
1946
- *
1947
- * // With all options
1948
- * throw new NotAcceptableException({
1949
- * type: 'https://api.example.com/errors/format-not-supported',
1950
- * label: 'Unsupported Media Type',
1951
- * detail: 'This API only supports JSON responses',
1952
- * errors: [{ field: 'accept', message: 'XML format is not available' }],
1953
- * });
1954
- */
1955
463
  declare class NotAcceptableException extends HttpProblemException {
1956
464
  constructor(detailOrOptions?: string | ProblemOptions);
1957
465
  }
1958
466
 
1959
- /**
1960
- * Exception thrown when a requested resource cannot be found (HTTP 404).
1961
- *
1962
- * @example
1963
- * // Simple message
1964
- * throw new NotFoundException('Resource not found');
1965
- *
1966
- * // With custom label and detail
1967
- * throw new NotFoundException({
1968
- * label: 'User Not Found',
1969
- * detail: 'The requested user does not exist',
1970
- * });
1971
- *
1972
- * // With field errors
1973
- * throw new NotFoundException({
1974
- * detail: 'The requested resource could not be located',
1975
- * errors: [{ field: 'userId', message: 'User does not exist' }],
1976
- * });
1977
- */
1978
467
  declare class NotFoundException extends HttpProblemException {
1979
468
  constructor(detailOrOptions?: string | ProblemOptions);
1980
469
  }
1981
470
 
1982
- /**
1983
- * Exception thrown when a feature or endpoint is not yet implemented (HTTP 501).
1984
- * Used for planned but unavailable functionality.
1985
- *
1986
- * @example
1987
- * // Simple message
1988
- * throw new NotImplementedException('Feature not yet implemented');
1989
- *
1990
- * // With options
1991
- * throw new NotImplementedException({
1992
- * detail: 'This feature is coming soon',
1993
- * instance: '/api/v1/export',
1994
- * });
1995
- */
1996
471
  declare class NotImplementedException extends HttpProblemException {
1997
472
  constructor(detailOrOptions?: string | ProblemOptions);
1998
473
  }
1999
474
 
2000
- /**
2001
- * Exception thrown when request payload exceeds size limits (HTTP 413).
2002
- * Commonly used for file upload size restrictions or large request bodies.
2003
- *
2004
- * @example
2005
- * // Simple message
2006
- * throw new PayloadTooLargeException('Request payload too large');
2007
- *
2008
- * // With detail
2009
- * throw new PayloadTooLargeException({
2010
- * detail: 'Request payload too large',
2011
- * instance: '/api/upload',
2012
- * });
2013
- *
2014
- * // With custom title
2015
- * throw new PayloadTooLargeException({
2016
- * title: 'File Size Limit Exceeded',
2017
- * detail: 'The uploaded file is too large. Maximum size is 10MB',
2018
- * instance: '/api/files/upload',
2019
- * });
2020
- */
2021
475
  declare class PayloadTooLargeException extends HttpProblemException {
2022
476
  constructor(detailOrOptions?: string | ProblemOptions);
2023
477
  }
2024
478
 
2025
- /**
2026
- * Exception thrown when a request takes too long to process (HTTP 408).
2027
- * Used when the client or server times out while waiting for completion.
2028
- *
2029
- * @example
2030
- * // Simple message
2031
- * throw new RequestTimeoutException('Request timeout');
2032
- *
2033
- * // With custom title and detail
2034
- * throw new RequestTimeoutException({
2035
- * title: 'Operation Timeout',
2036
- * detail: 'The request took too long to complete',
2037
- * });
2038
- *
2039
- * // With instance for tracking
2040
- * throw new RequestTimeoutException({
2041
- * title: 'Database Timeout',
2042
- * detail: 'Query execution exceeded time limit',
2043
- * instance: '/api/queries/123',
2044
- * });
2045
- */
2046
479
  declare class RequestTimeoutException extends HttpProblemException {
2047
480
  constructor(detailOrOptions?: string | ProblemOptions);
2048
481
  }
2049
482
 
2050
- /**
2051
- * Exception thrown when the service is temporarily unavailable (HTTP 503).
2052
- * Used during maintenance, overload, or temporary outages.
2053
- *
2054
- * @example
2055
- * // Simple message
2056
- * throw new ServiceUnavailableException('Service temporarily unavailable');
2057
- *
2058
- * // With custom title and detail
2059
- * throw new ServiceUnavailableException({
2060
- * title: 'Scheduled Maintenance',
2061
- * detail: 'Expected completion: 2 PM EST',
2062
- * });
2063
- *
2064
- * // With field errors
2065
- * throw new ServiceUnavailableException({
2066
- * title: 'External Service Unavailable',
2067
- * detail: 'Payment service is down',
2068
- * errors: [{ field: 'paymentGateway', message: 'Payment gateway unavailable' }],
2069
- * });
2070
- */
2071
483
  declare class ServiceUnavailableException extends HttpProblemException {
2072
484
  constructor(detailOrOptions?: string | ProblemOptions);
2073
485
  }
2074
486
 
2075
- /**
2076
- * Exception thrown when rate limiting is triggered (HTTP 429).
2077
- * Used to prevent abuse and ensure fair resource usage.
2078
- *
2079
- * @example
2080
- * // Simple message
2081
- * throw new TooManyRequestsException('Too many requests');
2082
- *
2083
- * // With custom title and detail
2084
- * throw new TooManyRequestsException({
2085
- * title: 'Rate Limit Exceeded',
2086
- * detail: 'You have exceeded the allowed number of requests',
2087
- * });
2088
- *
2089
- * // With field errors
2090
- * throw new TooManyRequestsException({
2091
- * title: 'API Throttled',
2092
- * detail: 'Too many requests to this endpoint',
2093
- * errors: [{ field: 'requests', message: 'Rate limit exceeded' }],
2094
- * });
2095
- *
2096
- * // With instance and additional metadata
2097
- * throw new TooManyRequestsException({
2098
- * detail: 'Rate limit exceeded',
2099
- * instance: '/api/v1/users',
2100
- * retryAfter: 60,
2101
- * limit: 100,
2102
- * remaining: 0,
2103
- * });
2104
- */
2105
487
  declare class TooManyRequestsException extends HttpProblemException {
2106
488
  constructor(detailOrOptions?: string | ProblemOptions);
2107
489
  }
2108
490
 
2109
- /**
2110
- * Exception thrown when authentication is required or has failed (HTTP 401).
2111
- *
2112
- * @example
2113
- * // Simple message
2114
- * throw new UnauthorizedException('Authentication required');
2115
- *
2116
- * // With problem details
2117
- * throw new UnauthorizedException({
2118
- * detail: 'Invalid or expired token',
2119
- * instance: '/api/auth/verify'
2120
- * });
2121
- */
2122
491
  declare class UnauthorizedException extends HttpProblemException {
2123
492
  constructor(detailOrOptions?: string | ProblemOptions);
2124
493
  }
2125
494
 
2126
- /**
2127
- * Exception thrown when the request is well-formed but contains semantic errors (HTTP 422).
2128
- * Used for business logic validation failures that prevent processing.
2129
- *
2130
- * @example
2131
- * // Simple message
2132
- * throw new UnprocessableEntityException('Cannot process the request');
2133
- *
2134
- * // With detail
2135
- * throw new UnprocessableEntityException({
2136
- * detail: 'Cannot process the order due to stock limitations',
2137
- * });
2138
- *
2139
- * // With custom title
2140
- * throw new UnprocessableEntityException({
2141
- * title: 'Business Rule Violation',
2142
- * detail: 'Cannot process the order due to stock limitations',
2143
- * });
2144
- *
2145
- * // With field errors
2146
- * throw new UnprocessableEntityException({
2147
- * detail: 'One or more items exceed available inventory',
2148
- * errors: [{ field: 'quantity', message: 'Insufficient stock available' }],
2149
- * });
2150
- *
2151
- * // With custom title and field errors
2152
- * throw new UnprocessableEntityException({
2153
- * title: 'Validation Failed',
2154
- * detail: 'One or more items exceed available inventory',
2155
- * errors: [{ field: 'quantity', message: 'Insufficient stock available' }],
2156
- * });
2157
- */
2158
495
  declare class UnprocessableEntityException extends HttpProblemException {
2159
496
  constructor(detailOrOptions?: string | ProblemOptions);
2160
497
  }
2161
498
 
2162
- /**
2163
- * Exception thrown when the media type of the request is not supported (HTTP 415).
2164
- * Used when the Content-Type header specifies an unsupported format.
2165
- *
2166
- * @example
2167
- * // Simple message
2168
- * throw new UnsupportedMediaTypeException('Unsupported media type');
2169
- *
2170
- * // With label and detail
2171
- * throw new UnsupportedMediaTypeException({
2172
- * label: 'Invalid Content Type',
2173
- * detail: 'The content type is not supported',
2174
- * });
2175
- *
2176
- * // With field errors
2177
- * throw new UnsupportedMediaTypeException({
2178
- * label: 'Unsupported File Format',
2179
- * detail: 'Accepted formats: JPEG, PNG, GIF',
2180
- * errors: [{ field: 'file', message: 'PDF format is not accepted for this upload' }],
2181
- * });
2182
- */
2183
499
  declare class UnsupportedMediaTypeException extends HttpProblemException {
2184
500
  constructor(detailOrOptions?: string | ProblemOptions);
2185
501
  }
2186
502
 
2187
- /**
2188
- * Exception thrown when request validation fails (HTTP 400).
2189
- * Typically used for form validation or DTO validation errors.
2190
- *
2191
- * @example
2192
- * // Simple message
2193
- * throw new ValidationException('Validation failed');
2194
- *
2195
- * // With custom label and detail
2196
- * throw new ValidationException({
2197
- * label: 'Invalid Input',
2198
- * detail: 'The provided data is invalid',
2199
- * });
2200
- *
2201
- * // With field-specific errors
2202
- * throw new ValidationException({
2203
- * detail: 'Please correct the highlighted fields',
2204
- * errors: [
2205
- * { field: 'email', message: 'Invalid email format' },
2206
- * { field: 'password', message: 'Password must be at least 8 characters' }
2207
- * ],
2208
- * });
2209
- *
2210
- * // With custom label and field errors
2211
- * throw new ValidationException({
2212
- * label: 'Form Validation Failed',
2213
- * detail: 'Please correct the highlighted fields',
2214
- * errors: [
2215
- * { field: 'email', message: 'Invalid email format' },
2216
- * { field: 'password', message: 'Password too weak' }
2217
- * ],
2218
- * });
2219
- */
2220
503
  declare class ValidationException extends HttpProblemException {
2221
504
  constructor(detailOrOptions?: string | ProblemOptions);
2222
505
  }
2223
506
 
2224
- /**
2225
- * Converts an HTTP status code to its corresponding title string.
2226
- * Uses the HttpStatus enum to map status codes to human-readable titles.
2227
- *
2228
- * @param status - The HTTP status code
2229
- * @returns The human-readable title for the status code
2230
- *
2231
- * @example
2232
- * getHttpStatusTitle(400) // Returns: "Bad Request"
2233
- * getHttpStatusTitle(404) // Returns: "Not Found"
2234
- * getHttpStatusTitle(500) // Returns: "Internal Server Error"
2235
- */
2236
507
  declare function getHttpStatusTitle(status: number): string;
2237
- /**
2238
- * Global HTTP Exception Filter implementing RFC 9457 Problem Details
2239
- *
2240
- * Transforms all exceptions into a standardized RFC 9457 format:
2241
- * {
2242
- * type: string, // Problem type URI (default: "about:blank")
2243
- * title: string, // HTTP status phrase (e.g., "Unauthorized")
2244
- * status: number, // HTTP status code
2245
- * label?: string, // Root error heading (maps to AlertTitle)
2246
- * detail: string, // Root error description (maps to AlertDescription)
2247
- * instance: string, // Request path
2248
- * errors: FieldError[] // Field-specific errors (field is required)
2249
- * }
2250
- *
2251
- * Handles:
2252
- * - Custom HttpProblemException from @vritti/api-sdk
2253
- * - Class-validator DTO validation errors
2254
- * - Standard NestJS HTTP exceptions
2255
- * - Unknown errors
2256
- */
2257
508
  declare class HttpExceptionFilter implements ExceptionFilter {
2258
509
  private readonly logger;
2259
510
  catch(exception: unknown, host: ArgumentsHost): void;
2260
511
  }
2261
512
 
2262
- declare const SKIP_CSRF_KEY = "skipCsrf";
2263
- /**
2264
- * Decorator to skip CSRF validation for specific routes or controllers.
2265
- * Use this for webhook endpoints that receive requests from external services
2266
- * (e.g., WhatsApp, Twilio) which cannot include CSRF tokens.
2267
- *
2268
- * @example
2269
- * // Skip CSRF for entire controller
2270
- * @Controller('webhooks')
2271
- * @SkipCsrf()
2272
- * export class WebhookController { ... }
2273
- *
2274
- * @example
2275
- * // Skip CSRF for specific route
2276
- * @Post()
2277
- * @SkipCsrf()
2278
- * async handleWebhook() { ... }
2279
- */
2280
- declare const SkipCsrf: () => _nestjs_common.CustomDecorator<string>;
2281
-
2282
- /**
2283
- * Extract ISO country code from E.164 phone number
2284
- * @param phone Phone number in E.164 format (e.g., +919876543210)
2285
- * @returns ISO 3166-1 alpha-2 country code (e.g., "IN") or undefined
2286
- */
2287
- declare function extractCountryFromPhone(phone: string): string | undefined;
2288
- /**
2289
- * Normalize phone number to E.164 format with + prefix
2290
- * @param phone Phone number (with or without + prefix)
2291
- * @returns Phone number in E.164 format
2292
- */
2293
- declare function normalizePhoneNumber(phone: string): string;
2294
-
2295
- /**
2296
- * Supported log levels for the logging system.
2297
- */
2298
513
  type LogLevel = 'error' | 'warn' | 'log' | 'debug' | 'verbose';
2299
- /**
2300
- * Supported log output formats.
2301
- */
2302
514
  type LogFormat = 'json' | 'text';
2303
- /**
2304
- * Metadata that can be attached to log entries.
2305
- */
2306
515
  interface LogMetadata {
2307
516
  correlationId?: string;
2308
517
  method?: string;
@@ -2313,9 +522,6 @@ interface LogMetadata {
2313
522
  userAgent?: string;
2314
523
  [key: string]: unknown;
2315
524
  }
2316
- /**
2317
- * Configuration options for the logger module.
2318
- */
2319
525
  interface LoggerModuleOptions {
2320
526
  provider?: 'default' | 'winston';
2321
527
  level?: LogLevel;
@@ -2330,31 +536,19 @@ interface LoggerModuleOptions {
2330
536
  environment?: string;
2331
537
  defaultMeta?: Record<string, unknown>;
2332
538
  }
2333
- /**
2334
- * Factory function for creating logger options asynchronously.
2335
- */
2336
539
  interface LoggerOptionsFactory {
2337
540
  createLoggerOptions(): Promise<LoggerModuleOptions> | LoggerModuleOptions;
2338
541
  }
2339
- /**
2340
- * Async configuration options for the logger module.
2341
- */
2342
542
  interface LoggerModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
2343
543
  useExisting?: Type<LoggerOptionsFactory>;
2344
544
  useClass?: Type<LoggerOptionsFactory>;
2345
545
  useFactory?: (...args: unknown[]) => Promise<LoggerModuleOptions> | LoggerModuleOptions;
2346
546
  inject?: unknown[];
2347
547
  }
2348
- /**
2349
- * Context object for correlation tracking across async operations.
2350
- */
2351
548
  interface CorrelationContext {
2352
549
  correlationId: string;
2353
550
  [key: string]: unknown;
2354
551
  }
2355
- /**
2356
- * Configuration options for HTTP request/response logger interceptor.
2357
- */
2358
552
  interface HttpLoggerOptions {
2359
553
  enableRequestLog?: boolean;
2360
554
  enableResponseLog?: boolean;
@@ -2366,331 +560,74 @@ interface HttpLoggerOptions {
2366
560
  maxBodySize?: number;
2367
561
  }
2368
562
 
2369
- /**
2370
- * Unified Logger Service
2371
- *
2372
- * Single service that provides both default NestJS Logger and Winston logger implementations.
2373
- * Automatically delegates to the configured provider (default or winston).
2374
- * @module logger/logger.service
2375
- */
2376
-
2377
- /**
2378
- * Unified logger service implementing NestJS LoggerService interface.
2379
- * Supports both default NestJS Logger and Winston implementations via facade pattern.
2380
- */
563
+ type LogMessage = string | Error | object;
2381
564
  declare class LoggerService implements LoggerService$1 {
2382
565
  private readonly defaultLogger?;
2383
566
  private readonly activeLogger;
2384
567
  private readonly options;
2385
568
  private context?;
2386
569
  constructor(options?: LoggerModuleOptions, defaultLogger?: Logger | undefined);
2387
- /**
2388
- * Creates a Winston logger instance with inline configuration.
2389
- * Consolidates winston-config.factory.ts logic.
2390
- */
2391
570
  private createWinstonLogger;
2392
- log(message: any, context?: string): void;
2393
- error(message: any, trace?: string, context?: string): void;
2394
- warn(message: any, context?: string): void;
2395
- debug(message: any, context?: string): void;
2396
- 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;
2397
576
  setContext(context: string): void;
2398
- /**
2399
- * Unified internal logging method that handles both Winston and NestJS Logger.
2400
- */
2401
577
  private _log;
2402
- /**
2403
- * Logs with custom metadata (Winston only).
2404
- */
2405
- logWithMetadata(level: LogLevel, message: any, metadata?: LogMetadata, context?: string): void;
578
+ logWithMetadata(level: LogLevel, message: LogMessage, metadata?: LogMetadata, context?: string): void;
2406
579
  private formatMessage;
2407
- /**
2408
- * Enriches metadata with correlation context from AsyncLocalStorage.
2409
- * Inline from winston-logger.service.ts
2410
- */
2411
580
  private enrichMetadata;
2412
581
  child(context: string): LoggerService;
2413
582
  }
2414
583
 
2415
- /**
2416
- * HTTP Logger Interceptor
2417
- *
2418
- * Automatically logs HTTP requests and responses with correlation tracking.
2419
- * @module logger/http-logger.interceptor
2420
- */
2421
-
2422
- /**
2423
- * HTTP Logger Interceptor for NestJS applications.
2424
- *
2425
- * Logs all HTTP requests and responses with metadata including
2426
- * correlation IDs, performance metrics, and error details.
2427
- */
2428
584
  declare class HttpLoggerInterceptor implements NestInterceptor {
2429
585
  private readonly logger;
2430
586
  private readonly enableRequestLog;
2431
587
  private readonly enableResponseLog;
2432
588
  private readonly slowRequestThreshold;
2433
589
  constructor(logger: LoggerService, options?: HttpLoggerOptions);
2434
- intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
590
+ intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
2435
591
  private logRequest;
2436
592
  private logResponse;
2437
593
  private logError;
2438
594
  }
2439
595
 
2440
- /**
2441
- * Logger Module
2442
- *
2443
- * Dynamic NestJS module providing unified logging infrastructure with:
2444
- * - Environment presets (development, staging, production, test)
2445
- * - Transparent switching between default NestJS Logger and Winston
2446
- * - Correlation ID tracking via middleware
2447
- * - HTTP request/response logging via interceptor
2448
- * - PII masking and file logging support
2449
- *
2450
- * @module logger/logger.module
2451
- */
2452
-
2453
- /**
2454
- * Dependency injection token for logger module options
2455
- */
2456
596
  declare const LOGGER_MODULE_OPTIONS: unique symbol;
2457
- /**
2458
- * Global logger module providing unified logging infrastructure.
2459
- *
2460
- * Features:
2461
- * - Environment presets (development, staging, production, test)
2462
- * - Single `LoggerService` interface for all logging needs
2463
- * - Transparent provider switching (default ↔ Winston)
2464
- * - Correlation ID tracking across async operations
2465
- * - HTTP request/response logging
2466
- * - PII masking for GDPR compliance
2467
- * - File-based logging with rotation
2468
- *
2469
- * @example
2470
- * ```typescript
2471
- * // Production environment with explicit config
2472
- * @Module({
2473
- * imports: [
2474
- * LoggerModule.forRoot({
2475
- * environment: 'production',
2476
- * appName: 'my-service'
2477
- * })
2478
- * ],
2479
- * })
2480
- * export class AppModule {}
2481
- *
2482
- * // Development environment with custom override
2483
- * @Module({
2484
- * imports: [
2485
- * LoggerModule.forRoot({
2486
- * environment: 'development',
2487
- * level: 'verbose' // Override preset's debug
2488
- * })
2489
- * ],
2490
- * })
2491
- * export class AppModule {}
2492
- *
2493
- * // Use default NestJS logger
2494
- * @Module({
2495
- * imports: [
2496
- * LoggerModule.forRoot({
2497
- * provider: 'default',
2498
- * environment: 'development'
2499
- * })
2500
- * ],
2501
- * })
2502
- * export class AppModule {}
2503
- *
2504
- * // Dynamic configuration with ConfigService
2505
- * @Module({
2506
- * imports: [
2507
- * LoggerModule.forRootAsync({
2508
- * imports: [ConfigModule],
2509
- * useFactory: (config: ConfigService) => ({
2510
- * environment: config.get('NODE_ENV', 'development'),
2511
- * provider: config.get('LOG_PROVIDER', 'winston'),
2512
- * appName: config.get('APP_NAME')
2513
- * }),
2514
- * inject: [ConfigService]
2515
- * })
2516
- * ],
2517
- * })
2518
- * export class AppModule {}
2519
- * ```
2520
- */
2521
597
  declare class LoggerModule implements NestModule {
2522
- /**
2523
- * Configures the logger module with static options.
2524
- *
2525
- * Users must explicitly pass `environment` to select a preset.
2526
- * All preset values can be overridden by passing explicit options.
2527
- *
2528
- * @param options - Logger configuration options
2529
- * @returns Dynamic module configuration
2530
- *
2531
- * @example
2532
- * ```typescript
2533
- * // Production preset with app name
2534
- * LoggerModule.forRoot({
2535
- * environment: 'production',
2536
- * appName: 'my-service'
2537
- * })
2538
- *
2539
- * // Development preset with custom level
2540
- * LoggerModule.forRoot({
2541
- * environment: 'development',
2542
- * level: 'verbose',
2543
- * enableFileLogger: true
2544
- * })
2545
- *
2546
- * // Use default NestJS logger
2547
- * LoggerModule.forRoot({
2548
- * provider: 'default',
2549
- * environment: 'development'
2550
- * })
2551
- * ```
2552
- */
2553
598
  static forRoot(options?: LoggerModuleOptions): DynamicModule;
2554
- /**
2555
- * Configures the logger module with async options.
2556
- *
2557
- * Supports dynamic configuration using:
2558
- * - `useFactory`: Factory function with dependency injection
2559
- * - `useClass`: Class implementing `LoggerOptionsFactory`
2560
- * - `useExisting`: Existing provider implementing `LoggerOptionsFactory`
2561
- *
2562
- * Options from the factory/class are merged with environment preset defaults.
2563
- *
2564
- * @param options - Async configuration options
2565
- * @returns Dynamic module configuration
2566
- *
2567
- * @example
2568
- * ```typescript
2569
- * // Factory with ConfigService
2570
- * LoggerModule.forRootAsync({
2571
- * imports: [ConfigModule],
2572
- * useFactory: (config: ConfigService) => ({
2573
- * environment: config.get('NODE_ENV', 'development'),
2574
- * provider: config.get('LOG_PROVIDER', 'winston'),
2575
- * level: config.get('LOG_LEVEL'),
2576
- * appName: config.get('APP_NAME'),
2577
- * }),
2578
- * inject: [ConfigService]
2579
- * })
2580
- *
2581
- * // Factory class
2582
- * @Injectable()
2583
- * class LoggerConfigService implements LoggerOptionsFactory {
2584
- * createLoggerOptions(): LoggerModuleOptions {
2585
- * return {
2586
- * environment: 'production',
2587
- * appName: 'my-service'
2588
- * };
2589
- * }
2590
- * }
2591
- *
2592
- * LoggerModule.forRootAsync({
2593
- * useClass: LoggerConfigService
2594
- * })
2595
- * ```
2596
- */
2597
599
  static forRootAsync(options: LoggerModuleAsyncOptions): DynamicModule;
2598
- /**
2599
- * Configures middleware for the module.
2600
- * Middleware is registered globally in main.ts using Fastify hooks.
2601
- */
2602
600
  configure(_consumer: MiddlewareConsumer): void;
2603
- /**
2604
- * Creates async providers for dynamic module configuration.
2605
- */
2606
601
  private static createAsyncProviders;
2607
- /**
2608
- * Creates the async options provider.
2609
- */
2610
602
  private static createAsyncOptionsProvider;
2611
603
  }
2612
604
 
2613
- /**
2614
- * Correlation ID Middleware
2615
- *
2616
- * Generates unique correlation IDs for request tracking across async operations.
2617
- * Stores correlation ID in AsyncLocalStorage for access throughout the request lifecycle.
2618
- * @module logger/correlation-id.middleware
2619
- */
2620
-
2621
- /**
2622
- * Configuration options for the Correlation ID middleware.
2623
- */
2624
605
  interface CorrelationIdMiddlewareOptions {
2625
- /**
2626
- * If true, adds the correlation ID to response headers.
2627
- * @default true
2628
- */
2629
606
  includeInResponse?: boolean;
2630
- /**
2631
- * The header name to use when adding correlation ID to response.
2632
- * @default 'x-correlation-id'
2633
- */
2634
607
  responseHeader?: string;
2635
608
  }
2636
- /**
2637
- * Correlation ID Middleware for Fastify/NestJS applications.
2638
- *
2639
- * Generates a unique correlation ID for each request,
2640
- * stores it in AsyncLocalStorage for access throughout the request lifecycle,
2641
- * and optionally adds it to response headers.
2642
- */
2643
609
  declare class CorrelationIdMiddleware implements NestMiddleware {
2644
610
  private readonly includeInResponse;
2645
611
  private readonly responseHeader;
2646
612
  constructor(options?: CorrelationIdMiddlewareOptions);
2647
- /**
2648
- * Middleware handler for processing requests.
2649
- */
2650
613
  use(_req: FastifyRequest, reply: FastifyReply, next: () => void): void;
2651
- /**
2652
- * Fastify hook handler for onRequest.
2653
- * This is an async function that returns a Promise, ensuring the AsyncLocalStorage
2654
- * context persists throughout the entire request lifecycle.
2655
- */
2656
614
  onRequest(_req: FastifyRequest, reply: FastifyReply): Promise<void>;
2657
615
  }
2658
616
 
2659
- /**
2660
- * Logging Utilities
2661
- *
2662
- * Consolidated utilities for correlation tracking, PII masking, and async context management.
2663
- * @module logging/utils
2664
- */
2665
-
2666
- /**
2667
- * Async local storage for correlation context tracking across async operations.
2668
- */
2669
617
  declare const correlationStorage: AsyncLocalStorage<CorrelationContext>;
2670
- /**
2671
- * Gets the current correlation context from async local storage.
2672
- */
2673
618
  declare function getCorrelationContext(): CorrelationContext | undefined;
2674
- /**
2675
- * Runs a callback within a correlation context.
2676
- */
2677
619
  declare function runWithCorrelationContext<T>(context: CorrelationContext, callback: () => T): T;
2678
- /**
2679
- * Updates the current correlation context with new values.
2680
- */
2681
620
  declare function updateCorrelationContext(updates: Partial<CorrelationContext>): void;
2682
- /**
2683
- * Default header name for setting correlation ID in responses.
2684
- */
2685
621
  declare const DEFAULT_CORRELATION_HEADER = "x-correlation-id";
2686
- /**
2687
- * Generates a new correlation ID using UUID v4.
2688
- * Always creates a fresh ID for each request.
2689
- */
2690
622
  declare function generateCorrelationId(): string;
2691
- /**
2692
- * Adds correlation ID to Fastify response headers.
2693
- */
2694
623
  declare function addCorrelationIdToResponse(reply: FastifyReply, correlationId: string, headerName?: string): void;
2695
624
 
2696
- export { type ApiErrorResponse, type ApiSdkConfig, AuthConfigModule, BadGatewayException, BadRequestException, ConflictException, type CookieConfig, type CorrelationContext, CorrelationIdMiddleware, DEFAULT_CORRELATION_HEADER, DatabaseModule, type DatabaseModuleOptions, type FieldError, ForbiddenException, GoneException, type GuardConfig, HttpExceptionFilter, HttpLoggerInterceptor, type HttpLoggerOptions, HttpProblemException, 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, type ProblemOptions, 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 };