@vritti/api-sdk 0.0.8 → 0.1.0
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 +267 -0
- package/dist/drizzle-orm.cjs +25 -0
- package/dist/drizzle-orm.cjs.map +1 -0
- package/dist/drizzle-orm.d.cts +2 -0
- package/dist/drizzle-orm.d.ts +2 -0
- package/dist/drizzle-orm.js +3 -0
- package/dist/drizzle-orm.js.map +1 -0
- package/dist/drizzle-pg-core.cjs +25 -0
- package/dist/drizzle-pg-core.cjs.map +1 -0
- package/dist/drizzle-pg-core.d.cts +1 -0
- package/dist/drizzle-pg-core.d.ts +1 -0
- package/dist/drizzle-pg-core.js +3 -0
- package/dist/drizzle-pg-core.js.map +1 -0
- package/dist/index.cjs +1222 -280
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +723 -217
- package/dist/index.d.ts +723 -217
- package/dist/index.js +1209 -279
- package/dist/index.js.map +1 -1
- package/package.json +33 -6
package/dist/index.d.cts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import * as _nestjs_common from '@nestjs/common';
|
|
2
|
-
import { DynamicModule, OnModuleDestroy, OnModuleInit, Logger, CanActivate, ExecutionContext, ExceptionFilter, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';
|
|
2
|
+
import { DynamicModule, OnModuleDestroy, OnModuleInit, Logger, CanActivate, ExecutionContext, ExceptionFilter, ArgumentsHost, HttpException, HttpStatus, ModuleMetadata, Type, NestModule, MiddlewareConsumer, LoggerService as LoggerService$1, NestMiddleware, NestInterceptor, CallHandler } from '@nestjs/common';
|
|
3
|
+
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
|
4
|
+
import { InferInsertModel, InferSelectModel, SQL } from 'drizzle-orm';
|
|
5
|
+
import { PgTable } from 'drizzle-orm/pg-core';
|
|
3
6
|
import { ConfigService } from '@nestjs/config';
|
|
4
7
|
import { Reflector } from '@nestjs/core';
|
|
5
8
|
import { JwtService } from '@nestjs/jwt';
|
|
6
|
-
import { FastifyRequest } from 'fastify';
|
|
9
|
+
import { FastifyRequest, FastifyReply } from 'fastify';
|
|
10
|
+
import { Observable } from 'rxjs';
|
|
11
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
7
12
|
|
|
8
13
|
/**
|
|
9
14
|
* Global authentication configuration module
|
|
@@ -79,6 +84,34 @@ declare class AuthConfigModule {
|
|
|
79
84
|
static forRootAsync(): DynamicModule;
|
|
80
85
|
}
|
|
81
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Schema Registry Interface
|
|
89
|
+
*
|
|
90
|
+
* Projects augment this interface to register their Drizzle schema.
|
|
91
|
+
* This enables type-safe db.query access without passing schema types everywhere.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* // In your project's schema.registry.ts:
|
|
95
|
+
* declare module '@vritti/api-sdk' {
|
|
96
|
+
* interface SchemaRegistry {
|
|
97
|
+
* schema: typeof import('./schema');
|
|
98
|
+
* }
|
|
99
|
+
* }
|
|
100
|
+
*/
|
|
101
|
+
interface SchemaRegistry {
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Extracts the registered schema type.
|
|
105
|
+
* Falls back to Record<string, unknown> if no schema is registered.
|
|
106
|
+
*/
|
|
107
|
+
type RegisteredSchema = SchemaRegistry extends {
|
|
108
|
+
schema: infer S;
|
|
109
|
+
} ? S : Record<string, unknown>;
|
|
110
|
+
/**
|
|
111
|
+
* Type alias for the Drizzle database client with registered schema
|
|
112
|
+
*/
|
|
113
|
+
type TypedDrizzleClient = NodePgDatabase<RegisteredSchema>;
|
|
114
|
+
|
|
82
115
|
/**
|
|
83
116
|
* Primary database connection configuration
|
|
84
117
|
*/
|
|
@@ -95,8 +128,8 @@ interface PrimaryDbConfig {
|
|
|
95
128
|
database: string;
|
|
96
129
|
/** Default schema (default: 'public') */
|
|
97
130
|
schema?: string;
|
|
98
|
-
/** SSL mode: 'require' | 'prefer' | 'disable' (default: 'require') */
|
|
99
|
-
sslMode?: 'require' | 'prefer' | 'disable';
|
|
131
|
+
/** SSL mode: 'require' | 'prefer' | 'disable' | 'no-verify' (default: 'require') */
|
|
132
|
+
sslMode?: 'require' | 'prefer' | 'disable' | 'no-verify';
|
|
100
133
|
}
|
|
101
134
|
/**
|
|
102
135
|
* Configuration options for DatabaseModule
|
|
@@ -118,11 +151,11 @@ interface DatabaseModuleOptions {
|
|
|
118
151
|
*/
|
|
119
152
|
primaryDb: PrimaryDbConfig;
|
|
120
153
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* @example import
|
|
154
|
+
* Drizzle schema object containing all tables and relations
|
|
155
|
+
* Import your schema from db/schema/index.ts and pass it here
|
|
156
|
+
* @example import * as schema from '@/db/schema'
|
|
124
157
|
*/
|
|
125
|
-
|
|
158
|
+
drizzleSchema: RegisteredSchema;
|
|
126
159
|
/**
|
|
127
160
|
* Connection cache TTL in milliseconds
|
|
128
161
|
* Idle connections will be closed after this period
|
|
@@ -354,7 +387,7 @@ declare class TenantContextService {
|
|
|
354
387
|
* Service responsible for managing tenant-scoped database connections
|
|
355
388
|
*
|
|
356
389
|
* This service:
|
|
357
|
-
* - Maintains a connection pool (Map<cacheKey,
|
|
390
|
+
* - Maintains a connection pool (Map<cacheKey, TenantConnection>)
|
|
358
391
|
* - Creates new connections dynamically based on tenant context
|
|
359
392
|
* - Reuses existing connections for the same tenant
|
|
360
393
|
* - Supports both cloud schemas and enterprise databases
|
|
@@ -362,14 +395,14 @@ declare class TenantContextService {
|
|
|
362
395
|
*
|
|
363
396
|
* @example
|
|
364
397
|
* // In a controller or service
|
|
365
|
-
* const
|
|
366
|
-
* const users = await
|
|
398
|
+
* const db = this.tenantDatabase.drizzleClient;
|
|
399
|
+
* const users = await db.select().from(usersTable);
|
|
367
400
|
*/
|
|
368
401
|
declare class TenantDatabaseService implements OnModuleDestroy {
|
|
369
402
|
private readonly options;
|
|
370
403
|
private readonly tenantContext;
|
|
371
404
|
private readonly logger;
|
|
372
|
-
/** Connection pool: Map<cacheKey,
|
|
405
|
+
/** Connection pool: Map<cacheKey, TenantConnection> */
|
|
373
406
|
private readonly clients;
|
|
374
407
|
/** Track last usage time for idle connection cleanup */
|
|
375
408
|
private readonly clientLastUsed;
|
|
@@ -377,14 +410,18 @@ declare class TenantDatabaseService implements OnModuleDestroy {
|
|
|
377
410
|
private cleanupInterval?;
|
|
378
411
|
constructor(options: DatabaseModuleOptions, tenantContext: TenantContextService);
|
|
379
412
|
/**
|
|
380
|
-
* Get the
|
|
413
|
+
* Get the Drizzle client for the current tenant's database.
|
|
381
414
|
* This returns the tenant-scoped database client.
|
|
382
415
|
*
|
|
383
|
-
* @returns Tenant-scoped database
|
|
416
|
+
* @returns Tenant-scoped Drizzle database instance
|
|
384
417
|
* @throws UnauthorizedException if tenant context not set
|
|
385
418
|
* @throws InternalServerErrorException if connection fails
|
|
386
419
|
*/
|
|
387
|
-
get
|
|
420
|
+
get drizzleClient(): TypedDrizzleClient;
|
|
421
|
+
/**
|
|
422
|
+
* Get the Drizzle schema
|
|
423
|
+
*/
|
|
424
|
+
get schema(): Record<string, unknown>;
|
|
388
425
|
/**
|
|
389
426
|
* Get tenant-scoped database client for the current request/message
|
|
390
427
|
*
|
|
@@ -393,21 +430,17 @@ declare class TenantDatabaseService implements OnModuleDestroy {
|
|
|
393
430
|
* 2. Builds a connection URL based on tenant type
|
|
394
431
|
* 3. Returns cached client if exists, otherwise creates new one
|
|
395
432
|
*
|
|
396
|
-
* @returns
|
|
433
|
+
* @returns Drizzle database instance
|
|
397
434
|
* @throws UnauthorizedException if tenant context not set
|
|
398
435
|
* @throws InternalServerErrorException if connection fails
|
|
399
|
-
*
|
|
400
|
-
* @example
|
|
401
|
-
* const dbClient = await tenantDatabase.getDbClient<PrismaClient>();
|
|
402
|
-
* const users = await dbClient.user.findMany();
|
|
403
436
|
*/
|
|
404
437
|
private getDbClient;
|
|
405
438
|
/**
|
|
406
|
-
* Create a new database client for the given tenant
|
|
439
|
+
* Create a new database client for the given tenant (synchronous)
|
|
407
440
|
*/
|
|
408
|
-
private
|
|
441
|
+
private createDbClientSync;
|
|
409
442
|
/**
|
|
410
|
-
* Build connection URL for
|
|
443
|
+
* Build connection URL for tenant (dedicated database)
|
|
411
444
|
*/
|
|
412
445
|
private buildTenantDbUrl;
|
|
413
446
|
/**
|
|
@@ -453,8 +486,10 @@ declare class TenantDatabaseService implements OnModuleDestroy {
|
|
|
453
486
|
declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
|
|
454
487
|
private readonly options;
|
|
455
488
|
private readonly logger;
|
|
456
|
-
/**
|
|
457
|
-
private
|
|
489
|
+
/** PostgreSQL connection pool */
|
|
490
|
+
private pool;
|
|
491
|
+
/** Drizzle database instance */
|
|
492
|
+
private db;
|
|
458
493
|
/** In-memory cache: Map<tenantIdentifier, TenantConfig> */
|
|
459
494
|
private readonly tenantConfigCache;
|
|
460
495
|
/** Cache TTL in milliseconds */
|
|
@@ -462,9 +497,9 @@ declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
|
|
|
462
497
|
constructor(options: DatabaseModuleOptions);
|
|
463
498
|
onModuleInit(): Promise<void>;
|
|
464
499
|
/**
|
|
465
|
-
* Initialize connection to primary database
|
|
500
|
+
* Initialize connection to primary database using Drizzle
|
|
466
501
|
*/
|
|
467
|
-
private
|
|
502
|
+
private initializeDrizzleClient;
|
|
468
503
|
/**
|
|
469
504
|
* Build connection URL from primary database properties
|
|
470
505
|
*/
|
|
@@ -474,9 +509,9 @@ declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
|
|
|
474
509
|
*/
|
|
475
510
|
private maskPassword;
|
|
476
511
|
/**
|
|
477
|
-
* Get tenant configuration by identifier (ID or
|
|
512
|
+
* Get tenant configuration by identifier (ID or subdomain)
|
|
478
513
|
*
|
|
479
|
-
* @param tenantIdentifier Tenant ID or
|
|
514
|
+
* @param tenantIdentifier Tenant ID or subdomain
|
|
480
515
|
* @returns Tenant configuration or null if not found
|
|
481
516
|
*/
|
|
482
517
|
getTenantInfo(tenantIdentifier: string): Promise<TenantInfo | null>;
|
|
@@ -489,7 +524,7 @@ declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
|
|
|
489
524
|
*
|
|
490
525
|
* Useful when tenant settings are updated and cache needs to be invalidated
|
|
491
526
|
*
|
|
492
|
-
* @param tenantIdentifier Tenant ID or
|
|
527
|
+
* @param tenantIdentifier Tenant ID or subdomain
|
|
493
528
|
*/
|
|
494
529
|
clearTenantCache(tenantIdentifier: string): void;
|
|
495
530
|
/**
|
|
@@ -497,13 +532,17 @@ declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
|
|
|
497
532
|
*/
|
|
498
533
|
clearAllCaches(): void;
|
|
499
534
|
/**
|
|
500
|
-
* Get the
|
|
501
|
-
* This is a synchronous property that returns the initialized
|
|
535
|
+
* Get the Drizzle database instance for the primary database.
|
|
536
|
+
* This is a synchronous property that returns the initialized Drizzle client.
|
|
502
537
|
*
|
|
503
|
-
* @returns Primary database
|
|
538
|
+
* @returns Primary database Drizzle instance
|
|
504
539
|
* @throws Error if primary database client is not initialized
|
|
505
540
|
*/
|
|
506
|
-
get
|
|
541
|
+
get drizzleClient(): TypedDrizzleClient;
|
|
542
|
+
/**
|
|
543
|
+
* Get the Drizzle schema
|
|
544
|
+
*/
|
|
545
|
+
get schema(): typeof this$1.options.drizzleSchema;
|
|
507
546
|
/**
|
|
508
547
|
* Decrypt database credentials
|
|
509
548
|
*
|
|
@@ -517,90 +556,123 @@ declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
|
|
|
517
556
|
}
|
|
518
557
|
|
|
519
558
|
/**
|
|
520
|
-
*
|
|
559
|
+
* Type-safe wrapper for Drizzle's RelationalQueryBuilder.
|
|
560
|
+
* This interface matches the method signatures of RelationalQueryBuilder
|
|
561
|
+
* but properly binds the TSelect generic for type safety.
|
|
562
|
+
*
|
|
563
|
+
* We use this instead of RelationalQueryBuilder directly because
|
|
564
|
+
* TypeScript cannot infer TSelect from the generic base repository context.
|
|
565
|
+
*/
|
|
566
|
+
interface TypedRelationalQueryBuilder<TSelect> {
|
|
567
|
+
findFirst(config?: {
|
|
568
|
+
where?: SQL;
|
|
569
|
+
with?: Record<string, unknown>;
|
|
570
|
+
columns?: Record<string, boolean>;
|
|
571
|
+
}): Promise<TSelect | undefined>;
|
|
572
|
+
findMany(config?: {
|
|
573
|
+
where?: SQL;
|
|
574
|
+
orderBy?: SQL;
|
|
575
|
+
limit?: number;
|
|
576
|
+
offset?: number;
|
|
577
|
+
with?: Record<string, unknown>;
|
|
578
|
+
columns?: Record<string, boolean>;
|
|
579
|
+
}): Promise<TSelect[]>;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Abstract base repository for primary database operations using Drizzle ORM.
|
|
521
583
|
* Provides common CRUD operations with automatic logging.
|
|
522
584
|
*
|
|
523
|
-
* @template
|
|
524
|
-
* @template
|
|
525
|
-
* @template
|
|
585
|
+
* @template TTable - The Drizzle table type (must be registered in SchemaRegistry)
|
|
586
|
+
* @template TInsert - Type for insert operations (inferred from table.$inferInsert)
|
|
587
|
+
* @template TSelect - Type for select operations (inferred from table.$inferSelect)
|
|
588
|
+
*
|
|
589
|
+
* @remarks
|
|
590
|
+
* **Type Assertion Pattern:** This repository uses `as any` casts when passing
|
|
591
|
+
* the generic table to Drizzle methods. This is necessary because TypeScript
|
|
592
|
+
* cannot prove that a generic `TTable extends PgTable` satisfies Drizzle's
|
|
593
|
+
* stricter internal type requirements for `insert()`, `update()`, and `delete()`.
|
|
594
|
+
*
|
|
595
|
+
* The public API maintains full type safety:
|
|
596
|
+
* - Input parameters are typed as `TInsert` (inferred from table)
|
|
597
|
+
* - Return values are typed as `TSelect` (inferred from table)
|
|
598
|
+
* - The casts are implementation details that don't leak to consumers
|
|
526
599
|
*
|
|
527
600
|
* @example
|
|
528
601
|
* ```typescript
|
|
529
|
-
*
|
|
530
|
-
* // Type-safe, IDE autocomplete, refactor-friendly
|
|
531
|
-
* @Injectable()
|
|
532
|
-
* export class UserRepository extends PrimaryBaseRepository<
|
|
533
|
-
* User,
|
|
534
|
-
* CreateUserDto,
|
|
535
|
-
* UpdateUserDto
|
|
536
|
-
* > {
|
|
537
|
-
* constructor(database: PrimaryDatabaseService) {
|
|
538
|
-
* super(database, (prisma) => prisma.user); // ✅ Type-safe with autocomplete!
|
|
539
|
-
* }
|
|
602
|
+
* import { users } from '@/db/schema';
|
|
540
603
|
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
* return this.model.findUnique({ where: { email } });
|
|
544
|
-
* }
|
|
545
|
-
* }
|
|
604
|
+
* type User = typeof users.$inferSelect;
|
|
605
|
+
* type NewUser = typeof users.$inferInsert;
|
|
546
606
|
*
|
|
547
|
-
* // Short syntax is also supported
|
|
548
607
|
* @Injectable()
|
|
549
|
-
* export class
|
|
608
|
+
* export class UserRepository extends PrimaryBaseRepository<typeof users> {
|
|
550
609
|
* constructor(database: PrimaryDatabaseService) {
|
|
551
|
-
* super(database,
|
|
610
|
+
* super(database, users);
|
|
552
611
|
* }
|
|
553
|
-
* }
|
|
554
612
|
*
|
|
555
|
-
*
|
|
556
|
-
*
|
|
557
|
-
*
|
|
558
|
-
*
|
|
559
|
-
*
|
|
613
|
+
* // Use Prisma-like relational query syntax (recommended)
|
|
614
|
+
* async findByEmail(email: string): Promise<User | undefined> {
|
|
615
|
+
* return this.model.findFirst({
|
|
616
|
+
* where: eq(users.email, email),
|
|
617
|
+
* });
|
|
618
|
+
* }
|
|
619
|
+
*
|
|
620
|
+
* // Use Prisma-like with relations
|
|
621
|
+
* async findWithRelations(id: string): Promise<User | undefined> {
|
|
622
|
+
* return this.model.findFirst({
|
|
623
|
+
* where: eq(users.id, id),
|
|
624
|
+
* with: { posts: true, profile: true }
|
|
625
|
+
* });
|
|
560
626
|
* }
|
|
561
627
|
* }
|
|
562
628
|
* ```
|
|
563
629
|
*/
|
|
564
|
-
declare abstract class PrimaryBaseRepository<
|
|
630
|
+
declare abstract class PrimaryBaseRepository<TTable extends PgTable, TInsert = InferInsertModel<TTable>, TSelect = InferSelectModel<TTable>> {
|
|
565
631
|
protected readonly database: PrimaryDatabaseService;
|
|
632
|
+
protected readonly table: TTable;
|
|
566
633
|
protected readonly logger: Logger;
|
|
567
|
-
private readonly modelGetter;
|
|
568
634
|
/**
|
|
569
|
-
*
|
|
635
|
+
* The table name extracted from the Drizzle table at runtime.
|
|
636
|
+
* Used to access the query API for this repository's table.
|
|
637
|
+
*/
|
|
638
|
+
private readonly tableName;
|
|
639
|
+
/**
|
|
640
|
+
* Lazy getter for Drizzle client.
|
|
570
641
|
* Accesses the client from the database service only when needed,
|
|
571
642
|
* avoiding initialization timing issues with NestJS lifecycle.
|
|
572
643
|
*/
|
|
573
|
-
protected get
|
|
644
|
+
protected get db(): TypedDrizzleClient;
|
|
574
645
|
/**
|
|
575
|
-
*
|
|
576
|
-
*
|
|
646
|
+
* Model query API for THIS repository's table (Prisma-like syntax)
|
|
647
|
+
* Scoped to only the table this repository manages.
|
|
648
|
+
* Returns a type-safe wrapper around Drizzle's RelationalQueryBuilder.
|
|
649
|
+
*
|
|
650
|
+
* @example
|
|
651
|
+
* ```typescript
|
|
652
|
+
* // Use relational queries with type safety
|
|
653
|
+
* const user = await this.model.findFirst({
|
|
654
|
+
* where: eq(users.id, id),
|
|
655
|
+
* with: { posts: true, profile: true }
|
|
656
|
+
* });
|
|
657
|
+
* ```
|
|
577
658
|
*/
|
|
578
|
-
protected get model():
|
|
659
|
+
protected get model(): TypedRelationalQueryBuilder<TSelect>;
|
|
579
660
|
/**
|
|
580
661
|
* Create a new repository instance
|
|
581
662
|
*
|
|
582
663
|
* @param database - The primary database service
|
|
583
|
-
* @param
|
|
664
|
+
* @param table - The Drizzle table schema object
|
|
584
665
|
*
|
|
585
666
|
* @example
|
|
586
667
|
* ```typescript
|
|
587
|
-
*
|
|
588
|
-
* constructor(database: PrimaryDatabaseService) {
|
|
589
|
-
* super(database, (prisma) => prisma.user);
|
|
590
|
-
* }
|
|
591
|
-
*
|
|
592
|
-
* // Short syntax
|
|
593
|
-
* constructor(database: PrimaryDatabaseService) {
|
|
594
|
-
* super(database, (p) => p.user);
|
|
595
|
-
* }
|
|
668
|
+
* import { users } from '@/db/schema';
|
|
596
669
|
*
|
|
597
|
-
* // Complex model names
|
|
598
670
|
* constructor(database: PrimaryDatabaseService) {
|
|
599
|
-
* super(database,
|
|
671
|
+
* super(database, users);
|
|
600
672
|
* }
|
|
601
673
|
* ```
|
|
602
674
|
*/
|
|
603
|
-
constructor(database: PrimaryDatabaseService,
|
|
675
|
+
constructor(database: PrimaryDatabaseService, table: TTable);
|
|
604
676
|
/**
|
|
605
677
|
* Create a new record
|
|
606
678
|
*
|
|
@@ -611,63 +683,64 @@ declare abstract class PrimaryBaseRepository<TModel, TCreateDTO = any, TUpdateDT
|
|
|
611
683
|
* ```typescript
|
|
612
684
|
* const user = await userRepository.create({
|
|
613
685
|
* email: 'user@example.com',
|
|
614
|
-
*
|
|
686
|
+
* firstName: 'John'
|
|
615
687
|
* });
|
|
616
688
|
* ```
|
|
617
689
|
*/
|
|
618
|
-
create(data:
|
|
690
|
+
create(data: TInsert): Promise<TSelect>;
|
|
619
691
|
/**
|
|
620
692
|
* Find a single record by ID
|
|
621
693
|
*
|
|
622
694
|
* @param id - The record ID
|
|
623
|
-
* @returns Promise resolving to the record or
|
|
695
|
+
* @returns Promise resolving to the record or undefined if not found
|
|
624
696
|
*
|
|
625
697
|
* @example
|
|
626
698
|
* ```typescript
|
|
627
699
|
* const user = await userRepository.findById('user-id-123');
|
|
628
700
|
* ```
|
|
629
701
|
*/
|
|
630
|
-
findById(id: string): Promise<
|
|
702
|
+
findById(id: string): Promise<TSelect | undefined>;
|
|
631
703
|
/**
|
|
632
704
|
* Find a single record with custom where clause
|
|
633
705
|
*
|
|
634
|
-
* @param where -
|
|
635
|
-
* @returns Promise resolving to the record or
|
|
706
|
+
* @param where - SQL condition
|
|
707
|
+
* @returns Promise resolving to the record or undefined if not found
|
|
636
708
|
*
|
|
637
709
|
* @example
|
|
638
710
|
* ```typescript
|
|
639
|
-
*
|
|
640
|
-
* const user = await userRepository.findOne(
|
|
641
|
-
*
|
|
642
|
-
* // With include
|
|
643
|
-
* const user = await userRepository.findOne({
|
|
644
|
-
* where: { email: 'user@example.com' },
|
|
645
|
-
* include: { posts: true }
|
|
646
|
-
* });
|
|
711
|
+
* import { eq } from 'drizzle-orm';
|
|
712
|
+
* const user = await userRepository.findOne(eq(users.email, 'user@example.com'));
|
|
647
713
|
* ```
|
|
648
714
|
*/
|
|
649
|
-
findOne(where:
|
|
715
|
+
findOne(where: SQL): Promise<TSelect | undefined>;
|
|
650
716
|
/**
|
|
651
717
|
* Find multiple records
|
|
652
718
|
*
|
|
653
|
-
* @param
|
|
719
|
+
* @param options - Query options (where, orderBy, limit, offset)
|
|
654
720
|
* @returns Promise resolving to an array of records
|
|
655
721
|
*
|
|
656
722
|
* @example
|
|
657
723
|
* ```typescript
|
|
724
|
+
* import { eq, desc } from 'drizzle-orm';
|
|
725
|
+
*
|
|
658
726
|
* // Find all users
|
|
659
727
|
* const users = await userRepository.findMany();
|
|
660
728
|
*
|
|
661
729
|
* // Find with filtering and pagination
|
|
662
730
|
* const users = await userRepository.findMany({
|
|
663
|
-
* where:
|
|
664
|
-
* orderBy:
|
|
665
|
-
*
|
|
666
|
-
*
|
|
731
|
+
* where: eq(users.accountStatus, 'ACTIVE'),
|
|
732
|
+
* orderBy: desc(users.createdAt),
|
|
733
|
+
* limit: 10,
|
|
734
|
+
* offset: 0
|
|
667
735
|
* });
|
|
668
736
|
* ```
|
|
669
737
|
*/
|
|
670
|
-
findMany(
|
|
738
|
+
findMany(options?: {
|
|
739
|
+
where?: SQL;
|
|
740
|
+
orderBy?: SQL;
|
|
741
|
+
limit?: number;
|
|
742
|
+
offset?: number;
|
|
743
|
+
}): Promise<TSelect[]>;
|
|
671
744
|
/**
|
|
672
745
|
* Update a record by ID
|
|
673
746
|
*
|
|
@@ -678,28 +751,30 @@ declare abstract class PrimaryBaseRepository<TModel, TCreateDTO = any, TUpdateDT
|
|
|
678
751
|
* @example
|
|
679
752
|
* ```typescript
|
|
680
753
|
* const user = await userRepository.update('user-id-123', {
|
|
681
|
-
*
|
|
754
|
+
* firstName: 'Jane'
|
|
682
755
|
* });
|
|
683
756
|
* ```
|
|
684
757
|
*/
|
|
685
|
-
update(id: string, data:
|
|
758
|
+
update(id: string, data: Partial<TInsert>): Promise<TSelect>;
|
|
686
759
|
/**
|
|
687
760
|
* Update multiple records
|
|
688
761
|
*
|
|
689
|
-
* @param where -
|
|
762
|
+
* @param where - SQL condition to match records
|
|
690
763
|
* @param data - The data to update
|
|
691
764
|
* @returns Promise resolving to the count of updated records
|
|
692
765
|
*
|
|
693
766
|
* @example
|
|
694
767
|
* ```typescript
|
|
768
|
+
* import { eq } from 'drizzle-orm';
|
|
769
|
+
*
|
|
695
770
|
* const result = await userRepository.updateMany(
|
|
696
|
-
*
|
|
697
|
-
* {
|
|
771
|
+
* eq(users.accountStatus, 'PENDING'),
|
|
772
|
+
* { accountStatus: 'ACTIVE' }
|
|
698
773
|
* );
|
|
699
774
|
* console.log(`Updated ${result.count} users`);
|
|
700
775
|
* ```
|
|
701
776
|
*/
|
|
702
|
-
updateMany(where:
|
|
777
|
+
updateMany(where: SQL, data: Partial<TInsert>): Promise<{
|
|
703
778
|
count: number;
|
|
704
779
|
}>;
|
|
705
780
|
/**
|
|
@@ -713,142 +788,166 @@ declare abstract class PrimaryBaseRepository<TModel, TCreateDTO = any, TUpdateDT
|
|
|
713
788
|
* const user = await userRepository.delete('user-id-123');
|
|
714
789
|
* ```
|
|
715
790
|
*/
|
|
716
|
-
delete(id: string): Promise<
|
|
791
|
+
delete(id: string): Promise<TSelect>;
|
|
717
792
|
/**
|
|
718
793
|
* Delete multiple records
|
|
719
794
|
*
|
|
720
|
-
* @param where -
|
|
795
|
+
* @param where - SQL condition to match records
|
|
721
796
|
* @returns Promise resolving to the count of deleted records
|
|
722
797
|
*
|
|
723
798
|
* @example
|
|
724
799
|
* ```typescript
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
*
|
|
728
|
-
*
|
|
800
|
+
* import { lt } from 'drizzle-orm';
|
|
801
|
+
*
|
|
802
|
+
* const result = await userRepository.deleteMany(
|
|
803
|
+
* lt(users.createdAt, new Date('2020-01-01'))
|
|
804
|
+
* );
|
|
729
805
|
* console.log(`Deleted ${result.count} users`);
|
|
730
806
|
* ```
|
|
731
807
|
*/
|
|
732
|
-
deleteMany(where:
|
|
808
|
+
deleteMany(where: SQL): Promise<{
|
|
733
809
|
count: number;
|
|
734
810
|
}>;
|
|
735
811
|
/**
|
|
736
812
|
* Count records
|
|
737
813
|
*
|
|
738
|
-
* @param where - Optional
|
|
814
|
+
* @param where - Optional SQL condition to filter records
|
|
739
815
|
* @returns Promise resolving to the count of records
|
|
740
816
|
*
|
|
741
817
|
* @example
|
|
742
818
|
* ```typescript
|
|
819
|
+
* import { eq } from 'drizzle-orm';
|
|
820
|
+
*
|
|
743
821
|
* // Count all users
|
|
744
822
|
* const total = await userRepository.count();
|
|
745
823
|
*
|
|
746
824
|
* // Count active users
|
|
747
|
-
* const activeCount = await userRepository.count(
|
|
825
|
+
* const activeCount = await userRepository.count(
|
|
826
|
+
* eq(users.accountStatus, 'ACTIVE')
|
|
827
|
+
* );
|
|
748
828
|
* ```
|
|
749
829
|
*/
|
|
750
|
-
count(where?:
|
|
830
|
+
count(where?: SQL): Promise<number>;
|
|
751
831
|
/**
|
|
752
832
|
* Check if a record exists
|
|
753
833
|
*
|
|
754
|
-
* @param where -
|
|
834
|
+
* @param where - SQL condition to match records
|
|
755
835
|
* @returns Promise resolving to true if at least one record exists, false otherwise
|
|
756
836
|
*
|
|
757
837
|
* @example
|
|
758
838
|
* ```typescript
|
|
759
|
-
*
|
|
760
|
-
*
|
|
761
|
-
*
|
|
839
|
+
* import { eq } from 'drizzle-orm';
|
|
840
|
+
*
|
|
841
|
+
* const emailExists = await userRepository.exists(
|
|
842
|
+
* eq(users.email, 'user@example.com')
|
|
843
|
+
* );
|
|
762
844
|
* ```
|
|
763
845
|
*/
|
|
764
|
-
exists(where:
|
|
846
|
+
exists(where: SQL): Promise<boolean>;
|
|
765
847
|
}
|
|
766
848
|
|
|
767
849
|
/**
|
|
768
|
-
*
|
|
850
|
+
* Type helper to extract table name from Drizzle table.
|
|
851
|
+
* TTable['_']['name'] gives us the string literal type (e.g., 'products')
|
|
852
|
+
*/
|
|
853
|
+
type ExtractTableName<TTable extends PgTable> = TTable['_']['name'];
|
|
854
|
+
/**
|
|
855
|
+
* Abstract base repository for tenant-scoped database operations using Drizzle ORM.
|
|
769
856
|
* All operations are automatically scoped to the current tenant.
|
|
770
857
|
*
|
|
771
|
-
* @template
|
|
772
|
-
* @template
|
|
773
|
-
* @template
|
|
858
|
+
* @template TTable - The Drizzle table type (must be registered in SchemaRegistry)
|
|
859
|
+
* @template TInsert - Type for insert operations (inferred from table.$inferInsert)
|
|
860
|
+
* @template TSelect - Type for select operations (inferred from table.$inferSelect)
|
|
861
|
+
*
|
|
862
|
+
* @remarks
|
|
863
|
+
* **Type Assertion Pattern:** This repository uses `as any` casts when passing
|
|
864
|
+
* the generic table to Drizzle methods. This is necessary because TypeScript
|
|
865
|
+
* cannot prove that a generic `TTable extends PgTable` satisfies Drizzle's
|
|
866
|
+
* stricter internal type requirements for `insert()`, `update()`, and `delete()`.
|
|
867
|
+
*
|
|
868
|
+
* The public API maintains full type safety:
|
|
869
|
+
* - Input parameters are typed as `TInsert` (inferred from table)
|
|
870
|
+
* - Return values are typed as `TSelect` (inferred from table)
|
|
871
|
+
* - The casts are implementation details that don't leak to consumers
|
|
774
872
|
*
|
|
775
873
|
* @example
|
|
776
874
|
* ```typescript
|
|
777
|
-
*
|
|
778
|
-
*
|
|
875
|
+
* import { products } from '@/db/schema';
|
|
876
|
+
*
|
|
877
|
+
* type Product = typeof products.$inferSelect;
|
|
878
|
+
* type NewProduct = typeof products.$inferInsert;
|
|
879
|
+
*
|
|
779
880
|
* @Injectable()
|
|
780
|
-
* export class ProductRepository extends TenantBaseRepository<
|
|
781
|
-
* Product,
|
|
782
|
-
* CreateProductDto,
|
|
783
|
-
* UpdateProductDto
|
|
784
|
-
* > {
|
|
881
|
+
* export class ProductRepository extends TenantBaseRepository<typeof products> {
|
|
785
882
|
* constructor(database: TenantDatabaseService) {
|
|
786
|
-
* super(database,
|
|
883
|
+
* super(database, products);
|
|
787
884
|
* }
|
|
788
885
|
*
|
|
789
|
-
* //
|
|
886
|
+
* // Use SQL-builder syntax
|
|
790
887
|
* async findBySku(sku: string): Promise<Product | null> {
|
|
791
|
-
*
|
|
888
|
+
* const [result] = await this.db
|
|
889
|
+
* .select()
|
|
890
|
+
* .from(this.table)
|
|
891
|
+
* .where(eq(products.sku, sku))
|
|
892
|
+
* .limit(1);
|
|
893
|
+
* return result ?? null;
|
|
792
894
|
* }
|
|
793
|
-
* }
|
|
794
895
|
*
|
|
795
|
-
*
|
|
796
|
-
*
|
|
797
|
-
*
|
|
798
|
-
*
|
|
799
|
-
*
|
|
800
|
-
*
|
|
801
|
-
* }
|
|
802
|
-
*
|
|
803
|
-
* // Works with complex model names
|
|
804
|
-
* @Injectable()
|
|
805
|
-
* export class InventoryItemRepository extends TenantBaseRepository<InventoryItem> {
|
|
806
|
-
* constructor(database: TenantDatabaseService) {
|
|
807
|
-
* super(database, (p) => p.inventoryItem); // ✅ Matches Prisma naming
|
|
896
|
+
* // Use Prisma-like relational query syntax
|
|
897
|
+
* async findWithRelations(id: string): Promise<Product | null> {
|
|
898
|
+
* return await this.model.findFirst({
|
|
899
|
+
* where: eq(products.id, id),
|
|
900
|
+
* with: { category: true, variants: true }
|
|
901
|
+
* });
|
|
808
902
|
* }
|
|
809
903
|
* }
|
|
810
904
|
* ```
|
|
811
905
|
*/
|
|
812
|
-
declare abstract class TenantBaseRepository<
|
|
906
|
+
declare abstract class TenantBaseRepository<TTable extends PgTable, TInsert = InferInsertModel<TTable>, TSelect = InferSelectModel<TTable>> {
|
|
813
907
|
protected readonly database: TenantDatabaseService;
|
|
908
|
+
protected readonly table: TTable;
|
|
814
909
|
protected readonly logger: Logger;
|
|
815
|
-
private readonly modelGetter;
|
|
816
910
|
/**
|
|
817
|
-
*
|
|
911
|
+
* The table name extracted from the Drizzle table at runtime.
|
|
912
|
+
* Used to access the query API for this repository's table.
|
|
913
|
+
*/
|
|
914
|
+
private readonly tableName;
|
|
915
|
+
/**
|
|
916
|
+
* Lazy getter for Drizzle client.
|
|
818
917
|
* Accesses the client from the database service only when needed,
|
|
819
918
|
* avoiding initialization timing issues with NestJS lifecycle.
|
|
820
919
|
*/
|
|
821
|
-
protected get
|
|
920
|
+
protected get db(): TypedDrizzleClient;
|
|
822
921
|
/**
|
|
823
|
-
*
|
|
824
|
-
*
|
|
922
|
+
* Model query API for THIS repository's table (Prisma-like syntax)
|
|
923
|
+
* Scoped to only the table this repository manages
|
|
924
|
+
*
|
|
925
|
+
* @example
|
|
926
|
+
* ```typescript
|
|
927
|
+
* // Use relational queries with type safety
|
|
928
|
+
* const product = await this.model.findFirst({
|
|
929
|
+
* where: eq(products.id, id),
|
|
930
|
+
* with: { category: true, variants: true }
|
|
931
|
+
* });
|
|
932
|
+
* ```
|
|
825
933
|
*/
|
|
826
|
-
protected get model():
|
|
934
|
+
protected get model(): TypedDrizzleClient['query'][ExtractTableName<TTable> & keyof TypedDrizzleClient['query']];
|
|
827
935
|
/**
|
|
828
936
|
* Create a new repository instance
|
|
829
937
|
*
|
|
830
938
|
* @param database - The tenant database service
|
|
831
|
-
* @param
|
|
939
|
+
* @param table - The Drizzle table schema object
|
|
832
940
|
*
|
|
833
941
|
* @example
|
|
834
942
|
* ```typescript
|
|
835
|
-
*
|
|
836
|
-
* constructor(database: TenantDatabaseService) {
|
|
837
|
-
* super(database, (prisma) => prisma.product);
|
|
838
|
-
* }
|
|
943
|
+
* import { products } from '@/db/schema';
|
|
839
944
|
*
|
|
840
|
-
* // Short syntax
|
|
841
945
|
* constructor(database: TenantDatabaseService) {
|
|
842
|
-
* super(database,
|
|
843
|
-
* }
|
|
844
|
-
*
|
|
845
|
-
* // Complex model names
|
|
846
|
-
* constructor(database: TenantDatabaseService) {
|
|
847
|
-
* super(database, (p) => p.inventoryItem);
|
|
946
|
+
* super(database, products);
|
|
848
947
|
* }
|
|
849
948
|
* ```
|
|
850
949
|
*/
|
|
851
|
-
constructor(database: TenantDatabaseService,
|
|
950
|
+
constructor(database: TenantDatabaseService, table: TTable);
|
|
852
951
|
/**
|
|
853
952
|
* Create a new record
|
|
854
953
|
*
|
|
@@ -864,7 +963,7 @@ declare abstract class TenantBaseRepository<TModel, TCreateDTO = any, TUpdateDTO
|
|
|
864
963
|
* });
|
|
865
964
|
* ```
|
|
866
965
|
*/
|
|
867
|
-
create(data:
|
|
966
|
+
create(data: TInsert): Promise<TSelect>;
|
|
868
967
|
/**
|
|
869
968
|
* Find a single record by ID
|
|
870
969
|
*
|
|
@@ -876,47 +975,48 @@ declare abstract class TenantBaseRepository<TModel, TCreateDTO = any, TUpdateDTO
|
|
|
876
975
|
* const product = await productRepository.findById('product-id-123');
|
|
877
976
|
* ```
|
|
878
977
|
*/
|
|
879
|
-
findById(id: string): Promise<
|
|
978
|
+
findById(id: string): Promise<TSelect | null>;
|
|
880
979
|
/**
|
|
881
980
|
* Find a single record with custom where clause
|
|
882
981
|
*
|
|
883
|
-
* @param where -
|
|
982
|
+
* @param where - SQL condition
|
|
884
983
|
* @returns Promise resolving to the record or null if not found
|
|
885
984
|
*
|
|
886
985
|
* @example
|
|
887
986
|
* ```typescript
|
|
888
|
-
*
|
|
889
|
-
* const product = await productRepository.findOne(
|
|
890
|
-
*
|
|
891
|
-
* // With include
|
|
892
|
-
* const product = await productRepository.findOne({
|
|
893
|
-
* where: { sku: 'WDG-001' },
|
|
894
|
-
* include: { category: true }
|
|
895
|
-
* });
|
|
987
|
+
* import { eq } from 'drizzle-orm';
|
|
988
|
+
* const product = await productRepository.findOne(eq(products.sku, 'WDG-001'));
|
|
896
989
|
* ```
|
|
897
990
|
*/
|
|
898
|
-
findOne(where:
|
|
991
|
+
findOne(where: SQL): Promise<TSelect | null>;
|
|
899
992
|
/**
|
|
900
993
|
* Find multiple records
|
|
901
994
|
*
|
|
902
|
-
* @param
|
|
995
|
+
* @param options - Query options (where, orderBy, limit, offset)
|
|
903
996
|
* @returns Promise resolving to an array of records
|
|
904
997
|
*
|
|
905
998
|
* @example
|
|
906
999
|
* ```typescript
|
|
1000
|
+
* import { eq, desc } from 'drizzle-orm';
|
|
1001
|
+
*
|
|
907
1002
|
* // Find all products
|
|
908
1003
|
* const products = await productRepository.findMany();
|
|
909
1004
|
*
|
|
910
1005
|
* // Find with filtering and pagination
|
|
911
1006
|
* const products = await productRepository.findMany({
|
|
912
|
-
* where:
|
|
913
|
-
* orderBy:
|
|
914
|
-
*
|
|
915
|
-
*
|
|
1007
|
+
* where: eq(products.status, 'ACTIVE'),
|
|
1008
|
+
* orderBy: desc(products.createdAt),
|
|
1009
|
+
* limit: 10,
|
|
1010
|
+
* offset: 0
|
|
916
1011
|
* });
|
|
917
1012
|
* ```
|
|
918
1013
|
*/
|
|
919
|
-
findMany(
|
|
1014
|
+
findMany(options?: {
|
|
1015
|
+
where?: SQL;
|
|
1016
|
+
orderBy?: SQL;
|
|
1017
|
+
limit?: number;
|
|
1018
|
+
offset?: number;
|
|
1019
|
+
}): Promise<TSelect[]>;
|
|
920
1020
|
/**
|
|
921
1021
|
* Update a record by ID
|
|
922
1022
|
*
|
|
@@ -931,24 +1031,26 @@ declare abstract class TenantBaseRepository<TModel, TCreateDTO = any, TUpdateDTO
|
|
|
931
1031
|
* });
|
|
932
1032
|
* ```
|
|
933
1033
|
*/
|
|
934
|
-
update(id: string, data:
|
|
1034
|
+
update(id: string, data: Partial<TInsert>): Promise<TSelect>;
|
|
935
1035
|
/**
|
|
936
1036
|
* Update multiple records
|
|
937
1037
|
*
|
|
938
|
-
* @param where -
|
|
1038
|
+
* @param where - SQL condition to match records
|
|
939
1039
|
* @param data - The data to update
|
|
940
1040
|
* @returns Promise resolving to the count of updated records
|
|
941
1041
|
*
|
|
942
1042
|
* @example
|
|
943
1043
|
* ```typescript
|
|
1044
|
+
* import { eq } from 'drizzle-orm';
|
|
1045
|
+
*
|
|
944
1046
|
* const result = await productRepository.updateMany(
|
|
945
|
-
*
|
|
1047
|
+
* eq(products.status, 'PENDING'),
|
|
946
1048
|
* { status: 'ACTIVE' }
|
|
947
1049
|
* );
|
|
948
1050
|
* console.log(`Updated ${result.count} products`);
|
|
949
1051
|
* ```
|
|
950
1052
|
*/
|
|
951
|
-
updateMany(where:
|
|
1053
|
+
updateMany(where: SQL, data: Partial<TInsert>): Promise<{
|
|
952
1054
|
count: number;
|
|
953
1055
|
}>;
|
|
954
1056
|
/**
|
|
@@ -962,55 +1064,62 @@ declare abstract class TenantBaseRepository<TModel, TCreateDTO = any, TUpdateDTO
|
|
|
962
1064
|
* const product = await productRepository.delete('product-id-123');
|
|
963
1065
|
* ```
|
|
964
1066
|
*/
|
|
965
|
-
delete(id: string): Promise<
|
|
1067
|
+
delete(id: string): Promise<TSelect>;
|
|
966
1068
|
/**
|
|
967
1069
|
* Delete multiple records
|
|
968
1070
|
*
|
|
969
|
-
* @param where -
|
|
1071
|
+
* @param where - SQL condition to match records
|
|
970
1072
|
* @returns Promise resolving to the count of deleted records
|
|
971
1073
|
*
|
|
972
1074
|
* @example
|
|
973
1075
|
* ```typescript
|
|
974
|
-
*
|
|
975
|
-
*
|
|
976
|
-
*
|
|
977
|
-
*
|
|
1076
|
+
* import { lt } from 'drizzle-orm';
|
|
1077
|
+
*
|
|
1078
|
+
* const result = await productRepository.deleteMany(
|
|
1079
|
+
* lt(products.createdAt, new Date('2020-01-01'))
|
|
1080
|
+
* );
|
|
978
1081
|
* console.log(`Deleted ${result.count} products`);
|
|
979
1082
|
* ```
|
|
980
1083
|
*/
|
|
981
|
-
deleteMany(where:
|
|
1084
|
+
deleteMany(where: SQL): Promise<{
|
|
982
1085
|
count: number;
|
|
983
1086
|
}>;
|
|
984
1087
|
/**
|
|
985
1088
|
* Count records
|
|
986
1089
|
*
|
|
987
|
-
* @param where - Optional
|
|
1090
|
+
* @param where - Optional SQL condition to filter records
|
|
988
1091
|
* @returns Promise resolving to the count of records
|
|
989
1092
|
*
|
|
990
1093
|
* @example
|
|
991
1094
|
* ```typescript
|
|
1095
|
+
* import { eq } from 'drizzle-orm';
|
|
1096
|
+
*
|
|
992
1097
|
* // Count all products
|
|
993
1098
|
* const total = await productRepository.count();
|
|
994
1099
|
*
|
|
995
1100
|
* // Count active products
|
|
996
|
-
* const activeCount = await productRepository.count(
|
|
1101
|
+
* const activeCount = await productRepository.count(
|
|
1102
|
+
* eq(products.status, 'ACTIVE')
|
|
1103
|
+
* );
|
|
997
1104
|
* ```
|
|
998
1105
|
*/
|
|
999
|
-
count(where?:
|
|
1106
|
+
count(where?: SQL): Promise<number>;
|
|
1000
1107
|
/**
|
|
1001
1108
|
* Check if a record exists
|
|
1002
1109
|
*
|
|
1003
|
-
* @param where -
|
|
1110
|
+
* @param where - SQL condition to match records
|
|
1004
1111
|
* @returns Promise resolving to true if at least one record exists, false otherwise
|
|
1005
1112
|
*
|
|
1006
1113
|
* @example
|
|
1007
1114
|
* ```typescript
|
|
1008
|
-
*
|
|
1009
|
-
*
|
|
1010
|
-
*
|
|
1115
|
+
* import { eq } from 'drizzle-orm';
|
|
1116
|
+
*
|
|
1117
|
+
* const skuExists = await productRepository.exists(
|
|
1118
|
+
* eq(products.sku, 'WDG-001')
|
|
1119
|
+
* );
|
|
1011
1120
|
* ```
|
|
1012
1121
|
*/
|
|
1013
|
-
exists(where:
|
|
1122
|
+
exists(where: SQL): Promise<boolean>;
|
|
1014
1123
|
}
|
|
1015
1124
|
|
|
1016
1125
|
declare class RequestService {
|
|
@@ -1334,7 +1443,7 @@ declare class HttpExceptionFilter implements ExceptionFilter {
|
|
|
1334
1443
|
catch(exception: unknown, host: ArgumentsHost): void;
|
|
1335
1444
|
}
|
|
1336
1445
|
|
|
1337
|
-
interface FieldError
|
|
1446
|
+
interface FieldError {
|
|
1338
1447
|
field?: string;
|
|
1339
1448
|
message: string;
|
|
1340
1449
|
}
|
|
@@ -1344,13 +1453,9 @@ interface ProblemDetails {
|
|
|
1344
1453
|
detail: string;
|
|
1345
1454
|
}
|
|
1346
1455
|
interface ApiErrorResponse extends ProblemDetails {
|
|
1347
|
-
errors: FieldError
|
|
1456
|
+
errors: FieldError[];
|
|
1348
1457
|
}
|
|
1349
1458
|
|
|
1350
|
-
interface FieldError {
|
|
1351
|
-
field?: string;
|
|
1352
|
-
message: string;
|
|
1353
|
-
}
|
|
1354
1459
|
declare abstract class BaseFieldException extends HttpException {
|
|
1355
1460
|
constructor(statusOrMessageOrErrors: HttpStatus | string | FieldError[], messageOrStatus?: string | HttpStatus, statusOrDetail?: HttpStatus | string, detail?: string);
|
|
1356
1461
|
}
|
|
@@ -1764,4 +1869,405 @@ declare class BadGatewayException extends BaseFieldException {
|
|
|
1764
1869
|
constructor(messageOrField: string | FieldError[], fieldMessageOrDetail?: string, detail?: string);
|
|
1765
1870
|
}
|
|
1766
1871
|
|
|
1767
|
-
|
|
1872
|
+
/**
|
|
1873
|
+
* Supported log levels for the logging system.
|
|
1874
|
+
*/
|
|
1875
|
+
type LogLevel = 'error' | 'warn' | 'log' | 'debug' | 'verbose';
|
|
1876
|
+
/**
|
|
1877
|
+
* Supported log output formats.
|
|
1878
|
+
*/
|
|
1879
|
+
type LogFormat = 'json' | 'text';
|
|
1880
|
+
/**
|
|
1881
|
+
* Metadata that can be attached to log entries.
|
|
1882
|
+
*/
|
|
1883
|
+
interface LogMetadata {
|
|
1884
|
+
correlationId?: string;
|
|
1885
|
+
method?: string;
|
|
1886
|
+
url?: string;
|
|
1887
|
+
statusCode?: number;
|
|
1888
|
+
duration?: number;
|
|
1889
|
+
ip?: string;
|
|
1890
|
+
userAgent?: string;
|
|
1891
|
+
[key: string]: unknown;
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1894
|
+
* Configuration options for the logger module.
|
|
1895
|
+
*/
|
|
1896
|
+
interface LoggerModuleOptions {
|
|
1897
|
+
provider?: 'default' | 'winston';
|
|
1898
|
+
level?: LogLevel;
|
|
1899
|
+
format?: LogFormat;
|
|
1900
|
+
enableFileLogger?: boolean;
|
|
1901
|
+
filePath?: string;
|
|
1902
|
+
maxFiles?: string;
|
|
1903
|
+
enableCorrelationId?: boolean;
|
|
1904
|
+
enableHttpLogger?: boolean;
|
|
1905
|
+
httpLogger?: HttpLoggerOptions;
|
|
1906
|
+
appName?: string;
|
|
1907
|
+
environment?: string;
|
|
1908
|
+
defaultMeta?: Record<string, unknown>;
|
|
1909
|
+
}
|
|
1910
|
+
/**
|
|
1911
|
+
* Factory function for creating logger options asynchronously.
|
|
1912
|
+
*/
|
|
1913
|
+
interface LoggerOptionsFactory {
|
|
1914
|
+
createLoggerOptions(): Promise<LoggerModuleOptions> | LoggerModuleOptions;
|
|
1915
|
+
}
|
|
1916
|
+
/**
|
|
1917
|
+
* Async configuration options for the logger module.
|
|
1918
|
+
*/
|
|
1919
|
+
interface LoggerModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
|
|
1920
|
+
useExisting?: Type<LoggerOptionsFactory>;
|
|
1921
|
+
useClass?: Type<LoggerOptionsFactory>;
|
|
1922
|
+
useFactory?: (...args: unknown[]) => Promise<LoggerModuleOptions> | LoggerModuleOptions;
|
|
1923
|
+
inject?: unknown[];
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Context object for correlation tracking across async operations.
|
|
1927
|
+
*/
|
|
1928
|
+
interface CorrelationContext {
|
|
1929
|
+
correlationId: string;
|
|
1930
|
+
[key: string]: unknown;
|
|
1931
|
+
}
|
|
1932
|
+
/**
|
|
1933
|
+
* Configuration options for HTTP request/response logger interceptor.
|
|
1934
|
+
*/
|
|
1935
|
+
interface HttpLoggerOptions {
|
|
1936
|
+
enableRequestLog?: boolean;
|
|
1937
|
+
enableResponseLog?: boolean;
|
|
1938
|
+
enableRequestBodyLog?: boolean;
|
|
1939
|
+
enableResponseBodyLog?: boolean;
|
|
1940
|
+
slowRequestThreshold?: number;
|
|
1941
|
+
excludedRoutes?: string[];
|
|
1942
|
+
maskedHeaders?: string[];
|
|
1943
|
+
maxBodySize?: number;
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
/**
|
|
1947
|
+
* Logger Module
|
|
1948
|
+
*
|
|
1949
|
+
* Dynamic NestJS module providing unified logging infrastructure with:
|
|
1950
|
+
* - Environment presets (development, staging, production, test)
|
|
1951
|
+
* - Transparent switching between default NestJS Logger and Winston
|
|
1952
|
+
* - Correlation ID tracking via middleware
|
|
1953
|
+
* - HTTP request/response logging via interceptor
|
|
1954
|
+
* - PII masking and file logging support
|
|
1955
|
+
*
|
|
1956
|
+
* @module logger/logger.module
|
|
1957
|
+
*/
|
|
1958
|
+
|
|
1959
|
+
/**
|
|
1960
|
+
* Dependency injection token for logger module options
|
|
1961
|
+
*/
|
|
1962
|
+
declare const LOGGER_MODULE_OPTIONS: unique symbol;
|
|
1963
|
+
/**
|
|
1964
|
+
* Global logger module providing unified logging infrastructure.
|
|
1965
|
+
*
|
|
1966
|
+
* Features:
|
|
1967
|
+
* - Environment presets (development, staging, production, test)
|
|
1968
|
+
* - Single `LoggerService` interface for all logging needs
|
|
1969
|
+
* - Transparent provider switching (default ↔ Winston)
|
|
1970
|
+
* - Correlation ID tracking across async operations
|
|
1971
|
+
* - HTTP request/response logging
|
|
1972
|
+
* - PII masking for GDPR compliance
|
|
1973
|
+
* - File-based logging with rotation
|
|
1974
|
+
*
|
|
1975
|
+
* @example
|
|
1976
|
+
* ```typescript
|
|
1977
|
+
* // Production environment with explicit config
|
|
1978
|
+
* @Module({
|
|
1979
|
+
* imports: [
|
|
1980
|
+
* LoggerModule.forRoot({
|
|
1981
|
+
* environment: 'production',
|
|
1982
|
+
* appName: 'my-service'
|
|
1983
|
+
* })
|
|
1984
|
+
* ],
|
|
1985
|
+
* })
|
|
1986
|
+
* export class AppModule {}
|
|
1987
|
+
*
|
|
1988
|
+
* // Development environment with custom override
|
|
1989
|
+
* @Module({
|
|
1990
|
+
* imports: [
|
|
1991
|
+
* LoggerModule.forRoot({
|
|
1992
|
+
* environment: 'development',
|
|
1993
|
+
* level: 'verbose' // Override preset's debug
|
|
1994
|
+
* })
|
|
1995
|
+
* ],
|
|
1996
|
+
* })
|
|
1997
|
+
* export class AppModule {}
|
|
1998
|
+
*
|
|
1999
|
+
* // Use default NestJS logger
|
|
2000
|
+
* @Module({
|
|
2001
|
+
* imports: [
|
|
2002
|
+
* LoggerModule.forRoot({
|
|
2003
|
+
* provider: 'default',
|
|
2004
|
+
* environment: 'development'
|
|
2005
|
+
* })
|
|
2006
|
+
* ],
|
|
2007
|
+
* })
|
|
2008
|
+
* export class AppModule {}
|
|
2009
|
+
*
|
|
2010
|
+
* // Dynamic configuration with ConfigService
|
|
2011
|
+
* @Module({
|
|
2012
|
+
* imports: [
|
|
2013
|
+
* LoggerModule.forRootAsync({
|
|
2014
|
+
* imports: [ConfigModule],
|
|
2015
|
+
* useFactory: (config: ConfigService) => ({
|
|
2016
|
+
* environment: config.get('NODE_ENV', 'development'),
|
|
2017
|
+
* provider: config.get('LOG_PROVIDER', 'winston'),
|
|
2018
|
+
* appName: config.get('APP_NAME')
|
|
2019
|
+
* }),
|
|
2020
|
+
* inject: [ConfigService]
|
|
2021
|
+
* })
|
|
2022
|
+
* ],
|
|
2023
|
+
* })
|
|
2024
|
+
* export class AppModule {}
|
|
2025
|
+
* ```
|
|
2026
|
+
*/
|
|
2027
|
+
declare class LoggerModule implements NestModule {
|
|
2028
|
+
/**
|
|
2029
|
+
* Configures the logger module with static options.
|
|
2030
|
+
*
|
|
2031
|
+
* Users must explicitly pass `environment` to select a preset.
|
|
2032
|
+
* All preset values can be overridden by passing explicit options.
|
|
2033
|
+
*
|
|
2034
|
+
* @param options - Logger configuration options
|
|
2035
|
+
* @returns Dynamic module configuration
|
|
2036
|
+
*
|
|
2037
|
+
* @example
|
|
2038
|
+
* ```typescript
|
|
2039
|
+
* // Production preset with app name
|
|
2040
|
+
* LoggerModule.forRoot({
|
|
2041
|
+
* environment: 'production',
|
|
2042
|
+
* appName: 'my-service'
|
|
2043
|
+
* })
|
|
2044
|
+
*
|
|
2045
|
+
* // Development preset with custom level
|
|
2046
|
+
* LoggerModule.forRoot({
|
|
2047
|
+
* environment: 'development',
|
|
2048
|
+
* level: 'verbose',
|
|
2049
|
+
* enableFileLogger: true
|
|
2050
|
+
* })
|
|
2051
|
+
*
|
|
2052
|
+
* // Use default NestJS logger
|
|
2053
|
+
* LoggerModule.forRoot({
|
|
2054
|
+
* provider: 'default',
|
|
2055
|
+
* environment: 'development'
|
|
2056
|
+
* })
|
|
2057
|
+
* ```
|
|
2058
|
+
*/
|
|
2059
|
+
static forRoot(options?: LoggerModuleOptions): DynamicModule;
|
|
2060
|
+
/**
|
|
2061
|
+
* Configures the logger module with async options.
|
|
2062
|
+
*
|
|
2063
|
+
* Supports dynamic configuration using:
|
|
2064
|
+
* - `useFactory`: Factory function with dependency injection
|
|
2065
|
+
* - `useClass`: Class implementing `LoggerOptionsFactory`
|
|
2066
|
+
* - `useExisting`: Existing provider implementing `LoggerOptionsFactory`
|
|
2067
|
+
*
|
|
2068
|
+
* Options from the factory/class are merged with environment preset defaults.
|
|
2069
|
+
*
|
|
2070
|
+
* @param options - Async configuration options
|
|
2071
|
+
* @returns Dynamic module configuration
|
|
2072
|
+
*
|
|
2073
|
+
* @example
|
|
2074
|
+
* ```typescript
|
|
2075
|
+
* // Factory with ConfigService
|
|
2076
|
+
* LoggerModule.forRootAsync({
|
|
2077
|
+
* imports: [ConfigModule],
|
|
2078
|
+
* useFactory: (config: ConfigService) => ({
|
|
2079
|
+
* environment: config.get('NODE_ENV', 'development'),
|
|
2080
|
+
* provider: config.get('LOG_PROVIDER', 'winston'),
|
|
2081
|
+
* level: config.get('LOG_LEVEL'),
|
|
2082
|
+
* appName: config.get('APP_NAME'),
|
|
2083
|
+
* }),
|
|
2084
|
+
* inject: [ConfigService]
|
|
2085
|
+
* })
|
|
2086
|
+
*
|
|
2087
|
+
* // Factory class
|
|
2088
|
+
* @Injectable()
|
|
2089
|
+
* class LoggerConfigService implements LoggerOptionsFactory {
|
|
2090
|
+
* createLoggerOptions(): LoggerModuleOptions {
|
|
2091
|
+
* return {
|
|
2092
|
+
* environment: 'production',
|
|
2093
|
+
* appName: 'my-service'
|
|
2094
|
+
* };
|
|
2095
|
+
* }
|
|
2096
|
+
* }
|
|
2097
|
+
*
|
|
2098
|
+
* LoggerModule.forRootAsync({
|
|
2099
|
+
* useClass: LoggerConfigService
|
|
2100
|
+
* })
|
|
2101
|
+
* ```
|
|
2102
|
+
*/
|
|
2103
|
+
static forRootAsync(options: LoggerModuleAsyncOptions): DynamicModule;
|
|
2104
|
+
/**
|
|
2105
|
+
* Configures middleware for the module.
|
|
2106
|
+
* Middleware is registered globally in main.ts using Fastify hooks.
|
|
2107
|
+
*/
|
|
2108
|
+
configure(consumer: MiddlewareConsumer): void;
|
|
2109
|
+
/**
|
|
2110
|
+
* Creates async providers for dynamic module configuration.
|
|
2111
|
+
*/
|
|
2112
|
+
private static createAsyncProviders;
|
|
2113
|
+
/**
|
|
2114
|
+
* Creates the async options provider.
|
|
2115
|
+
*/
|
|
2116
|
+
private static createAsyncOptionsProvider;
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
/**
|
|
2120
|
+
* Unified Logger Service
|
|
2121
|
+
*
|
|
2122
|
+
* Single service that provides both default NestJS Logger and Winston logger implementations.
|
|
2123
|
+
* Automatically delegates to the configured provider (default or winston).
|
|
2124
|
+
* @module logger/logger.service
|
|
2125
|
+
*/
|
|
2126
|
+
|
|
2127
|
+
/**
|
|
2128
|
+
* Unified logger service implementing NestJS LoggerService interface.
|
|
2129
|
+
* Supports both default NestJS Logger and Winston implementations via facade pattern.
|
|
2130
|
+
*/
|
|
2131
|
+
declare class LoggerService implements LoggerService$1 {
|
|
2132
|
+
private readonly defaultLogger?;
|
|
2133
|
+
private readonly activeLogger;
|
|
2134
|
+
private readonly options;
|
|
2135
|
+
private context?;
|
|
2136
|
+
constructor(options?: LoggerModuleOptions, defaultLogger?: Logger | undefined);
|
|
2137
|
+
/**
|
|
2138
|
+
* Creates a Winston logger instance with inline configuration.
|
|
2139
|
+
* Consolidates winston-config.factory.ts logic.
|
|
2140
|
+
*/
|
|
2141
|
+
private createWinstonLogger;
|
|
2142
|
+
log(message: any, context?: string): void;
|
|
2143
|
+
error(message: any, trace?: string, context?: string): void;
|
|
2144
|
+
warn(message: any, context?: string): void;
|
|
2145
|
+
debug(message: any, context?: string): void;
|
|
2146
|
+
verbose(message: any, context?: string): void;
|
|
2147
|
+
setContext(context: string): void;
|
|
2148
|
+
/**
|
|
2149
|
+
* Unified internal logging method that handles both Winston and NestJS Logger.
|
|
2150
|
+
*/
|
|
2151
|
+
private _log;
|
|
2152
|
+
/**
|
|
2153
|
+
* Logs with custom metadata (Winston only).
|
|
2154
|
+
*/
|
|
2155
|
+
logWithMetadata(level: LogLevel, message: any, metadata?: LogMetadata, context?: string): void;
|
|
2156
|
+
private formatMessage;
|
|
2157
|
+
/**
|
|
2158
|
+
* Enriches metadata with correlation context from AsyncLocalStorage.
|
|
2159
|
+
* Inline from winston-logger.service.ts
|
|
2160
|
+
*/
|
|
2161
|
+
private enrichMetadata;
|
|
2162
|
+
child(context: string): LoggerService;
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
/**
|
|
2166
|
+
* Correlation ID Middleware
|
|
2167
|
+
*
|
|
2168
|
+
* Generates unique correlation IDs for request tracking across async operations.
|
|
2169
|
+
* Stores correlation ID in AsyncLocalStorage for access throughout the request lifecycle.
|
|
2170
|
+
* @module logger/correlation-id.middleware
|
|
2171
|
+
*/
|
|
2172
|
+
|
|
2173
|
+
/**
|
|
2174
|
+
* Configuration options for the Correlation ID middleware.
|
|
2175
|
+
*/
|
|
2176
|
+
interface CorrelationIdMiddlewareOptions {
|
|
2177
|
+
/**
|
|
2178
|
+
* If true, adds the correlation ID to response headers.
|
|
2179
|
+
* @default true
|
|
2180
|
+
*/
|
|
2181
|
+
includeInResponse?: boolean;
|
|
2182
|
+
/**
|
|
2183
|
+
* The header name to use when adding correlation ID to response.
|
|
2184
|
+
* @default 'x-correlation-id'
|
|
2185
|
+
*/
|
|
2186
|
+
responseHeader?: string;
|
|
2187
|
+
}
|
|
2188
|
+
/**
|
|
2189
|
+
* Correlation ID Middleware for Fastify/NestJS applications.
|
|
2190
|
+
*
|
|
2191
|
+
* Generates a unique correlation ID for each request,
|
|
2192
|
+
* stores it in AsyncLocalStorage for access throughout the request lifecycle,
|
|
2193
|
+
* and optionally adds it to response headers.
|
|
2194
|
+
*/
|
|
2195
|
+
declare class CorrelationIdMiddleware implements NestMiddleware {
|
|
2196
|
+
private readonly includeInResponse;
|
|
2197
|
+
private readonly responseHeader;
|
|
2198
|
+
constructor(options?: CorrelationIdMiddlewareOptions);
|
|
2199
|
+
/**
|
|
2200
|
+
* Middleware handler for processing requests.
|
|
2201
|
+
*/
|
|
2202
|
+
use(req: FastifyRequest, reply: FastifyReply, next: () => void): void;
|
|
2203
|
+
/**
|
|
2204
|
+
* Fastify hook handler for onRequest.
|
|
2205
|
+
* This is an async function that returns a Promise, ensuring the AsyncLocalStorage
|
|
2206
|
+
* context persists throughout the entire request lifecycle.
|
|
2207
|
+
*/
|
|
2208
|
+
onRequest(req: FastifyRequest, reply: FastifyReply): Promise<void>;
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
/**
|
|
2212
|
+
* HTTP Logger Interceptor
|
|
2213
|
+
*
|
|
2214
|
+
* Automatically logs HTTP requests and responses with correlation tracking.
|
|
2215
|
+
* @module logger/http-logger.interceptor
|
|
2216
|
+
*/
|
|
2217
|
+
|
|
2218
|
+
/**
|
|
2219
|
+
* HTTP Logger Interceptor for NestJS applications.
|
|
2220
|
+
*
|
|
2221
|
+
* Logs all HTTP requests and responses with metadata including
|
|
2222
|
+
* correlation IDs, performance metrics, and error details.
|
|
2223
|
+
*/
|
|
2224
|
+
declare class HttpLoggerInterceptor implements NestInterceptor {
|
|
2225
|
+
private readonly logger;
|
|
2226
|
+
private readonly enableRequestLog;
|
|
2227
|
+
private readonly enableResponseLog;
|
|
2228
|
+
private readonly slowRequestThreshold;
|
|
2229
|
+
constructor(logger: LoggerService, options?: HttpLoggerOptions);
|
|
2230
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
|
|
2231
|
+
private logRequest;
|
|
2232
|
+
private logResponse;
|
|
2233
|
+
private logError;
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
/**
|
|
2237
|
+
* Logging Utilities
|
|
2238
|
+
*
|
|
2239
|
+
* Consolidated utilities for correlation tracking, PII masking, and async context management.
|
|
2240
|
+
* @module logging/utils
|
|
2241
|
+
*/
|
|
2242
|
+
|
|
2243
|
+
/**
|
|
2244
|
+
* Async local storage for correlation context tracking across async operations.
|
|
2245
|
+
*/
|
|
2246
|
+
declare const correlationStorage: AsyncLocalStorage<CorrelationContext>;
|
|
2247
|
+
/**
|
|
2248
|
+
* Gets the current correlation context from async local storage.
|
|
2249
|
+
*/
|
|
2250
|
+
declare function getCorrelationContext(): CorrelationContext | undefined;
|
|
2251
|
+
/**
|
|
2252
|
+
* Runs a callback within a correlation context.
|
|
2253
|
+
*/
|
|
2254
|
+
declare function runWithCorrelationContext<T>(context: CorrelationContext, callback: () => T): T;
|
|
2255
|
+
/**
|
|
2256
|
+
* Updates the current correlation context with new values.
|
|
2257
|
+
*/
|
|
2258
|
+
declare function updateCorrelationContext(updates: Partial<CorrelationContext>): void;
|
|
2259
|
+
/**
|
|
2260
|
+
* Default header name for setting correlation ID in responses.
|
|
2261
|
+
*/
|
|
2262
|
+
declare const DEFAULT_CORRELATION_HEADER = "x-correlation-id";
|
|
2263
|
+
/**
|
|
2264
|
+
* Generates a new correlation ID using UUID v4.
|
|
2265
|
+
* Always creates a fresh ID for each request.
|
|
2266
|
+
*/
|
|
2267
|
+
declare function generateCorrelationId(): string;
|
|
2268
|
+
/**
|
|
2269
|
+
* Adds correlation ID to Fastify response headers.
|
|
2270
|
+
*/
|
|
2271
|
+
declare function addCorrelationIdToResponse(reply: FastifyReply, correlationId: string, headerName?: string): void;
|
|
2272
|
+
|
|
2273
|
+
export { type ApiErrorResponse, AuthConfigModule, BadGatewayException, BadRequestException, BaseFieldException, ConflictException, type CorrelationContext, CorrelationIdMiddleware, CsrfGuard, DEFAULT_CORRELATION_HEADER, DatabaseModule, type DatabaseModuleOptions, type FieldError, ForbiddenException, GoneException, HttpExceptionFilter, HttpLoggerInterceptor, type HttpLoggerOptions, HttpModule, InternalServerErrorException, LOGGER_MODULE_OPTIONS, type LogFormat, type LogLevel, type LogMetadata, LoggerModule, type LoggerModuleAsyncOptions, type LoggerModuleOptions, type LoggerOptionsFactory, LoggerService, MethodNotAllowedException, NotAcceptableException, NotFoundException, NotImplementedException, Onboarding, PayloadTooLargeException, PrimaryBaseRepository, PrimaryDatabaseService, type PrimaryDbConfig, type ProblemDetails, Public, type RegisteredSchema, RequestTimeoutException, type SchemaRegistry, ServiceUnavailableException, Tenant, TenantBaseRepository, TenantContextService, TenantDatabaseService, type TenantInfo, TooManyRequestsException, type TypedDrizzleClient, UnauthorizedException, UnprocessableEntityException, UnsupportedMediaTypeException, ValidationException, VrittiAuthGuard, addCorrelationIdToResponse, correlationStorage, generateCorrelationId, getCorrelationContext, getHttpStatusTitle, runWithCorrelationContext, updateCorrelationContext };
|