@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/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
- * Primary database client constructor (for querying tenant registry)
122
- * Only required in gateway mode
123
- * @example import { PrismaClient } from '@prisma/client'
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
- prismaClientConstructor: any;
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, DbClient>)
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 dbClient = await this.tenantDatabase.getDbClient<PrismaClient>();
366
- * const users = await dbClient.user.findMany();
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, DbClient> */
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 Prisma client for the current tenant's database.
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 client instance
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 prismaClient(): any;
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 Promise<Database client instance>
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 createDbClient;
441
+ private createDbClientSync;
409
442
  /**
410
- * Build connection URL for enterprise tenant (dedicated database)
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
- /** Primary database client for querying tenant registry */
457
- private primaryDbClient;
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 initializePrimaryDbClient;
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 slug)
512
+ * Get tenant configuration by identifier (ID or subdomain)
478
513
  *
479
- * @param tenantIdentifier Tenant ID or slug
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 slug
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 Prisma client for the primary database.
501
- * This is a synchronous property that returns the initialized Prisma client.
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 client instance
538
+ * @returns Primary database Drizzle instance
504
539
  * @throws Error if primary database client is not initialized
505
540
  */
506
- get prismaClient(): any;
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
- * Abstract base repository for primary database operations.
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 TModel - The Prisma model type
524
- * @template TCreateDTO - DTO type for create operations
525
- * @template TUpdateDTO - DTO type for update operations
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
- * // Using the model delegate pattern (RECOMMENDED)
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
- * // Add custom methods as needed
542
- * async findByEmail(email: string): Promise<User | null> {
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 TenantRepository extends PrimaryBaseRepository<Tenant> {
608
+ * export class UserRepository extends PrimaryBaseRepository<typeof users> {
550
609
  * constructor(database: PrimaryDatabaseService) {
551
- * super(database, (p) => p.tenant); // ✅ Concise!
610
+ * super(database, users);
552
611
  * }
553
- * }
554
612
  *
555
- * // Works with complex model names
556
- * @Injectable()
557
- * export class EmailVerificationRepository extends PrimaryBaseRepository<EmailVerification> {
558
- * constructor(database: PrimaryDatabaseService) {
559
- * super(database, (p) => p.emailVerification); // ✅ Matches Prisma naming
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<TModel, TCreateDTO = any, TUpdateDTO = any> {
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
- * Lazy getter for Prisma client.
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 prisma(): any;
644
+ protected get db(): TypedDrizzleClient;
574
645
  /**
575
- * Lazy getter for the Prisma model delegate.
576
- * Returns the specific model (e.g., prisma.user, prisma.tenant) for this repository.
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(): any;
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 getModel - Function that returns the Prisma model delegate from the client
664
+ * @param table - The Drizzle table schema object
584
665
  *
585
666
  * @example
586
667
  * ```typescript
587
- * // Standard usage with full parameter name
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, (p) => p.emailVerification);
671
+ * super(database, users);
600
672
  * }
601
673
  * ```
602
674
  */
603
- constructor(database: PrimaryDatabaseService, getModel: (prisma: any) => any);
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
- * name: 'John Doe'
686
+ * firstName: 'John'
615
687
  * });
616
688
  * ```
617
689
  */
618
- create(data: TCreateDTO): Promise<TModel>;
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 null if not found
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<TModel | null>;
702
+ findById(id: string): Promise<TSelect | undefined>;
631
703
  /**
632
704
  * Find a single record with custom where clause
633
705
  *
634
- * @param where - The where clause or findUnique args
635
- * @returns Promise resolving to the record or null if not found
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
- * // Simple where clause
640
- * const user = await userRepository.findOne({ email: 'user@example.com' });
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: any): Promise<TModel | null>;
715
+ findOne(where: SQL): Promise<TSelect | undefined>;
650
716
  /**
651
717
  * Find multiple records
652
718
  *
653
- * @param args - Prisma findMany arguments (where, orderBy, take, skip, etc.)
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: { status: 'ACTIVE' },
664
- * orderBy: { createdAt: 'desc' },
665
- * take: 10,
666
- * skip: 0
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(args?: any): Promise<TModel[]>;
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
- * name: 'Jane Doe'
754
+ * firstName: 'Jane'
682
755
  * });
683
756
  * ```
684
757
  */
685
- update(id: string, data: TUpdateDTO): Promise<TModel>;
758
+ update(id: string, data: Partial<TInsert>): Promise<TSelect>;
686
759
  /**
687
760
  * Update multiple records
688
761
  *
689
- * @param where - The where clause to match records
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
- * { status: 'PENDING' },
697
- * { status: 'ACTIVE' }
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: any, data: TUpdateDTO): Promise<{
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<TModel>;
791
+ delete(id: string): Promise<TSelect>;
717
792
  /**
718
793
  * Delete multiple records
719
794
  *
720
- * @param where - The where clause to match records
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
- * const result = await userRepository.deleteMany({
726
- * status: 'INACTIVE',
727
- * createdAt: { lt: new Date('2020-01-01') }
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: any): Promise<{
808
+ deleteMany(where: SQL): Promise<{
733
809
  count: number;
734
810
  }>;
735
811
  /**
736
812
  * Count records
737
813
  *
738
- * @param where - Optional where clause to filter records
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({ status: 'ACTIVE' });
825
+ * const activeCount = await userRepository.count(
826
+ * eq(users.accountStatus, 'ACTIVE')
827
+ * );
748
828
  * ```
749
829
  */
750
- count(where?: any): Promise<number>;
830
+ count(where?: SQL): Promise<number>;
751
831
  /**
752
832
  * Check if a record exists
753
833
  *
754
- * @param where - The where clause to match records
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
- * const emailExists = await userRepository.exists({
760
- * email: 'user@example.com'
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: any): Promise<boolean>;
846
+ exists(where: SQL): Promise<boolean>;
765
847
  }
766
848
 
767
849
  /**
768
- * Abstract base repository for tenant-scoped database operations.
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 TModel - The Prisma model type
772
- * @template TCreateDTO - DTO type for create operations
773
- * @template TUpdateDTO - DTO type for update operations
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
- * // Using the model delegate pattern (RECOMMENDED)
778
- * // Type-safe, IDE autocomplete, refactor-friendly
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, (prisma) => prisma.product); // ✅ Type-safe with autocomplete!
883
+ * super(database, products);
787
884
  * }
788
885
  *
789
- * // Add custom methods as needed
886
+ * // Use SQL-builder syntax
790
887
  * async findBySku(sku: string): Promise<Product | null> {
791
- * return this.model.findUnique({ where: { sku } });
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
- * // Short syntax is also supported
796
- * @Injectable()
797
- * export class OrderRepository extends TenantBaseRepository<Order> {
798
- * constructor(database: TenantDatabaseService) {
799
- * super(database, (p) => p.order); // Concise!
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<TModel, TCreateDTO = any, TUpdateDTO = any> {
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
- * Lazy getter for Prisma client.
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 prisma(): any;
920
+ protected get db(): TypedDrizzleClient;
822
921
  /**
823
- * Lazy getter for the Prisma model delegate.
824
- * Returns the specific model (e.g., prisma.product, prisma.order) for this repository.
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(): any;
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 getModel - Function that returns the Prisma model delegate from the client
939
+ * @param table - The Drizzle table schema object
832
940
  *
833
941
  * @example
834
942
  * ```typescript
835
- * // Standard usage with full parameter name
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, (p) => p.product);
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, getModel: (prisma: any) => any);
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: TCreateDTO): Promise<TModel>;
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<TModel | null>;
978
+ findById(id: string): Promise<TSelect | null>;
880
979
  /**
881
980
  * Find a single record with custom where clause
882
981
  *
883
- * @param where - The where clause or findUnique args
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
- * // Simple where clause
889
- * const product = await productRepository.findOne({ sku: 'WDG-001' });
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: any): Promise<TModel | null>;
991
+ findOne(where: SQL): Promise<TSelect | null>;
899
992
  /**
900
993
  * Find multiple records
901
994
  *
902
- * @param args - Prisma findMany arguments (where, orderBy, take, skip, etc.)
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: { status: 'ACTIVE' },
913
- * orderBy: { createdAt: 'desc' },
914
- * take: 10,
915
- * skip: 0
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(args?: any): Promise<TModel[]>;
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: TUpdateDTO): Promise<TModel>;
1034
+ update(id: string, data: Partial<TInsert>): Promise<TSelect>;
935
1035
  /**
936
1036
  * Update multiple records
937
1037
  *
938
- * @param where - The where clause to match records
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
- * { status: 'PENDING' },
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: any, data: TUpdateDTO): Promise<{
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<TModel>;
1067
+ delete(id: string): Promise<TSelect>;
966
1068
  /**
967
1069
  * Delete multiple records
968
1070
  *
969
- * @param where - The where clause to match records
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
- * const result = await productRepository.deleteMany({
975
- * status: 'INACTIVE',
976
- * createdAt: { lt: new Date('2020-01-01') }
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: any): Promise<{
1084
+ deleteMany(where: SQL): Promise<{
982
1085
  count: number;
983
1086
  }>;
984
1087
  /**
985
1088
  * Count records
986
1089
  *
987
- * @param where - Optional where clause to filter records
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({ status: 'ACTIVE' });
1101
+ * const activeCount = await productRepository.count(
1102
+ * eq(products.status, 'ACTIVE')
1103
+ * );
997
1104
  * ```
998
1105
  */
999
- count(where?: any): Promise<number>;
1106
+ count(where?: SQL): Promise<number>;
1000
1107
  /**
1001
1108
  * Check if a record exists
1002
1109
  *
1003
- * @param where - The where clause to match records
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
- * const skuExists = await productRepository.exists({
1009
- * sku: 'WDG-001'
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: any): Promise<boolean>;
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$1 {
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$1[];
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
- export { type ApiErrorResponse, AuthConfigModule, BadGatewayException, BadRequestException, BaseFieldException, ConflictException, CsrfGuard, DatabaseModule, type DatabaseModuleOptions, type FieldError, ForbiddenException, GoneException, HttpExceptionFilter, HttpModule, InternalServerErrorException, MethodNotAllowedException, NotAcceptableException, NotFoundException, NotImplementedException, Onboarding, PayloadTooLargeException, PrimaryBaseRepository, PrimaryDatabaseService, type PrimaryDbConfig, type ProblemDetails, Public, RequestTimeoutException, ServiceUnavailableException, Tenant, TenantBaseRepository, TenantContextService, TenantDatabaseService, type TenantInfo, TooManyRequestsException, UnauthorizedException, UnprocessableEntityException, UnsupportedMediaTypeException, ValidationException, VrittiAuthGuard, getHttpStatusTitle };
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 };