@vritti/api-sdk 0.1.7 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -6
- package/dist/index.cjs +2713 -2709
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +262 -2201
- package/dist/index.d.ts +262 -2201
- package/dist/index.js +2085 -2093
- package/dist/index.js.map +1 -1
- package/package.json +37 -20
package/dist/index.d.ts
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
|
-
* // In API Gateway
|
|
317
|
-
* const config = await primaryDatabase.getTenantConfig('acme');
|
|
318
|
-
* // Returns: { id, slug, type, databaseHost, databaseName, ... }
|
|
319
|
-
*/
|
|
320
|
-
declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
|
|
321
|
-
private readonly options;
|
|
322
|
-
private readonly logger;
|
|
323
|
-
/** PostgreSQL connection pool */
|
|
324
|
-
private pool;
|
|
325
|
-
/** Drizzle database instance */
|
|
326
|
-
private db;
|
|
327
|
-
/** In-memory cache: Map<tenantIdentifier, TenantConfig> */
|
|
328
|
-
private readonly tenantConfigCache;
|
|
329
|
-
/** Cache TTL in milliseconds */
|
|
330
|
-
private readonly cacheTTL;
|
|
331
|
-
constructor(options: DatabaseModuleOptions);
|
|
332
|
-
onModuleInit(): Promise<void>;
|
|
333
|
-
/**
|
|
334
|
-
* Initialize connection to primary database using Drizzle
|
|
335
|
-
*/
|
|
336
|
-
private initializeDrizzleClient;
|
|
337
|
-
/**
|
|
338
|
-
* Build connection URL from primary database properties
|
|
339
|
-
*/
|
|
340
|
-
private buildPrimaryDbUrl;
|
|
341
|
-
/**
|
|
342
|
-
* Mask password in connection URL for logging
|
|
343
|
-
*/
|
|
344
|
-
private maskPassword;
|
|
345
|
-
/**
|
|
346
|
-
* Get tenant configuration by identifier (ID or subdomain)
|
|
347
|
-
*
|
|
348
|
-
* @param tenantIdentifier Tenant ID or subdomain
|
|
349
|
-
* @returns Tenant configuration or null if not found
|
|
350
|
-
*/
|
|
351
|
-
getTenantInfo(tenantIdentifier: string): Promise<TenantInfo | null>;
|
|
352
|
-
/**
|
|
353
|
-
* Cache tenant information with TTL
|
|
354
|
-
*/
|
|
355
|
-
private cacheInfo;
|
|
356
|
-
/**
|
|
357
|
-
* Clear cached tenant information
|
|
358
|
-
*
|
|
359
|
-
* Useful when tenant settings are updated and cache needs to be invalidated
|
|
360
|
-
*
|
|
361
|
-
* @param tenantIdentifier Tenant ID or subdomain
|
|
362
|
-
*/
|
|
363
|
-
clearTenantCache(tenantIdentifier: string): void;
|
|
364
|
-
/**
|
|
365
|
-
* Clear all cached tenant configurations
|
|
366
|
-
*/
|
|
367
|
-
clearAllCaches(): void;
|
|
368
|
-
/**
|
|
369
|
-
* Get the Drizzle database instance for the primary database.
|
|
370
|
-
* This is a synchronous property that returns the initialized Drizzle client.
|
|
371
|
-
*
|
|
372
|
-
* @returns Primary database Drizzle instance
|
|
373
|
-
* @throws Error if primary database client is not initialized
|
|
374
|
-
*/
|
|
375
|
-
get drizzleClient(): TypedDrizzleClient;
|
|
376
|
-
/**
|
|
377
|
-
* Get the Drizzle schema
|
|
378
|
-
*/
|
|
379
|
-
get schema(): typeof this$1.options.drizzleSchema;
|
|
380
|
-
/**
|
|
381
|
-
* Decrypt database credentials
|
|
382
|
-
*
|
|
383
|
-
* Override this method to implement your encryption strategy
|
|
384
|
-
*
|
|
385
|
-
* @param encrypted Encrypted value
|
|
386
|
-
* @returns Decrypted value
|
|
387
|
-
*/
|
|
388
|
-
private decrypt;
|
|
389
|
-
onModuleDestroy(): Promise<void>;
|
|
28
|
+
declare module 'fastify' {
|
|
29
|
+
interface FastifyRequest {
|
|
30
|
+
sessionInfo?: {
|
|
31
|
+
userId: string;
|
|
32
|
+
sessionId: string;
|
|
33
|
+
sessionType: string;
|
|
34
|
+
};
|
|
35
|
+
tenant?: TenantInfo;
|
|
36
|
+
cookies?: Record<string, string>;
|
|
37
|
+
}
|
|
390
38
|
}
|
|
391
39
|
|
|
40
|
+
declare class AuthConfigModule {
|
|
41
|
+
static forRootAsync(): DynamicModule;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
declare const AccessToken: (...dataOrPipes: unknown[]) => ParameterDecorator;
|
|
45
|
+
|
|
46
|
+
declare const Onboarding: () => _nestjs_common.CustomDecorator<string>;
|
|
47
|
+
|
|
48
|
+
declare const Public: () => _nestjs_common.CustomDecorator<string>;
|
|
49
|
+
|
|
50
|
+
declare const RefreshTokenCookie: (...dataOrPipes: unknown[]) => ParameterDecorator;
|
|
51
|
+
|
|
52
|
+
declare const RESET_KEY = "isReset";
|
|
53
|
+
declare const Reset: () => _nestjs_common.CustomDecorator<string>;
|
|
54
|
+
|
|
55
|
+
interface SessionInfo {
|
|
56
|
+
userId: string;
|
|
57
|
+
sessionId: string;
|
|
58
|
+
sessionType: string;
|
|
59
|
+
}
|
|
60
|
+
declare const SessionData: (...dataOrPipes: unknown[]) => ParameterDecorator;
|
|
61
|
+
|
|
62
|
+
declare const UserId: (...dataOrPipes: unknown[]) => ParameterDecorator;
|
|
63
|
+
|
|
392
64
|
declare class RequestService {
|
|
393
65
|
private readonly request;
|
|
394
66
|
constructor(request: FastifyRequest);
|
|
395
|
-
/**
|
|
396
|
-
* Extract tenant identifier from request headers
|
|
397
|
-
* Priority: x-tenant-id > x-subdomain
|
|
398
|
-
* @returns Tenant identifier or null if not found
|
|
399
|
-
*/
|
|
400
67
|
getTenantIdentifier(): string | null;
|
|
401
|
-
/**
|
|
402
|
-
* Extract access token from Authorization header
|
|
403
|
-
* Format: "Bearer <token>"
|
|
404
|
-
* @returns Access token or null if not found
|
|
405
|
-
*/
|
|
406
68
|
getAccessToken(): string | null;
|
|
407
|
-
/**
|
|
408
|
-
* Extract refresh token from httpOnly cookie
|
|
409
|
-
* Cookie name is configurable via api-sdk config
|
|
410
|
-
* @returns Refresh token or null if not found
|
|
411
|
-
*/
|
|
412
69
|
getRefreshToken(): string | null;
|
|
413
|
-
/**
|
|
414
|
-
* Get a specific header value
|
|
415
|
-
* @param key Header key
|
|
416
|
-
* @returns Header value (string, array, or undefined)
|
|
417
|
-
*/
|
|
418
70
|
getHeader(key: string): string | string[] | undefined;
|
|
419
|
-
/**
|
|
420
|
-
* Get all headers
|
|
421
|
-
* @returns Record of all headers
|
|
422
|
-
*/
|
|
423
71
|
getAllHeaders(): FastifyRequest['headers'];
|
|
424
72
|
}
|
|
425
73
|
|
|
426
|
-
/**
|
|
427
|
-
* Vritti Authentication Guard - Validates JWT access tokens and tenant context
|
|
428
|
-
*
|
|
429
|
-
* This guard performs access token validation and attaches user data to request.
|
|
430
|
-
* NOTE: Refresh tokens are NOT validated here - they are only validated in
|
|
431
|
-
* /auth/token and /auth/refresh endpoints (session.service.ts).
|
|
432
|
-
*
|
|
433
|
-
* Validation Flow:
|
|
434
|
-
* 1. Checks if endpoint is marked with @Public() decorator → skip all validation
|
|
435
|
-
* 2. Checks if endpoint is marked with @Onboarding() decorator:
|
|
436
|
-
* - Requires token type='onboarding'
|
|
437
|
-
* - Validates JWT signature and expiry only
|
|
438
|
-
* - Skips tenant validation
|
|
439
|
-
* - Attaches user data to request.user
|
|
440
|
-
* 3. For regular endpoints (no decorator):
|
|
441
|
-
* - Rejects tokens with type='onboarding'
|
|
442
|
-
* - Validates access token (JWT signature, expiry, nbf)
|
|
443
|
-
* - Validates tenant exists and is ACTIVE
|
|
444
|
-
* - Attaches user data to request.user
|
|
445
|
-
*
|
|
446
|
-
* Token Format:
|
|
447
|
-
* - Access Token: "Authorization: Bearer <jwt_token>"
|
|
448
|
-
*
|
|
449
|
-
* Token Types:
|
|
450
|
-
* - type='onboarding': Limited access during registration flow (@Onboarding endpoints only)
|
|
451
|
-
* - type='access': Full access to authenticated endpoints
|
|
452
|
-
*
|
|
453
|
-
* Environment Variables Required:
|
|
454
|
-
* - JWT_SECRET: Secret key to verify access tokens (required)
|
|
455
|
-
*
|
|
456
|
-
* Error Responses:
|
|
457
|
-
* - 401: Invalid/expired access token
|
|
458
|
-
* - 401: Tenant not found or inactive
|
|
459
|
-
* - 401: Tenant identifier not found
|
|
460
|
-
* - 401: Token type mismatch (onboarding token on regular endpoint or vice versa)
|
|
461
|
-
*
|
|
462
|
-
* @example
|
|
463
|
-
* // Automatically registered by AuthConfigModule.forRootAsync()
|
|
464
|
-
* // No manual registration needed
|
|
465
|
-
* //
|
|
466
|
-
* // Internal registration uses useExisting pattern:
|
|
467
|
-
* // providers: [
|
|
468
|
-
* // VrittiAuthGuard,
|
|
469
|
-
* // {
|
|
470
|
-
* // provide: APP_GUARD,
|
|
471
|
-
* // useExisting: VrittiAuthGuard,
|
|
472
|
-
* // },
|
|
473
|
-
* // ]
|
|
474
|
-
*
|
|
475
|
-
* @example
|
|
476
|
-
* // Bypass guard with @Public() decorator
|
|
477
|
-
* @Public()
|
|
478
|
-
* @Post('auth/login')
|
|
479
|
-
* async login(@Body() dto: LoginDto) { ... }
|
|
480
|
-
*
|
|
481
|
-
* @example
|
|
482
|
-
* // Restrict to onboarding tokens with @Onboarding() decorator
|
|
483
|
-
* @Onboarding()
|
|
484
|
-
* @Post('onboarding/verify-email')
|
|
485
|
-
* async verifyEmail(@Request() req) {
|
|
486
|
-
* const userId = req.user.id; // Available from guard
|
|
487
|
-
* ...
|
|
488
|
-
* }
|
|
489
|
-
*/
|
|
490
74
|
declare class VrittiAuthGuard implements CanActivate {
|
|
491
75
|
private readonly reflector;
|
|
492
76
|
readonly _configService: ConfigService;
|
|
493
77
|
private readonly jwtService;
|
|
494
|
-
private readonly primaryDatabase;
|
|
495
78
|
private readonly requestService;
|
|
496
79
|
private readonly logger;
|
|
497
|
-
constructor(reflector: Reflector, _configService: ConfigService, jwtService: JwtService,
|
|
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,1240 +293,225 @@ interface TypedRelationalQueryBuilder<TSelect> {
|
|
|
945
293
|
columns?: Record<string, boolean>;
|
|
946
294
|
}): Promise<TSelect[]>;
|
|
947
295
|
}
|
|
948
|
-
/**
|
|
949
|
-
* Abstract base repository for primary database operations using Drizzle ORM.
|
|
950
|
-
* Provides common CRUD operations with automatic logging.
|
|
951
|
-
*
|
|
952
|
-
* @template TTable - The Drizzle table type (must be registered in SchemaRegistry)
|
|
953
|
-
* @template TInsert - Type for insert operations (inferred from table.$inferInsert)
|
|
954
|
-
* @template TSelect - Type for select operations (inferred from table.$inferSelect)
|
|
955
|
-
*
|
|
956
|
-
* @remarks
|
|
957
|
-
* **Type Assertion Pattern:** This repository uses `as any` casts when passing
|
|
958
|
-
* the generic table to Drizzle methods. This is necessary because TypeScript
|
|
959
|
-
* cannot prove that a generic `TTable extends PgTable` satisfies Drizzle's
|
|
960
|
-
* stricter internal type requirements for `insert()`, `update()`, and `delete()`.
|
|
961
|
-
*
|
|
962
|
-
* The public API maintains full type safety:
|
|
963
|
-
* - Input parameters are typed as `TInsert` (inferred from table)
|
|
964
|
-
* - Return values are typed as `TSelect` (inferred from table)
|
|
965
|
-
* - The casts are implementation details that don't leak to consumers
|
|
966
|
-
*
|
|
967
|
-
* @example
|
|
968
|
-
* ```typescript
|
|
969
|
-
* import { users } from '@/db/schema';
|
|
970
|
-
*
|
|
971
|
-
* type User = typeof users.$inferSelect;
|
|
972
|
-
* type NewUser = typeof users.$inferInsert;
|
|
973
|
-
*
|
|
974
|
-
* @Injectable()
|
|
975
|
-
* export class UserRepository extends PrimaryBaseRepository<typeof users> {
|
|
976
|
-
* constructor(database: PrimaryDatabaseService) {
|
|
977
|
-
* super(database, users);
|
|
978
|
-
* }
|
|
979
|
-
*
|
|
980
|
-
* // Use Drizzle v2 object-based where syntax (recommended)
|
|
981
|
-
* async findByEmail(email: string): Promise<User | undefined> {
|
|
982
|
-
* return this.model.findFirst({
|
|
983
|
-
* where: { email },
|
|
984
|
-
* });
|
|
985
|
-
* }
|
|
986
|
-
*
|
|
987
|
-
* // With relations
|
|
988
|
-
* async findWithRelations(id: string): Promise<User | undefined> {
|
|
989
|
-
* return this.model.findFirst({
|
|
990
|
-
* where: { id },
|
|
991
|
-
* with: { posts: true, profile: true }
|
|
992
|
-
* });
|
|
993
|
-
* }
|
|
994
|
-
* }
|
|
995
|
-
* ```
|
|
996
|
-
*/
|
|
997
296
|
declare abstract class PrimaryBaseRepository<TTable extends PgTable, TInsert = InferInsertModel<TTable>, TSelect = InferSelectModel<TTable>> {
|
|
998
297
|
protected readonly database: PrimaryDatabaseService;
|
|
999
298
|
protected readonly table: TTable;
|
|
1000
299
|
protected readonly logger: Logger;
|
|
1001
|
-
/**
|
|
1002
|
-
* The table name extracted from the Drizzle table at runtime.
|
|
1003
|
-
* Stored in camelCase to match Drizzle's query object keys.
|
|
1004
|
-
* Example: 'email_verifications' -> 'emailVerifications'
|
|
1005
|
-
*/
|
|
1006
300
|
private readonly tableName;
|
|
1007
|
-
/**
|
|
1008
|
-
* Lazy getter for Drizzle client.
|
|
1009
|
-
* Accesses the client from the database service only when needed,
|
|
1010
|
-
* avoiding initialization timing issues with NestJS lifecycle.
|
|
1011
|
-
*/
|
|
1012
301
|
protected get db(): TypedDrizzleClient;
|
|
1013
|
-
/**
|
|
1014
|
-
* Model query API for THIS repository's table (Drizzle v2 relational queries)
|
|
1015
|
-
* Scoped to only the table this repository manages.
|
|
1016
|
-
* Returns a type-safe wrapper around Drizzle's RelationalQueryBuilder.
|
|
1017
|
-
*
|
|
1018
|
-
* @example
|
|
1019
|
-
* ```typescript
|
|
1020
|
-
* // Use relational queries with v2 object-based where syntax
|
|
1021
|
-
* const user = await this.model.findFirst({
|
|
1022
|
-
* where: { id },
|
|
1023
|
-
* with: { posts: true, profile: true }
|
|
1024
|
-
* });
|
|
1025
|
-
* ```
|
|
1026
|
-
*/
|
|
1027
302
|
protected get model(): TypedRelationalQueryBuilder<TSelect>;
|
|
1028
|
-
/**
|
|
1029
|
-
* Create a new repository instance
|
|
1030
|
-
*
|
|
1031
|
-
* @param database - The primary database service
|
|
1032
|
-
* @param table - The Drizzle table schema object
|
|
1033
|
-
*
|
|
1034
|
-
* @example
|
|
1035
|
-
* ```typescript
|
|
1036
|
-
* import { users } from '@/db/schema';
|
|
1037
|
-
*
|
|
1038
|
-
* constructor(database: PrimaryDatabaseService) {
|
|
1039
|
-
* super(database, users);
|
|
1040
|
-
* }
|
|
1041
|
-
* ```
|
|
1042
|
-
*/
|
|
1043
303
|
constructor(database: PrimaryDatabaseService, table: TTable);
|
|
1044
|
-
/**
|
|
1045
|
-
* Create a new record
|
|
1046
|
-
*
|
|
1047
|
-
* @param data - The data to create the record with
|
|
1048
|
-
* @returns Promise resolving to the created record
|
|
1049
|
-
*
|
|
1050
|
-
* @example
|
|
1051
|
-
* ```typescript
|
|
1052
|
-
* const user = await userRepository.create({
|
|
1053
|
-
* email: 'user@example.com',
|
|
1054
|
-
* firstName: 'John'
|
|
1055
|
-
* });
|
|
1056
|
-
* ```
|
|
1057
|
-
*/
|
|
1058
304
|
create(data: TInsert): Promise<TSelect>;
|
|
1059
|
-
/**
|
|
1060
|
-
* Find a single record by ID
|
|
1061
|
-
*
|
|
1062
|
-
* @param id - The record ID
|
|
1063
|
-
* @returns Promise resolving to the record or undefined if not found
|
|
1064
|
-
*
|
|
1065
|
-
* @example
|
|
1066
|
-
* ```typescript
|
|
1067
|
-
* const user = await userRepository.findById('user-id-123');
|
|
1068
|
-
* ```
|
|
1069
|
-
*/
|
|
1070
305
|
findById(id: string): Promise<TSelect | undefined>;
|
|
1071
|
-
/**
|
|
1072
|
-
* Find a single record with custom where clause (Drizzle v2 object-based syntax)
|
|
1073
|
-
*
|
|
1074
|
-
* @param where - Object-based filter condition
|
|
1075
|
-
* @returns Promise resolving to the record or undefined if not found
|
|
1076
|
-
*
|
|
1077
|
-
* @example
|
|
1078
|
-
* ```typescript
|
|
1079
|
-
* // Simple equality
|
|
1080
|
-
* const user = await userRepository.findOne({ email: 'user@example.com' });
|
|
1081
|
-
*
|
|
1082
|
-
* // With operators
|
|
1083
|
-
* const user = await userRepository.findOne({ age: { gte: 18 } });
|
|
1084
|
-
*
|
|
1085
|
-
* // Multiple conditions (AND)
|
|
1086
|
-
* const user = await userRepository.findOne({
|
|
1087
|
-
* email: 'user@example.com',
|
|
1088
|
-
* status: 'ACTIVE'
|
|
1089
|
-
* });
|
|
1090
|
-
* ```
|
|
1091
|
-
*/
|
|
1092
306
|
findOne(where: RelationsWhereFilter): Promise<TSelect | undefined>;
|
|
1093
|
-
/**
|
|
1094
|
-
* Find multiple records (Drizzle v2 object-based syntax)
|
|
1095
|
-
*
|
|
1096
|
-
* @param options - Query options (where, orderBy, limit, offset)
|
|
1097
|
-
* @returns Promise resolving to an array of records
|
|
1098
|
-
*
|
|
1099
|
-
* @example
|
|
1100
|
-
* ```typescript
|
|
1101
|
-
* // Find all users
|
|
1102
|
-
* const users = await userRepository.findMany();
|
|
1103
|
-
*
|
|
1104
|
-
* // Find with filtering and pagination (v2 object syntax)
|
|
1105
|
-
* const users = await userRepository.findMany({
|
|
1106
|
-
* where: { accountStatus: 'ACTIVE' },
|
|
1107
|
-
* orderBy: { createdAt: 'desc' },
|
|
1108
|
-
* limit: 10,
|
|
1109
|
-
* offset: 0
|
|
1110
|
-
* });
|
|
1111
|
-
*
|
|
1112
|
-
* // Multiple conditions
|
|
1113
|
-
* const users = await userRepository.findMany({
|
|
1114
|
-
* where: {
|
|
1115
|
-
* AND: [
|
|
1116
|
-
* { status: 'ACTIVE' },
|
|
1117
|
-
* { age: { gte: 18 } }
|
|
1118
|
-
* ]
|
|
1119
|
-
* }
|
|
1120
|
-
* });
|
|
1121
|
-
* ```
|
|
1122
|
-
*/
|
|
1123
307
|
findMany(options?: {
|
|
1124
308
|
where?: RelationsWhereFilter;
|
|
1125
309
|
orderBy?: Record<string, 'asc' | 'desc'>;
|
|
1126
310
|
limit?: number;
|
|
1127
311
|
offset?: number;
|
|
1128
312
|
}): Promise<TSelect[]>;
|
|
1129
|
-
/**
|
|
1130
|
-
* Update a record by ID
|
|
1131
|
-
*
|
|
1132
|
-
* @param id - The record ID
|
|
1133
|
-
* @param data - The data to update
|
|
1134
|
-
* @returns Promise resolving to the updated record
|
|
1135
|
-
*
|
|
1136
|
-
* @example
|
|
1137
|
-
* ```typescript
|
|
1138
|
-
* const user = await userRepository.update('user-id-123', {
|
|
1139
|
-
* firstName: 'Jane'
|
|
1140
|
-
* });
|
|
1141
|
-
* ```
|
|
1142
|
-
*/
|
|
1143
313
|
update(id: string, data: Partial<TInsert>): Promise<TSelect>;
|
|
1144
|
-
/**
|
|
1145
|
-
* Update multiple records
|
|
1146
|
-
*
|
|
1147
|
-
* @param where - SQL condition to match records
|
|
1148
|
-
* @param data - The data to update
|
|
1149
|
-
* @returns Promise resolving to the count of updated records
|
|
1150
|
-
*
|
|
1151
|
-
* @example
|
|
1152
|
-
* ```typescript
|
|
1153
|
-
* import { eq } from 'drizzle-orm';
|
|
1154
|
-
*
|
|
1155
|
-
* const result = await userRepository.updateMany(
|
|
1156
|
-
* eq(users.accountStatus, 'PENDING'),
|
|
1157
|
-
* { accountStatus: 'ACTIVE' }
|
|
1158
|
-
* );
|
|
1159
|
-
* console.log(`Updated ${result.count} users`);
|
|
1160
|
-
* ```
|
|
1161
|
-
*/
|
|
1162
314
|
updateMany(where: SQL, data: Partial<TInsert>): Promise<{
|
|
1163
315
|
count: number;
|
|
1164
316
|
}>;
|
|
1165
|
-
/**
|
|
1166
|
-
* Delete a record by ID
|
|
1167
|
-
*
|
|
1168
|
-
* @param id - The record ID
|
|
1169
|
-
* @returns Promise resolving to the deleted record
|
|
1170
|
-
*
|
|
1171
|
-
* @example
|
|
1172
|
-
* ```typescript
|
|
1173
|
-
* const user = await userRepository.delete('user-id-123');
|
|
1174
|
-
* ```
|
|
1175
|
-
*/
|
|
1176
317
|
delete(id: string): Promise<TSelect>;
|
|
1177
|
-
/**
|
|
1178
|
-
* Delete multiple records
|
|
1179
|
-
*
|
|
1180
|
-
* @param where - SQL condition to match records
|
|
1181
|
-
* @returns Promise resolving to the count of deleted records
|
|
1182
|
-
*
|
|
1183
|
-
* @example
|
|
1184
|
-
* ```typescript
|
|
1185
|
-
* import { lt } from 'drizzle-orm';
|
|
1186
|
-
*
|
|
1187
|
-
* const result = await userRepository.deleteMany(
|
|
1188
|
-
* lt(users.createdAt, new Date('2020-01-01'))
|
|
1189
|
-
* );
|
|
1190
|
-
* console.log(`Deleted ${result.count} users`);
|
|
1191
|
-
* ```
|
|
1192
|
-
*/
|
|
1193
318
|
deleteMany(where: SQL): Promise<{
|
|
1194
319
|
count: number;
|
|
1195
320
|
}>;
|
|
1196
|
-
/**
|
|
1197
|
-
* Count records
|
|
1198
|
-
*
|
|
1199
|
-
* @param where - Optional SQL condition to filter records
|
|
1200
|
-
* @returns Promise resolving to the count of records
|
|
1201
|
-
*
|
|
1202
|
-
* @example
|
|
1203
|
-
* ```typescript
|
|
1204
|
-
* import { eq } from 'drizzle-orm';
|
|
1205
|
-
*
|
|
1206
|
-
* // Count all users
|
|
1207
|
-
* const total = await userRepository.count();
|
|
1208
|
-
*
|
|
1209
|
-
* // Count active users
|
|
1210
|
-
* const activeCount = await userRepository.count(
|
|
1211
|
-
* eq(users.accountStatus, 'ACTIVE')
|
|
1212
|
-
* );
|
|
1213
|
-
* ```
|
|
1214
|
-
*/
|
|
1215
321
|
count(where?: SQL): Promise<number>;
|
|
1216
|
-
/**
|
|
1217
|
-
* Check if a record exists
|
|
1218
|
-
*
|
|
1219
|
-
* @param where - SQL condition to match records
|
|
1220
|
-
* @returns Promise resolving to true if at least one record exists, false otherwise
|
|
1221
|
-
*
|
|
1222
|
-
* @example
|
|
1223
|
-
* ```typescript
|
|
1224
|
-
* import { eq } from 'drizzle-orm';
|
|
1225
|
-
*
|
|
1226
|
-
* const emailExists = await userRepository.exists(
|
|
1227
|
-
* eq(users.email, 'user@example.com')
|
|
1228
|
-
* );
|
|
1229
|
-
* ```
|
|
1230
|
-
*/
|
|
1231
322
|
exists(where: SQL): Promise<boolean>;
|
|
323
|
+
findForSelect(config: FindForSelectConfig): Promise<SelectQueryResult>;
|
|
1232
324
|
}
|
|
1233
325
|
|
|
1234
|
-
/**
|
|
1235
|
-
* Request-scoped service that holds tenant context for the current request or RabbitMQ message
|
|
1236
|
-
*
|
|
1237
|
-
* IMPORTANT: This service is REQUEST-SCOPED, meaning NestJS creates a new instance
|
|
1238
|
-
* for each HTTP request or RabbitMQ message. This ensures tenant isolation and
|
|
1239
|
-
* prevents cross-tenant data leaks in concurrent scenarios.
|
|
1240
|
-
*
|
|
1241
|
-
* @example
|
|
1242
|
-
* // In a controller or service
|
|
1243
|
-
* constructor(private readonly tenantContext: TenantContextService) {}
|
|
1244
|
-
*
|
|
1245
|
-
* async handleRequest() {
|
|
1246
|
-
* const tenant = this.tenantContext.getTenant();
|
|
1247
|
-
* console.log(`Processing request for tenant: ${tenant.tenantSlug}`);
|
|
1248
|
-
* }
|
|
1249
|
-
*/
|
|
1250
326
|
declare class TenantContextService {
|
|
1251
327
|
private tenantInfo;
|
|
1252
|
-
/**
|
|
1253
|
-
* Set tenant information for this request/message
|
|
1254
|
-
*
|
|
1255
|
-
* This is typically called by:
|
|
1256
|
-
* - TenantContextInterceptor (for HTTP requests in gateway)
|
|
1257
|
-
* - MessageTenantContextInterceptor (for RabbitMQ messages in microservices)
|
|
1258
|
-
* - Manual context setup in message handlers
|
|
1259
|
-
*
|
|
1260
|
-
* @param tenantInfo Complete tenant information
|
|
1261
|
-
* @throws Error if tenant context is already set (prevents accidental overwrites)
|
|
1262
|
-
*/
|
|
1263
328
|
setTenant(tenantInfo: TenantInfo): void;
|
|
1264
|
-
/**
|
|
1265
|
-
* Get tenant information for this request/message
|
|
1266
|
-
*
|
|
1267
|
-
* @returns Tenant information
|
|
1268
|
-
* @throws UnauthorizedException if tenant context hasn't been set
|
|
1269
|
-
*/
|
|
1270
329
|
getTenant(): TenantInfo;
|
|
1271
|
-
/**
|
|
1272
|
-
* Check if tenant context has been set
|
|
1273
|
-
*
|
|
1274
|
-
* @returns true if tenant context is available
|
|
1275
|
-
*/
|
|
1276
330
|
hasTenant(): boolean;
|
|
1277
|
-
/**
|
|
1278
|
-
* Clear tenant context
|
|
1279
|
-
*
|
|
1280
|
-
* This is useful for cleanup in RabbitMQ message handlers
|
|
1281
|
-
* after the message has been processed.
|
|
1282
|
-
*
|
|
1283
|
-
* HTTP requests don't need manual cleanup as the service
|
|
1284
|
-
* instance is destroyed when the request ends.
|
|
1285
|
-
*/
|
|
1286
331
|
clearTenant(): void;
|
|
1287
|
-
/**
|
|
1288
|
-
* Get tenant ID safely (returns null if not set)
|
|
1289
|
-
*
|
|
1290
|
-
* @returns Tenant ID or null
|
|
1291
|
-
*/
|
|
1292
332
|
getTenantIdSafe(): string | null;
|
|
1293
|
-
/**
|
|
1294
|
-
* Get tenant subdomain safely (returns null if not set)
|
|
1295
|
-
*
|
|
1296
|
-
* @returns Tenant subdomain or null
|
|
1297
|
-
*/
|
|
1298
333
|
getTenantSubdomainSafe(): string | null;
|
|
1299
334
|
}
|
|
1300
335
|
|
|
1301
|
-
/**
|
|
1302
|
-
* Service responsible for managing tenant-scoped database connections
|
|
1303
|
-
*
|
|
1304
|
-
* This service:
|
|
1305
|
-
* - Maintains a connection pool (Map<cacheKey, TenantConnection>)
|
|
1306
|
-
* - Creates new connections dynamically based on tenant context
|
|
1307
|
-
* - Reuses existing connections for the same tenant
|
|
1308
|
-
* - Supports both cloud schemas and enterprise databases
|
|
1309
|
-
* - Automatically cleans up idle connections
|
|
1310
|
-
*
|
|
1311
|
-
* @example
|
|
1312
|
-
* // In a controller or service
|
|
1313
|
-
* const db = this.tenantDatabase.drizzleClient;
|
|
1314
|
-
* const users = await db.select().from(usersTable);
|
|
1315
|
-
*/
|
|
1316
336
|
declare class TenantDatabaseService implements OnModuleDestroy {
|
|
1317
337
|
private readonly options;
|
|
1318
338
|
private readonly tenantContext;
|
|
1319
339
|
private readonly logger;
|
|
1320
|
-
/** Connection pool: Map<cacheKey, TenantConnection> */
|
|
1321
340
|
private readonly clients;
|
|
1322
|
-
/** Track last usage time for idle connection cleanup */
|
|
1323
341
|
private readonly clientLastUsed;
|
|
1324
|
-
/** Cleanup interval timer */
|
|
1325
342
|
private cleanupInterval?;
|
|
1326
343
|
constructor(options: DatabaseModuleOptions, tenantContext: TenantContextService);
|
|
1327
|
-
/**
|
|
1328
|
-
* Get the Drizzle client for the current tenant's database.
|
|
1329
|
-
* This returns the tenant-scoped database client.
|
|
1330
|
-
*
|
|
1331
|
-
* @returns Tenant-scoped Drizzle database instance
|
|
1332
|
-
* @throws UnauthorizedException if tenant context not set
|
|
1333
|
-
* @throws InternalServerErrorException if connection fails
|
|
1334
|
-
*/
|
|
1335
344
|
get drizzleClient(): TypedDrizzleClient;
|
|
1336
|
-
/**
|
|
1337
|
-
* Get the Drizzle schema
|
|
1338
|
-
*/
|
|
1339
345
|
get schema(): Record<string, unknown>;
|
|
1340
|
-
/**
|
|
1341
|
-
* Get tenant-scoped database client for the current request/message
|
|
1342
|
-
*
|
|
1343
|
-
* This method:
|
|
1344
|
-
* 1. Gets tenant info from TenantContextService
|
|
1345
|
-
* 2. Builds a connection URL based on tenant type
|
|
1346
|
-
* 3. Returns cached client if exists, otherwise creates new one
|
|
1347
|
-
*
|
|
1348
|
-
* @returns Drizzle database instance
|
|
1349
|
-
* @throws UnauthorizedException if tenant context not set
|
|
1350
|
-
* @throws InternalServerErrorException if connection fails
|
|
1351
|
-
*/
|
|
1352
346
|
private getDbClient;
|
|
1353
|
-
/**
|
|
1354
|
-
* Create a new database client for the given tenant (synchronous)
|
|
1355
|
-
*/
|
|
1356
347
|
private createDbClientSync;
|
|
1357
|
-
/**
|
|
1358
|
-
* Build connection URL for tenant (dedicated database)
|
|
1359
|
-
*/
|
|
1360
348
|
private buildTenantDbUrl;
|
|
1361
|
-
/**
|
|
1362
|
-
* Build cache key for connection pooling
|
|
1363
|
-
*/
|
|
1364
349
|
private buildCacheKey;
|
|
1365
|
-
/**
|
|
1366
|
-
* Start periodic cleanup of idle connections
|
|
1367
|
-
*/
|
|
1368
350
|
private startConnectionCleaner;
|
|
1369
|
-
/**
|
|
1370
|
-
* Clean up idle connections that haven't been used recently
|
|
1371
|
-
*/
|
|
1372
351
|
private cleanupIdleConnections;
|
|
1373
|
-
/**
|
|
1374
|
-
* Get current connection pool statistics
|
|
1375
|
-
*/
|
|
1376
352
|
getPoolStats(): {
|
|
1377
353
|
activeConnections: number;
|
|
1378
354
|
tenants: string[];
|
|
1379
355
|
};
|
|
1380
|
-
/**
|
|
1381
|
-
* Mask password in connection URL for logging
|
|
1382
|
-
*/
|
|
1383
356
|
private maskPassword;
|
|
1384
357
|
onModuleDestroy(): Promise<void>;
|
|
1385
358
|
}
|
|
1386
359
|
|
|
1387
|
-
/**
|
|
1388
|
-
* Type helper to extract table name from Drizzle table.
|
|
1389
|
-
* TTable['_']['name'] gives us the string literal type (e.g., 'products')
|
|
1390
|
-
*/
|
|
1391
360
|
type ExtractTableName<TTable extends PgTable> = TTable['_']['name'];
|
|
1392
|
-
/**
|
|
1393
|
-
* Abstract base repository for tenant-scoped database operations using Drizzle ORM.
|
|
1394
|
-
* All operations are automatically scoped to the current tenant.
|
|
1395
|
-
*
|
|
1396
|
-
* @template TTable - The Drizzle table type (must be registered in SchemaRegistry)
|
|
1397
|
-
* @template TInsert - Type for insert operations (inferred from table.$inferInsert)
|
|
1398
|
-
* @template TSelect - Type for select operations (inferred from table.$inferSelect)
|
|
1399
|
-
*
|
|
1400
|
-
* @remarks
|
|
1401
|
-
* **Type Assertion Pattern:** This repository uses `as any` casts when passing
|
|
1402
|
-
* the generic table to Drizzle methods. This is necessary because TypeScript
|
|
1403
|
-
* cannot prove that a generic `TTable extends PgTable` satisfies Drizzle's
|
|
1404
|
-
* stricter internal type requirements for `insert()`, `update()`, and `delete()`.
|
|
1405
|
-
*
|
|
1406
|
-
* The public API maintains full type safety:
|
|
1407
|
-
* - Input parameters are typed as `TInsert` (inferred from table)
|
|
1408
|
-
* - Return values are typed as `TSelect` (inferred from table)
|
|
1409
|
-
* - The casts are implementation details that don't leak to consumers
|
|
1410
|
-
*
|
|
1411
|
-
* @example
|
|
1412
|
-
* ```typescript
|
|
1413
|
-
* import { products } from '@/db/schema';
|
|
1414
|
-
*
|
|
1415
|
-
* type Product = typeof products.$inferSelect;
|
|
1416
|
-
* type NewProduct = typeof products.$inferInsert;
|
|
1417
|
-
*
|
|
1418
|
-
* @Injectable()
|
|
1419
|
-
* export class ProductRepository extends TenantBaseRepository<typeof products> {
|
|
1420
|
-
* constructor(database: TenantDatabaseService) {
|
|
1421
|
-
* super(database, products);
|
|
1422
|
-
* }
|
|
1423
|
-
*
|
|
1424
|
-
* // Use SQL-builder syntax
|
|
1425
|
-
* async findBySku(sku: string): Promise<Product | null> {
|
|
1426
|
-
* const [result] = await this.db
|
|
1427
|
-
* .select()
|
|
1428
|
-
* .from(this.table)
|
|
1429
|
-
* .where(eq(products.sku, sku))
|
|
1430
|
-
* .limit(1);
|
|
1431
|
-
* return result ?? null;
|
|
1432
|
-
* }
|
|
1433
|
-
*
|
|
1434
|
-
* // Use Prisma-like relational query syntax
|
|
1435
|
-
* async findWithRelations(id: string): Promise<Product | null> {
|
|
1436
|
-
* return await this.model.findFirst({
|
|
1437
|
-
* where: eq(products.id, id),
|
|
1438
|
-
* with: { category: true, variants: true }
|
|
1439
|
-
* });
|
|
1440
|
-
* }
|
|
1441
|
-
* }
|
|
1442
|
-
* ```
|
|
1443
|
-
*/
|
|
1444
361
|
declare abstract class TenantBaseRepository<TTable extends PgTable, TInsert = InferInsertModel<TTable>, TSelect = InferSelectModel<TTable>> {
|
|
1445
362
|
protected readonly database: TenantDatabaseService;
|
|
1446
363
|
protected readonly table: TTable;
|
|
1447
364
|
protected readonly logger: Logger;
|
|
1448
|
-
/**
|
|
1449
|
-
* The table name extracted from the Drizzle table at runtime.
|
|
1450
|
-
* Used to access the query API for this repository's table.
|
|
1451
|
-
*/
|
|
1452
365
|
private readonly tableName;
|
|
1453
|
-
/**
|
|
1454
|
-
* Lazy getter for Drizzle client.
|
|
1455
|
-
* Accesses the client from the database service only when needed,
|
|
1456
|
-
* avoiding initialization timing issues with NestJS lifecycle.
|
|
1457
|
-
*/
|
|
1458
366
|
protected get db(): TypedDrizzleClient;
|
|
1459
|
-
/**
|
|
1460
|
-
* Model query API for THIS repository's table (Prisma-like syntax)
|
|
1461
|
-
* Scoped to only the table this repository manages
|
|
1462
|
-
*
|
|
1463
|
-
* @example
|
|
1464
|
-
* ```typescript
|
|
1465
|
-
* // Use relational queries with type safety
|
|
1466
|
-
* const product = await this.model.findFirst({
|
|
1467
|
-
* where: eq(products.id, id),
|
|
1468
|
-
* with: { category: true, variants: true }
|
|
1469
|
-
* });
|
|
1470
|
-
* ```
|
|
1471
|
-
*/
|
|
1472
367
|
protected get model(): TypedDrizzleClient['query'][ExtractTableName<TTable> & keyof TypedDrizzleClient['query']];
|
|
1473
|
-
/**
|
|
1474
|
-
* Create a new repository instance
|
|
1475
|
-
*
|
|
1476
|
-
* @param database - The tenant database service
|
|
1477
|
-
* @param table - The Drizzle table schema object
|
|
1478
|
-
*
|
|
1479
|
-
* @example
|
|
1480
|
-
* ```typescript
|
|
1481
|
-
* import { products } from '@/db/schema';
|
|
1482
|
-
*
|
|
1483
|
-
* constructor(database: TenantDatabaseService) {
|
|
1484
|
-
* super(database, products);
|
|
1485
|
-
* }
|
|
1486
|
-
* ```
|
|
1487
|
-
*/
|
|
1488
368
|
constructor(database: TenantDatabaseService, table: TTable);
|
|
1489
|
-
/**
|
|
1490
|
-
* Create a new record
|
|
1491
|
-
*
|
|
1492
|
-
* @param data - The data to create the record with
|
|
1493
|
-
* @returns Promise resolving to the created record
|
|
1494
|
-
*
|
|
1495
|
-
* @example
|
|
1496
|
-
* ```typescript
|
|
1497
|
-
* const product = await productRepository.create({
|
|
1498
|
-
* name: 'Widget',
|
|
1499
|
-
* sku: 'WDG-001',
|
|
1500
|
-
* price: 9.99
|
|
1501
|
-
* });
|
|
1502
|
-
* ```
|
|
1503
|
-
*/
|
|
1504
369
|
create(data: TInsert): Promise<TSelect>;
|
|
1505
|
-
/**
|
|
1506
|
-
* Find a single record by ID
|
|
1507
|
-
*
|
|
1508
|
-
* @param id - The record ID
|
|
1509
|
-
* @returns Promise resolving to the record or null if not found
|
|
1510
|
-
*
|
|
1511
|
-
* @example
|
|
1512
|
-
* ```typescript
|
|
1513
|
-
* const product = await productRepository.findById('product-id-123');
|
|
1514
|
-
* ```
|
|
1515
|
-
*/
|
|
1516
370
|
findById(id: string): Promise<TSelect | null>;
|
|
1517
|
-
/**
|
|
1518
|
-
* Find a single record with custom where clause
|
|
1519
|
-
*
|
|
1520
|
-
* @param where - SQL condition
|
|
1521
|
-
* @returns Promise resolving to the record or null if not found
|
|
1522
|
-
*
|
|
1523
|
-
* @example
|
|
1524
|
-
* ```typescript
|
|
1525
|
-
* import { eq } from 'drizzle-orm';
|
|
1526
|
-
* const product = await productRepository.findOne(eq(products.sku, 'WDG-001'));
|
|
1527
|
-
* ```
|
|
1528
|
-
*/
|
|
1529
371
|
findOne(where: SQL): Promise<TSelect | null>;
|
|
1530
|
-
/**
|
|
1531
|
-
* Find multiple records
|
|
1532
|
-
*
|
|
1533
|
-
* @param options - Query options (where, orderBy, limit, offset)
|
|
1534
|
-
* @returns Promise resolving to an array of records
|
|
1535
|
-
*
|
|
1536
|
-
* @example
|
|
1537
|
-
* ```typescript
|
|
1538
|
-
* import { eq, desc } from 'drizzle-orm';
|
|
1539
|
-
*
|
|
1540
|
-
* // Find all products
|
|
1541
|
-
* const products = await productRepository.findMany();
|
|
1542
|
-
*
|
|
1543
|
-
* // Find with filtering and pagination
|
|
1544
|
-
* const products = await productRepository.findMany({
|
|
1545
|
-
* where: eq(products.status, 'ACTIVE'),
|
|
1546
|
-
* orderBy: desc(products.createdAt),
|
|
1547
|
-
* limit: 10,
|
|
1548
|
-
* offset: 0
|
|
1549
|
-
* });
|
|
1550
|
-
* ```
|
|
1551
|
-
*/
|
|
1552
372
|
findMany(options?: {
|
|
1553
373
|
where?: SQL;
|
|
1554
374
|
orderBy?: SQL;
|
|
1555
375
|
limit?: number;
|
|
1556
376
|
offset?: number;
|
|
1557
377
|
}): Promise<TSelect[]>;
|
|
1558
|
-
/**
|
|
1559
|
-
* Update a record by ID
|
|
1560
|
-
*
|
|
1561
|
-
* @param id - The record ID
|
|
1562
|
-
* @param data - The data to update
|
|
1563
|
-
* @returns Promise resolving to the updated record
|
|
1564
|
-
*
|
|
1565
|
-
* @example
|
|
1566
|
-
* ```typescript
|
|
1567
|
-
* const product = await productRepository.update('product-id-123', {
|
|
1568
|
-
* price: 12.99
|
|
1569
|
-
* });
|
|
1570
|
-
* ```
|
|
1571
|
-
*/
|
|
1572
378
|
update(id: string, data: Partial<TInsert>): Promise<TSelect>;
|
|
1573
|
-
/**
|
|
1574
|
-
* Update multiple records
|
|
1575
|
-
*
|
|
1576
|
-
* @param where - SQL condition to match records
|
|
1577
|
-
* @param data - The data to update
|
|
1578
|
-
* @returns Promise resolving to the count of updated records
|
|
1579
|
-
*
|
|
1580
|
-
* @example
|
|
1581
|
-
* ```typescript
|
|
1582
|
-
* import { eq } from 'drizzle-orm';
|
|
1583
|
-
*
|
|
1584
|
-
* const result = await productRepository.updateMany(
|
|
1585
|
-
* eq(products.status, 'PENDING'),
|
|
1586
|
-
* { status: 'ACTIVE' }
|
|
1587
|
-
* );
|
|
1588
|
-
* console.log(`Updated ${result.count} products`);
|
|
1589
|
-
* ```
|
|
1590
|
-
*/
|
|
1591
379
|
updateMany(where: SQL, data: Partial<TInsert>): Promise<{
|
|
1592
380
|
count: number;
|
|
1593
381
|
}>;
|
|
1594
|
-
/**
|
|
1595
|
-
* Delete a record by ID
|
|
1596
|
-
*
|
|
1597
|
-
* @param id - The record ID
|
|
1598
|
-
* @returns Promise resolving to the deleted record
|
|
1599
|
-
*
|
|
1600
|
-
* @example
|
|
1601
|
-
* ```typescript
|
|
1602
|
-
* const product = await productRepository.delete('product-id-123');
|
|
1603
|
-
* ```
|
|
1604
|
-
*/
|
|
1605
382
|
delete(id: string): Promise<TSelect>;
|
|
1606
|
-
/**
|
|
1607
|
-
* Delete multiple records
|
|
1608
|
-
*
|
|
1609
|
-
* @param where - SQL condition to match records
|
|
1610
|
-
* @returns Promise resolving to the count of deleted records
|
|
1611
|
-
*
|
|
1612
|
-
* @example
|
|
1613
|
-
* ```typescript
|
|
1614
|
-
* import { lt } from 'drizzle-orm';
|
|
1615
|
-
*
|
|
1616
|
-
* const result = await productRepository.deleteMany(
|
|
1617
|
-
* lt(products.createdAt, new Date('2020-01-01'))
|
|
1618
|
-
* );
|
|
1619
|
-
* console.log(`Deleted ${result.count} products`);
|
|
1620
|
-
* ```
|
|
1621
|
-
*/
|
|
1622
383
|
deleteMany(where: SQL): Promise<{
|
|
1623
384
|
count: number;
|
|
1624
385
|
}>;
|
|
1625
|
-
/**
|
|
1626
|
-
* Count records
|
|
1627
|
-
*
|
|
1628
|
-
* @param where - Optional SQL condition to filter records
|
|
1629
|
-
* @returns Promise resolving to the count of records
|
|
1630
|
-
*
|
|
1631
|
-
* @example
|
|
1632
|
-
* ```typescript
|
|
1633
|
-
* import { eq } from 'drizzle-orm';
|
|
1634
|
-
*
|
|
1635
|
-
* // Count all products
|
|
1636
|
-
* const total = await productRepository.count();
|
|
1637
|
-
*
|
|
1638
|
-
* // Count active products
|
|
1639
|
-
* const activeCount = await productRepository.count(
|
|
1640
|
-
* eq(products.status, 'ACTIVE')
|
|
1641
|
-
* );
|
|
1642
|
-
* ```
|
|
1643
|
-
*/
|
|
1644
386
|
count(where?: SQL): Promise<number>;
|
|
1645
|
-
/**
|
|
1646
|
-
* Check if a record exists
|
|
1647
|
-
*
|
|
1648
|
-
* @param where - SQL condition to match records
|
|
1649
|
-
* @returns Promise resolving to true if at least one record exists, false otherwise
|
|
1650
|
-
*
|
|
1651
|
-
* @example
|
|
1652
|
-
* ```typescript
|
|
1653
|
-
* import { eq } from 'drizzle-orm';
|
|
1654
|
-
*
|
|
1655
|
-
* const skuExists = await productRepository.exists(
|
|
1656
|
-
* eq(products.sku, 'WDG-001')
|
|
1657
|
-
* );
|
|
1658
|
-
* ```
|
|
1659
|
-
*/
|
|
1660
387
|
exists(where: SQL): Promise<boolean>;
|
|
388
|
+
findForSelect(config: FindForSelectConfig): Promise<SelectQueryResult>;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
declare class EmailModule {
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
declare class EmailService {
|
|
395
|
+
private readonly configService;
|
|
396
|
+
private readonly logger;
|
|
397
|
+
private readonly brevoClient;
|
|
398
|
+
private readonly senderEmail;
|
|
399
|
+
private readonly senderName;
|
|
400
|
+
constructor(configService: ConfigService);
|
|
401
|
+
sendVerificationEmail(email: string, otp: string, expiresAt: Date, displayName?: string): Promise<void>;
|
|
402
|
+
sendPasswordResetEmail(email: string, otp: string, expiresAt: Date, displayName?: string): Promise<void>;
|
|
403
|
+
sendEmailChangeNotification(oldEmail: string, newEmail: string, revertToken: string, revertExpiresAt: Date, displayName?: string): Promise<void>;
|
|
404
|
+
sendEmailRevertConfirmation(email: string, displayName?: string): Promise<void>;
|
|
405
|
+
verifyConnection(): Promise<boolean>;
|
|
406
|
+
private sendEmail;
|
|
1661
407
|
}
|
|
1662
408
|
|
|
1663
409
|
interface FieldError {
|
|
1664
|
-
field
|
|
410
|
+
field: string;
|
|
1665
411
|
message: string;
|
|
1666
412
|
}
|
|
1667
413
|
interface ProblemDetails {
|
|
414
|
+
type: string;
|
|
1668
415
|
title: string;
|
|
1669
416
|
status: number;
|
|
417
|
+
label?: string;
|
|
1670
418
|
detail: string;
|
|
419
|
+
instance?: string;
|
|
1671
420
|
}
|
|
1672
421
|
interface ApiErrorResponse extends ProblemDetails {
|
|
1673
422
|
errors: FieldError[];
|
|
1674
423
|
}
|
|
1675
424
|
|
|
1676
|
-
|
|
1677
|
-
|
|
425
|
+
interface ProblemOptions {
|
|
426
|
+
type?: string;
|
|
427
|
+
label?: string;
|
|
428
|
+
detail?: string;
|
|
429
|
+
errors?: FieldError[];
|
|
430
|
+
}
|
|
431
|
+
declare abstract class HttpProblemException extends HttpException {
|
|
432
|
+
constructor(detailOrOptions: string | ProblemOptions, httpStatus: HttpStatus);
|
|
1678
433
|
}
|
|
1679
434
|
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
* Used when a server acting as a gateway gets an error from an upstream server.
|
|
1683
|
-
*
|
|
1684
|
-
* @example
|
|
1685
|
-
* // Simple message
|
|
1686
|
-
* throw new BadGatewayException('Bad gateway');
|
|
1687
|
-
*
|
|
1688
|
-
* // Field-specific error
|
|
1689
|
-
* throw new BadGatewayException('upstream', 'Upstream service returned invalid response');
|
|
1690
|
-
*
|
|
1691
|
-
* // With detail
|
|
1692
|
-
* throw new BadGatewayException('proxy', 'Gateway error', 'Payment service is not responding correctly');
|
|
1693
|
-
*
|
|
1694
|
-
* // Multiple field errors
|
|
1695
|
-
* throw new BadGatewayException([
|
|
1696
|
-
* { field: 'gateway', message: 'Invalid response from upstream server' }
|
|
1697
|
-
* ]);
|
|
1698
|
-
*/
|
|
1699
|
-
declare class BadGatewayException extends BaseFieldException {
|
|
1700
|
-
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
435
|
+
declare class BadGatewayException extends HttpProblemException {
|
|
436
|
+
constructor(detailOrOptions?: string | ProblemOptions);
|
|
1701
437
|
}
|
|
1702
438
|
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
*
|
|
1706
|
-
* @example
|
|
1707
|
-
* // Simple message
|
|
1708
|
-
* throw new BadRequestException('Invalid request data');
|
|
1709
|
-
*
|
|
1710
|
-
* // Field-specific error
|
|
1711
|
-
* throw new BadRequestException('email', 'Invalid email format');
|
|
1712
|
-
*
|
|
1713
|
-
* // With detail
|
|
1714
|
-
* throw new BadRequestException('email', 'Invalid email format', 'Email must be in valid format');
|
|
1715
|
-
*
|
|
1716
|
-
* // Multiple field errors
|
|
1717
|
-
* throw new BadRequestException([
|
|
1718
|
-
* { field: 'email', message: 'Invalid email' },
|
|
1719
|
-
* { field: 'password', message: 'Password too short' }
|
|
1720
|
-
* ]);
|
|
1721
|
-
*/
|
|
1722
|
-
declare class BadRequestException extends BaseFieldException {
|
|
1723
|
-
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
439
|
+
declare class BadRequestException extends HttpProblemException {
|
|
440
|
+
constructor(detailOrOptions?: string | ProblemOptions);
|
|
1724
441
|
}
|
|
1725
442
|
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
* Commonly used for duplicate resources or concurrent modification issues.
|
|
1729
|
-
*
|
|
1730
|
-
* @example
|
|
1731
|
-
* // Simple message
|
|
1732
|
-
* throw new ConflictException('Resource already exists');
|
|
1733
|
-
*
|
|
1734
|
-
* // Field-specific error
|
|
1735
|
-
* throw new ConflictException('email', 'Email already registered');
|
|
1736
|
-
*
|
|
1737
|
-
* // With detail
|
|
1738
|
-
* throw new ConflictException('email', 'Email already exists', 'Try logging in instead');
|
|
1739
|
-
*
|
|
1740
|
-
* // Multiple field errors
|
|
1741
|
-
* throw new ConflictException([
|
|
1742
|
-
* { field: 'email', message: 'Email already in use' }
|
|
1743
|
-
* ]);
|
|
1744
|
-
*/
|
|
1745
|
-
declare class ConflictException extends BaseFieldException {
|
|
1746
|
-
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
443
|
+
declare class ConflictException extends HttpProblemException {
|
|
444
|
+
constructor(detailOrOptions?: string | ProblemOptions);
|
|
1747
445
|
}
|
|
1748
446
|
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
*
|
|
1752
|
-
* @example
|
|
1753
|
-
* // Simple message
|
|
1754
|
-
* throw new ForbiddenException('Access denied');
|
|
1755
|
-
*
|
|
1756
|
-
* // Field-specific error
|
|
1757
|
-
* throw new ForbiddenException('resource', 'You do not have permission');
|
|
1758
|
-
*
|
|
1759
|
-
* // With detail
|
|
1760
|
-
* throw new ForbiddenException('resource', 'Access denied', 'Admin role required');
|
|
1761
|
-
*
|
|
1762
|
-
* // Multiple field errors
|
|
1763
|
-
* throw new ForbiddenException([
|
|
1764
|
-
* { field: 'action', message: 'Insufficient permissions' }
|
|
1765
|
-
* ]);
|
|
1766
|
-
*/
|
|
1767
|
-
declare class ForbiddenException extends BaseFieldException {
|
|
1768
|
-
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
447
|
+
declare class ForbiddenException extends HttpProblemException {
|
|
448
|
+
constructor(detailOrOptions?: string | ProblemOptions);
|
|
1769
449
|
}
|
|
1770
450
|
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
* Unlike 404, this indicates the resource existed but is intentionally gone.
|
|
1774
|
-
*
|
|
1775
|
-
* @example
|
|
1776
|
-
* // Simple message
|
|
1777
|
-
* throw new GoneException('Resource permanently deleted');
|
|
1778
|
-
*
|
|
1779
|
-
* // Field-specific error
|
|
1780
|
-
* throw new GoneException('account', 'Account has been permanently deleted');
|
|
1781
|
-
*
|
|
1782
|
-
* // With detail
|
|
1783
|
-
* throw new GoneException('account', 'Deleted', 'This account was removed on user request');
|
|
1784
|
-
*
|
|
1785
|
-
* // Multiple field errors
|
|
1786
|
-
* throw new GoneException([
|
|
1787
|
-
* { field: 'resource', message: 'This content has been permanently removed' }
|
|
1788
|
-
* ]);
|
|
1789
|
-
*/
|
|
1790
|
-
declare class GoneException extends BaseFieldException {
|
|
1791
|
-
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
451
|
+
declare class GoneException extends HttpProblemException {
|
|
452
|
+
constructor(detailOrOptions?: string | ProblemOptions);
|
|
1792
453
|
}
|
|
1793
454
|
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
*
|
|
1797
|
-
* @example
|
|
1798
|
-
* // Simple message
|
|
1799
|
-
* throw new InternalServerErrorException('An unexpected error occurred');
|
|
1800
|
-
*
|
|
1801
|
-
* // Field-specific error
|
|
1802
|
-
* throw new InternalServerErrorException('database', 'Database connection failed');
|
|
1803
|
-
*
|
|
1804
|
-
* // With detail
|
|
1805
|
-
* throw new InternalServerErrorException('database', 'Connection failed', 'Please try again later');
|
|
1806
|
-
*
|
|
1807
|
-
* // Multiple field errors
|
|
1808
|
-
* throw new InternalServerErrorException([
|
|
1809
|
-
* { field: 'system', message: 'Internal error' }
|
|
1810
|
-
* ]);
|
|
1811
|
-
*/
|
|
1812
|
-
declare class InternalServerErrorException extends BaseFieldException {
|
|
1813
|
-
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
455
|
+
declare class InternalServerErrorException extends HttpProblemException {
|
|
456
|
+
constructor(detailOrOptions?: string | ProblemOptions);
|
|
1814
457
|
}
|
|
1815
458
|
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
* For example, when a POST is sent to a GET-only endpoint.
|
|
1819
|
-
*
|
|
1820
|
-
* @example
|
|
1821
|
-
* // Simple message
|
|
1822
|
-
* throw new MethodNotAllowedException('Method not allowed');
|
|
1823
|
-
*
|
|
1824
|
-
* // Field-specific error
|
|
1825
|
-
* throw new MethodNotAllowedException('method', 'POST method not allowed on this endpoint');
|
|
1826
|
-
*
|
|
1827
|
-
* // With detail
|
|
1828
|
-
* throw new MethodNotAllowedException('method', 'Not allowed', 'Only GET and PUT are supported');
|
|
1829
|
-
*
|
|
1830
|
-
* // Multiple field errors
|
|
1831
|
-
* throw new MethodNotAllowedException([
|
|
1832
|
-
* { field: 'method', message: 'DELETE is not allowed on this resource' }
|
|
1833
|
-
* ]);
|
|
1834
|
-
*/
|
|
1835
|
-
declare class MethodNotAllowedException extends BaseFieldException {
|
|
1836
|
-
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
459
|
+
declare class MethodNotAllowedException extends HttpProblemException {
|
|
460
|
+
constructor(detailOrOptions?: string | ProblemOptions);
|
|
1837
461
|
}
|
|
1838
462
|
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
* Used when the server cannot produce a response matching the Accept headers.
|
|
1842
|
-
*
|
|
1843
|
-
* @example
|
|
1844
|
-
* // Simple message
|
|
1845
|
-
* throw new NotAcceptableException('Requested format not available');
|
|
1846
|
-
*
|
|
1847
|
-
* // Field-specific error
|
|
1848
|
-
* throw new NotAcceptableException('accept', 'Cannot produce response in requested format');
|
|
1849
|
-
*
|
|
1850
|
-
* // With detail
|
|
1851
|
-
* throw new NotAcceptableException('accept', 'Format not supported', 'Only JSON is available');
|
|
1852
|
-
*
|
|
1853
|
-
* // Multiple field errors
|
|
1854
|
-
* throw new NotAcceptableException([
|
|
1855
|
-
* { field: 'contentType', message: 'XML format is not supported' }
|
|
1856
|
-
* ]);
|
|
1857
|
-
*/
|
|
1858
|
-
declare class NotAcceptableException extends BaseFieldException {
|
|
1859
|
-
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
463
|
+
declare class NotAcceptableException extends HttpProblemException {
|
|
464
|
+
constructor(detailOrOptions?: string | ProblemOptions);
|
|
1860
465
|
}
|
|
1861
466
|
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
*
|
|
1865
|
-
* @example
|
|
1866
|
-
* // Simple message
|
|
1867
|
-
* throw new NotFoundException('Resource not found');
|
|
1868
|
-
*
|
|
1869
|
-
* // Field-specific error
|
|
1870
|
-
* throw new NotFoundException('userId', 'User not found');
|
|
1871
|
-
*
|
|
1872
|
-
* // With detail
|
|
1873
|
-
* throw new NotFoundException('userId', 'User not found', 'No user exists with the provided ID');
|
|
1874
|
-
*
|
|
1875
|
-
* // Multiple field errors
|
|
1876
|
-
* throw new NotFoundException([
|
|
1877
|
-
* { field: 'userId', message: 'User does not exist' }
|
|
1878
|
-
* ]);
|
|
1879
|
-
*/
|
|
1880
|
-
declare class NotFoundException extends BaseFieldException {
|
|
1881
|
-
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
467
|
+
declare class NotFoundException extends HttpProblemException {
|
|
468
|
+
constructor(detailOrOptions?: string | ProblemOptions);
|
|
1882
469
|
}
|
|
1883
470
|
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
* Used for planned but unavailable functionality.
|
|
1887
|
-
*
|
|
1888
|
-
* @example
|
|
1889
|
-
* // Simple message
|
|
1890
|
-
* throw new NotImplementedException('Feature not yet implemented');
|
|
1891
|
-
*
|
|
1892
|
-
* // Field-specific error
|
|
1893
|
-
* throw new NotImplementedException('feature', 'This feature is coming soon');
|
|
1894
|
-
*
|
|
1895
|
-
* // With detail
|
|
1896
|
-
* throw new NotImplementedException('export', 'Not implemented', 'PDF export will be available in v2.0');
|
|
1897
|
-
*
|
|
1898
|
-
* // Multiple field errors
|
|
1899
|
-
* throw new NotImplementedException([
|
|
1900
|
-
* { field: 'functionality', message: 'This functionality is not available yet' }
|
|
1901
|
-
* ]);
|
|
1902
|
-
*/
|
|
1903
|
-
declare class NotImplementedException extends BaseFieldException {
|
|
1904
|
-
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
471
|
+
declare class NotImplementedException extends HttpProblemException {
|
|
472
|
+
constructor(detailOrOptions?: string | ProblemOptions);
|
|
1905
473
|
}
|
|
1906
474
|
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
* Commonly used for file upload size restrictions or large request bodies.
|
|
1910
|
-
*
|
|
1911
|
-
* @example
|
|
1912
|
-
* // Simple message
|
|
1913
|
-
* throw new PayloadTooLargeException('Request payload too large');
|
|
1914
|
-
*
|
|
1915
|
-
* // Field-specific error
|
|
1916
|
-
* throw new PayloadTooLargeException('file', 'File size exceeds maximum allowed');
|
|
1917
|
-
*
|
|
1918
|
-
* // With detail
|
|
1919
|
-
* throw new PayloadTooLargeException('file', 'File too large', 'Maximum size is 10MB');
|
|
1920
|
-
*
|
|
1921
|
-
* // Multiple field errors
|
|
1922
|
-
* throw new PayloadTooLargeException([
|
|
1923
|
-
* { field: 'upload', message: 'File exceeds 10MB limit' }
|
|
1924
|
-
* ]);
|
|
1925
|
-
*/
|
|
1926
|
-
declare class PayloadTooLargeException extends BaseFieldException {
|
|
1927
|
-
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
475
|
+
declare class PayloadTooLargeException extends HttpProblemException {
|
|
476
|
+
constructor(detailOrOptions?: string | ProblemOptions);
|
|
1928
477
|
}
|
|
1929
478
|
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
* Used when the client or server times out while waiting for completion.
|
|
1933
|
-
*
|
|
1934
|
-
* @example
|
|
1935
|
-
* // Simple message
|
|
1936
|
-
* throw new RequestTimeoutException('Request timeout');
|
|
1937
|
-
*
|
|
1938
|
-
* // Field-specific error
|
|
1939
|
-
* throw new RequestTimeoutException('operation', 'Operation timed out');
|
|
1940
|
-
*
|
|
1941
|
-
* // With detail
|
|
1942
|
-
* throw new RequestTimeoutException('query', 'Database query timeout', 'Try with fewer filters');
|
|
1943
|
-
*
|
|
1944
|
-
* // Multiple field errors
|
|
1945
|
-
* throw new RequestTimeoutException([
|
|
1946
|
-
* { field: 'processing', message: 'Request took too long to complete' }
|
|
1947
|
-
* ]);
|
|
1948
|
-
*/
|
|
1949
|
-
declare class RequestTimeoutException extends BaseFieldException {
|
|
1950
|
-
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
479
|
+
declare class RequestTimeoutException extends HttpProblemException {
|
|
480
|
+
constructor(detailOrOptions?: string | ProblemOptions);
|
|
1951
481
|
}
|
|
1952
482
|
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
* Used during maintenance, overload, or temporary outages.
|
|
1956
|
-
*
|
|
1957
|
-
* @example
|
|
1958
|
-
* // Simple message
|
|
1959
|
-
* throw new ServiceUnavailableException('Service temporarily unavailable');
|
|
1960
|
-
*
|
|
1961
|
-
* // Field-specific error
|
|
1962
|
-
* throw new ServiceUnavailableException('service', 'Scheduled maintenance in progress');
|
|
1963
|
-
*
|
|
1964
|
-
* // With detail
|
|
1965
|
-
* throw new ServiceUnavailableException('service', 'Maintenance', 'Service will be back at 2 PM EST');
|
|
1966
|
-
*
|
|
1967
|
-
* // Multiple field errors
|
|
1968
|
-
* throw new ServiceUnavailableException([
|
|
1969
|
-
* { field: 'database', message: 'Database is temporarily unavailable' }
|
|
1970
|
-
* ]);
|
|
1971
|
-
*/
|
|
1972
|
-
declare class ServiceUnavailableException extends BaseFieldException {
|
|
1973
|
-
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
483
|
+
declare class ServiceUnavailableException extends HttpProblemException {
|
|
484
|
+
constructor(detailOrOptions?: string | ProblemOptions);
|
|
1974
485
|
}
|
|
1975
486
|
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
* Used to prevent abuse and ensure fair resource usage.
|
|
1979
|
-
*
|
|
1980
|
-
* @example
|
|
1981
|
-
* // Simple message
|
|
1982
|
-
* throw new TooManyRequestsException('Too many requests');
|
|
1983
|
-
*
|
|
1984
|
-
* // Field-specific error
|
|
1985
|
-
* throw new TooManyRequestsException('api', 'Rate limit exceeded');
|
|
1986
|
-
*
|
|
1987
|
-
* // With detail
|
|
1988
|
-
* throw new TooManyRequestsException('api', 'Rate limit exceeded', 'Try again in 60 seconds');
|
|
1989
|
-
*
|
|
1990
|
-
* // Multiple field errors
|
|
1991
|
-
* throw new TooManyRequestsException([
|
|
1992
|
-
* { field: 'requests', message: 'Rate limit exceeded for this endpoint' }
|
|
1993
|
-
* ]);
|
|
1994
|
-
*/
|
|
1995
|
-
declare class TooManyRequestsException extends BaseFieldException {
|
|
1996
|
-
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
487
|
+
declare class TooManyRequestsException extends HttpProblemException {
|
|
488
|
+
constructor(detailOrOptions?: string | ProblemOptions);
|
|
1997
489
|
}
|
|
1998
490
|
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
*
|
|
2002
|
-
* @example
|
|
2003
|
-
* // Simple message
|
|
2004
|
-
* throw new UnauthorizedException('Authentication required');
|
|
2005
|
-
*
|
|
2006
|
-
* // Field-specific error
|
|
2007
|
-
* throw new UnauthorizedException('token', 'Invalid or expired token');
|
|
2008
|
-
*
|
|
2009
|
-
* // With detail
|
|
2010
|
-
* throw new UnauthorizedException('token', 'Invalid token', 'Please login again');
|
|
2011
|
-
*
|
|
2012
|
-
* // Multiple field errors
|
|
2013
|
-
* throw new UnauthorizedException([
|
|
2014
|
-
* { field: 'token', message: 'Token expired' }
|
|
2015
|
-
* ]);
|
|
2016
|
-
*/
|
|
2017
|
-
declare class UnauthorizedException extends BaseFieldException {
|
|
2018
|
-
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
491
|
+
declare class UnauthorizedException extends HttpProblemException {
|
|
492
|
+
constructor(detailOrOptions?: string | ProblemOptions);
|
|
2019
493
|
}
|
|
2020
494
|
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
* Used for business logic validation failures that prevent processing.
|
|
2024
|
-
*
|
|
2025
|
-
* @example
|
|
2026
|
-
* // Simple message
|
|
2027
|
-
* throw new UnprocessableEntityException('Cannot process the request');
|
|
2028
|
-
*
|
|
2029
|
-
* // Field-specific error
|
|
2030
|
-
* throw new UnprocessableEntityException('age', 'Age must be 18 or older');
|
|
2031
|
-
*
|
|
2032
|
-
* // With detail
|
|
2033
|
-
* throw new UnprocessableEntityException('quantity', 'Insufficient stock', 'Only 5 items available');
|
|
2034
|
-
*
|
|
2035
|
-
* // Multiple field errors
|
|
2036
|
-
* throw new UnprocessableEntityException([
|
|
2037
|
-
* { field: 'startDate', message: 'Start date must be before end date' },
|
|
2038
|
-
* { field: 'endDate', message: 'End date cannot be in the past' }
|
|
2039
|
-
* ]);
|
|
2040
|
-
*/
|
|
2041
|
-
declare class UnprocessableEntityException extends BaseFieldException {
|
|
2042
|
-
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
495
|
+
declare class UnprocessableEntityException extends HttpProblemException {
|
|
496
|
+
constructor(detailOrOptions?: string | ProblemOptions);
|
|
2043
497
|
}
|
|
2044
498
|
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
* Used when the Content-Type header specifies an unsupported format.
|
|
2048
|
-
*
|
|
2049
|
-
* @example
|
|
2050
|
-
* // Simple message
|
|
2051
|
-
* throw new UnsupportedMediaTypeException('Unsupported media type');
|
|
2052
|
-
*
|
|
2053
|
-
* // Field-specific error
|
|
2054
|
-
* throw new UnsupportedMediaTypeException('contentType', 'XML is not supported');
|
|
2055
|
-
*
|
|
2056
|
-
* // With detail
|
|
2057
|
-
* throw new UnsupportedMediaTypeException('contentType', 'Not supported', 'Only JSON and form-data are accepted');
|
|
2058
|
-
*
|
|
2059
|
-
* // Multiple field errors
|
|
2060
|
-
* throw new UnsupportedMediaTypeException([
|
|
2061
|
-
* { field: 'contentType', message: 'application/xml is not supported' }
|
|
2062
|
-
* ]);
|
|
2063
|
-
*/
|
|
2064
|
-
declare class UnsupportedMediaTypeException extends BaseFieldException {
|
|
2065
|
-
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
499
|
+
declare class UnsupportedMediaTypeException extends HttpProblemException {
|
|
500
|
+
constructor(detailOrOptions?: string | ProblemOptions);
|
|
2066
501
|
}
|
|
2067
502
|
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
* Typically used for form validation or DTO validation errors.
|
|
2071
|
-
*
|
|
2072
|
-
* @example
|
|
2073
|
-
* // Multiple validation errors
|
|
2074
|
-
* throw new ValidationException([
|
|
2075
|
-
* { field: 'email', message: 'Invalid email format' },
|
|
2076
|
-
* { field: 'password', message: 'Password must be at least 8 characters' }
|
|
2077
|
-
* ]);
|
|
2078
|
-
*
|
|
2079
|
-
* // With detail
|
|
2080
|
-
* throw new ValidationException(
|
|
2081
|
-
* [{ field: 'email', message: 'Invalid format' }],
|
|
2082
|
-
* 'Please correct the errors and try again'
|
|
2083
|
-
* );
|
|
2084
|
-
*/
|
|
2085
|
-
declare class ValidationException extends BaseFieldException {
|
|
2086
|
-
constructor(errors: FieldError[], detail?: string);
|
|
503
|
+
declare class ValidationException extends HttpProblemException {
|
|
504
|
+
constructor(detailOrOptions?: string | ProblemOptions);
|
|
2087
505
|
}
|
|
2088
506
|
|
|
2089
|
-
/**
|
|
2090
|
-
* Converts an HTTP status code to its corresponding title string.
|
|
2091
|
-
* Uses the HttpStatus enum to map status codes to human-readable titles.
|
|
2092
|
-
*
|
|
2093
|
-
* @param status - The HTTP status code
|
|
2094
|
-
* @returns The human-readable title for the status code
|
|
2095
|
-
*
|
|
2096
|
-
* @example
|
|
2097
|
-
* getHttpStatusTitle(400) // Returns: "Bad Request"
|
|
2098
|
-
* getHttpStatusTitle(404) // Returns: "Not Found"
|
|
2099
|
-
* getHttpStatusTitle(500) // Returns: "Internal Server Error"
|
|
2100
|
-
*/
|
|
2101
507
|
declare function getHttpStatusTitle(status: number): string;
|
|
2102
|
-
/**
|
|
2103
|
-
* Global HTTP Exception Filter implementing RFC 7807 Problem Details
|
|
2104
|
-
*
|
|
2105
|
-
* Transforms all exceptions into a standardized RFC 7807 format:
|
|
2106
|
-
* {
|
|
2107
|
-
* title: string, // Human-readable status title
|
|
2108
|
-
* status: number, // HTTP status code
|
|
2109
|
-
* detail: string, // Detailed error description
|
|
2110
|
-
* errors: FieldError[] // Field-specific error messages
|
|
2111
|
-
* }
|
|
2112
|
-
*
|
|
2113
|
-
* Handles:
|
|
2114
|
-
* - Custom field exceptions from @vritti/api-sdk (BaseFieldException)
|
|
2115
|
-
* - Class-validator DTO validation errors
|
|
2116
|
-
* - Standard NestJS HTTP exceptions
|
|
2117
|
-
* - Unknown errors
|
|
2118
|
-
*/
|
|
2119
508
|
declare class HttpExceptionFilter implements ExceptionFilter {
|
|
2120
509
|
private readonly logger;
|
|
2121
510
|
catch(exception: unknown, host: ArgumentsHost): void;
|
|
2122
511
|
}
|
|
2123
512
|
|
|
2124
|
-
declare const SKIP_CSRF_KEY = "skipCsrf";
|
|
2125
|
-
/**
|
|
2126
|
-
* Decorator to skip CSRF validation for specific routes or controllers.
|
|
2127
|
-
* Use this for webhook endpoints that receive requests from external services
|
|
2128
|
-
* (e.g., WhatsApp, Twilio) which cannot include CSRF tokens.
|
|
2129
|
-
*
|
|
2130
|
-
* @example
|
|
2131
|
-
* // Skip CSRF for entire controller
|
|
2132
|
-
* @Controller('webhooks')
|
|
2133
|
-
* @SkipCsrf()
|
|
2134
|
-
* export class WebhookController { ... }
|
|
2135
|
-
*
|
|
2136
|
-
* @example
|
|
2137
|
-
* // Skip CSRF for specific route
|
|
2138
|
-
* @Post()
|
|
2139
|
-
* @SkipCsrf()
|
|
2140
|
-
* async handleWebhook() { ... }
|
|
2141
|
-
*/
|
|
2142
|
-
declare const SkipCsrf: () => _nestjs_common.CustomDecorator<string>;
|
|
2143
|
-
|
|
2144
|
-
/**
|
|
2145
|
-
* HTTP Module
|
|
2146
|
-
*
|
|
2147
|
-
* Provides HTTP utilities including:
|
|
2148
|
-
* - CSRF Guard for request protection
|
|
2149
|
-
* - HTTP Exception Filter for standardized error responses
|
|
2150
|
-
*
|
|
2151
|
-
* Usage:
|
|
2152
|
-
* Import this module to access HTTP guards and filters.
|
|
2153
|
-
* Guards and filters are registered globally in the main application.
|
|
2154
|
-
*/
|
|
2155
|
-
declare class HttpModule {
|
|
2156
|
-
}
|
|
2157
|
-
|
|
2158
|
-
/**
|
|
2159
|
-
* Extract ISO country code from E.164 phone number
|
|
2160
|
-
* @param phone Phone number in E.164 format (e.g., +919876543210)
|
|
2161
|
-
* @returns ISO 3166-1 alpha-2 country code (e.g., "IN") or undefined
|
|
2162
|
-
*/
|
|
2163
|
-
declare function extractCountryFromPhone(phone: string): string | undefined;
|
|
2164
|
-
/**
|
|
2165
|
-
* Normalize phone number to E.164 format with + prefix
|
|
2166
|
-
* @param phone Phone number (with or without + prefix)
|
|
2167
|
-
* @returns Phone number in E.164 format
|
|
2168
|
-
*/
|
|
2169
|
-
declare function normalizePhoneNumber(phone: string): string;
|
|
2170
|
-
|
|
2171
|
-
/**
|
|
2172
|
-
* Supported log levels for the logging system.
|
|
2173
|
-
*/
|
|
2174
513
|
type LogLevel = 'error' | 'warn' | 'log' | 'debug' | 'verbose';
|
|
2175
|
-
/**
|
|
2176
|
-
* Supported log output formats.
|
|
2177
|
-
*/
|
|
2178
514
|
type LogFormat = 'json' | 'text';
|
|
2179
|
-
/**
|
|
2180
|
-
* Metadata that can be attached to log entries.
|
|
2181
|
-
*/
|
|
2182
515
|
interface LogMetadata {
|
|
2183
516
|
correlationId?: string;
|
|
2184
517
|
method?: string;
|
|
@@ -2189,9 +522,6 @@ interface LogMetadata {
|
|
|
2189
522
|
userAgent?: string;
|
|
2190
523
|
[key: string]: unknown;
|
|
2191
524
|
}
|
|
2192
|
-
/**
|
|
2193
|
-
* Configuration options for the logger module.
|
|
2194
|
-
*/
|
|
2195
525
|
interface LoggerModuleOptions {
|
|
2196
526
|
provider?: 'default' | 'winston';
|
|
2197
527
|
level?: LogLevel;
|
|
@@ -2206,31 +536,19 @@ interface LoggerModuleOptions {
|
|
|
2206
536
|
environment?: string;
|
|
2207
537
|
defaultMeta?: Record<string, unknown>;
|
|
2208
538
|
}
|
|
2209
|
-
/**
|
|
2210
|
-
* Factory function for creating logger options asynchronously.
|
|
2211
|
-
*/
|
|
2212
539
|
interface LoggerOptionsFactory {
|
|
2213
540
|
createLoggerOptions(): Promise<LoggerModuleOptions> | LoggerModuleOptions;
|
|
2214
541
|
}
|
|
2215
|
-
/**
|
|
2216
|
-
* Async configuration options for the logger module.
|
|
2217
|
-
*/
|
|
2218
542
|
interface LoggerModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
|
|
2219
543
|
useExisting?: Type<LoggerOptionsFactory>;
|
|
2220
544
|
useClass?: Type<LoggerOptionsFactory>;
|
|
2221
545
|
useFactory?: (...args: unknown[]) => Promise<LoggerModuleOptions> | LoggerModuleOptions;
|
|
2222
546
|
inject?: unknown[];
|
|
2223
547
|
}
|
|
2224
|
-
/**
|
|
2225
|
-
* Context object for correlation tracking across async operations.
|
|
2226
|
-
*/
|
|
2227
548
|
interface CorrelationContext {
|
|
2228
549
|
correlationId: string;
|
|
2229
550
|
[key: string]: unknown;
|
|
2230
551
|
}
|
|
2231
|
-
/**
|
|
2232
|
-
* Configuration options for HTTP request/response logger interceptor.
|
|
2233
|
-
*/
|
|
2234
552
|
interface HttpLoggerOptions {
|
|
2235
553
|
enableRequestLog?: boolean;
|
|
2236
554
|
enableResponseLog?: boolean;
|
|
@@ -2242,331 +560,74 @@ interface HttpLoggerOptions {
|
|
|
2242
560
|
maxBodySize?: number;
|
|
2243
561
|
}
|
|
2244
562
|
|
|
2245
|
-
|
|
2246
|
-
* Unified Logger Service
|
|
2247
|
-
*
|
|
2248
|
-
* Single service that provides both default NestJS Logger and Winston logger implementations.
|
|
2249
|
-
* Automatically delegates to the configured provider (default or winston).
|
|
2250
|
-
* @module logger/logger.service
|
|
2251
|
-
*/
|
|
2252
|
-
|
|
2253
|
-
/**
|
|
2254
|
-
* Unified logger service implementing NestJS LoggerService interface.
|
|
2255
|
-
* Supports both default NestJS Logger and Winston implementations via facade pattern.
|
|
2256
|
-
*/
|
|
563
|
+
type LogMessage = string | Error | object;
|
|
2257
564
|
declare class LoggerService implements LoggerService$1 {
|
|
2258
565
|
private readonly defaultLogger?;
|
|
2259
566
|
private readonly activeLogger;
|
|
2260
567
|
private readonly options;
|
|
2261
568
|
private context?;
|
|
2262
569
|
constructor(options?: LoggerModuleOptions, defaultLogger?: Logger | undefined);
|
|
2263
|
-
/**
|
|
2264
|
-
* Creates a Winston logger instance with inline configuration.
|
|
2265
|
-
* Consolidates winston-config.factory.ts logic.
|
|
2266
|
-
*/
|
|
2267
570
|
private createWinstonLogger;
|
|
2268
|
-
log(message:
|
|
2269
|
-
error(message:
|
|
2270
|
-
warn(message:
|
|
2271
|
-
debug(message:
|
|
2272
|
-
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;
|
|
2273
576
|
setContext(context: string): void;
|
|
2274
|
-
/**
|
|
2275
|
-
* Unified internal logging method that handles both Winston and NestJS Logger.
|
|
2276
|
-
*/
|
|
2277
577
|
private _log;
|
|
2278
|
-
|
|
2279
|
-
* Logs with custom metadata (Winston only).
|
|
2280
|
-
*/
|
|
2281
|
-
logWithMetadata(level: LogLevel, message: any, metadata?: LogMetadata, context?: string): void;
|
|
578
|
+
logWithMetadata(level: LogLevel, message: LogMessage, metadata?: LogMetadata, context?: string): void;
|
|
2282
579
|
private formatMessage;
|
|
2283
|
-
/**
|
|
2284
|
-
* Enriches metadata with correlation context from AsyncLocalStorage.
|
|
2285
|
-
* Inline from winston-logger.service.ts
|
|
2286
|
-
*/
|
|
2287
580
|
private enrichMetadata;
|
|
2288
581
|
child(context: string): LoggerService;
|
|
2289
582
|
}
|
|
2290
583
|
|
|
2291
|
-
/**
|
|
2292
|
-
* HTTP Logger Interceptor
|
|
2293
|
-
*
|
|
2294
|
-
* Automatically logs HTTP requests and responses with correlation tracking.
|
|
2295
|
-
* @module logger/http-logger.interceptor
|
|
2296
|
-
*/
|
|
2297
|
-
|
|
2298
|
-
/**
|
|
2299
|
-
* HTTP Logger Interceptor for NestJS applications.
|
|
2300
|
-
*
|
|
2301
|
-
* Logs all HTTP requests and responses with metadata including
|
|
2302
|
-
* correlation IDs, performance metrics, and error details.
|
|
2303
|
-
*/
|
|
2304
584
|
declare class HttpLoggerInterceptor implements NestInterceptor {
|
|
2305
585
|
private readonly logger;
|
|
2306
586
|
private readonly enableRequestLog;
|
|
2307
587
|
private readonly enableResponseLog;
|
|
2308
588
|
private readonly slowRequestThreshold;
|
|
2309
589
|
constructor(logger: LoggerService, options?: HttpLoggerOptions);
|
|
2310
|
-
intercept(context: ExecutionContext, next: CallHandler): Observable<
|
|
590
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
|
|
2311
591
|
private logRequest;
|
|
2312
592
|
private logResponse;
|
|
2313
593
|
private logError;
|
|
2314
594
|
}
|
|
2315
595
|
|
|
2316
|
-
/**
|
|
2317
|
-
* Logger Module
|
|
2318
|
-
*
|
|
2319
|
-
* Dynamic NestJS module providing unified logging infrastructure with:
|
|
2320
|
-
* - Environment presets (development, staging, production, test)
|
|
2321
|
-
* - Transparent switching between default NestJS Logger and Winston
|
|
2322
|
-
* - Correlation ID tracking via middleware
|
|
2323
|
-
* - HTTP request/response logging via interceptor
|
|
2324
|
-
* - PII masking and file logging support
|
|
2325
|
-
*
|
|
2326
|
-
* @module logger/logger.module
|
|
2327
|
-
*/
|
|
2328
|
-
|
|
2329
|
-
/**
|
|
2330
|
-
* Dependency injection token for logger module options
|
|
2331
|
-
*/
|
|
2332
596
|
declare const LOGGER_MODULE_OPTIONS: unique symbol;
|
|
2333
|
-
/**
|
|
2334
|
-
* Global logger module providing unified logging infrastructure.
|
|
2335
|
-
*
|
|
2336
|
-
* Features:
|
|
2337
|
-
* - Environment presets (development, staging, production, test)
|
|
2338
|
-
* - Single `LoggerService` interface for all logging needs
|
|
2339
|
-
* - Transparent provider switching (default ↔ Winston)
|
|
2340
|
-
* - Correlation ID tracking across async operations
|
|
2341
|
-
* - HTTP request/response logging
|
|
2342
|
-
* - PII masking for GDPR compliance
|
|
2343
|
-
* - File-based logging with rotation
|
|
2344
|
-
*
|
|
2345
|
-
* @example
|
|
2346
|
-
* ```typescript
|
|
2347
|
-
* // Production environment with explicit config
|
|
2348
|
-
* @Module({
|
|
2349
|
-
* imports: [
|
|
2350
|
-
* LoggerModule.forRoot({
|
|
2351
|
-
* environment: 'production',
|
|
2352
|
-
* appName: 'my-service'
|
|
2353
|
-
* })
|
|
2354
|
-
* ],
|
|
2355
|
-
* })
|
|
2356
|
-
* export class AppModule {}
|
|
2357
|
-
*
|
|
2358
|
-
* // Development environment with custom override
|
|
2359
|
-
* @Module({
|
|
2360
|
-
* imports: [
|
|
2361
|
-
* LoggerModule.forRoot({
|
|
2362
|
-
* environment: 'development',
|
|
2363
|
-
* level: 'verbose' // Override preset's debug
|
|
2364
|
-
* })
|
|
2365
|
-
* ],
|
|
2366
|
-
* })
|
|
2367
|
-
* export class AppModule {}
|
|
2368
|
-
*
|
|
2369
|
-
* // Use default NestJS logger
|
|
2370
|
-
* @Module({
|
|
2371
|
-
* imports: [
|
|
2372
|
-
* LoggerModule.forRoot({
|
|
2373
|
-
* provider: 'default',
|
|
2374
|
-
* environment: 'development'
|
|
2375
|
-
* })
|
|
2376
|
-
* ],
|
|
2377
|
-
* })
|
|
2378
|
-
* export class AppModule {}
|
|
2379
|
-
*
|
|
2380
|
-
* // Dynamic configuration with ConfigService
|
|
2381
|
-
* @Module({
|
|
2382
|
-
* imports: [
|
|
2383
|
-
* LoggerModule.forRootAsync({
|
|
2384
|
-
* imports: [ConfigModule],
|
|
2385
|
-
* useFactory: (config: ConfigService) => ({
|
|
2386
|
-
* environment: config.get('NODE_ENV', 'development'),
|
|
2387
|
-
* provider: config.get('LOG_PROVIDER', 'winston'),
|
|
2388
|
-
* appName: config.get('APP_NAME')
|
|
2389
|
-
* }),
|
|
2390
|
-
* inject: [ConfigService]
|
|
2391
|
-
* })
|
|
2392
|
-
* ],
|
|
2393
|
-
* })
|
|
2394
|
-
* export class AppModule {}
|
|
2395
|
-
* ```
|
|
2396
|
-
*/
|
|
2397
597
|
declare class LoggerModule implements NestModule {
|
|
2398
|
-
/**
|
|
2399
|
-
* Configures the logger module with static options.
|
|
2400
|
-
*
|
|
2401
|
-
* Users must explicitly pass `environment` to select a preset.
|
|
2402
|
-
* All preset values can be overridden by passing explicit options.
|
|
2403
|
-
*
|
|
2404
|
-
* @param options - Logger configuration options
|
|
2405
|
-
* @returns Dynamic module configuration
|
|
2406
|
-
*
|
|
2407
|
-
* @example
|
|
2408
|
-
* ```typescript
|
|
2409
|
-
* // Production preset with app name
|
|
2410
|
-
* LoggerModule.forRoot({
|
|
2411
|
-
* environment: 'production',
|
|
2412
|
-
* appName: 'my-service'
|
|
2413
|
-
* })
|
|
2414
|
-
*
|
|
2415
|
-
* // Development preset with custom level
|
|
2416
|
-
* LoggerModule.forRoot({
|
|
2417
|
-
* environment: 'development',
|
|
2418
|
-
* level: 'verbose',
|
|
2419
|
-
* enableFileLogger: true
|
|
2420
|
-
* })
|
|
2421
|
-
*
|
|
2422
|
-
* // Use default NestJS logger
|
|
2423
|
-
* LoggerModule.forRoot({
|
|
2424
|
-
* provider: 'default',
|
|
2425
|
-
* environment: 'development'
|
|
2426
|
-
* })
|
|
2427
|
-
* ```
|
|
2428
|
-
*/
|
|
2429
598
|
static forRoot(options?: LoggerModuleOptions): DynamicModule;
|
|
2430
|
-
/**
|
|
2431
|
-
* Configures the logger module with async options.
|
|
2432
|
-
*
|
|
2433
|
-
* Supports dynamic configuration using:
|
|
2434
|
-
* - `useFactory`: Factory function with dependency injection
|
|
2435
|
-
* - `useClass`: Class implementing `LoggerOptionsFactory`
|
|
2436
|
-
* - `useExisting`: Existing provider implementing `LoggerOptionsFactory`
|
|
2437
|
-
*
|
|
2438
|
-
* Options from the factory/class are merged with environment preset defaults.
|
|
2439
|
-
*
|
|
2440
|
-
* @param options - Async configuration options
|
|
2441
|
-
* @returns Dynamic module configuration
|
|
2442
|
-
*
|
|
2443
|
-
* @example
|
|
2444
|
-
* ```typescript
|
|
2445
|
-
* // Factory with ConfigService
|
|
2446
|
-
* LoggerModule.forRootAsync({
|
|
2447
|
-
* imports: [ConfigModule],
|
|
2448
|
-
* useFactory: (config: ConfigService) => ({
|
|
2449
|
-
* environment: config.get('NODE_ENV', 'development'),
|
|
2450
|
-
* provider: config.get('LOG_PROVIDER', 'winston'),
|
|
2451
|
-
* level: config.get('LOG_LEVEL'),
|
|
2452
|
-
* appName: config.get('APP_NAME'),
|
|
2453
|
-
* }),
|
|
2454
|
-
* inject: [ConfigService]
|
|
2455
|
-
* })
|
|
2456
|
-
*
|
|
2457
|
-
* // Factory class
|
|
2458
|
-
* @Injectable()
|
|
2459
|
-
* class LoggerConfigService implements LoggerOptionsFactory {
|
|
2460
|
-
* createLoggerOptions(): LoggerModuleOptions {
|
|
2461
|
-
* return {
|
|
2462
|
-
* environment: 'production',
|
|
2463
|
-
* appName: 'my-service'
|
|
2464
|
-
* };
|
|
2465
|
-
* }
|
|
2466
|
-
* }
|
|
2467
|
-
*
|
|
2468
|
-
* LoggerModule.forRootAsync({
|
|
2469
|
-
* useClass: LoggerConfigService
|
|
2470
|
-
* })
|
|
2471
|
-
* ```
|
|
2472
|
-
*/
|
|
2473
599
|
static forRootAsync(options: LoggerModuleAsyncOptions): DynamicModule;
|
|
2474
|
-
/**
|
|
2475
|
-
* Configures middleware for the module.
|
|
2476
|
-
* Middleware is registered globally in main.ts using Fastify hooks.
|
|
2477
|
-
*/
|
|
2478
600
|
configure(_consumer: MiddlewareConsumer): void;
|
|
2479
|
-
/**
|
|
2480
|
-
* Creates async providers for dynamic module configuration.
|
|
2481
|
-
*/
|
|
2482
601
|
private static createAsyncProviders;
|
|
2483
|
-
/**
|
|
2484
|
-
* Creates the async options provider.
|
|
2485
|
-
*/
|
|
2486
602
|
private static createAsyncOptionsProvider;
|
|
2487
603
|
}
|
|
2488
604
|
|
|
2489
|
-
/**
|
|
2490
|
-
* Correlation ID Middleware
|
|
2491
|
-
*
|
|
2492
|
-
* Generates unique correlation IDs for request tracking across async operations.
|
|
2493
|
-
* Stores correlation ID in AsyncLocalStorage for access throughout the request lifecycle.
|
|
2494
|
-
* @module logger/correlation-id.middleware
|
|
2495
|
-
*/
|
|
2496
|
-
|
|
2497
|
-
/**
|
|
2498
|
-
* Configuration options for the Correlation ID middleware.
|
|
2499
|
-
*/
|
|
2500
605
|
interface CorrelationIdMiddlewareOptions {
|
|
2501
|
-
/**
|
|
2502
|
-
* If true, adds the correlation ID to response headers.
|
|
2503
|
-
* @default true
|
|
2504
|
-
*/
|
|
2505
606
|
includeInResponse?: boolean;
|
|
2506
|
-
/**
|
|
2507
|
-
* The header name to use when adding correlation ID to response.
|
|
2508
|
-
* @default 'x-correlation-id'
|
|
2509
|
-
*/
|
|
2510
607
|
responseHeader?: string;
|
|
2511
608
|
}
|
|
2512
|
-
/**
|
|
2513
|
-
* Correlation ID Middleware for Fastify/NestJS applications.
|
|
2514
|
-
*
|
|
2515
|
-
* Generates a unique correlation ID for each request,
|
|
2516
|
-
* stores it in AsyncLocalStorage for access throughout the request lifecycle,
|
|
2517
|
-
* and optionally adds it to response headers.
|
|
2518
|
-
*/
|
|
2519
609
|
declare class CorrelationIdMiddleware implements NestMiddleware {
|
|
2520
610
|
private readonly includeInResponse;
|
|
2521
611
|
private readonly responseHeader;
|
|
2522
612
|
constructor(options?: CorrelationIdMiddlewareOptions);
|
|
2523
|
-
/**
|
|
2524
|
-
* Middleware handler for processing requests.
|
|
2525
|
-
*/
|
|
2526
613
|
use(_req: FastifyRequest, reply: FastifyReply, next: () => void): void;
|
|
2527
|
-
/**
|
|
2528
|
-
* Fastify hook handler for onRequest.
|
|
2529
|
-
* This is an async function that returns a Promise, ensuring the AsyncLocalStorage
|
|
2530
|
-
* context persists throughout the entire request lifecycle.
|
|
2531
|
-
*/
|
|
2532
614
|
onRequest(_req: FastifyRequest, reply: FastifyReply): Promise<void>;
|
|
2533
615
|
}
|
|
2534
616
|
|
|
2535
|
-
/**
|
|
2536
|
-
* Logging Utilities
|
|
2537
|
-
*
|
|
2538
|
-
* Consolidated utilities for correlation tracking, PII masking, and async context management.
|
|
2539
|
-
* @module logging/utils
|
|
2540
|
-
*/
|
|
2541
|
-
|
|
2542
|
-
/**
|
|
2543
|
-
* Async local storage for correlation context tracking across async operations.
|
|
2544
|
-
*/
|
|
2545
617
|
declare const correlationStorage: AsyncLocalStorage<CorrelationContext>;
|
|
2546
|
-
/**
|
|
2547
|
-
* Gets the current correlation context from async local storage.
|
|
2548
|
-
*/
|
|
2549
618
|
declare function getCorrelationContext(): CorrelationContext | undefined;
|
|
2550
|
-
/**
|
|
2551
|
-
* Runs a callback within a correlation context.
|
|
2552
|
-
*/
|
|
2553
619
|
declare function runWithCorrelationContext<T>(context: CorrelationContext, callback: () => T): T;
|
|
2554
|
-
/**
|
|
2555
|
-
* Updates the current correlation context with new values.
|
|
2556
|
-
*/
|
|
2557
620
|
declare function updateCorrelationContext(updates: Partial<CorrelationContext>): void;
|
|
2558
|
-
/**
|
|
2559
|
-
* Default header name for setting correlation ID in responses.
|
|
2560
|
-
*/
|
|
2561
621
|
declare const DEFAULT_CORRELATION_HEADER = "x-correlation-id";
|
|
2562
|
-
/**
|
|
2563
|
-
* Generates a new correlation ID using UUID v4.
|
|
2564
|
-
* Always creates a fresh ID for each request.
|
|
2565
|
-
*/
|
|
2566
622
|
declare function generateCorrelationId(): string;
|
|
2567
|
-
/**
|
|
2568
|
-
* Adds correlation ID to Fastify response headers.
|
|
2569
|
-
*/
|
|
2570
623
|
declare function addCorrelationIdToResponse(reply: FastifyReply, correlationId: string, headerName?: string): void;
|
|
2571
624
|
|
|
2572
|
-
|
|
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 };
|