@vritti/api-sdk 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1390 -1328
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1066 -892
- package/dist/index.d.ts +1066 -892
- package/dist/index.js +1645 -1591
- package/dist/index.js.map +1 -1
- package/package.json +9 -15
package/dist/index.d.cts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as _nestjs_common from '@nestjs/common';
|
|
2
|
-
import { DynamicModule,
|
|
3
|
-
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
|
4
|
-
import { InferInsertModel, InferSelectModel, SQL } from 'drizzle-orm';
|
|
5
|
-
import { PgTable } from 'drizzle-orm/pg-core';
|
|
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';
|
|
6
3
|
import { ConfigService } from '@nestjs/config';
|
|
7
4
|
import { Reflector } from '@nestjs/core';
|
|
8
5
|
import { JwtService } from '@nestjs/jwt';
|
|
6
|
+
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
|
9
7
|
import { FastifyRequest, FastifyReply } from 'fastify';
|
|
8
|
+
import { InferInsertModel, InferSelectModel, SQL } from 'drizzle-orm';
|
|
9
|
+
import { PgTable } from 'drizzle-orm/pg-core';
|
|
10
10
|
import { Observable } from 'rxjs';
|
|
11
11
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
12
12
|
|
|
@@ -84,6 +84,78 @@ declare class AuthConfigModule {
|
|
|
84
84
|
static forRootAsync(): DynamicModule;
|
|
85
85
|
}
|
|
86
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
|
+
|
|
87
159
|
/**
|
|
88
160
|
* Schema Registry Interface
|
|
89
161
|
*
|
|
@@ -98,8 +170,7 @@ declare class AuthConfigModule {
|
|
|
98
170
|
* }
|
|
99
171
|
* }
|
|
100
172
|
*/
|
|
101
|
-
|
|
102
|
-
}
|
|
173
|
+
type SchemaRegistry = {};
|
|
103
174
|
/**
|
|
104
175
|
* Extracts the registered schema type.
|
|
105
176
|
* Falls back to Record<string, unknown> if no schema is registered.
|
|
@@ -209,358 +280,554 @@ interface TenantInfo {
|
|
|
209
280
|
}
|
|
210
281
|
|
|
211
282
|
/**
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
* This module provides:
|
|
215
|
-
* - Tenant context management (request-scoped)
|
|
216
|
-
* - Database connection pooling
|
|
217
|
-
* - Dynamic schema/cluster routing
|
|
218
|
-
* - Support for both gateway and microservice modes
|
|
219
|
-
*
|
|
220
|
-
* ## Gateway Mode (API Gateway)
|
|
221
|
-
* - Use DatabaseModule.forServer() method
|
|
222
|
-
* - Automatically extracts tenant from subdomain, falls back to x-tenant-id header
|
|
223
|
-
* - Provide primaryDb configuration and prismaClientConstructor
|
|
224
|
-
* - Automatically queries primary DB for tenant config
|
|
225
|
-
* - Automatically registers TenantContextInterceptor globally
|
|
226
|
-
* - No manual interceptor registration needed
|
|
227
|
-
*
|
|
228
|
-
* ## Microservice Mode (RabbitMQ Workers)
|
|
229
|
-
* - Use DatabaseModule.forMicroservice() method
|
|
230
|
-
* - Only provide prismaClientConstructor
|
|
231
|
-
* - Tenant context comes from RabbitMQ messages
|
|
232
|
-
* - Automatically registers MessageTenantContextInterceptor globally
|
|
233
|
-
* - No manual interceptor registration needed
|
|
283
|
+
* Service responsible for querying the primary database to resolve tenant configurations
|
|
234
284
|
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
* primaryDb: {
|
|
241
|
-
* host: config.get('PRIMARY_DB_HOST'),
|
|
242
|
-
* port: config.get('PRIMARY_DB_PORT'),
|
|
243
|
-
* username: config.get('PRIMARY_DB_USERNAME'),
|
|
244
|
-
* password: config.get('PRIMARY_DB_PASSWORD'),
|
|
245
|
-
* database: config.get('PRIMARY_DB_DATABASE'),
|
|
246
|
-
* },
|
|
247
|
-
* prismaClientConstructor: PrismaClient,
|
|
248
|
-
* }),
|
|
249
|
-
* })
|
|
285
|
+
* This service:
|
|
286
|
+
* - Connects to the primary database (tenant registry)
|
|
287
|
+
* - Queries tenant metadata (database location, credentials, etc.)
|
|
288
|
+
* - Caches tenant configs in memory to reduce database load
|
|
289
|
+
* - Only used in GATEWAY MODE (microservices receive tenant config from messages)
|
|
250
290
|
*
|
|
251
291
|
* @example
|
|
252
|
-
* //
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
* useFactory: (config: ConfigService) => ({
|
|
256
|
-
* prismaClientConstructor: PrismaClient,
|
|
257
|
-
* }),
|
|
258
|
-
* })
|
|
292
|
+
* // In API Gateway
|
|
293
|
+
* const config = await primaryDatabase.getTenantConfig('acme');
|
|
294
|
+
* // Returns: { id, slug, type, databaseHost, databaseName, ... }
|
|
259
295
|
*/
|
|
260
|
-
declare class
|
|
296
|
+
declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
|
|
297
|
+
private readonly options;
|
|
298
|
+
private readonly logger;
|
|
299
|
+
/** PostgreSQL connection pool */
|
|
300
|
+
private pool;
|
|
301
|
+
/** Drizzle database instance */
|
|
302
|
+
private db;
|
|
303
|
+
/** In-memory cache: Map<tenantIdentifier, TenantConfig> */
|
|
304
|
+
private readonly tenantConfigCache;
|
|
305
|
+
/** Cache TTL in milliseconds */
|
|
306
|
+
private readonly cacheTTL;
|
|
307
|
+
constructor(options: DatabaseModuleOptions);
|
|
308
|
+
onModuleInit(): Promise<void>;
|
|
261
309
|
/**
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
* This mode is for API Gateways that handle HTTP requests:
|
|
265
|
-
* - Automatically registers TenantContextInterceptor
|
|
266
|
-
* - Extracts tenant from subdomain or x-tenant-id header
|
|
267
|
-
* - Queries primary database for tenant configuration
|
|
268
|
-
* - Provides PrimaryDatabaseService for tenant lookup
|
|
269
|
-
*
|
|
270
|
-
* @param options Async configuration options
|
|
271
|
-
* @returns Dynamic module configuration with HTTP interceptor
|
|
272
|
-
*
|
|
273
|
-
* @example
|
|
274
|
-
* DatabaseModule.forServer({
|
|
275
|
-
* inject: [ConfigService],
|
|
276
|
-
* useFactory: (config: ConfigService) => ({
|
|
277
|
-
* primaryDb: {
|
|
278
|
-
* host: config.get('PRIMARY_DB_HOST'),
|
|
279
|
-
* port: config.get('PRIMARY_DB_PORT'),
|
|
280
|
-
* username: config.get('PRIMARY_DB_USERNAME'),
|
|
281
|
-
* password: config.get('PRIMARY_DB_PASSWORD'),
|
|
282
|
-
* database: config.get('PRIMARY_DB_DATABASE'),
|
|
283
|
-
* },
|
|
284
|
-
* prismaClientConstructor: PrismaClient,
|
|
285
|
-
* }),
|
|
286
|
-
* })
|
|
310
|
+
* Initialize connection to primary database using Drizzle
|
|
287
311
|
*/
|
|
288
|
-
|
|
289
|
-
useFactory: (...args: any[]) => Promise<DatabaseModuleOptions> | DatabaseModuleOptions;
|
|
290
|
-
inject?: any[];
|
|
291
|
-
}): DynamicModule;
|
|
312
|
+
private initializeDrizzleClient;
|
|
292
313
|
/**
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
* This mode is for microservices that process messages from queues:
|
|
296
|
-
* - Automatically registers MessageTenantContextInterceptor
|
|
297
|
-
* - Extracts tenant from RabbitMQ message patterns
|
|
298
|
-
* - No primary database needed (tenant comes from message context)
|
|
299
|
-
*
|
|
300
|
-
* @param options Async configuration options
|
|
301
|
-
* @returns Dynamic module configuration with message interceptor
|
|
302
|
-
*
|
|
303
|
-
* @example
|
|
304
|
-
* DatabaseModule.forMicroservice({
|
|
305
|
-
* inject: [ConfigService],
|
|
306
|
-
* useFactory: (config: ConfigService) => ({
|
|
307
|
-
* prismaClientConstructor: PrismaClient,
|
|
308
|
-
* }),
|
|
309
|
-
* })
|
|
314
|
+
* Build connection URL from primary database properties
|
|
310
315
|
*/
|
|
311
|
-
|
|
312
|
-
useFactory: (...args: any[]) => Promise<DatabaseModuleOptions> | DatabaseModuleOptions;
|
|
313
|
-
inject?: any[];
|
|
314
|
-
}): DynamicModule;
|
|
316
|
+
private buildPrimaryDbUrl;
|
|
315
317
|
/**
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
* @param options Configuration options
|
|
319
|
-
* @param mode Mode of operation (gateway or microservice)
|
|
320
|
-
* @returns Dynamic module configuration
|
|
318
|
+
* Mask password in connection URL for logging
|
|
321
319
|
*/
|
|
322
|
-
private
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Request-scoped service that holds tenant context for the current request or RabbitMQ message
|
|
327
|
-
*
|
|
328
|
-
* IMPORTANT: This service is REQUEST-SCOPED, meaning NestJS creates a new instance
|
|
329
|
-
* for each HTTP request or RabbitMQ message. This ensures tenant isolation and
|
|
330
|
-
* prevents cross-tenant data leaks in concurrent scenarios.
|
|
331
|
-
*
|
|
332
|
-
* @example
|
|
333
|
-
* // In a controller or service
|
|
334
|
-
* constructor(private readonly tenantContext: TenantContextService) {}
|
|
335
|
-
*
|
|
336
|
-
* async handleRequest() {
|
|
337
|
-
* const tenant = this.tenantContext.getTenant();
|
|
338
|
-
* console.log(`Processing request for tenant: ${tenant.tenantSlug}`);
|
|
339
|
-
* }
|
|
340
|
-
*/
|
|
341
|
-
declare class TenantContextService {
|
|
342
|
-
private tenantInfo;
|
|
320
|
+
private maskPassword;
|
|
343
321
|
/**
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
* This is typically called by:
|
|
347
|
-
* - TenantContextInterceptor (for HTTP requests in gateway)
|
|
348
|
-
* - MessageTenantContextInterceptor (for RabbitMQ messages in microservices)
|
|
349
|
-
* - Manual context setup in message handlers
|
|
322
|
+
* Get tenant configuration by identifier (ID or subdomain)
|
|
350
323
|
*
|
|
351
|
-
* @param
|
|
352
|
-
* @
|
|
324
|
+
* @param tenantIdentifier Tenant ID or subdomain
|
|
325
|
+
* @returns Tenant configuration or null if not found
|
|
353
326
|
*/
|
|
354
|
-
|
|
327
|
+
getTenantInfo(tenantIdentifier: string): Promise<TenantInfo | null>;
|
|
355
328
|
/**
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
* @returns Tenant information
|
|
359
|
-
* @throws UnauthorizedException if tenant context hasn't been set
|
|
329
|
+
* Cache tenant information with TTL
|
|
360
330
|
*/
|
|
361
|
-
|
|
331
|
+
private cacheInfo;
|
|
362
332
|
/**
|
|
363
|
-
*
|
|
333
|
+
* Clear cached tenant information
|
|
364
334
|
*
|
|
365
|
-
*
|
|
335
|
+
* Useful when tenant settings are updated and cache needs to be invalidated
|
|
336
|
+
*
|
|
337
|
+
* @param tenantIdentifier Tenant ID or subdomain
|
|
366
338
|
*/
|
|
367
|
-
|
|
339
|
+
clearTenantCache(tenantIdentifier: string): void;
|
|
368
340
|
/**
|
|
369
|
-
* Clear tenant
|
|
370
|
-
*
|
|
371
|
-
* This is useful for cleanup in RabbitMQ message handlers
|
|
372
|
-
* after the message has been processed.
|
|
373
|
-
*
|
|
374
|
-
* HTTP requests don't need manual cleanup as the service
|
|
375
|
-
* instance is destroyed when the request ends.
|
|
376
|
-
*/
|
|
377
|
-
clearTenant(): void;
|
|
378
|
-
/**
|
|
379
|
-
* Get tenant ID safely (returns null if not set)
|
|
380
|
-
*
|
|
381
|
-
* @returns Tenant ID or null
|
|
382
|
-
*/
|
|
383
|
-
getTenantIdSafe(): string | null;
|
|
384
|
-
/**
|
|
385
|
-
* Get tenant subdomain safely (returns null if not set)
|
|
386
|
-
*
|
|
387
|
-
* @returns Tenant subdomain or null
|
|
341
|
+
* Clear all cached tenant configurations
|
|
388
342
|
*/
|
|
389
|
-
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
/**
|
|
393
|
-
* Service responsible for managing tenant-scoped database connections
|
|
394
|
-
*
|
|
395
|
-
* This service:
|
|
396
|
-
* - Maintains a connection pool (Map<cacheKey, TenantConnection>)
|
|
397
|
-
* - Creates new connections dynamically based on tenant context
|
|
398
|
-
* - Reuses existing connections for the same tenant
|
|
399
|
-
* - Supports both cloud schemas and enterprise databases
|
|
400
|
-
* - Automatically cleans up idle connections
|
|
401
|
-
*
|
|
402
|
-
* @example
|
|
403
|
-
* // In a controller or service
|
|
404
|
-
* const db = this.tenantDatabase.drizzleClient;
|
|
405
|
-
* const users = await db.select().from(usersTable);
|
|
406
|
-
*/
|
|
407
|
-
declare class TenantDatabaseService implements OnModuleDestroy {
|
|
408
|
-
private readonly options;
|
|
409
|
-
private readonly tenantContext;
|
|
410
|
-
private readonly logger;
|
|
411
|
-
/** Connection pool: Map<cacheKey, TenantConnection> */
|
|
412
|
-
private readonly clients;
|
|
413
|
-
/** Track last usage time for idle connection cleanup */
|
|
414
|
-
private readonly clientLastUsed;
|
|
415
|
-
/** Cleanup interval timer */
|
|
416
|
-
private cleanupInterval?;
|
|
417
|
-
constructor(options: DatabaseModuleOptions, tenantContext: TenantContextService);
|
|
343
|
+
clearAllCaches(): void;
|
|
418
344
|
/**
|
|
419
|
-
* Get the Drizzle
|
|
420
|
-
* This returns the
|
|
345
|
+
* Get the Drizzle database instance for the primary database.
|
|
346
|
+
* This is a synchronous property that returns the initialized Drizzle client.
|
|
421
347
|
*
|
|
422
|
-
* @returns
|
|
423
|
-
* @throws
|
|
424
|
-
* @throws InternalServerErrorException if connection fails
|
|
348
|
+
* @returns Primary database Drizzle instance
|
|
349
|
+
* @throws Error if primary database client is not initialized
|
|
425
350
|
*/
|
|
426
351
|
get drizzleClient(): TypedDrizzleClient;
|
|
427
352
|
/**
|
|
428
353
|
* Get the Drizzle schema
|
|
429
354
|
*/
|
|
430
|
-
get schema():
|
|
355
|
+
get schema(): typeof this$1.options.drizzleSchema;
|
|
431
356
|
/**
|
|
432
|
-
*
|
|
357
|
+
* Decrypt database credentials
|
|
433
358
|
*
|
|
434
|
-
*
|
|
435
|
-
* 1. Gets tenant info from TenantContextService
|
|
436
|
-
* 2. Builds a connection URL based on tenant type
|
|
437
|
-
* 3. Returns cached client if exists, otherwise creates new one
|
|
359
|
+
* Override this method to implement your encryption strategy
|
|
438
360
|
*
|
|
439
|
-
* @
|
|
440
|
-
* @
|
|
441
|
-
* @throws InternalServerErrorException if connection fails
|
|
361
|
+
* @param encrypted Encrypted value
|
|
362
|
+
* @returns Decrypted value
|
|
442
363
|
*/
|
|
443
|
-
private
|
|
364
|
+
private decrypt;
|
|
365
|
+
onModuleDestroy(): Promise<void>;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
declare class RequestService {
|
|
369
|
+
private readonly request;
|
|
370
|
+
constructor(request: FastifyRequest);
|
|
444
371
|
/**
|
|
445
|
-
*
|
|
372
|
+
* Extract tenant identifier from request headers
|
|
373
|
+
* Priority: x-tenant-id > x-subdomain
|
|
374
|
+
* @returns Tenant identifier or null if not found
|
|
446
375
|
*/
|
|
447
|
-
|
|
376
|
+
getTenantIdentifier(): string | null;
|
|
448
377
|
/**
|
|
449
|
-
*
|
|
378
|
+
* Extract access token from Authorization header
|
|
379
|
+
* Format: "Bearer <token>"
|
|
380
|
+
* @returns Access token or null if not found
|
|
450
381
|
*/
|
|
451
|
-
|
|
382
|
+
getAccessToken(): string | null;
|
|
452
383
|
/**
|
|
453
|
-
*
|
|
384
|
+
* Extract refresh token from httpOnly cookie
|
|
385
|
+
* Cookie name is configurable via api-sdk config
|
|
386
|
+
* @returns Refresh token or null if not found
|
|
454
387
|
*/
|
|
455
|
-
|
|
388
|
+
getRefreshToken(): string | null;
|
|
456
389
|
/**
|
|
457
|
-
*
|
|
390
|
+
* Get a specific header value
|
|
391
|
+
* @param key Header key
|
|
392
|
+
* @returns Header value (string, array, or undefined)
|
|
458
393
|
*/
|
|
459
|
-
|
|
394
|
+
getHeader(key: string): string | string[] | undefined;
|
|
460
395
|
/**
|
|
461
|
-
*
|
|
396
|
+
* Get all headers
|
|
397
|
+
* @returns Record of all headers
|
|
462
398
|
*/
|
|
463
|
-
|
|
399
|
+
getAllHeaders(): FastifyRequest['headers'];
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Vritti Authentication Guard - Validates JWT access tokens and tenant context
|
|
404
|
+
*
|
|
405
|
+
* This guard performs access token validation and attaches user data to request.
|
|
406
|
+
* NOTE: Refresh tokens are NOT validated here - they are only validated in
|
|
407
|
+
* /auth/token and /auth/refresh endpoints (session.service.ts).
|
|
408
|
+
*
|
|
409
|
+
* Validation Flow:
|
|
410
|
+
* 1. Checks if endpoint is marked with @Public() decorator → skip all validation
|
|
411
|
+
* 2. Checks if endpoint is marked with @Onboarding() decorator:
|
|
412
|
+
* - Requires token type='onboarding'
|
|
413
|
+
* - Validates JWT signature and expiry only
|
|
414
|
+
* - Skips tenant validation
|
|
415
|
+
* - Attaches user data to request.user
|
|
416
|
+
* 3. For regular endpoints (no decorator):
|
|
417
|
+
* - Rejects tokens with type='onboarding'
|
|
418
|
+
* - Validates access token (JWT signature, expiry, nbf)
|
|
419
|
+
* - Validates tenant exists and is ACTIVE
|
|
420
|
+
* - Attaches user data to request.user
|
|
421
|
+
*
|
|
422
|
+
* Token Format:
|
|
423
|
+
* - Access Token: "Authorization: Bearer <jwt_token>"
|
|
424
|
+
*
|
|
425
|
+
* Token Types:
|
|
426
|
+
* - type='onboarding': Limited access during registration flow (@Onboarding endpoints only)
|
|
427
|
+
* - type='access': Full access to authenticated endpoints
|
|
428
|
+
*
|
|
429
|
+
* Environment Variables Required:
|
|
430
|
+
* - JWT_SECRET: Secret key to verify access tokens (required)
|
|
431
|
+
*
|
|
432
|
+
* Error Responses:
|
|
433
|
+
* - 401: Invalid/expired access token
|
|
434
|
+
* - 401: Tenant not found or inactive
|
|
435
|
+
* - 401: Tenant identifier not found
|
|
436
|
+
* - 401: Token type mismatch (onboarding token on regular endpoint or vice versa)
|
|
437
|
+
*
|
|
438
|
+
* @example
|
|
439
|
+
* // Automatically registered by AuthConfigModule.forRootAsync()
|
|
440
|
+
* // No manual registration needed
|
|
441
|
+
* //
|
|
442
|
+
* // Internal registration uses useExisting pattern:
|
|
443
|
+
* // providers: [
|
|
444
|
+
* // VrittiAuthGuard,
|
|
445
|
+
* // {
|
|
446
|
+
* // provide: APP_GUARD,
|
|
447
|
+
* // useExisting: VrittiAuthGuard,
|
|
448
|
+
* // },
|
|
449
|
+
* // ]
|
|
450
|
+
*
|
|
451
|
+
* @example
|
|
452
|
+
* // Bypass guard with @Public() decorator
|
|
453
|
+
* @Public()
|
|
454
|
+
* @Post('auth/login')
|
|
455
|
+
* async login(@Body() dto: LoginDto) { ... }
|
|
456
|
+
*
|
|
457
|
+
* @example
|
|
458
|
+
* // Restrict to onboarding tokens with @Onboarding() decorator
|
|
459
|
+
* @Onboarding()
|
|
460
|
+
* @Post('onboarding/verify-email')
|
|
461
|
+
* async verifyEmail(@Request() req) {
|
|
462
|
+
* const userId = req.user.id; // Available from guard
|
|
463
|
+
* ...
|
|
464
|
+
* }
|
|
465
|
+
*/
|
|
466
|
+
declare class VrittiAuthGuard implements CanActivate {
|
|
467
|
+
private readonly reflector;
|
|
468
|
+
readonly _configService: ConfigService;
|
|
469
|
+
private readonly jwtService;
|
|
470
|
+
private readonly primaryDatabase;
|
|
471
|
+
private readonly requestService;
|
|
472
|
+
private readonly logger;
|
|
473
|
+
constructor(reflector: Reflector, _configService: ConfigService, jwtService: JwtService, primaryDatabase: PrimaryDatabaseService, requestService: RequestService);
|
|
474
|
+
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
464
475
|
/**
|
|
465
|
-
*
|
|
476
|
+
* Validate access token with proper expiry checks
|
|
477
|
+
* Throws UnauthorizedException if token is invalid or expired
|
|
466
478
|
*/
|
|
467
|
-
|
|
468
|
-
activeConnections: number;
|
|
469
|
-
tenants: string[];
|
|
470
|
-
};
|
|
479
|
+
private validateAccessToken;
|
|
471
480
|
/**
|
|
472
|
-
*
|
|
481
|
+
* Validate that the access token is bound to the refresh token in the cookie.
|
|
482
|
+
* This prevents token theft - a stolen access token is useless without the
|
|
483
|
+
* corresponding refresh token cookie.
|
|
484
|
+
*
|
|
485
|
+
* @param context - The execution context containing the request
|
|
486
|
+
* @param validatedToken - The decoded and validated JWT token
|
|
487
|
+
* @throws UnauthorizedException if token binding validation fails
|
|
473
488
|
*/
|
|
474
|
-
private
|
|
475
|
-
onModuleDestroy(): Promise<void>;
|
|
489
|
+
private validateRefreshTokenBinding;
|
|
476
490
|
}
|
|
477
491
|
|
|
478
492
|
/**
|
|
479
|
-
*
|
|
493
|
+
* Hash a token using SHA-256
|
|
494
|
+
* @param token The token to hash
|
|
495
|
+
* @returns The hex-encoded SHA-256 hash
|
|
496
|
+
*/
|
|
497
|
+
declare function hashToken(token: string): string;
|
|
498
|
+
/**
|
|
499
|
+
* Verify a token against its expected hash using constant-time comparison
|
|
500
|
+
* @param token The token to verify
|
|
501
|
+
* @param expectedHash The expected SHA-256 hash
|
|
502
|
+
* @returns true if the token matches the hash
|
|
503
|
+
*/
|
|
504
|
+
declare function verifyTokenHash(token: string, expectedHash: string): boolean;
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* api-sdk Configuration System
|
|
480
508
|
*
|
|
481
|
-
*
|
|
482
|
-
* - Connects to the primary database (tenant registry)
|
|
483
|
-
* - Queries tenant metadata (database location, credentials, etc.)
|
|
484
|
-
* - Caches tenant configs in memory to reduce database load
|
|
485
|
-
* - Only used in GATEWAY MODE (microservices receive tenant config from messages)
|
|
509
|
+
* Similar to quantum-ui's config pattern - provides a type-safe configuration system
|
|
486
510
|
*
|
|
487
511
|
* @example
|
|
488
|
-
*
|
|
489
|
-
*
|
|
490
|
-
*
|
|
512
|
+
* ```typescript
|
|
513
|
+
* // In vritti-api-nexus/src/main.ts
|
|
514
|
+
* import { configureApiSdk } from '@vritti/api-sdk';
|
|
515
|
+
*
|
|
516
|
+
* configureApiSdk({
|
|
517
|
+
* cookie: {
|
|
518
|
+
* refreshCookieName: 'vritti_refresh',
|
|
519
|
+
* refreshCookieMaxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
|
|
520
|
+
* },
|
|
521
|
+
* jwt: {
|
|
522
|
+
* accessTokenExpiry: '15m',
|
|
523
|
+
* refreshTokenExpiry: '30d',
|
|
524
|
+
* validateTokenBinding: true,
|
|
525
|
+
* },
|
|
526
|
+
* guard: {
|
|
527
|
+
* tenantHeaderName: 'x-tenant-id',
|
|
528
|
+
* },
|
|
529
|
+
* });
|
|
530
|
+
* ```
|
|
491
531
|
*/
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
private pool;
|
|
497
|
-
/** Drizzle database instance */
|
|
498
|
-
private db;
|
|
499
|
-
/** In-memory cache: Map<tenantIdentifier, TenantConfig> */
|
|
500
|
-
private readonly tenantConfigCache;
|
|
501
|
-
/** Cache TTL in milliseconds */
|
|
502
|
-
private readonly cacheTTL;
|
|
503
|
-
constructor(options: DatabaseModuleOptions);
|
|
504
|
-
onModuleInit(): Promise<void>;
|
|
532
|
+
/**
|
|
533
|
+
* Cookie configuration options
|
|
534
|
+
*/
|
|
535
|
+
interface CookieConfig {
|
|
505
536
|
/**
|
|
506
|
-
*
|
|
537
|
+
* The name of the httpOnly cookie containing the refresh token
|
|
538
|
+
* @default 'vritti_refresh'
|
|
507
539
|
*/
|
|
508
|
-
|
|
540
|
+
refreshCookieName: string;
|
|
509
541
|
/**
|
|
510
|
-
*
|
|
542
|
+
* Max age of the refresh cookie in milliseconds
|
|
543
|
+
* @default 2592000000 (30 days)
|
|
511
544
|
*/
|
|
512
|
-
|
|
545
|
+
refreshCookieMaxAge: number;
|
|
513
546
|
/**
|
|
514
|
-
*
|
|
547
|
+
* Cookie path
|
|
548
|
+
* @default '/'
|
|
515
549
|
*/
|
|
516
|
-
|
|
550
|
+
refreshCookiePath: string;
|
|
517
551
|
/**
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
-
* @param tenantIdentifier Tenant ID or subdomain
|
|
521
|
-
* @returns Tenant configuration or null if not found
|
|
552
|
+
* Whether the cookie is secure (HTTPS only)
|
|
553
|
+
* @default true in production
|
|
522
554
|
*/
|
|
523
|
-
|
|
555
|
+
refreshCookieSecure: boolean;
|
|
524
556
|
/**
|
|
525
|
-
*
|
|
557
|
+
* SameSite attribute for the cookie
|
|
558
|
+
* @default 'strict'
|
|
526
559
|
*/
|
|
527
|
-
|
|
560
|
+
refreshCookieSameSite: 'strict' | 'lax' | 'none';
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* JWT token configuration options
|
|
564
|
+
*/
|
|
565
|
+
interface JwtConfig {
|
|
528
566
|
/**
|
|
529
|
-
*
|
|
530
|
-
*
|
|
531
|
-
* Useful when tenant settings are updated and cache needs to be invalidated
|
|
532
|
-
*
|
|
533
|
-
* @param tenantIdentifier Tenant ID or subdomain
|
|
567
|
+
* Access token expiry time
|
|
568
|
+
* @default '15m'
|
|
534
569
|
*/
|
|
535
|
-
|
|
570
|
+
accessTokenExpiry: string;
|
|
536
571
|
/**
|
|
537
|
-
*
|
|
572
|
+
* Refresh token expiry time
|
|
573
|
+
* @default '30d'
|
|
538
574
|
*/
|
|
539
|
-
|
|
575
|
+
refreshTokenExpiry: string;
|
|
540
576
|
/**
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
* @returns Primary database Drizzle instance
|
|
545
|
-
* @throws Error if primary database client is not initialized
|
|
577
|
+
* Onboarding token expiry time
|
|
578
|
+
* @default '24h'
|
|
546
579
|
*/
|
|
547
|
-
|
|
580
|
+
onboardingTokenExpiry: string;
|
|
548
581
|
/**
|
|
549
|
-
*
|
|
582
|
+
* Whether to validate refresh token binding (hash in access token)
|
|
583
|
+
* @default true
|
|
550
584
|
*/
|
|
551
|
-
|
|
585
|
+
validateTokenBinding: boolean;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Auth guard configuration options
|
|
589
|
+
*/
|
|
590
|
+
interface GuardConfig {
|
|
552
591
|
/**
|
|
553
|
-
*
|
|
592
|
+
* Header name for tenant ID
|
|
593
|
+
* @default 'x-tenant-id'
|
|
594
|
+
*/
|
|
595
|
+
tenantHeaderName: string;
|
|
596
|
+
/**
|
|
597
|
+
* Header name for authorization
|
|
598
|
+
* @default 'authorization'
|
|
599
|
+
*/
|
|
600
|
+
authHeaderName: string;
|
|
601
|
+
/**
|
|
602
|
+
* Token prefix (e.g., 'Bearer')
|
|
603
|
+
* @default 'Bearer'
|
|
604
|
+
*/
|
|
605
|
+
tokenPrefix: string;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Complete api-sdk configuration interface
|
|
609
|
+
*/
|
|
610
|
+
interface ApiSdkConfig {
|
|
611
|
+
/**
|
|
612
|
+
* Cookie configuration
|
|
613
|
+
*/
|
|
614
|
+
cookie?: Partial<CookieConfig>;
|
|
615
|
+
/**
|
|
616
|
+
* JWT token configuration
|
|
617
|
+
*/
|
|
618
|
+
jwt?: Partial<JwtConfig>;
|
|
619
|
+
/**
|
|
620
|
+
* Auth guard configuration
|
|
621
|
+
*/
|
|
622
|
+
guard?: Partial<GuardConfig>;
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Full configuration type with all properties required
|
|
626
|
+
*/
|
|
627
|
+
interface FullConfig {
|
|
628
|
+
cookie: CookieConfig;
|
|
629
|
+
jwt: JwtConfig;
|
|
630
|
+
guard: GuardConfig;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Helper function to define configuration with type safety
|
|
634
|
+
* Similar to Tailwind's defineConfig()
|
|
635
|
+
*/
|
|
636
|
+
declare function defineConfig(config: ApiSdkConfig): ApiSdkConfig;
|
|
637
|
+
/**
|
|
638
|
+
* Configure api-sdk with user settings
|
|
639
|
+
* This should be called once in the application's bootstrap (main.ts)
|
|
640
|
+
*/
|
|
641
|
+
declare function configureApiSdk(userConfig: ApiSdkConfig): void;
|
|
642
|
+
/**
|
|
643
|
+
* Get the current configuration
|
|
644
|
+
*/
|
|
645
|
+
declare function getConfig(): FullConfig;
|
|
646
|
+
/**
|
|
647
|
+
* Reset configuration to defaults (for testing)
|
|
648
|
+
*/
|
|
649
|
+
declare function resetConfig(): void;
|
|
650
|
+
/**
|
|
651
|
+
* Get refresh cookie options (convenience method)
|
|
652
|
+
*/
|
|
653
|
+
declare function getRefreshCookieOptions(): {
|
|
654
|
+
httpOnly: boolean;
|
|
655
|
+
secure: boolean;
|
|
656
|
+
sameSite: "strict" | "lax" | "none";
|
|
657
|
+
path: string;
|
|
658
|
+
maxAge: number;
|
|
659
|
+
};
|
|
660
|
+
/**
|
|
661
|
+
* Get JWT expiry settings (convenience method)
|
|
662
|
+
*/
|
|
663
|
+
declare function getJwtExpiry(): {
|
|
664
|
+
access: string;
|
|
665
|
+
refresh: string;
|
|
666
|
+
onboarding: string;
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Dynamic module for multi-tenant database management
|
|
671
|
+
*
|
|
672
|
+
* This module provides:
|
|
673
|
+
* - Tenant context management (request-scoped)
|
|
674
|
+
* - Database connection pooling
|
|
675
|
+
* - Dynamic schema/cluster routing
|
|
676
|
+
* - Support for both gateway and microservice modes
|
|
677
|
+
*
|
|
678
|
+
* ## Gateway Mode (API Gateway)
|
|
679
|
+
* - Use DatabaseModule.forServer() method
|
|
680
|
+
* - Automatically extracts tenant from subdomain, falls back to x-tenant-id header
|
|
681
|
+
* - Provide primaryDb configuration and prismaClientConstructor
|
|
682
|
+
* - Automatically queries primary DB for tenant config
|
|
683
|
+
* - Automatically registers TenantContextInterceptor globally
|
|
684
|
+
* - No manual interceptor registration needed
|
|
685
|
+
*
|
|
686
|
+
* ## Microservice Mode (RabbitMQ Workers)
|
|
687
|
+
* - Use DatabaseModule.forMicroservice() method
|
|
688
|
+
* - Only provide prismaClientConstructor
|
|
689
|
+
* - Tenant context comes from RabbitMQ messages
|
|
690
|
+
* - Automatically registers MessageTenantContextInterceptor globally
|
|
691
|
+
* - No manual interceptor registration needed
|
|
692
|
+
*
|
|
693
|
+
* @example
|
|
694
|
+
* // Gateway configuration
|
|
695
|
+
* DatabaseModule.forServer({
|
|
696
|
+
* inject: [ConfigService],
|
|
697
|
+
* useFactory: (config: ConfigService) => ({
|
|
698
|
+
* primaryDb: {
|
|
699
|
+
* host: config.get('PRIMARY_DB_HOST'),
|
|
700
|
+
* port: config.get('PRIMARY_DB_PORT'),
|
|
701
|
+
* username: config.get('PRIMARY_DB_USERNAME'),
|
|
702
|
+
* password: config.get('PRIMARY_DB_PASSWORD'),
|
|
703
|
+
* database: config.get('PRIMARY_DB_DATABASE'),
|
|
704
|
+
* },
|
|
705
|
+
* prismaClientConstructor: PrismaClient,
|
|
706
|
+
* }),
|
|
707
|
+
* })
|
|
708
|
+
*
|
|
709
|
+
* @example
|
|
710
|
+
* // Microservice configuration
|
|
711
|
+
* DatabaseModule.forMicroservice({
|
|
712
|
+
* inject: [ConfigService],
|
|
713
|
+
* useFactory: (config: ConfigService) => ({
|
|
714
|
+
* prismaClientConstructor: PrismaClient,
|
|
715
|
+
* }),
|
|
716
|
+
* })
|
|
717
|
+
*/
|
|
718
|
+
declare class DatabaseModule {
|
|
719
|
+
/**
|
|
720
|
+
* Configure DatabaseModule for Gateway/HTTP mode (multi-tenant web servers)
|
|
554
721
|
*
|
|
555
|
-
*
|
|
722
|
+
* This mode is for API Gateways that handle HTTP requests:
|
|
723
|
+
* - Automatically registers TenantContextInterceptor
|
|
724
|
+
* - Extracts tenant from subdomain or x-tenant-id header
|
|
725
|
+
* - Queries primary database for tenant configuration
|
|
726
|
+
* - Provides PrimaryDatabaseService for tenant lookup
|
|
556
727
|
*
|
|
557
|
-
* @param
|
|
558
|
-
* @returns
|
|
728
|
+
* @param options Async configuration options
|
|
729
|
+
* @returns Dynamic module configuration with HTTP interceptor
|
|
730
|
+
*
|
|
731
|
+
* @example
|
|
732
|
+
* DatabaseModule.forServer({
|
|
733
|
+
* inject: [ConfigService],
|
|
734
|
+
* useFactory: (config: ConfigService) => ({
|
|
735
|
+
* primaryDb: {
|
|
736
|
+
* host: config.get('PRIMARY_DB_HOST'),
|
|
737
|
+
* port: config.get('PRIMARY_DB_PORT'),
|
|
738
|
+
* username: config.get('PRIMARY_DB_USERNAME'),
|
|
739
|
+
* password: config.get('PRIMARY_DB_PASSWORD'),
|
|
740
|
+
* database: config.get('PRIMARY_DB_DATABASE'),
|
|
741
|
+
* },
|
|
742
|
+
* prismaClientConstructor: PrismaClient,
|
|
743
|
+
* }),
|
|
744
|
+
* })
|
|
559
745
|
*/
|
|
560
|
-
|
|
561
|
-
|
|
746
|
+
static forServer(options: {
|
|
747
|
+
useFactory: (...args: any[]) => Promise<DatabaseModuleOptions> | DatabaseModuleOptions;
|
|
748
|
+
inject?: any[];
|
|
749
|
+
}): DynamicModule;
|
|
750
|
+
/**
|
|
751
|
+
* Configure DatabaseModule for Microservice/Messaging mode (RabbitMQ workers)
|
|
752
|
+
*
|
|
753
|
+
* This mode is for microservices that process messages from queues:
|
|
754
|
+
* - Automatically registers MessageTenantContextInterceptor
|
|
755
|
+
* - Extracts tenant from RabbitMQ message patterns
|
|
756
|
+
* - No primary database needed (tenant comes from message context)
|
|
757
|
+
*
|
|
758
|
+
* @param options Async configuration options
|
|
759
|
+
* @returns Dynamic module configuration with message interceptor
|
|
760
|
+
*
|
|
761
|
+
* @example
|
|
762
|
+
* DatabaseModule.forMicroservice({
|
|
763
|
+
* inject: [ConfigService],
|
|
764
|
+
* useFactory: (config: ConfigService) => ({
|
|
765
|
+
* prismaClientConstructor: PrismaClient,
|
|
766
|
+
* }),
|
|
767
|
+
* })
|
|
768
|
+
*/
|
|
769
|
+
static forMicroservice(options: {
|
|
770
|
+
useFactory: (...args: any[]) => Promise<DatabaseModuleOptions> | DatabaseModuleOptions;
|
|
771
|
+
inject?: any[];
|
|
772
|
+
}): DynamicModule;
|
|
773
|
+
/**
|
|
774
|
+
* Internal helper to create dynamic module with conditional interceptor registration
|
|
775
|
+
*
|
|
776
|
+
* @param options Configuration options
|
|
777
|
+
* @param mode Mode of operation (gateway or microservice)
|
|
778
|
+
* @returns Dynamic module configuration
|
|
779
|
+
*/
|
|
780
|
+
private static createDynamicModule;
|
|
562
781
|
}
|
|
563
782
|
|
|
783
|
+
/**
|
|
784
|
+
* Parameter decorator that injects tenant metadata into controller method
|
|
785
|
+
*
|
|
786
|
+
* This decorator retrieves tenant information (ID, slug, type, etc.)
|
|
787
|
+
* from the REQUEST-SCOPED TenantContextService.
|
|
788
|
+
*
|
|
789
|
+
* Useful for:
|
|
790
|
+
* - Logging tenant-specific information
|
|
791
|
+
* - Implementing tenant-specific business logic
|
|
792
|
+
* - Auditing and tracking
|
|
793
|
+
* - Conditional feature flags
|
|
794
|
+
*
|
|
795
|
+
* @returns TenantInfo object with tenant metadata
|
|
796
|
+
*
|
|
797
|
+
* @example
|
|
798
|
+
* // Access tenant metadata
|
|
799
|
+
* @Get('info')
|
|
800
|
+
* async getTenantInfo(@Tenant() tenant: TenantInfo) {
|
|
801
|
+
* return {
|
|
802
|
+
* id: tenant.id,
|
|
803
|
+
* subdomain: tenant.subdomain,
|
|
804
|
+
* type: tenant.type,
|
|
805
|
+
* };
|
|
806
|
+
* }
|
|
807
|
+
*
|
|
808
|
+
* @example
|
|
809
|
+
* // Use for logging
|
|
810
|
+
* @Post()
|
|
811
|
+
* async createUser(
|
|
812
|
+
* @Body() dto: CreateUserDto,
|
|
813
|
+
* @Tenant() tenant: TenantInfo,
|
|
814
|
+
* ) {
|
|
815
|
+
* this.logger.log(`Creating user for tenant: ${tenant.subdomain}`);
|
|
816
|
+
* // ...
|
|
817
|
+
* }
|
|
818
|
+
*
|
|
819
|
+
* @example
|
|
820
|
+
* // Conditional business logic
|
|
821
|
+
* @Get('features')
|
|
822
|
+
* async getFeatures(@Tenant() tenant: TenantInfo) {
|
|
823
|
+
* if (tenant.type === 'ENTERPRISE') {
|
|
824
|
+
* return ['feature-a', 'feature-b', 'feature-c'];
|
|
825
|
+
* }
|
|
826
|
+
* return ['feature-a'];
|
|
827
|
+
* }
|
|
828
|
+
*/
|
|
829
|
+
declare const Tenant: (...dataOrPipes: unknown[]) => ParameterDecorator;
|
|
830
|
+
|
|
564
831
|
/**
|
|
565
832
|
* Drizzle ORM v2 object-based where filter type.
|
|
566
833
|
* Supports simple equality, operators, AND/OR/NOT, and RAW SQL.
|
|
@@ -663,7 +930,8 @@ declare abstract class PrimaryBaseRepository<TTable extends PgTable, TInsert = I
|
|
|
663
930
|
protected readonly logger: Logger;
|
|
664
931
|
/**
|
|
665
932
|
* The table name extracted from the Drizzle table at runtime.
|
|
666
|
-
*
|
|
933
|
+
* Stored in camelCase to match Drizzle's query object keys.
|
|
934
|
+
* Example: 'email_verifications' -> 'emailVerifications'
|
|
667
935
|
*/
|
|
668
936
|
private readonly tableName;
|
|
669
937
|
/**
|
|
@@ -894,65 +1162,218 @@ declare abstract class PrimaryBaseRepository<TTable extends PgTable, TInsert = I
|
|
|
894
1162
|
}
|
|
895
1163
|
|
|
896
1164
|
/**
|
|
897
|
-
*
|
|
898
|
-
* TTable['_']['name'] gives us the string literal type (e.g., 'products')
|
|
899
|
-
*/
|
|
900
|
-
type ExtractTableName<TTable extends PgTable> = TTable['_']['name'];
|
|
901
|
-
/**
|
|
902
|
-
* Abstract base repository for tenant-scoped database operations using Drizzle ORM.
|
|
903
|
-
* All operations are automatically scoped to the current tenant.
|
|
904
|
-
*
|
|
905
|
-
* @template TTable - The Drizzle table type (must be registered in SchemaRegistry)
|
|
906
|
-
* @template TInsert - Type for insert operations (inferred from table.$inferInsert)
|
|
907
|
-
* @template TSelect - Type for select operations (inferred from table.$inferSelect)
|
|
908
|
-
*
|
|
909
|
-
* @remarks
|
|
910
|
-
* **Type Assertion Pattern:** This repository uses `as any` casts when passing
|
|
911
|
-
* the generic table to Drizzle methods. This is necessary because TypeScript
|
|
912
|
-
* cannot prove that a generic `TTable extends PgTable` satisfies Drizzle's
|
|
913
|
-
* stricter internal type requirements for `insert()`, `update()`, and `delete()`.
|
|
1165
|
+
* Request-scoped service that holds tenant context for the current request or RabbitMQ message
|
|
914
1166
|
*
|
|
915
|
-
*
|
|
916
|
-
*
|
|
917
|
-
* -
|
|
918
|
-
* - The casts are implementation details that don't leak to consumers
|
|
1167
|
+
* IMPORTANT: This service is REQUEST-SCOPED, meaning NestJS creates a new instance
|
|
1168
|
+
* for each HTTP request or RabbitMQ message. This ensures tenant isolation and
|
|
1169
|
+
* prevents cross-tenant data leaks in concurrent scenarios.
|
|
919
1170
|
*
|
|
920
1171
|
* @example
|
|
921
|
-
*
|
|
922
|
-
*
|
|
923
|
-
*
|
|
924
|
-
* type Product = typeof products.$inferSelect;
|
|
925
|
-
* type NewProduct = typeof products.$inferInsert;
|
|
926
|
-
*
|
|
927
|
-
* @Injectable()
|
|
928
|
-
* export class ProductRepository extends TenantBaseRepository<typeof products> {
|
|
929
|
-
* constructor(database: TenantDatabaseService) {
|
|
930
|
-
* super(database, products);
|
|
931
|
-
* }
|
|
932
|
-
*
|
|
933
|
-
* // Use SQL-builder syntax
|
|
934
|
-
* async findBySku(sku: string): Promise<Product | null> {
|
|
935
|
-
* const [result] = await this.db
|
|
936
|
-
* .select()
|
|
937
|
-
* .from(this.table)
|
|
938
|
-
* .where(eq(products.sku, sku))
|
|
939
|
-
* .limit(1);
|
|
940
|
-
* return result ?? null;
|
|
941
|
-
* }
|
|
1172
|
+
* // In a controller or service
|
|
1173
|
+
* constructor(private readonly tenantContext: TenantContextService) {}
|
|
942
1174
|
*
|
|
943
|
-
*
|
|
944
|
-
*
|
|
945
|
-
*
|
|
946
|
-
* where: eq(products.id, id),
|
|
947
|
-
* with: { category: true, variants: true }
|
|
948
|
-
* });
|
|
949
|
-
* }
|
|
1175
|
+
* async handleRequest() {
|
|
1176
|
+
* const tenant = this.tenantContext.getTenant();
|
|
1177
|
+
* console.log(`Processing request for tenant: ${tenant.tenantSlug}`);
|
|
950
1178
|
* }
|
|
951
|
-
* ```
|
|
952
1179
|
*/
|
|
953
|
-
declare
|
|
954
|
-
|
|
955
|
-
|
|
1180
|
+
declare class TenantContextService {
|
|
1181
|
+
private tenantInfo;
|
|
1182
|
+
/**
|
|
1183
|
+
* Set tenant information for this request/message
|
|
1184
|
+
*
|
|
1185
|
+
* This is typically called by:
|
|
1186
|
+
* - TenantContextInterceptor (for HTTP requests in gateway)
|
|
1187
|
+
* - MessageTenantContextInterceptor (for RabbitMQ messages in microservices)
|
|
1188
|
+
* - Manual context setup in message handlers
|
|
1189
|
+
*
|
|
1190
|
+
* @param tenantInfo Complete tenant information
|
|
1191
|
+
* @throws Error if tenant context is already set (prevents accidental overwrites)
|
|
1192
|
+
*/
|
|
1193
|
+
setTenant(tenantInfo: TenantInfo): void;
|
|
1194
|
+
/**
|
|
1195
|
+
* Get tenant information for this request/message
|
|
1196
|
+
*
|
|
1197
|
+
* @returns Tenant information
|
|
1198
|
+
* @throws UnauthorizedException if tenant context hasn't been set
|
|
1199
|
+
*/
|
|
1200
|
+
getTenant(): TenantInfo;
|
|
1201
|
+
/**
|
|
1202
|
+
* Check if tenant context has been set
|
|
1203
|
+
*
|
|
1204
|
+
* @returns true if tenant context is available
|
|
1205
|
+
*/
|
|
1206
|
+
hasTenant(): boolean;
|
|
1207
|
+
/**
|
|
1208
|
+
* Clear tenant context
|
|
1209
|
+
*
|
|
1210
|
+
* This is useful for cleanup in RabbitMQ message handlers
|
|
1211
|
+
* after the message has been processed.
|
|
1212
|
+
*
|
|
1213
|
+
* HTTP requests don't need manual cleanup as the service
|
|
1214
|
+
* instance is destroyed when the request ends.
|
|
1215
|
+
*/
|
|
1216
|
+
clearTenant(): void;
|
|
1217
|
+
/**
|
|
1218
|
+
* Get tenant ID safely (returns null if not set)
|
|
1219
|
+
*
|
|
1220
|
+
* @returns Tenant ID or null
|
|
1221
|
+
*/
|
|
1222
|
+
getTenantIdSafe(): string | null;
|
|
1223
|
+
/**
|
|
1224
|
+
* Get tenant subdomain safely (returns null if not set)
|
|
1225
|
+
*
|
|
1226
|
+
* @returns Tenant subdomain or null
|
|
1227
|
+
*/
|
|
1228
|
+
getTenantSubdomainSafe(): string | null;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
/**
|
|
1232
|
+
* Service responsible for managing tenant-scoped database connections
|
|
1233
|
+
*
|
|
1234
|
+
* This service:
|
|
1235
|
+
* - Maintains a connection pool (Map<cacheKey, TenantConnection>)
|
|
1236
|
+
* - Creates new connections dynamically based on tenant context
|
|
1237
|
+
* - Reuses existing connections for the same tenant
|
|
1238
|
+
* - Supports both cloud schemas and enterprise databases
|
|
1239
|
+
* - Automatically cleans up idle connections
|
|
1240
|
+
*
|
|
1241
|
+
* @example
|
|
1242
|
+
* // In a controller or service
|
|
1243
|
+
* const db = this.tenantDatabase.drizzleClient;
|
|
1244
|
+
* const users = await db.select().from(usersTable);
|
|
1245
|
+
*/
|
|
1246
|
+
declare class TenantDatabaseService implements OnModuleDestroy {
|
|
1247
|
+
private readonly options;
|
|
1248
|
+
private readonly tenantContext;
|
|
1249
|
+
private readonly logger;
|
|
1250
|
+
/** Connection pool: Map<cacheKey, TenantConnection> */
|
|
1251
|
+
private readonly clients;
|
|
1252
|
+
/** Track last usage time for idle connection cleanup */
|
|
1253
|
+
private readonly clientLastUsed;
|
|
1254
|
+
/** Cleanup interval timer */
|
|
1255
|
+
private cleanupInterval?;
|
|
1256
|
+
constructor(options: DatabaseModuleOptions, tenantContext: TenantContextService);
|
|
1257
|
+
/**
|
|
1258
|
+
* Get the Drizzle client for the current tenant's database.
|
|
1259
|
+
* This returns the tenant-scoped database client.
|
|
1260
|
+
*
|
|
1261
|
+
* @returns Tenant-scoped Drizzle database instance
|
|
1262
|
+
* @throws UnauthorizedException if tenant context not set
|
|
1263
|
+
* @throws InternalServerErrorException if connection fails
|
|
1264
|
+
*/
|
|
1265
|
+
get drizzleClient(): TypedDrizzleClient;
|
|
1266
|
+
/**
|
|
1267
|
+
* Get the Drizzle schema
|
|
1268
|
+
*/
|
|
1269
|
+
get schema(): Record<string, unknown>;
|
|
1270
|
+
/**
|
|
1271
|
+
* Get tenant-scoped database client for the current request/message
|
|
1272
|
+
*
|
|
1273
|
+
* This method:
|
|
1274
|
+
* 1. Gets tenant info from TenantContextService
|
|
1275
|
+
* 2. Builds a connection URL based on tenant type
|
|
1276
|
+
* 3. Returns cached client if exists, otherwise creates new one
|
|
1277
|
+
*
|
|
1278
|
+
* @returns Drizzle database instance
|
|
1279
|
+
* @throws UnauthorizedException if tenant context not set
|
|
1280
|
+
* @throws InternalServerErrorException if connection fails
|
|
1281
|
+
*/
|
|
1282
|
+
private getDbClient;
|
|
1283
|
+
/**
|
|
1284
|
+
* Create a new database client for the given tenant (synchronous)
|
|
1285
|
+
*/
|
|
1286
|
+
private createDbClientSync;
|
|
1287
|
+
/**
|
|
1288
|
+
* Build connection URL for tenant (dedicated database)
|
|
1289
|
+
*/
|
|
1290
|
+
private buildTenantDbUrl;
|
|
1291
|
+
/**
|
|
1292
|
+
* Build cache key for connection pooling
|
|
1293
|
+
*/
|
|
1294
|
+
private buildCacheKey;
|
|
1295
|
+
/**
|
|
1296
|
+
* Start periodic cleanup of idle connections
|
|
1297
|
+
*/
|
|
1298
|
+
private startConnectionCleaner;
|
|
1299
|
+
/**
|
|
1300
|
+
* Clean up idle connections that haven't been used recently
|
|
1301
|
+
*/
|
|
1302
|
+
private cleanupIdleConnections;
|
|
1303
|
+
/**
|
|
1304
|
+
* Get current connection pool statistics
|
|
1305
|
+
*/
|
|
1306
|
+
getPoolStats(): {
|
|
1307
|
+
activeConnections: number;
|
|
1308
|
+
tenants: string[];
|
|
1309
|
+
};
|
|
1310
|
+
/**
|
|
1311
|
+
* Mask password in connection URL for logging
|
|
1312
|
+
*/
|
|
1313
|
+
private maskPassword;
|
|
1314
|
+
onModuleDestroy(): Promise<void>;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
/**
|
|
1318
|
+
* Type helper to extract table name from Drizzle table.
|
|
1319
|
+
* TTable['_']['name'] gives us the string literal type (e.g., 'products')
|
|
1320
|
+
*/
|
|
1321
|
+
type ExtractTableName<TTable extends PgTable> = TTable['_']['name'];
|
|
1322
|
+
/**
|
|
1323
|
+
* Abstract base repository for tenant-scoped database operations using Drizzle ORM.
|
|
1324
|
+
* All operations are automatically scoped to the current tenant.
|
|
1325
|
+
*
|
|
1326
|
+
* @template TTable - The Drizzle table type (must be registered in SchemaRegistry)
|
|
1327
|
+
* @template TInsert - Type for insert operations (inferred from table.$inferInsert)
|
|
1328
|
+
* @template TSelect - Type for select operations (inferred from table.$inferSelect)
|
|
1329
|
+
*
|
|
1330
|
+
* @remarks
|
|
1331
|
+
* **Type Assertion Pattern:** This repository uses `as any` casts when passing
|
|
1332
|
+
* the generic table to Drizzle methods. This is necessary because TypeScript
|
|
1333
|
+
* cannot prove that a generic `TTable extends PgTable` satisfies Drizzle's
|
|
1334
|
+
* stricter internal type requirements for `insert()`, `update()`, and `delete()`.
|
|
1335
|
+
*
|
|
1336
|
+
* The public API maintains full type safety:
|
|
1337
|
+
* - Input parameters are typed as `TInsert` (inferred from table)
|
|
1338
|
+
* - Return values are typed as `TSelect` (inferred from table)
|
|
1339
|
+
* - The casts are implementation details that don't leak to consumers
|
|
1340
|
+
*
|
|
1341
|
+
* @example
|
|
1342
|
+
* ```typescript
|
|
1343
|
+
* import { products } from '@/db/schema';
|
|
1344
|
+
*
|
|
1345
|
+
* type Product = typeof products.$inferSelect;
|
|
1346
|
+
* type NewProduct = typeof products.$inferInsert;
|
|
1347
|
+
*
|
|
1348
|
+
* @Injectable()
|
|
1349
|
+
* export class ProductRepository extends TenantBaseRepository<typeof products> {
|
|
1350
|
+
* constructor(database: TenantDatabaseService) {
|
|
1351
|
+
* super(database, products);
|
|
1352
|
+
* }
|
|
1353
|
+
*
|
|
1354
|
+
* // Use SQL-builder syntax
|
|
1355
|
+
* async findBySku(sku: string): Promise<Product | null> {
|
|
1356
|
+
* const [result] = await this.db
|
|
1357
|
+
* .select()
|
|
1358
|
+
* .from(this.table)
|
|
1359
|
+
* .where(eq(products.sku, sku))
|
|
1360
|
+
* .limit(1);
|
|
1361
|
+
* return result ?? null;
|
|
1362
|
+
* }
|
|
1363
|
+
*
|
|
1364
|
+
* // Use Prisma-like relational query syntax
|
|
1365
|
+
* async findWithRelations(id: string): Promise<Product | null> {
|
|
1366
|
+
* return await this.model.findFirst({
|
|
1367
|
+
* where: eq(products.id, id),
|
|
1368
|
+
* with: { category: true, variants: true }
|
|
1369
|
+
* });
|
|
1370
|
+
* }
|
|
1371
|
+
* }
|
|
1372
|
+
* ```
|
|
1373
|
+
*/
|
|
1374
|
+
declare abstract class TenantBaseRepository<TTable extends PgTable, TInsert = InferInsertModel<TTable>, TSelect = InferSelectModel<TTable>> {
|
|
1375
|
+
protected readonly database: TenantDatabaseService;
|
|
1376
|
+
protected readonly table: TTable;
|
|
956
1377
|
protected readonly logger: Logger;
|
|
957
1378
|
/**
|
|
958
1379
|
* The table name extracted from the Drizzle table at runtime.
|
|
@@ -1143,351 +1564,30 @@ declare abstract class TenantBaseRepository<TTable extends PgTable, TInsert = In
|
|
|
1143
1564
|
*
|
|
1144
1565
|
* // Count all products
|
|
1145
1566
|
* const total = await productRepository.count();
|
|
1146
|
-
*
|
|
1147
|
-
* // Count active products
|
|
1148
|
-
* const activeCount = await productRepository.count(
|
|
1149
|
-
* eq(products.status, 'ACTIVE')
|
|
1150
|
-
* );
|
|
1151
|
-
* ```
|
|
1152
|
-
*/
|
|
1153
|
-
count(where?: SQL): Promise<number>;
|
|
1154
|
-
/**
|
|
1155
|
-
* Check if a record exists
|
|
1156
|
-
*
|
|
1157
|
-
* @param where - SQL condition to match records
|
|
1158
|
-
* @returns Promise resolving to true if at least one record exists, false otherwise
|
|
1159
|
-
*
|
|
1160
|
-
* @example
|
|
1161
|
-
* ```typescript
|
|
1162
|
-
* import { eq } from 'drizzle-orm';
|
|
1163
|
-
*
|
|
1164
|
-
* const skuExists = await productRepository.exists(
|
|
1165
|
-
* eq(products.sku, 'WDG-001')
|
|
1166
|
-
* );
|
|
1167
|
-
* ```
|
|
1168
|
-
*/
|
|
1169
|
-
exists(where: SQL): Promise<boolean>;
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
declare class RequestService {
|
|
1173
|
-
private readonly request;
|
|
1174
|
-
constructor(request: FastifyRequest);
|
|
1175
|
-
/**
|
|
1176
|
-
* Extract tenant identifier from request headers
|
|
1177
|
-
* Priority: x-tenant-id > x-subdomain
|
|
1178
|
-
* @returns Tenant identifier or null if not found
|
|
1179
|
-
*/
|
|
1180
|
-
getTenantIdentifier(): string | null;
|
|
1181
|
-
/**
|
|
1182
|
-
* Extract access token from Authorization header
|
|
1183
|
-
* Format: "Bearer <token>"
|
|
1184
|
-
* @returns Access token or null if not found
|
|
1185
|
-
*/
|
|
1186
|
-
getAccessToken(): string | null;
|
|
1187
|
-
/**
|
|
1188
|
-
* Extract refresh token from session-id cookie
|
|
1189
|
-
* Cookie name: session-id
|
|
1190
|
-
* @returns Refresh token or null if not found
|
|
1191
|
-
*/
|
|
1192
|
-
getRefreshToken(): string | null;
|
|
1193
|
-
/**
|
|
1194
|
-
* Get a specific header value
|
|
1195
|
-
* @param key Header key
|
|
1196
|
-
* @returns Header value (string, array, or undefined)
|
|
1197
|
-
*/
|
|
1198
|
-
getHeader(key: string): string | string[] | undefined;
|
|
1199
|
-
/**
|
|
1200
|
-
* Get all headers
|
|
1201
|
-
* @returns Record of all headers
|
|
1202
|
-
*/
|
|
1203
|
-
getAllHeaders(): FastifyRequest['headers'];
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
/**
|
|
1207
|
-
* Vritti Authentication Guard - Validates JWT tokens and tenant context
|
|
1208
|
-
*
|
|
1209
|
-
* This guard performs comprehensive validation and attaches user data to request.
|
|
1210
|
-
*
|
|
1211
|
-
* Validation Flow:
|
|
1212
|
-
* 1. Checks if endpoint is marked with @Public() decorator → skip all validation
|
|
1213
|
-
* 2. Checks if endpoint is marked with @Onboarding() decorator:
|
|
1214
|
-
* - Requires token type='onboarding'
|
|
1215
|
-
* - Validates JWT signature and expiry only
|
|
1216
|
-
* - Skips tenant and refresh token validation
|
|
1217
|
-
* - Attaches user data to request.user
|
|
1218
|
-
* 3. For regular endpoints (no decorator):
|
|
1219
|
-
* - Rejects tokens with type='onboarding'
|
|
1220
|
-
* - Validates access token (JWT signature, expiry, nbf)
|
|
1221
|
-
* - Validates refresh token from session-id cookie
|
|
1222
|
-
* - Validates tenant exists and is ACTIVE
|
|
1223
|
-
* - Attaches user data to request.user
|
|
1224
|
-
*
|
|
1225
|
-
* Token Format:
|
|
1226
|
-
* - Access Token: "Authorization: Bearer <jwt_token>"
|
|
1227
|
-
* - Refresh Token: "session-id" cookie
|
|
1228
|
-
*
|
|
1229
|
-
* Token Types:
|
|
1230
|
-
* - type='onboarding': Limited access during registration flow (@Onboarding endpoints only)
|
|
1231
|
-
* - type='access': Full access to authenticated endpoints
|
|
1232
|
-
*
|
|
1233
|
-
* Environment Variables Required:
|
|
1234
|
-
* - JWT_SECRET: Secret key to verify access tokens (required)
|
|
1235
|
-
* - JWT_REFRESH_SECRET: Secret key for refresh tokens (optional, falls back to JWT_SECRET)
|
|
1236
|
-
*
|
|
1237
|
-
* Error Responses:
|
|
1238
|
-
* - 401: Invalid/expired access token
|
|
1239
|
-
* - 401: Invalid/expired refresh token
|
|
1240
|
-
* - 401: Tenant not found or inactive
|
|
1241
|
-
* - 401: Tenant identifier not found
|
|
1242
|
-
* - 401: Token type mismatch (onboarding token on regular endpoint or vice versa)
|
|
1243
|
-
*
|
|
1244
|
-
* @example
|
|
1245
|
-
* // Automatically registered by AuthConfigModule.forRootAsync()
|
|
1246
|
-
* // No manual registration needed
|
|
1247
|
-
* //
|
|
1248
|
-
* // Internal registration uses useExisting pattern:
|
|
1249
|
-
* // providers: [
|
|
1250
|
-
* // VrittiAuthGuard,
|
|
1251
|
-
* // {
|
|
1252
|
-
* // provide: APP_GUARD,
|
|
1253
|
-
* // useExisting: VrittiAuthGuard,
|
|
1254
|
-
* // },
|
|
1255
|
-
* // ]
|
|
1256
|
-
*
|
|
1257
|
-
* @example
|
|
1258
|
-
* // Bypass guard with @Public() decorator
|
|
1259
|
-
* @Public()
|
|
1260
|
-
* @Post('auth/login')
|
|
1261
|
-
* async login(@Body() dto: LoginDto) { ... }
|
|
1262
|
-
*
|
|
1263
|
-
* @example
|
|
1264
|
-
* // Restrict to onboarding tokens with @Onboarding() decorator
|
|
1265
|
-
* @Onboarding()
|
|
1266
|
-
* @Post('onboarding/verify-email')
|
|
1267
|
-
* async verifyEmail(@Request() req) {
|
|
1268
|
-
* const userId = req.user.id; // Available from guard
|
|
1269
|
-
* ...
|
|
1270
|
-
* }
|
|
1271
|
-
*/
|
|
1272
|
-
declare class VrittiAuthGuard implements CanActivate {
|
|
1273
|
-
private readonly reflector;
|
|
1274
|
-
private readonly configService;
|
|
1275
|
-
private readonly jwtService;
|
|
1276
|
-
private readonly primaryDatabase;
|
|
1277
|
-
private readonly requestService;
|
|
1278
|
-
private readonly logger;
|
|
1279
|
-
constructor(reflector: Reflector, configService: ConfigService, jwtService: JwtService, primaryDatabase: PrimaryDatabaseService, requestService: RequestService);
|
|
1280
|
-
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
1281
|
-
/**
|
|
1282
|
-
* Validate access token with proper expiry checks
|
|
1283
|
-
* Throws UnauthorizedException if token is invalid or expired
|
|
1284
|
-
*/
|
|
1285
|
-
private validateAccessToken;
|
|
1286
|
-
/**
|
|
1287
|
-
* Validate refresh token with proper expiry checks
|
|
1288
|
-
* Throws UnauthorizedException if token is invalid or expired
|
|
1289
|
-
*/
|
|
1290
|
-
private validateRefreshToken;
|
|
1291
|
-
/**
|
|
1292
|
-
* Helper to validate refresh token with specific secret
|
|
1293
|
-
*/
|
|
1294
|
-
private validateRefreshTokenWithSecret;
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
/**
|
|
1298
|
-
* Parameter decorator that injects tenant metadata into controller method
|
|
1299
|
-
*
|
|
1300
|
-
* This decorator retrieves tenant information (ID, slug, type, etc.)
|
|
1301
|
-
* from the REQUEST-SCOPED TenantContextService.
|
|
1302
|
-
*
|
|
1303
|
-
* Useful for:
|
|
1304
|
-
* - Logging tenant-specific information
|
|
1305
|
-
* - Implementing tenant-specific business logic
|
|
1306
|
-
* - Auditing and tracking
|
|
1307
|
-
* - Conditional feature flags
|
|
1308
|
-
*
|
|
1309
|
-
* @returns TenantInfo object with tenant metadata
|
|
1310
|
-
*
|
|
1311
|
-
* @example
|
|
1312
|
-
* // Access tenant metadata
|
|
1313
|
-
* @Get('info')
|
|
1314
|
-
* async getTenantInfo(@Tenant() tenant: TenantInfo) {
|
|
1315
|
-
* return {
|
|
1316
|
-
* id: tenant.id,
|
|
1317
|
-
* subdomain: tenant.subdomain,
|
|
1318
|
-
* type: tenant.type,
|
|
1319
|
-
* };
|
|
1320
|
-
* }
|
|
1321
|
-
*
|
|
1322
|
-
* @example
|
|
1323
|
-
* // Use for logging
|
|
1324
|
-
* @Post()
|
|
1325
|
-
* async createUser(
|
|
1326
|
-
* @Body() dto: CreateUserDto,
|
|
1327
|
-
* @Tenant() tenant: TenantInfo,
|
|
1328
|
-
* ) {
|
|
1329
|
-
* this.logger.log(`Creating user for tenant: ${tenant.subdomain}`);
|
|
1330
|
-
* // ...
|
|
1331
|
-
* }
|
|
1332
|
-
*
|
|
1333
|
-
* @example
|
|
1334
|
-
* // Conditional business logic
|
|
1335
|
-
* @Get('features')
|
|
1336
|
-
* async getFeatures(@Tenant() tenant: TenantInfo) {
|
|
1337
|
-
* if (tenant.type === 'ENTERPRISE') {
|
|
1338
|
-
* return ['feature-a', 'feature-b', 'feature-c'];
|
|
1339
|
-
* }
|
|
1340
|
-
* return ['feature-a'];
|
|
1341
|
-
* }
|
|
1342
|
-
*/
|
|
1343
|
-
declare const Tenant: (...dataOrPipes: unknown[]) => ParameterDecorator;
|
|
1344
|
-
|
|
1345
|
-
/**
|
|
1346
|
-
* Onboarding Decorator - Marks endpoints that require onboarding token
|
|
1347
|
-
*
|
|
1348
|
-
* Use this decorator on controllers or route handlers that should only be
|
|
1349
|
-
* accessible during the onboarding flow with JWT tokens containing type='onboarding'.
|
|
1350
|
-
*
|
|
1351
|
-
* These endpoints:
|
|
1352
|
-
* - Accept ONLY tokens with type='onboarding'
|
|
1353
|
-
* - Reject regular access tokens (type='access')
|
|
1354
|
-
* - Skip tenant validation and refresh token checks
|
|
1355
|
-
* - Only validate JWT signature and expiry
|
|
1356
|
-
*
|
|
1357
|
-
* Useful for:
|
|
1358
|
-
* - Email/phone verification during onboarding
|
|
1359
|
-
* - Onboarding status checks
|
|
1360
|
-
* - Resending OTPs during registration
|
|
1361
|
-
*
|
|
1362
|
-
* @example
|
|
1363
|
-
* // On a controller method
|
|
1364
|
-
* @Post('verify-email')
|
|
1365
|
-
* @Onboarding()
|
|
1366
|
-
* async verifyEmail(@Request() req, @Body() dto: VerifyEmailDto) {
|
|
1367
|
-
* const userId = req.user.id; // Available from VrittiAuthGuard
|
|
1368
|
-
* return this.service.verifyEmail(userId, dto.otp);
|
|
1369
|
-
* }
|
|
1370
|
-
*
|
|
1371
|
-
* @example
|
|
1372
|
-
* // Multiple onboarding endpoints
|
|
1373
|
-
* @Controller('onboarding')
|
|
1374
|
-
* export class OnboardingController {
|
|
1375
|
-
* @Post('verify-email')
|
|
1376
|
-
* @Onboarding()
|
|
1377
|
-
* async verifyEmail() { ... }
|
|
1378
|
-
*
|
|
1379
|
-
* @Post('resend-otp')
|
|
1380
|
-
* @Onboarding()
|
|
1381
|
-
* async resendOtp() { ... }
|
|
1382
|
-
* }
|
|
1383
|
-
*/
|
|
1384
|
-
declare const Onboarding: () => _nestjs_common.CustomDecorator<string>;
|
|
1385
|
-
|
|
1386
|
-
/**
|
|
1387
|
-
* Public Decorator - Marks endpoints that don't require authentication
|
|
1388
|
-
*
|
|
1389
|
-
* Use this decorator on controllers or route handlers to bypass VrittiAuthGuard
|
|
1390
|
-
* tenant validation. Useful for:
|
|
1391
|
-
* - Login/signup endpoints
|
|
1392
|
-
* - Health checks
|
|
1393
|
-
* - Public documentation endpoints
|
|
1394
|
-
* - Webhook endpoints that don't require tenant context
|
|
1395
|
-
*
|
|
1396
|
-
* @example
|
|
1397
|
-
* // On a controller method
|
|
1398
|
-
* @Public()
|
|
1399
|
-
* @Post('auth/login')
|
|
1400
|
-
* async login(@Body() dto: LoginDto) {
|
|
1401
|
-
* return this.authService.login(dto);
|
|
1402
|
-
* }
|
|
1403
|
-
*
|
|
1404
|
-
* @example
|
|
1405
|
-
* // On an entire controller
|
|
1406
|
-
* @Public()
|
|
1407
|
-
* @Controller('health')
|
|
1408
|
-
* export class HealthController {
|
|
1409
|
-
* @Get()
|
|
1410
|
-
* check() {
|
|
1411
|
-
* return { status: 'ok' };
|
|
1412
|
-
* }
|
|
1413
|
-
* }
|
|
1414
|
-
*/
|
|
1415
|
-
declare const Public: () => _nestjs_common.CustomDecorator<string>;
|
|
1416
|
-
|
|
1417
|
-
/**
|
|
1418
|
-
* HTTP Module
|
|
1419
|
-
*
|
|
1420
|
-
* Provides HTTP utilities including:
|
|
1421
|
-
* - CSRF Guard for request protection
|
|
1422
|
-
* - HTTP Exception Filter for standardized error responses
|
|
1423
|
-
*
|
|
1424
|
-
* Usage:
|
|
1425
|
-
* Import this module to access HTTP guards and filters.
|
|
1426
|
-
* Guards and filters are registered globally in the main application.
|
|
1427
|
-
*/
|
|
1428
|
-
declare class HttpModule {
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
/**
|
|
1432
|
-
* CSRF Guard
|
|
1433
|
-
*
|
|
1434
|
-
* Global guard that automatically protects all state-changing requests (POST, PUT, PATCH, DELETE)
|
|
1435
|
-
* from CSRF attacks using Fastify's csrf-protection plugin.
|
|
1436
|
-
*
|
|
1437
|
-
* Flow:
|
|
1438
|
-
* 1. Skip safe methods (GET, HEAD, OPTIONS)
|
|
1439
|
-
* 2. Skip endpoints marked with @Public()
|
|
1440
|
-
* 3. Validate CSRF token for all other requests
|
|
1441
|
-
*
|
|
1442
|
-
* Token Sources (in priority order by @fastify/csrf-protection):
|
|
1443
|
-
* 1. req.headers['csrf-token']
|
|
1444
|
-
* 2. req.headers['xsrf-token']
|
|
1445
|
-
* 3. req.headers['x-csrf-token']
|
|
1446
|
-
* 4. req.headers['x-xsrf-token']
|
|
1447
|
-
* 5. req.body._csrf
|
|
1448
|
-
*
|
|
1449
|
-
* This guard should be registered globally in main.ts after CSRF plugin registration.
|
|
1450
|
-
*/
|
|
1451
|
-
declare class CsrfGuard implements CanActivate {
|
|
1452
|
-
private reflector;
|
|
1453
|
-
private readonly logger;
|
|
1454
|
-
constructor(reflector: Reflector);
|
|
1455
|
-
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
/**
|
|
1459
|
-
* Converts an HTTP status code to its corresponding title string.
|
|
1460
|
-
* Uses the HttpStatus enum to map status codes to human-readable titles.
|
|
1461
|
-
*
|
|
1462
|
-
* @param status - The HTTP status code
|
|
1463
|
-
* @returns The human-readable title for the status code
|
|
1464
|
-
*
|
|
1465
|
-
* @example
|
|
1466
|
-
* getHttpStatusTitle(400) // Returns: "Bad Request"
|
|
1467
|
-
* getHttpStatusTitle(404) // Returns: "Not Found"
|
|
1468
|
-
* getHttpStatusTitle(500) // Returns: "Internal Server Error"
|
|
1469
|
-
*/
|
|
1470
|
-
declare function getHttpStatusTitle(status: number): string;
|
|
1471
|
-
/**
|
|
1472
|
-
* Global HTTP Exception Filter implementing RFC 7807 Problem Details
|
|
1473
|
-
*
|
|
1474
|
-
* Transforms all exceptions into a standardized RFC 7807 format:
|
|
1475
|
-
* {
|
|
1476
|
-
* title: string, // Human-readable status title
|
|
1477
|
-
* status: number, // HTTP status code
|
|
1478
|
-
* detail: string, // Detailed error description
|
|
1479
|
-
* errors: FieldError[] // Field-specific error messages
|
|
1480
|
-
* }
|
|
1481
|
-
*
|
|
1482
|
-
* Handles:
|
|
1483
|
-
* - Custom field exceptions from @vritti/api-sdk (BaseFieldException)
|
|
1484
|
-
* - Class-validator DTO validation errors
|
|
1485
|
-
* - Standard NestJS HTTP exceptions
|
|
1486
|
-
* - Unknown errors
|
|
1487
|
-
*/
|
|
1488
|
-
declare class HttpExceptionFilter implements ExceptionFilter {
|
|
1489
|
-
private readonly logger;
|
|
1490
|
-
catch(exception: unknown, host: ArgumentsHost): void;
|
|
1567
|
+
*
|
|
1568
|
+
* // Count active products
|
|
1569
|
+
* const activeCount = await productRepository.count(
|
|
1570
|
+
* eq(products.status, 'ACTIVE')
|
|
1571
|
+
* );
|
|
1572
|
+
* ```
|
|
1573
|
+
*/
|
|
1574
|
+
count(where?: SQL): Promise<number>;
|
|
1575
|
+
/**
|
|
1576
|
+
* Check if a record exists
|
|
1577
|
+
*
|
|
1578
|
+
* @param where - SQL condition to match records
|
|
1579
|
+
* @returns Promise resolving to true if at least one record exists, false otherwise
|
|
1580
|
+
*
|
|
1581
|
+
* @example
|
|
1582
|
+
* ```typescript
|
|
1583
|
+
* import { eq } from 'drizzle-orm';
|
|
1584
|
+
*
|
|
1585
|
+
* const skuExists = await productRepository.exists(
|
|
1586
|
+
* eq(products.sku, 'WDG-001')
|
|
1587
|
+
* );
|
|
1588
|
+
* ```
|
|
1589
|
+
*/
|
|
1590
|
+
exists(where: SQL): Promise<boolean>;
|
|
1491
1591
|
}
|
|
1492
1592
|
|
|
1493
1593
|
interface FieldError {
|
|
@@ -1507,6 +1607,29 @@ declare abstract class BaseFieldException extends HttpException {
|
|
|
1507
1607
|
constructor(statusOrMessageOrErrors: HttpStatus | string | FieldError[], messageOrStatus?: string | HttpStatus, statusOrDetail?: HttpStatus | string, detail?: string);
|
|
1508
1608
|
}
|
|
1509
1609
|
|
|
1610
|
+
/**
|
|
1611
|
+
* Exception thrown when a gateway or proxy receives an invalid response (HTTP 502).
|
|
1612
|
+
* Used when a server acting as a gateway gets an error from an upstream server.
|
|
1613
|
+
*
|
|
1614
|
+
* @example
|
|
1615
|
+
* // Simple message
|
|
1616
|
+
* throw new BadGatewayException('Bad gateway');
|
|
1617
|
+
*
|
|
1618
|
+
* // Field-specific error
|
|
1619
|
+
* throw new BadGatewayException('upstream', 'Upstream service returned invalid response');
|
|
1620
|
+
*
|
|
1621
|
+
* // With detail
|
|
1622
|
+
* throw new BadGatewayException('proxy', 'Gateway error', 'Payment service is not responding correctly');
|
|
1623
|
+
*
|
|
1624
|
+
* // Multiple field errors
|
|
1625
|
+
* throw new BadGatewayException([
|
|
1626
|
+
* { field: 'gateway', message: 'Invalid response from upstream server' }
|
|
1627
|
+
* ]);
|
|
1628
|
+
*/
|
|
1629
|
+
declare class BadGatewayException extends BaseFieldException {
|
|
1630
|
+
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1510
1633
|
/**
|
|
1511
1634
|
* Exception thrown when a request is malformed or contains invalid data (HTTP 400).
|
|
1512
1635
|
*
|
|
@@ -1531,24 +1654,25 @@ declare class BadRequestException extends BaseFieldException {
|
|
|
1531
1654
|
}
|
|
1532
1655
|
|
|
1533
1656
|
/**
|
|
1534
|
-
* Exception thrown when
|
|
1657
|
+
* Exception thrown when a request conflicts with the current state (HTTP 409).
|
|
1658
|
+
* Commonly used for duplicate resources or concurrent modification issues.
|
|
1535
1659
|
*
|
|
1536
1660
|
* @example
|
|
1537
1661
|
* // Simple message
|
|
1538
|
-
* throw new
|
|
1662
|
+
* throw new ConflictException('Resource already exists');
|
|
1539
1663
|
*
|
|
1540
1664
|
* // Field-specific error
|
|
1541
|
-
* throw new
|
|
1665
|
+
* throw new ConflictException('email', 'Email already registered');
|
|
1542
1666
|
*
|
|
1543
1667
|
* // With detail
|
|
1544
|
-
* throw new
|
|
1668
|
+
* throw new ConflictException('email', 'Email already exists', 'Try logging in instead');
|
|
1545
1669
|
*
|
|
1546
1670
|
* // Multiple field errors
|
|
1547
|
-
* throw new
|
|
1548
|
-
* { field: '
|
|
1671
|
+
* throw new ConflictException([
|
|
1672
|
+
* { field: 'email', message: 'Email already in use' }
|
|
1549
1673
|
* ]);
|
|
1550
1674
|
*/
|
|
1551
|
-
declare class
|
|
1675
|
+
declare class ConflictException extends BaseFieldException {
|
|
1552
1676
|
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
1553
1677
|
}
|
|
1554
1678
|
|
|
@@ -1575,275 +1699,276 @@ declare class ForbiddenException extends BaseFieldException {
|
|
|
1575
1699
|
}
|
|
1576
1700
|
|
|
1577
1701
|
/**
|
|
1578
|
-
* Exception thrown when a
|
|
1702
|
+
* Exception thrown when a resource has been permanently removed (HTTP 410).
|
|
1703
|
+
* Unlike 404, this indicates the resource existed but is intentionally gone.
|
|
1579
1704
|
*
|
|
1580
1705
|
* @example
|
|
1581
1706
|
* // Simple message
|
|
1582
|
-
* throw new
|
|
1707
|
+
* throw new GoneException('Resource permanently deleted');
|
|
1583
1708
|
*
|
|
1584
1709
|
* // Field-specific error
|
|
1585
|
-
* throw new
|
|
1710
|
+
* throw new GoneException('account', 'Account has been permanently deleted');
|
|
1586
1711
|
*
|
|
1587
1712
|
* // With detail
|
|
1588
|
-
* throw new
|
|
1713
|
+
* throw new GoneException('account', 'Deleted', 'This account was removed on user request');
|
|
1589
1714
|
*
|
|
1590
1715
|
* // Multiple field errors
|
|
1591
|
-
* throw new
|
|
1592
|
-
* { field: '
|
|
1716
|
+
* throw new GoneException([
|
|
1717
|
+
* { field: 'resource', message: 'This content has been permanently removed' }
|
|
1593
1718
|
* ]);
|
|
1594
1719
|
*/
|
|
1595
|
-
declare class
|
|
1720
|
+
declare class GoneException extends BaseFieldException {
|
|
1596
1721
|
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
1597
1722
|
}
|
|
1598
1723
|
|
|
1599
1724
|
/**
|
|
1600
|
-
* Exception thrown when
|
|
1601
|
-
* Commonly used for duplicate resources or concurrent modification issues.
|
|
1725
|
+
* Exception thrown when an unexpected server error occurs (HTTP 500).
|
|
1602
1726
|
*
|
|
1603
1727
|
* @example
|
|
1604
1728
|
* // Simple message
|
|
1605
|
-
* throw new
|
|
1729
|
+
* throw new InternalServerErrorException('An unexpected error occurred');
|
|
1606
1730
|
*
|
|
1607
1731
|
* // Field-specific error
|
|
1608
|
-
* throw new
|
|
1732
|
+
* throw new InternalServerErrorException('database', 'Database connection failed');
|
|
1609
1733
|
*
|
|
1610
1734
|
* // With detail
|
|
1611
|
-
* throw new
|
|
1735
|
+
* throw new InternalServerErrorException('database', 'Connection failed', 'Please try again later');
|
|
1612
1736
|
*
|
|
1613
1737
|
* // Multiple field errors
|
|
1614
|
-
* throw new
|
|
1615
|
-
* { field: '
|
|
1738
|
+
* throw new InternalServerErrorException([
|
|
1739
|
+
* { field: 'system', message: 'Internal error' }
|
|
1616
1740
|
* ]);
|
|
1617
1741
|
*/
|
|
1618
|
-
declare class
|
|
1742
|
+
declare class InternalServerErrorException extends BaseFieldException {
|
|
1619
1743
|
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
1620
1744
|
}
|
|
1621
1745
|
|
|
1622
1746
|
/**
|
|
1623
|
-
* Exception thrown when an
|
|
1747
|
+
* Exception thrown when an HTTP method is not supported for the endpoint (HTTP 405).
|
|
1748
|
+
* For example, when a POST is sent to a GET-only endpoint.
|
|
1624
1749
|
*
|
|
1625
1750
|
* @example
|
|
1626
1751
|
* // Simple message
|
|
1627
|
-
* throw new
|
|
1752
|
+
* throw new MethodNotAllowedException('Method not allowed');
|
|
1628
1753
|
*
|
|
1629
1754
|
* // Field-specific error
|
|
1630
|
-
* throw new
|
|
1755
|
+
* throw new MethodNotAllowedException('method', 'POST method not allowed on this endpoint');
|
|
1631
1756
|
*
|
|
1632
1757
|
* // With detail
|
|
1633
|
-
* throw new
|
|
1758
|
+
* throw new MethodNotAllowedException('method', 'Not allowed', 'Only GET and PUT are supported');
|
|
1634
1759
|
*
|
|
1635
1760
|
* // Multiple field errors
|
|
1636
|
-
* throw new
|
|
1637
|
-
* { field: '
|
|
1761
|
+
* throw new MethodNotAllowedException([
|
|
1762
|
+
* { field: 'method', message: 'DELETE is not allowed on this resource' }
|
|
1638
1763
|
* ]);
|
|
1639
1764
|
*/
|
|
1640
|
-
declare class
|
|
1765
|
+
declare class MethodNotAllowedException extends BaseFieldException {
|
|
1641
1766
|
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
1642
1767
|
}
|
|
1643
1768
|
|
|
1644
1769
|
/**
|
|
1645
|
-
* Exception thrown when
|
|
1646
|
-
*
|
|
1770
|
+
* Exception thrown when content negotiation fails (HTTP 406).
|
|
1771
|
+
* Used when the server cannot produce a response matching the Accept headers.
|
|
1647
1772
|
*
|
|
1648
1773
|
* @example
|
|
1649
|
-
* //
|
|
1650
|
-
* throw new
|
|
1651
|
-
*
|
|
1652
|
-
*
|
|
1653
|
-
*
|
|
1774
|
+
* // Simple message
|
|
1775
|
+
* throw new NotAcceptableException('Requested format not available');
|
|
1776
|
+
*
|
|
1777
|
+
* // Field-specific error
|
|
1778
|
+
* throw new NotAcceptableException('accept', 'Cannot produce response in requested format');
|
|
1654
1779
|
*
|
|
1655
1780
|
* // With detail
|
|
1656
|
-
* throw new
|
|
1657
|
-
*
|
|
1658
|
-
*
|
|
1659
|
-
*
|
|
1781
|
+
* throw new NotAcceptableException('accept', 'Format not supported', 'Only JSON is available');
|
|
1782
|
+
*
|
|
1783
|
+
* // Multiple field errors
|
|
1784
|
+
* throw new NotAcceptableException([
|
|
1785
|
+
* { field: 'contentType', message: 'XML format is not supported' }
|
|
1786
|
+
* ]);
|
|
1660
1787
|
*/
|
|
1661
|
-
declare class
|
|
1662
|
-
constructor(
|
|
1788
|
+
declare class NotAcceptableException extends BaseFieldException {
|
|
1789
|
+
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
1663
1790
|
}
|
|
1664
1791
|
|
|
1665
1792
|
/**
|
|
1666
|
-
* Exception thrown when
|
|
1667
|
-
* Used for business logic validation failures that prevent processing.
|
|
1793
|
+
* Exception thrown when a requested resource cannot be found (HTTP 404).
|
|
1668
1794
|
*
|
|
1669
1795
|
* @example
|
|
1670
1796
|
* // Simple message
|
|
1671
|
-
* throw new
|
|
1797
|
+
* throw new NotFoundException('Resource not found');
|
|
1672
1798
|
*
|
|
1673
1799
|
* // Field-specific error
|
|
1674
|
-
* throw new
|
|
1800
|
+
* throw new NotFoundException('userId', 'User not found');
|
|
1675
1801
|
*
|
|
1676
1802
|
* // With detail
|
|
1677
|
-
* throw new
|
|
1803
|
+
* throw new NotFoundException('userId', 'User not found', 'No user exists with the provided ID');
|
|
1678
1804
|
*
|
|
1679
1805
|
* // Multiple field errors
|
|
1680
|
-
* throw new
|
|
1681
|
-
* { field: '
|
|
1682
|
-
* { field: 'endDate', message: 'End date cannot be in the past' }
|
|
1806
|
+
* throw new NotFoundException([
|
|
1807
|
+
* { field: 'userId', message: 'User does not exist' }
|
|
1683
1808
|
* ]);
|
|
1684
1809
|
*/
|
|
1685
|
-
declare class
|
|
1810
|
+
declare class NotFoundException extends BaseFieldException {
|
|
1686
1811
|
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
1687
1812
|
}
|
|
1688
1813
|
|
|
1689
1814
|
/**
|
|
1690
|
-
* Exception thrown when
|
|
1691
|
-
* Used
|
|
1815
|
+
* Exception thrown when a feature or endpoint is not yet implemented (HTTP 501).
|
|
1816
|
+
* Used for planned but unavailable functionality.
|
|
1692
1817
|
*
|
|
1693
1818
|
* @example
|
|
1694
1819
|
* // Simple message
|
|
1695
|
-
* throw new
|
|
1820
|
+
* throw new NotImplementedException('Feature not yet implemented');
|
|
1696
1821
|
*
|
|
1697
1822
|
* // Field-specific error
|
|
1698
|
-
* throw new
|
|
1823
|
+
* throw new NotImplementedException('feature', 'This feature is coming soon');
|
|
1699
1824
|
*
|
|
1700
1825
|
* // With detail
|
|
1701
|
-
* throw new
|
|
1826
|
+
* throw new NotImplementedException('export', 'Not implemented', 'PDF export will be available in v2.0');
|
|
1702
1827
|
*
|
|
1703
1828
|
* // Multiple field errors
|
|
1704
|
-
* throw new
|
|
1705
|
-
* { field: '
|
|
1829
|
+
* throw new NotImplementedException([
|
|
1830
|
+
* { field: 'functionality', message: 'This functionality is not available yet' }
|
|
1706
1831
|
* ]);
|
|
1707
1832
|
*/
|
|
1708
|
-
declare class
|
|
1833
|
+
declare class NotImplementedException extends BaseFieldException {
|
|
1709
1834
|
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
1710
1835
|
}
|
|
1711
1836
|
|
|
1712
1837
|
/**
|
|
1713
|
-
* Exception thrown when
|
|
1714
|
-
*
|
|
1838
|
+
* Exception thrown when request payload exceeds size limits (HTTP 413).
|
|
1839
|
+
* Commonly used for file upload size restrictions or large request bodies.
|
|
1715
1840
|
*
|
|
1716
1841
|
* @example
|
|
1717
1842
|
* // Simple message
|
|
1718
|
-
* throw new
|
|
1843
|
+
* throw new PayloadTooLargeException('Request payload too large');
|
|
1719
1844
|
*
|
|
1720
1845
|
* // Field-specific error
|
|
1721
|
-
* throw new
|
|
1846
|
+
* throw new PayloadTooLargeException('file', 'File size exceeds maximum allowed');
|
|
1722
1847
|
*
|
|
1723
1848
|
* // With detail
|
|
1724
|
-
* throw new
|
|
1849
|
+
* throw new PayloadTooLargeException('file', 'File too large', 'Maximum size is 10MB');
|
|
1725
1850
|
*
|
|
1726
1851
|
* // Multiple field errors
|
|
1727
|
-
* throw new
|
|
1728
|
-
* { field: '
|
|
1852
|
+
* throw new PayloadTooLargeException([
|
|
1853
|
+
* { field: 'upload', message: 'File exceeds 10MB limit' }
|
|
1729
1854
|
* ]);
|
|
1730
1855
|
*/
|
|
1731
|
-
declare class
|
|
1856
|
+
declare class PayloadTooLargeException extends BaseFieldException {
|
|
1732
1857
|
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
1733
1858
|
}
|
|
1734
1859
|
|
|
1735
1860
|
/**
|
|
1736
|
-
* Exception thrown when
|
|
1737
|
-
*
|
|
1861
|
+
* Exception thrown when a request takes too long to process (HTTP 408).
|
|
1862
|
+
* Used when the client or server times out while waiting for completion.
|
|
1738
1863
|
*
|
|
1739
1864
|
* @example
|
|
1740
1865
|
* // Simple message
|
|
1741
|
-
* throw new
|
|
1866
|
+
* throw new RequestTimeoutException('Request timeout');
|
|
1742
1867
|
*
|
|
1743
1868
|
* // Field-specific error
|
|
1744
|
-
* throw new
|
|
1869
|
+
* throw new RequestTimeoutException('operation', 'Operation timed out');
|
|
1745
1870
|
*
|
|
1746
1871
|
* // With detail
|
|
1747
|
-
* throw new
|
|
1872
|
+
* throw new RequestTimeoutException('query', 'Database query timeout', 'Try with fewer filters');
|
|
1748
1873
|
*
|
|
1749
1874
|
* // Multiple field errors
|
|
1750
|
-
* throw new
|
|
1751
|
-
* { field: '
|
|
1875
|
+
* throw new RequestTimeoutException([
|
|
1876
|
+
* { field: 'processing', message: 'Request took too long to complete' }
|
|
1752
1877
|
* ]);
|
|
1753
1878
|
*/
|
|
1754
|
-
declare class
|
|
1879
|
+
declare class RequestTimeoutException extends BaseFieldException {
|
|
1755
1880
|
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
1756
1881
|
}
|
|
1757
1882
|
|
|
1758
1883
|
/**
|
|
1759
|
-
* Exception thrown when
|
|
1760
|
-
*
|
|
1884
|
+
* Exception thrown when the service is temporarily unavailable (HTTP 503).
|
|
1885
|
+
* Used during maintenance, overload, or temporary outages.
|
|
1761
1886
|
*
|
|
1762
1887
|
* @example
|
|
1763
1888
|
* // Simple message
|
|
1764
|
-
* throw new
|
|
1889
|
+
* throw new ServiceUnavailableException('Service temporarily unavailable');
|
|
1765
1890
|
*
|
|
1766
1891
|
* // Field-specific error
|
|
1767
|
-
* throw new
|
|
1892
|
+
* throw new ServiceUnavailableException('service', 'Scheduled maintenance in progress');
|
|
1768
1893
|
*
|
|
1769
1894
|
* // With detail
|
|
1770
|
-
* throw new
|
|
1895
|
+
* throw new ServiceUnavailableException('service', 'Maintenance', 'Service will be back at 2 PM EST');
|
|
1771
1896
|
*
|
|
1772
1897
|
* // Multiple field errors
|
|
1773
|
-
* throw new
|
|
1774
|
-
* { field: '
|
|
1898
|
+
* throw new ServiceUnavailableException([
|
|
1899
|
+
* { field: 'database', message: 'Database is temporarily unavailable' }
|
|
1775
1900
|
* ]);
|
|
1776
1901
|
*/
|
|
1777
|
-
declare class
|
|
1902
|
+
declare class ServiceUnavailableException extends BaseFieldException {
|
|
1778
1903
|
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
1779
1904
|
}
|
|
1780
1905
|
|
|
1781
1906
|
/**
|
|
1782
|
-
* Exception thrown when
|
|
1783
|
-
* Used
|
|
1907
|
+
* Exception thrown when rate limiting is triggered (HTTP 429).
|
|
1908
|
+
* Used to prevent abuse and ensure fair resource usage.
|
|
1784
1909
|
*
|
|
1785
1910
|
* @example
|
|
1786
1911
|
* // Simple message
|
|
1787
|
-
* throw new
|
|
1912
|
+
* throw new TooManyRequestsException('Too many requests');
|
|
1788
1913
|
*
|
|
1789
1914
|
* // Field-specific error
|
|
1790
|
-
* throw new
|
|
1915
|
+
* throw new TooManyRequestsException('api', 'Rate limit exceeded');
|
|
1791
1916
|
*
|
|
1792
1917
|
* // With detail
|
|
1793
|
-
* throw new
|
|
1918
|
+
* throw new TooManyRequestsException('api', 'Rate limit exceeded', 'Try again in 60 seconds');
|
|
1794
1919
|
*
|
|
1795
1920
|
* // Multiple field errors
|
|
1796
|
-
* throw new
|
|
1797
|
-
* { field: '
|
|
1921
|
+
* throw new TooManyRequestsException([
|
|
1922
|
+
* { field: 'requests', message: 'Rate limit exceeded for this endpoint' }
|
|
1798
1923
|
* ]);
|
|
1799
1924
|
*/
|
|
1800
|
-
declare class
|
|
1925
|
+
declare class TooManyRequestsException extends BaseFieldException {
|
|
1801
1926
|
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
1802
1927
|
}
|
|
1803
1928
|
|
|
1804
1929
|
/**
|
|
1805
|
-
* Exception thrown when
|
|
1806
|
-
* Used when the client or server times out while waiting for completion.
|
|
1930
|
+
* Exception thrown when authentication is required or has failed (HTTP 401).
|
|
1807
1931
|
*
|
|
1808
1932
|
* @example
|
|
1809
1933
|
* // Simple message
|
|
1810
|
-
* throw new
|
|
1934
|
+
* throw new UnauthorizedException('Authentication required');
|
|
1811
1935
|
*
|
|
1812
1936
|
* // Field-specific error
|
|
1813
|
-
* throw new
|
|
1937
|
+
* throw new UnauthorizedException('token', 'Invalid or expired token');
|
|
1814
1938
|
*
|
|
1815
1939
|
* // With detail
|
|
1816
|
-
* throw new
|
|
1940
|
+
* throw new UnauthorizedException('token', 'Invalid token', 'Please login again');
|
|
1817
1941
|
*
|
|
1818
1942
|
* // Multiple field errors
|
|
1819
|
-
* throw new
|
|
1820
|
-
* { field: '
|
|
1943
|
+
* throw new UnauthorizedException([
|
|
1944
|
+
* { field: 'token', message: 'Token expired' }
|
|
1821
1945
|
* ]);
|
|
1822
1946
|
*/
|
|
1823
|
-
declare class
|
|
1947
|
+
declare class UnauthorizedException extends BaseFieldException {
|
|
1824
1948
|
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
1825
1949
|
}
|
|
1826
1950
|
|
|
1827
1951
|
/**
|
|
1828
|
-
* Exception thrown when request
|
|
1829
|
-
*
|
|
1952
|
+
* Exception thrown when the request is well-formed but contains semantic errors (HTTP 422).
|
|
1953
|
+
* Used for business logic validation failures that prevent processing.
|
|
1830
1954
|
*
|
|
1831
1955
|
* @example
|
|
1832
1956
|
* // Simple message
|
|
1833
|
-
* throw new
|
|
1957
|
+
* throw new UnprocessableEntityException('Cannot process the request');
|
|
1834
1958
|
*
|
|
1835
1959
|
* // Field-specific error
|
|
1836
|
-
* throw new
|
|
1960
|
+
* throw new UnprocessableEntityException('age', 'Age must be 18 or older');
|
|
1837
1961
|
*
|
|
1838
1962
|
* // With detail
|
|
1839
|
-
* throw new
|
|
1963
|
+
* throw new UnprocessableEntityException('quantity', 'Insufficient stock', 'Only 5 items available');
|
|
1840
1964
|
*
|
|
1841
1965
|
* // Multiple field errors
|
|
1842
|
-
* throw new
|
|
1843
|
-
* { field: '
|
|
1966
|
+
* throw new UnprocessableEntityException([
|
|
1967
|
+
* { field: 'startDate', message: 'Start date must be before end date' },
|
|
1968
|
+
* { field: 'endDate', message: 'End date cannot be in the past' }
|
|
1844
1969
|
* ]);
|
|
1845
1970
|
*/
|
|
1846
|
-
declare class
|
|
1971
|
+
declare class UnprocessableEntityException extends BaseFieldException {
|
|
1847
1972
|
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
1848
1973
|
}
|
|
1849
1974
|
|
|
@@ -1871,49 +1996,98 @@ declare class UnsupportedMediaTypeException extends BaseFieldException {
|
|
|
1871
1996
|
}
|
|
1872
1997
|
|
|
1873
1998
|
/**
|
|
1874
|
-
* Exception thrown when
|
|
1875
|
-
*
|
|
1999
|
+
* Exception thrown when request validation fails (HTTP 400).
|
|
2000
|
+
* Typically used for form validation or DTO validation errors.
|
|
1876
2001
|
*
|
|
1877
2002
|
* @example
|
|
1878
|
-
* //
|
|
1879
|
-
* throw new
|
|
1880
|
-
*
|
|
1881
|
-
*
|
|
1882
|
-
*
|
|
2003
|
+
* // Multiple validation errors
|
|
2004
|
+
* throw new ValidationException([
|
|
2005
|
+
* { field: 'email', message: 'Invalid email format' },
|
|
2006
|
+
* { field: 'password', message: 'Password must be at least 8 characters' }
|
|
2007
|
+
* ]);
|
|
1883
2008
|
*
|
|
1884
2009
|
* // With detail
|
|
1885
|
-
* throw new
|
|
1886
|
-
*
|
|
1887
|
-
*
|
|
1888
|
-
*
|
|
1889
|
-
* { field: 'functionality', message: 'This functionality is not available yet' }
|
|
1890
|
-
* ]);
|
|
2010
|
+
* throw new ValidationException(
|
|
2011
|
+
* [{ field: 'email', message: 'Invalid format' }],
|
|
2012
|
+
* 'Please correct the errors and try again'
|
|
2013
|
+
* );
|
|
1891
2014
|
*/
|
|
1892
|
-
declare class
|
|
1893
|
-
constructor(
|
|
2015
|
+
declare class ValidationException extends BaseFieldException {
|
|
2016
|
+
constructor(errors: FieldError[], detail?: string);
|
|
1894
2017
|
}
|
|
1895
2018
|
|
|
1896
2019
|
/**
|
|
1897
|
-
*
|
|
1898
|
-
*
|
|
2020
|
+
* Converts an HTTP status code to its corresponding title string.
|
|
2021
|
+
* Uses the HttpStatus enum to map status codes to human-readable titles.
|
|
2022
|
+
*
|
|
2023
|
+
* @param status - The HTTP status code
|
|
2024
|
+
* @returns The human-readable title for the status code
|
|
1899
2025
|
*
|
|
1900
2026
|
* @example
|
|
1901
|
-
* //
|
|
1902
|
-
*
|
|
2027
|
+
* getHttpStatusTitle(400) // Returns: "Bad Request"
|
|
2028
|
+
* getHttpStatusTitle(404) // Returns: "Not Found"
|
|
2029
|
+
* getHttpStatusTitle(500) // Returns: "Internal Server Error"
|
|
2030
|
+
*/
|
|
2031
|
+
declare function getHttpStatusTitle(status: number): string;
|
|
2032
|
+
/**
|
|
2033
|
+
* Global HTTP Exception Filter implementing RFC 7807 Problem Details
|
|
1903
2034
|
*
|
|
1904
|
-
*
|
|
1905
|
-
*
|
|
2035
|
+
* Transforms all exceptions into a standardized RFC 7807 format:
|
|
2036
|
+
* {
|
|
2037
|
+
* title: string, // Human-readable status title
|
|
2038
|
+
* status: number, // HTTP status code
|
|
2039
|
+
* detail: string, // Detailed error description
|
|
2040
|
+
* errors: FieldError[] // Field-specific error messages
|
|
2041
|
+
* }
|
|
1906
2042
|
*
|
|
1907
|
-
*
|
|
1908
|
-
*
|
|
2043
|
+
* Handles:
|
|
2044
|
+
* - Custom field exceptions from @vritti/api-sdk (BaseFieldException)
|
|
2045
|
+
* - Class-validator DTO validation errors
|
|
2046
|
+
* - Standard NestJS HTTP exceptions
|
|
2047
|
+
* - Unknown errors
|
|
2048
|
+
*/
|
|
2049
|
+
declare class HttpExceptionFilter implements ExceptionFilter {
|
|
2050
|
+
private readonly logger;
|
|
2051
|
+
catch(exception: unknown, host: ArgumentsHost): void;
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
/**
|
|
2055
|
+
* CSRF Guard
|
|
1909
2056
|
*
|
|
1910
|
-
*
|
|
1911
|
-
*
|
|
1912
|
-
*
|
|
1913
|
-
*
|
|
2057
|
+
* Global guard that automatically protects all state-changing requests (POST, PUT, PATCH, DELETE)
|
|
2058
|
+
* from CSRF attacks using Fastify's csrf-protection plugin.
|
|
2059
|
+
*
|
|
2060
|
+
* Flow:
|
|
2061
|
+
* 1. Skip safe methods (GET, HEAD, OPTIONS)
|
|
2062
|
+
* 2. Skip endpoints marked with @Public()
|
|
2063
|
+
* 3. Validate CSRF token for all other requests
|
|
2064
|
+
*
|
|
2065
|
+
* Token Sources (in priority order by @fastify/csrf-protection):
|
|
2066
|
+
* 1. req.headers['csrf-token']
|
|
2067
|
+
* 2. req.headers['xsrf-token']
|
|
2068
|
+
* 3. req.headers['x-csrf-token']
|
|
2069
|
+
* 4. req.headers['x-xsrf-token']
|
|
2070
|
+
* 5. req.body._csrf
|
|
2071
|
+
*
|
|
2072
|
+
* This guard should be registered globally in main.ts after CSRF plugin registration.
|
|
1914
2073
|
*/
|
|
1915
|
-
declare class
|
|
1916
|
-
|
|
2074
|
+
declare class CsrfGuard implements CanActivate {
|
|
2075
|
+
private readonly logger;
|
|
2076
|
+
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
/**
|
|
2080
|
+
* HTTP Module
|
|
2081
|
+
*
|
|
2082
|
+
* Provides HTTP utilities including:
|
|
2083
|
+
* - CSRF Guard for request protection
|
|
2084
|
+
* - HTTP Exception Filter for standardized error responses
|
|
2085
|
+
*
|
|
2086
|
+
* Usage:
|
|
2087
|
+
* Import this module to access HTTP guards and filters.
|
|
2088
|
+
* Guards and filters are registered globally in the main application.
|
|
2089
|
+
*/
|
|
2090
|
+
declare class HttpModule {
|
|
1917
2091
|
}
|
|
1918
2092
|
|
|
1919
2093
|
/**
|
|
@@ -1990,6 +2164,77 @@ interface HttpLoggerOptions {
|
|
|
1990
2164
|
maxBodySize?: number;
|
|
1991
2165
|
}
|
|
1992
2166
|
|
|
2167
|
+
/**
|
|
2168
|
+
* Unified Logger Service
|
|
2169
|
+
*
|
|
2170
|
+
* Single service that provides both default NestJS Logger and Winston logger implementations.
|
|
2171
|
+
* Automatically delegates to the configured provider (default or winston).
|
|
2172
|
+
* @module logger/logger.service
|
|
2173
|
+
*/
|
|
2174
|
+
|
|
2175
|
+
/**
|
|
2176
|
+
* Unified logger service implementing NestJS LoggerService interface.
|
|
2177
|
+
* Supports both default NestJS Logger and Winston implementations via facade pattern.
|
|
2178
|
+
*/
|
|
2179
|
+
declare class LoggerService implements LoggerService$1 {
|
|
2180
|
+
private readonly defaultLogger?;
|
|
2181
|
+
private readonly activeLogger;
|
|
2182
|
+
private readonly options;
|
|
2183
|
+
private context?;
|
|
2184
|
+
constructor(options?: LoggerModuleOptions, defaultLogger?: Logger | undefined);
|
|
2185
|
+
/**
|
|
2186
|
+
* Creates a Winston logger instance with inline configuration.
|
|
2187
|
+
* Consolidates winston-config.factory.ts logic.
|
|
2188
|
+
*/
|
|
2189
|
+
private createWinstonLogger;
|
|
2190
|
+
log(message: any, context?: string): void;
|
|
2191
|
+
error(message: any, trace?: string, context?: string): void;
|
|
2192
|
+
warn(message: any, context?: string): void;
|
|
2193
|
+
debug(message: any, context?: string): void;
|
|
2194
|
+
verbose(message: any, context?: string): void;
|
|
2195
|
+
setContext(context: string): void;
|
|
2196
|
+
/**
|
|
2197
|
+
* Unified internal logging method that handles both Winston and NestJS Logger.
|
|
2198
|
+
*/
|
|
2199
|
+
private _log;
|
|
2200
|
+
/**
|
|
2201
|
+
* Logs with custom metadata (Winston only).
|
|
2202
|
+
*/
|
|
2203
|
+
logWithMetadata(level: LogLevel, message: any, metadata?: LogMetadata, context?: string): void;
|
|
2204
|
+
private formatMessage;
|
|
2205
|
+
/**
|
|
2206
|
+
* Enriches metadata with correlation context from AsyncLocalStorage.
|
|
2207
|
+
* Inline from winston-logger.service.ts
|
|
2208
|
+
*/
|
|
2209
|
+
private enrichMetadata;
|
|
2210
|
+
child(context: string): LoggerService;
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
/**
|
|
2214
|
+
* HTTP Logger Interceptor
|
|
2215
|
+
*
|
|
2216
|
+
* Automatically logs HTTP requests and responses with correlation tracking.
|
|
2217
|
+
* @module logger/http-logger.interceptor
|
|
2218
|
+
*/
|
|
2219
|
+
|
|
2220
|
+
/**
|
|
2221
|
+
* HTTP Logger Interceptor for NestJS applications.
|
|
2222
|
+
*
|
|
2223
|
+
* Logs all HTTP requests and responses with metadata including
|
|
2224
|
+
* correlation IDs, performance metrics, and error details.
|
|
2225
|
+
*/
|
|
2226
|
+
declare class HttpLoggerInterceptor implements NestInterceptor {
|
|
2227
|
+
private readonly logger;
|
|
2228
|
+
private readonly enableRequestLog;
|
|
2229
|
+
private readonly enableResponseLog;
|
|
2230
|
+
private readonly slowRequestThreshold;
|
|
2231
|
+
constructor(logger: LoggerService, options?: HttpLoggerOptions);
|
|
2232
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
|
|
2233
|
+
private logRequest;
|
|
2234
|
+
private logResponse;
|
|
2235
|
+
private logError;
|
|
2236
|
+
}
|
|
2237
|
+
|
|
1993
2238
|
/**
|
|
1994
2239
|
* Logger Module
|
|
1995
2240
|
*
|
|
@@ -2152,7 +2397,7 @@ declare class LoggerModule implements NestModule {
|
|
|
2152
2397
|
* Configures middleware for the module.
|
|
2153
2398
|
* Middleware is registered globally in main.ts using Fastify hooks.
|
|
2154
2399
|
*/
|
|
2155
|
-
configure(
|
|
2400
|
+
configure(_consumer: MiddlewareConsumer): void;
|
|
2156
2401
|
/**
|
|
2157
2402
|
* Creates async providers for dynamic module configuration.
|
|
2158
2403
|
*/
|
|
@@ -2163,52 +2408,6 @@ declare class LoggerModule implements NestModule {
|
|
|
2163
2408
|
private static createAsyncOptionsProvider;
|
|
2164
2409
|
}
|
|
2165
2410
|
|
|
2166
|
-
/**
|
|
2167
|
-
* Unified Logger Service
|
|
2168
|
-
*
|
|
2169
|
-
* Single service that provides both default NestJS Logger and Winston logger implementations.
|
|
2170
|
-
* Automatically delegates to the configured provider (default or winston).
|
|
2171
|
-
* @module logger/logger.service
|
|
2172
|
-
*/
|
|
2173
|
-
|
|
2174
|
-
/**
|
|
2175
|
-
* Unified logger service implementing NestJS LoggerService interface.
|
|
2176
|
-
* Supports both default NestJS Logger and Winston implementations via facade pattern.
|
|
2177
|
-
*/
|
|
2178
|
-
declare class LoggerService implements LoggerService$1 {
|
|
2179
|
-
private readonly defaultLogger?;
|
|
2180
|
-
private readonly activeLogger;
|
|
2181
|
-
private readonly options;
|
|
2182
|
-
private context?;
|
|
2183
|
-
constructor(options?: LoggerModuleOptions, defaultLogger?: Logger | undefined);
|
|
2184
|
-
/**
|
|
2185
|
-
* Creates a Winston logger instance with inline configuration.
|
|
2186
|
-
* Consolidates winston-config.factory.ts logic.
|
|
2187
|
-
*/
|
|
2188
|
-
private createWinstonLogger;
|
|
2189
|
-
log(message: any, context?: string): void;
|
|
2190
|
-
error(message: any, trace?: string, context?: string): void;
|
|
2191
|
-
warn(message: any, context?: string): void;
|
|
2192
|
-
debug(message: any, context?: string): void;
|
|
2193
|
-
verbose(message: any, context?: string): void;
|
|
2194
|
-
setContext(context: string): void;
|
|
2195
|
-
/**
|
|
2196
|
-
* Unified internal logging method that handles both Winston and NestJS Logger.
|
|
2197
|
-
*/
|
|
2198
|
-
private _log;
|
|
2199
|
-
/**
|
|
2200
|
-
* Logs with custom metadata (Winston only).
|
|
2201
|
-
*/
|
|
2202
|
-
logWithMetadata(level: LogLevel, message: any, metadata?: LogMetadata, context?: string): void;
|
|
2203
|
-
private formatMessage;
|
|
2204
|
-
/**
|
|
2205
|
-
* Enriches metadata with correlation context from AsyncLocalStorage.
|
|
2206
|
-
* Inline from winston-logger.service.ts
|
|
2207
|
-
*/
|
|
2208
|
-
private enrichMetadata;
|
|
2209
|
-
child(context: string): LoggerService;
|
|
2210
|
-
}
|
|
2211
|
-
|
|
2212
2411
|
/**
|
|
2213
2412
|
* Correlation ID Middleware
|
|
2214
2413
|
*
|
|
@@ -2246,38 +2445,13 @@ declare class CorrelationIdMiddleware implements NestMiddleware {
|
|
|
2246
2445
|
/**
|
|
2247
2446
|
* Middleware handler for processing requests.
|
|
2248
2447
|
*/
|
|
2249
|
-
use(
|
|
2448
|
+
use(_req: FastifyRequest, reply: FastifyReply, next: () => void): void;
|
|
2250
2449
|
/**
|
|
2251
2450
|
* Fastify hook handler for onRequest.
|
|
2252
2451
|
* This is an async function that returns a Promise, ensuring the AsyncLocalStorage
|
|
2253
2452
|
* context persists throughout the entire request lifecycle.
|
|
2254
2453
|
*/
|
|
2255
|
-
onRequest(
|
|
2256
|
-
}
|
|
2257
|
-
|
|
2258
|
-
/**
|
|
2259
|
-
* HTTP Logger Interceptor
|
|
2260
|
-
*
|
|
2261
|
-
* Automatically logs HTTP requests and responses with correlation tracking.
|
|
2262
|
-
* @module logger/http-logger.interceptor
|
|
2263
|
-
*/
|
|
2264
|
-
|
|
2265
|
-
/**
|
|
2266
|
-
* HTTP Logger Interceptor for NestJS applications.
|
|
2267
|
-
*
|
|
2268
|
-
* Logs all HTTP requests and responses with metadata including
|
|
2269
|
-
* correlation IDs, performance metrics, and error details.
|
|
2270
|
-
*/
|
|
2271
|
-
declare class HttpLoggerInterceptor implements NestInterceptor {
|
|
2272
|
-
private readonly logger;
|
|
2273
|
-
private readonly enableRequestLog;
|
|
2274
|
-
private readonly enableResponseLog;
|
|
2275
|
-
private readonly slowRequestThreshold;
|
|
2276
|
-
constructor(logger: LoggerService, options?: HttpLoggerOptions);
|
|
2277
|
-
intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
|
|
2278
|
-
private logRequest;
|
|
2279
|
-
private logResponse;
|
|
2280
|
-
private logError;
|
|
2454
|
+
onRequest(_req: FastifyRequest, reply: FastifyReply): Promise<void>;
|
|
2281
2455
|
}
|
|
2282
2456
|
|
|
2283
2457
|
/**
|
|
@@ -2317,4 +2491,4 @@ declare function generateCorrelationId(): string;
|
|
|
2317
2491
|
*/
|
|
2318
2492
|
declare function addCorrelationIdToResponse(reply: FastifyReply, correlationId: string, headerName?: string): void;
|
|
2319
2493
|
|
|
2320
|
-
export { type ApiErrorResponse, AuthConfigModule, BadGatewayException, BadRequestException, BaseFieldException, ConflictException, type CorrelationContext, CorrelationIdMiddleware, CsrfGuard, DEFAULT_CORRELATION_HEADER, DatabaseModule, type DatabaseModuleOptions, type FieldError, ForbiddenException, GoneException, HttpExceptionFilter, HttpLoggerInterceptor, type HttpLoggerOptions, HttpModule, InternalServerErrorException, LOGGER_MODULE_OPTIONS, type LogFormat, type LogLevel, type LogMetadata, LoggerModule, type LoggerModuleAsyncOptions, type LoggerModuleOptions, type LoggerOptionsFactory, LoggerService, MethodNotAllowedException, NotAcceptableException, NotFoundException, NotImplementedException, Onboarding, PayloadTooLargeException, PrimaryBaseRepository, PrimaryDatabaseService, type PrimaryDbConfig, type ProblemDetails, Public, type RegisteredSchema, RequestTimeoutException, type SchemaRegistry, ServiceUnavailableException, Tenant, TenantBaseRepository, TenantContextService, TenantDatabaseService, type TenantInfo, TooManyRequestsException, type TypedDrizzleClient, UnauthorizedException, UnprocessableEntityException, UnsupportedMediaTypeException, ValidationException, VrittiAuthGuard, addCorrelationIdToResponse, correlationStorage, generateCorrelationId, getCorrelationContext, getHttpStatusTitle, runWithCorrelationContext, updateCorrelationContext };
|
|
2494
|
+
export { type ApiErrorResponse, type ApiSdkConfig, AuthConfigModule, BadGatewayException, BadRequestException, BaseFieldException, ConflictException, type CookieConfig, type CorrelationContext, CorrelationIdMiddleware, CsrfGuard, DEFAULT_CORRELATION_HEADER, DatabaseModule, type DatabaseModuleOptions, type FieldError, ForbiddenException, GoneException, type GuardConfig, HttpExceptionFilter, HttpLoggerInterceptor, type HttpLoggerOptions, HttpModule, InternalServerErrorException, type JwtConfig, LOGGER_MODULE_OPTIONS, type LogFormat, type LogLevel, type LogMetadata, LoggerModule, type LoggerModuleAsyncOptions, type LoggerModuleOptions, type LoggerOptionsFactory, LoggerService, MethodNotAllowedException, NotAcceptableException, NotFoundException, NotImplementedException, Onboarding, PayloadTooLargeException, PrimaryBaseRepository, PrimaryDatabaseService, type PrimaryDbConfig, type ProblemDetails, Public, type RegisteredSchema, RequestTimeoutException, type SchemaRegistry, ServiceUnavailableException, Tenant, TenantBaseRepository, TenantContextService, TenantDatabaseService, type TenantInfo, TooManyRequestsException, type TypedDrizzleClient, UnauthorizedException, UnprocessableEntityException, UnsupportedMediaTypeException, ValidationException, VrittiAuthGuard, addCorrelationIdToResponse, configureApiSdk, correlationStorage, defineConfig, generateCorrelationId, getConfig, getCorrelationContext, getHttpStatusTitle, getJwtExpiry, getRefreshCookieOptions, hashToken, resetConfig, runWithCorrelationContext, updateCorrelationContext, verifyTokenHash };
|