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