@vritti/api-sdk 0.0.9 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +342 -274
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +363 -213
- package/dist/index.d.ts +363 -213
- package/dist/index.js +342 -274
- package/dist/index.js.map +1 -1
- package/package.json +33 -10
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import * as _nestjs_common from '@nestjs/common';
|
|
2
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';
|
|
@@ -81,6 +84,34 @@ declare class AuthConfigModule {
|
|
|
81
84
|
static forRootAsync(): DynamicModule;
|
|
82
85
|
}
|
|
83
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
|
+
|
|
84
115
|
/**
|
|
85
116
|
* Primary database connection configuration
|
|
86
117
|
*/
|
|
@@ -97,8 +128,8 @@ interface PrimaryDbConfig {
|
|
|
97
128
|
database: string;
|
|
98
129
|
/** Default schema (default: 'public') */
|
|
99
130
|
schema?: string;
|
|
100
|
-
/** SSL mode: 'require' | 'prefer' | 'disable' (default: 'require') */
|
|
101
|
-
sslMode?: 'require' | 'prefer' | 'disable';
|
|
131
|
+
/** SSL mode: 'require' | 'prefer' | 'disable' | 'no-verify' (default: 'require') */
|
|
132
|
+
sslMode?: 'require' | 'prefer' | 'disable' | 'no-verify';
|
|
102
133
|
}
|
|
103
134
|
/**
|
|
104
135
|
* Configuration options for DatabaseModule
|
|
@@ -120,11 +151,17 @@ interface DatabaseModuleOptions {
|
|
|
120
151
|
*/
|
|
121
152
|
primaryDb: PrimaryDbConfig;
|
|
122
153
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* @example import
|
|
154
|
+
* Drizzle schema object containing all tables
|
|
155
|
+
* Import your schema from db/schema/index.ts and pass it here
|
|
156
|
+
* @example import * as schema from '@/db/schema'
|
|
157
|
+
*/
|
|
158
|
+
drizzleSchema: RegisteredSchema;
|
|
159
|
+
/**
|
|
160
|
+
* Drizzle relations object from defineRelations()
|
|
161
|
+
* Required for relational queries (db.query.*.findFirst/findMany)
|
|
162
|
+
* @example import { relations } from '@/db/schema'
|
|
126
163
|
*/
|
|
127
|
-
|
|
164
|
+
drizzleRelations?: Record<string, any>;
|
|
128
165
|
/**
|
|
129
166
|
* Connection cache TTL in milliseconds
|
|
130
167
|
* Idle connections will be closed after this period
|
|
@@ -356,7 +393,7 @@ declare class TenantContextService {
|
|
|
356
393
|
* Service responsible for managing tenant-scoped database connections
|
|
357
394
|
*
|
|
358
395
|
* This service:
|
|
359
|
-
* - Maintains a connection pool (Map<cacheKey,
|
|
396
|
+
* - Maintains a connection pool (Map<cacheKey, TenantConnection>)
|
|
360
397
|
* - Creates new connections dynamically based on tenant context
|
|
361
398
|
* - Reuses existing connections for the same tenant
|
|
362
399
|
* - Supports both cloud schemas and enterprise databases
|
|
@@ -364,14 +401,14 @@ declare class TenantContextService {
|
|
|
364
401
|
*
|
|
365
402
|
* @example
|
|
366
403
|
* // In a controller or service
|
|
367
|
-
* const
|
|
368
|
-
* const users = await
|
|
404
|
+
* const db = this.tenantDatabase.drizzleClient;
|
|
405
|
+
* const users = await db.select().from(usersTable);
|
|
369
406
|
*/
|
|
370
407
|
declare class TenantDatabaseService implements OnModuleDestroy {
|
|
371
408
|
private readonly options;
|
|
372
409
|
private readonly tenantContext;
|
|
373
410
|
private readonly logger;
|
|
374
|
-
/** Connection pool: Map<cacheKey,
|
|
411
|
+
/** Connection pool: Map<cacheKey, TenantConnection> */
|
|
375
412
|
private readonly clients;
|
|
376
413
|
/** Track last usage time for idle connection cleanup */
|
|
377
414
|
private readonly clientLastUsed;
|
|
@@ -379,14 +416,18 @@ declare class TenantDatabaseService implements OnModuleDestroy {
|
|
|
379
416
|
private cleanupInterval?;
|
|
380
417
|
constructor(options: DatabaseModuleOptions, tenantContext: TenantContextService);
|
|
381
418
|
/**
|
|
382
|
-
* Get the
|
|
419
|
+
* Get the Drizzle client for the current tenant's database.
|
|
383
420
|
* This returns the tenant-scoped database client.
|
|
384
421
|
*
|
|
385
|
-
* @returns Tenant-scoped database
|
|
422
|
+
* @returns Tenant-scoped Drizzle database instance
|
|
386
423
|
* @throws UnauthorizedException if tenant context not set
|
|
387
424
|
* @throws InternalServerErrorException if connection fails
|
|
388
425
|
*/
|
|
389
|
-
get
|
|
426
|
+
get drizzleClient(): TypedDrizzleClient;
|
|
427
|
+
/**
|
|
428
|
+
* Get the Drizzle schema
|
|
429
|
+
*/
|
|
430
|
+
get schema(): Record<string, unknown>;
|
|
390
431
|
/**
|
|
391
432
|
* Get tenant-scoped database client for the current request/message
|
|
392
433
|
*
|
|
@@ -395,21 +436,17 @@ declare class TenantDatabaseService implements OnModuleDestroy {
|
|
|
395
436
|
* 2. Builds a connection URL based on tenant type
|
|
396
437
|
* 3. Returns cached client if exists, otherwise creates new one
|
|
397
438
|
*
|
|
398
|
-
* @returns
|
|
439
|
+
* @returns Drizzle database instance
|
|
399
440
|
* @throws UnauthorizedException if tenant context not set
|
|
400
441
|
* @throws InternalServerErrorException if connection fails
|
|
401
|
-
*
|
|
402
|
-
* @example
|
|
403
|
-
* const dbClient = await tenantDatabase.getDbClient<PrismaClient>();
|
|
404
|
-
* const users = await dbClient.user.findMany();
|
|
405
442
|
*/
|
|
406
443
|
private getDbClient;
|
|
407
444
|
/**
|
|
408
|
-
* Create a new database client for the given tenant
|
|
445
|
+
* Create a new database client for the given tenant (synchronous)
|
|
409
446
|
*/
|
|
410
|
-
private
|
|
447
|
+
private createDbClientSync;
|
|
411
448
|
/**
|
|
412
|
-
* Build connection URL for
|
|
449
|
+
* Build connection URL for tenant (dedicated database)
|
|
413
450
|
*/
|
|
414
451
|
private buildTenantDbUrl;
|
|
415
452
|
/**
|
|
@@ -455,8 +492,10 @@ declare class TenantDatabaseService implements OnModuleDestroy {
|
|
|
455
492
|
declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
|
|
456
493
|
private readonly options;
|
|
457
494
|
private readonly logger;
|
|
458
|
-
/**
|
|
459
|
-
private
|
|
495
|
+
/** PostgreSQL connection pool */
|
|
496
|
+
private pool;
|
|
497
|
+
/** Drizzle database instance */
|
|
498
|
+
private db;
|
|
460
499
|
/** In-memory cache: Map<tenantIdentifier, TenantConfig> */
|
|
461
500
|
private readonly tenantConfigCache;
|
|
462
501
|
/** Cache TTL in milliseconds */
|
|
@@ -464,9 +503,9 @@ declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
|
|
|
464
503
|
constructor(options: DatabaseModuleOptions);
|
|
465
504
|
onModuleInit(): Promise<void>;
|
|
466
505
|
/**
|
|
467
|
-
* Initialize connection to primary database
|
|
506
|
+
* Initialize connection to primary database using Drizzle
|
|
468
507
|
*/
|
|
469
|
-
private
|
|
508
|
+
private initializeDrizzleClient;
|
|
470
509
|
/**
|
|
471
510
|
* Build connection URL from primary database properties
|
|
472
511
|
*/
|
|
@@ -476,9 +515,9 @@ declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
|
|
|
476
515
|
*/
|
|
477
516
|
private maskPassword;
|
|
478
517
|
/**
|
|
479
|
-
* Get tenant configuration by identifier (ID or
|
|
518
|
+
* Get tenant configuration by identifier (ID or subdomain)
|
|
480
519
|
*
|
|
481
|
-
* @param tenantIdentifier Tenant ID or
|
|
520
|
+
* @param tenantIdentifier Tenant ID or subdomain
|
|
482
521
|
* @returns Tenant configuration or null if not found
|
|
483
522
|
*/
|
|
484
523
|
getTenantInfo(tenantIdentifier: string): Promise<TenantInfo | null>;
|
|
@@ -491,7 +530,7 @@ declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
|
|
|
491
530
|
*
|
|
492
531
|
* Useful when tenant settings are updated and cache needs to be invalidated
|
|
493
532
|
*
|
|
494
|
-
* @param tenantIdentifier Tenant ID or
|
|
533
|
+
* @param tenantIdentifier Tenant ID or subdomain
|
|
495
534
|
*/
|
|
496
535
|
clearTenantCache(tenantIdentifier: string): void;
|
|
497
536
|
/**
|
|
@@ -499,13 +538,17 @@ declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
|
|
|
499
538
|
*/
|
|
500
539
|
clearAllCaches(): void;
|
|
501
540
|
/**
|
|
502
|
-
* Get the
|
|
503
|
-
* This is a synchronous property that returns the initialized
|
|
541
|
+
* Get the Drizzle database instance for the primary database.
|
|
542
|
+
* This is a synchronous property that returns the initialized Drizzle client.
|
|
504
543
|
*
|
|
505
|
-
* @returns Primary database
|
|
544
|
+
* @returns Primary database Drizzle instance
|
|
506
545
|
* @throws Error if primary database client is not initialized
|
|
507
546
|
*/
|
|
508
|
-
get
|
|
547
|
+
get drizzleClient(): TypedDrizzleClient;
|
|
548
|
+
/**
|
|
549
|
+
* Get the Drizzle schema
|
|
550
|
+
*/
|
|
551
|
+
get schema(): typeof this$1.options.drizzleSchema;
|
|
509
552
|
/**
|
|
510
553
|
* Decrypt database credentials
|
|
511
554
|
*
|
|
@@ -519,90 +562,147 @@ declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
|
|
|
519
562
|
}
|
|
520
563
|
|
|
521
564
|
/**
|
|
522
|
-
*
|
|
565
|
+
* Drizzle ORM v2 object-based where filter type.
|
|
566
|
+
* Supports simple equality, operators, AND/OR/NOT, and RAW SQL.
|
|
567
|
+
*
|
|
568
|
+
* @example
|
|
569
|
+
* ```typescript
|
|
570
|
+
* // Simple equality
|
|
571
|
+
* { email: 'user@example.com' }
|
|
572
|
+
*
|
|
573
|
+
* // With operators
|
|
574
|
+
* { age: { gt: 18, lt: 65 } }
|
|
575
|
+
*
|
|
576
|
+
* // AND/OR combinations
|
|
577
|
+
* { AND: [{ status: 'ACTIVE' }, { age: { gte: 18 } }] }
|
|
578
|
+
*
|
|
579
|
+
* // RAW SQL expression
|
|
580
|
+
* { RAW: (table) => sql`${table.email} ILIKE '%@gmail.com'` }
|
|
581
|
+
* ```
|
|
582
|
+
*/
|
|
583
|
+
type RelationsWhereFilter = Record<string, any>;
|
|
584
|
+
/**
|
|
585
|
+
* Type-safe wrapper for Drizzle's RelationalQueryBuilder (v2 API).
|
|
586
|
+
* This interface matches the method signatures of RelationalQueryBuilder
|
|
587
|
+
* but properly binds the TSelect generic for type safety.
|
|
588
|
+
*
|
|
589
|
+
* We use this instead of RelationalQueryBuilder directly because
|
|
590
|
+
* TypeScript cannot infer TSelect from the generic base repository context.
|
|
591
|
+
*
|
|
592
|
+
* @remarks
|
|
593
|
+
* Drizzle ORM v2 uses object-based `where` filters instead of SQL expressions.
|
|
594
|
+
* See: https://orm.drizzle.team/docs/relations-v1-v2
|
|
595
|
+
*/
|
|
596
|
+
interface TypedRelationalQueryBuilder<TSelect> {
|
|
597
|
+
findFirst(config?: {
|
|
598
|
+
where?: RelationsWhereFilter;
|
|
599
|
+
with?: Record<string, unknown>;
|
|
600
|
+
columns?: Record<string, boolean>;
|
|
601
|
+
}): Promise<TSelect | undefined>;
|
|
602
|
+
findMany(config?: {
|
|
603
|
+
where?: RelationsWhereFilter;
|
|
604
|
+
orderBy?: Record<string, 'asc' | 'desc'>;
|
|
605
|
+
limit?: number;
|
|
606
|
+
offset?: number;
|
|
607
|
+
with?: Record<string, unknown>;
|
|
608
|
+
columns?: Record<string, boolean>;
|
|
609
|
+
}): Promise<TSelect[]>;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Abstract base repository for primary database operations using Drizzle ORM.
|
|
523
613
|
* Provides common CRUD operations with automatic logging.
|
|
524
614
|
*
|
|
525
|
-
* @template
|
|
526
|
-
* @template
|
|
527
|
-
* @template
|
|
615
|
+
* @template TTable - The Drizzle table type (must be registered in SchemaRegistry)
|
|
616
|
+
* @template TInsert - Type for insert operations (inferred from table.$inferInsert)
|
|
617
|
+
* @template TSelect - Type for select operations (inferred from table.$inferSelect)
|
|
618
|
+
*
|
|
619
|
+
* @remarks
|
|
620
|
+
* **Type Assertion Pattern:** This repository uses `as any` casts when passing
|
|
621
|
+
* the generic table to Drizzle methods. This is necessary because TypeScript
|
|
622
|
+
* cannot prove that a generic `TTable extends PgTable` satisfies Drizzle's
|
|
623
|
+
* stricter internal type requirements for `insert()`, `update()`, and `delete()`.
|
|
624
|
+
*
|
|
625
|
+
* The public API maintains full type safety:
|
|
626
|
+
* - Input parameters are typed as `TInsert` (inferred from table)
|
|
627
|
+
* - Return values are typed as `TSelect` (inferred from table)
|
|
628
|
+
* - The casts are implementation details that don't leak to consumers
|
|
528
629
|
*
|
|
529
630
|
* @example
|
|
530
631
|
* ```typescript
|
|
531
|
-
*
|
|
532
|
-
* // Type-safe, IDE autocomplete, refactor-friendly
|
|
533
|
-
* @Injectable()
|
|
534
|
-
* export class UserRepository extends PrimaryBaseRepository<
|
|
535
|
-
* User,
|
|
536
|
-
* CreateUserDto,
|
|
537
|
-
* UpdateUserDto
|
|
538
|
-
* > {
|
|
539
|
-
* constructor(database: PrimaryDatabaseService) {
|
|
540
|
-
* super(database, (prisma) => prisma.user); // ✅ Type-safe with autocomplete!
|
|
541
|
-
* }
|
|
632
|
+
* import { users } from '@/db/schema';
|
|
542
633
|
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
* return this.model.findUnique({ where: { email } });
|
|
546
|
-
* }
|
|
547
|
-
* }
|
|
634
|
+
* type User = typeof users.$inferSelect;
|
|
635
|
+
* type NewUser = typeof users.$inferInsert;
|
|
548
636
|
*
|
|
549
|
-
* // Short syntax is also supported
|
|
550
637
|
* @Injectable()
|
|
551
|
-
* export class
|
|
638
|
+
* export class UserRepository extends PrimaryBaseRepository<typeof users> {
|
|
552
639
|
* constructor(database: PrimaryDatabaseService) {
|
|
553
|
-
* super(database,
|
|
640
|
+
* super(database, users);
|
|
554
641
|
* }
|
|
555
|
-
* }
|
|
556
642
|
*
|
|
557
|
-
*
|
|
558
|
-
*
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
*
|
|
643
|
+
* // Use Drizzle v2 object-based where syntax (recommended)
|
|
644
|
+
* async findByEmail(email: string): Promise<User | undefined> {
|
|
645
|
+
* return this.model.findFirst({
|
|
646
|
+
* where: { email },
|
|
647
|
+
* });
|
|
648
|
+
* }
|
|
649
|
+
*
|
|
650
|
+
* // With relations
|
|
651
|
+
* async findWithRelations(id: string): Promise<User | undefined> {
|
|
652
|
+
* return this.model.findFirst({
|
|
653
|
+
* where: { id },
|
|
654
|
+
* with: { posts: true, profile: true }
|
|
655
|
+
* });
|
|
562
656
|
* }
|
|
563
657
|
* }
|
|
564
658
|
* ```
|
|
565
659
|
*/
|
|
566
|
-
declare abstract class PrimaryBaseRepository<
|
|
660
|
+
declare abstract class PrimaryBaseRepository<TTable extends PgTable, TInsert = InferInsertModel<TTable>, TSelect = InferSelectModel<TTable>> {
|
|
567
661
|
protected readonly database: PrimaryDatabaseService;
|
|
662
|
+
protected readonly table: TTable;
|
|
568
663
|
protected readonly logger: Logger;
|
|
569
|
-
private readonly modelGetter;
|
|
570
664
|
/**
|
|
571
|
-
*
|
|
665
|
+
* The table name extracted from the Drizzle table at runtime.
|
|
666
|
+
* Used to access the query API for this repository's table.
|
|
667
|
+
*/
|
|
668
|
+
private readonly tableName;
|
|
669
|
+
/**
|
|
670
|
+
* Lazy getter for Drizzle client.
|
|
572
671
|
* Accesses the client from the database service only when needed,
|
|
573
672
|
* avoiding initialization timing issues with NestJS lifecycle.
|
|
574
673
|
*/
|
|
575
|
-
protected get
|
|
674
|
+
protected get db(): TypedDrizzleClient;
|
|
576
675
|
/**
|
|
577
|
-
*
|
|
578
|
-
*
|
|
676
|
+
* Model query API for THIS repository's table (Drizzle v2 relational queries)
|
|
677
|
+
* Scoped to only the table this repository manages.
|
|
678
|
+
* Returns a type-safe wrapper around Drizzle's RelationalQueryBuilder.
|
|
679
|
+
*
|
|
680
|
+
* @example
|
|
681
|
+
* ```typescript
|
|
682
|
+
* // Use relational queries with v2 object-based where syntax
|
|
683
|
+
* const user = await this.model.findFirst({
|
|
684
|
+
* where: { id },
|
|
685
|
+
* with: { posts: true, profile: true }
|
|
686
|
+
* });
|
|
687
|
+
* ```
|
|
579
688
|
*/
|
|
580
|
-
protected get model():
|
|
689
|
+
protected get model(): TypedRelationalQueryBuilder<TSelect>;
|
|
581
690
|
/**
|
|
582
691
|
* Create a new repository instance
|
|
583
692
|
*
|
|
584
693
|
* @param database - The primary database service
|
|
585
|
-
* @param
|
|
694
|
+
* @param table - The Drizzle table schema object
|
|
586
695
|
*
|
|
587
696
|
* @example
|
|
588
697
|
* ```typescript
|
|
589
|
-
*
|
|
590
|
-
* constructor(database: PrimaryDatabaseService) {
|
|
591
|
-
* super(database, (prisma) => prisma.user);
|
|
592
|
-
* }
|
|
593
|
-
*
|
|
594
|
-
* // Short syntax
|
|
595
|
-
* constructor(database: PrimaryDatabaseService) {
|
|
596
|
-
* super(database, (p) => p.user);
|
|
597
|
-
* }
|
|
698
|
+
* import { users } from '@/db/schema';
|
|
598
699
|
*
|
|
599
|
-
* // Complex model names
|
|
600
700
|
* constructor(database: PrimaryDatabaseService) {
|
|
601
|
-
* super(database,
|
|
701
|
+
* super(database, users);
|
|
602
702
|
* }
|
|
603
703
|
* ```
|
|
604
704
|
*/
|
|
605
|
-
constructor(database: PrimaryDatabaseService,
|
|
705
|
+
constructor(database: PrimaryDatabaseService, table: TTable);
|
|
606
706
|
/**
|
|
607
707
|
* Create a new record
|
|
608
708
|
*
|
|
@@ -613,46 +713,49 @@ declare abstract class PrimaryBaseRepository<TModel, TCreateDTO = any, TUpdateDT
|
|
|
613
713
|
* ```typescript
|
|
614
714
|
* const user = await userRepository.create({
|
|
615
715
|
* email: 'user@example.com',
|
|
616
|
-
*
|
|
716
|
+
* firstName: 'John'
|
|
617
717
|
* });
|
|
618
718
|
* ```
|
|
619
719
|
*/
|
|
620
|
-
create(data:
|
|
720
|
+
create(data: TInsert): Promise<TSelect>;
|
|
621
721
|
/**
|
|
622
722
|
* Find a single record by ID
|
|
623
723
|
*
|
|
624
724
|
* @param id - The record ID
|
|
625
|
-
* @returns Promise resolving to the record or
|
|
725
|
+
* @returns Promise resolving to the record or undefined if not found
|
|
626
726
|
*
|
|
627
727
|
* @example
|
|
628
728
|
* ```typescript
|
|
629
729
|
* const user = await userRepository.findById('user-id-123');
|
|
630
730
|
* ```
|
|
631
731
|
*/
|
|
632
|
-
findById(id: string): Promise<
|
|
732
|
+
findById(id: string): Promise<TSelect | undefined>;
|
|
633
733
|
/**
|
|
634
|
-
* Find a single record with custom where clause
|
|
734
|
+
* Find a single record with custom where clause (Drizzle v2 object-based syntax)
|
|
635
735
|
*
|
|
636
|
-
* @param where -
|
|
637
|
-
* @returns Promise resolving to the record or
|
|
736
|
+
* @param where - Object-based filter condition
|
|
737
|
+
* @returns Promise resolving to the record or undefined if not found
|
|
638
738
|
*
|
|
639
739
|
* @example
|
|
640
740
|
* ```typescript
|
|
641
|
-
* // Simple
|
|
741
|
+
* // Simple equality
|
|
642
742
|
* const user = await userRepository.findOne({ email: 'user@example.com' });
|
|
643
743
|
*
|
|
644
|
-
* // With
|
|
744
|
+
* // With operators
|
|
745
|
+
* const user = await userRepository.findOne({ age: { gte: 18 } });
|
|
746
|
+
*
|
|
747
|
+
* // Multiple conditions (AND)
|
|
645
748
|
* const user = await userRepository.findOne({
|
|
646
|
-
*
|
|
647
|
-
*
|
|
749
|
+
* email: 'user@example.com',
|
|
750
|
+
* status: 'ACTIVE'
|
|
648
751
|
* });
|
|
649
752
|
* ```
|
|
650
753
|
*/
|
|
651
|
-
findOne(where:
|
|
754
|
+
findOne(where: RelationsWhereFilter): Promise<TSelect | undefined>;
|
|
652
755
|
/**
|
|
653
|
-
* Find multiple records
|
|
756
|
+
* Find multiple records (Drizzle v2 object-based syntax)
|
|
654
757
|
*
|
|
655
|
-
* @param
|
|
758
|
+
* @param options - Query options (where, orderBy, limit, offset)
|
|
656
759
|
* @returns Promise resolving to an array of records
|
|
657
760
|
*
|
|
658
761
|
* @example
|
|
@@ -660,16 +763,31 @@ declare abstract class PrimaryBaseRepository<TModel, TCreateDTO = any, TUpdateDT
|
|
|
660
763
|
* // Find all users
|
|
661
764
|
* const users = await userRepository.findMany();
|
|
662
765
|
*
|
|
663
|
-
* // Find with filtering and pagination
|
|
766
|
+
* // Find with filtering and pagination (v2 object syntax)
|
|
664
767
|
* const users = await userRepository.findMany({
|
|
665
|
-
* where: {
|
|
768
|
+
* where: { accountStatus: 'ACTIVE' },
|
|
666
769
|
* orderBy: { createdAt: 'desc' },
|
|
667
|
-
*
|
|
668
|
-
*
|
|
770
|
+
* limit: 10,
|
|
771
|
+
* offset: 0
|
|
772
|
+
* });
|
|
773
|
+
*
|
|
774
|
+
* // Multiple conditions
|
|
775
|
+
* const users = await userRepository.findMany({
|
|
776
|
+
* where: {
|
|
777
|
+
* AND: [
|
|
778
|
+
* { status: 'ACTIVE' },
|
|
779
|
+
* { age: { gte: 18 } }
|
|
780
|
+
* ]
|
|
781
|
+
* }
|
|
669
782
|
* });
|
|
670
783
|
* ```
|
|
671
784
|
*/
|
|
672
|
-
findMany(
|
|
785
|
+
findMany(options?: {
|
|
786
|
+
where?: RelationsWhereFilter;
|
|
787
|
+
orderBy?: Record<string, 'asc' | 'desc'>;
|
|
788
|
+
limit?: number;
|
|
789
|
+
offset?: number;
|
|
790
|
+
}): Promise<TSelect[]>;
|
|
673
791
|
/**
|
|
674
792
|
* Update a record by ID
|
|
675
793
|
*
|
|
@@ -680,28 +798,30 @@ declare abstract class PrimaryBaseRepository<TModel, TCreateDTO = any, TUpdateDT
|
|
|
680
798
|
* @example
|
|
681
799
|
* ```typescript
|
|
682
800
|
* const user = await userRepository.update('user-id-123', {
|
|
683
|
-
*
|
|
801
|
+
* firstName: 'Jane'
|
|
684
802
|
* });
|
|
685
803
|
* ```
|
|
686
804
|
*/
|
|
687
|
-
update(id: string, data:
|
|
805
|
+
update(id: string, data: Partial<TInsert>): Promise<TSelect>;
|
|
688
806
|
/**
|
|
689
807
|
* Update multiple records
|
|
690
808
|
*
|
|
691
|
-
* @param where -
|
|
809
|
+
* @param where - SQL condition to match records
|
|
692
810
|
* @param data - The data to update
|
|
693
811
|
* @returns Promise resolving to the count of updated records
|
|
694
812
|
*
|
|
695
813
|
* @example
|
|
696
814
|
* ```typescript
|
|
815
|
+
* import { eq } from 'drizzle-orm';
|
|
816
|
+
*
|
|
697
817
|
* const result = await userRepository.updateMany(
|
|
698
|
-
*
|
|
699
|
-
* {
|
|
818
|
+
* eq(users.accountStatus, 'PENDING'),
|
|
819
|
+
* { accountStatus: 'ACTIVE' }
|
|
700
820
|
* );
|
|
701
821
|
* console.log(`Updated ${result.count} users`);
|
|
702
822
|
* ```
|
|
703
823
|
*/
|
|
704
|
-
updateMany(where:
|
|
824
|
+
updateMany(where: SQL, data: Partial<TInsert>): Promise<{
|
|
705
825
|
count: number;
|
|
706
826
|
}>;
|
|
707
827
|
/**
|
|
@@ -715,142 +835,166 @@ declare abstract class PrimaryBaseRepository<TModel, TCreateDTO = any, TUpdateDT
|
|
|
715
835
|
* const user = await userRepository.delete('user-id-123');
|
|
716
836
|
* ```
|
|
717
837
|
*/
|
|
718
|
-
delete(id: string): Promise<
|
|
838
|
+
delete(id: string): Promise<TSelect>;
|
|
719
839
|
/**
|
|
720
840
|
* Delete multiple records
|
|
721
841
|
*
|
|
722
|
-
* @param where -
|
|
842
|
+
* @param where - SQL condition to match records
|
|
723
843
|
* @returns Promise resolving to the count of deleted records
|
|
724
844
|
*
|
|
725
845
|
* @example
|
|
726
846
|
* ```typescript
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
*
|
|
847
|
+
* import { lt } from 'drizzle-orm';
|
|
848
|
+
*
|
|
849
|
+
* const result = await userRepository.deleteMany(
|
|
850
|
+
* lt(users.createdAt, new Date('2020-01-01'))
|
|
851
|
+
* );
|
|
731
852
|
* console.log(`Deleted ${result.count} users`);
|
|
732
853
|
* ```
|
|
733
854
|
*/
|
|
734
|
-
deleteMany(where:
|
|
855
|
+
deleteMany(where: SQL): Promise<{
|
|
735
856
|
count: number;
|
|
736
857
|
}>;
|
|
737
858
|
/**
|
|
738
859
|
* Count records
|
|
739
860
|
*
|
|
740
|
-
* @param where - Optional
|
|
861
|
+
* @param where - Optional SQL condition to filter records
|
|
741
862
|
* @returns Promise resolving to the count of records
|
|
742
863
|
*
|
|
743
864
|
* @example
|
|
744
865
|
* ```typescript
|
|
866
|
+
* import { eq } from 'drizzle-orm';
|
|
867
|
+
*
|
|
745
868
|
* // Count all users
|
|
746
869
|
* const total = await userRepository.count();
|
|
747
870
|
*
|
|
748
871
|
* // Count active users
|
|
749
|
-
* const activeCount = await userRepository.count(
|
|
872
|
+
* const activeCount = await userRepository.count(
|
|
873
|
+
* eq(users.accountStatus, 'ACTIVE')
|
|
874
|
+
* );
|
|
750
875
|
* ```
|
|
751
876
|
*/
|
|
752
|
-
count(where?:
|
|
877
|
+
count(where?: SQL): Promise<number>;
|
|
753
878
|
/**
|
|
754
879
|
* Check if a record exists
|
|
755
880
|
*
|
|
756
|
-
* @param where -
|
|
881
|
+
* @param where - SQL condition to match records
|
|
757
882
|
* @returns Promise resolving to true if at least one record exists, false otherwise
|
|
758
883
|
*
|
|
759
884
|
* @example
|
|
760
885
|
* ```typescript
|
|
761
|
-
*
|
|
762
|
-
*
|
|
763
|
-
*
|
|
886
|
+
* import { eq } from 'drizzle-orm';
|
|
887
|
+
*
|
|
888
|
+
* const emailExists = await userRepository.exists(
|
|
889
|
+
* eq(users.email, 'user@example.com')
|
|
890
|
+
* );
|
|
764
891
|
* ```
|
|
765
892
|
*/
|
|
766
|
-
exists(where:
|
|
893
|
+
exists(where: SQL): Promise<boolean>;
|
|
767
894
|
}
|
|
768
895
|
|
|
769
896
|
/**
|
|
770
|
-
*
|
|
897
|
+
* Type helper to extract table name from Drizzle table.
|
|
898
|
+
* TTable['_']['name'] gives us the string literal type (e.g., 'products')
|
|
899
|
+
*/
|
|
900
|
+
type ExtractTableName<TTable extends PgTable> = TTable['_']['name'];
|
|
901
|
+
/**
|
|
902
|
+
* Abstract base repository for tenant-scoped database operations using Drizzle ORM.
|
|
771
903
|
* All operations are automatically scoped to the current tenant.
|
|
772
904
|
*
|
|
773
|
-
* @template
|
|
774
|
-
* @template
|
|
775
|
-
* @template
|
|
905
|
+
* @template TTable - The Drizzle table type (must be registered in SchemaRegistry)
|
|
906
|
+
* @template TInsert - Type for insert operations (inferred from table.$inferInsert)
|
|
907
|
+
* @template TSelect - Type for select operations (inferred from table.$inferSelect)
|
|
908
|
+
*
|
|
909
|
+
* @remarks
|
|
910
|
+
* **Type Assertion Pattern:** This repository uses `as any` casts when passing
|
|
911
|
+
* the generic table to Drizzle methods. This is necessary because TypeScript
|
|
912
|
+
* cannot prove that a generic `TTable extends PgTable` satisfies Drizzle's
|
|
913
|
+
* stricter internal type requirements for `insert()`, `update()`, and `delete()`.
|
|
914
|
+
*
|
|
915
|
+
* The public API maintains full type safety:
|
|
916
|
+
* - Input parameters are typed as `TInsert` (inferred from table)
|
|
917
|
+
* - Return values are typed as `TSelect` (inferred from table)
|
|
918
|
+
* - The casts are implementation details that don't leak to consumers
|
|
776
919
|
*
|
|
777
920
|
* @example
|
|
778
921
|
* ```typescript
|
|
779
|
-
*
|
|
780
|
-
*
|
|
922
|
+
* import { products } from '@/db/schema';
|
|
923
|
+
*
|
|
924
|
+
* type Product = typeof products.$inferSelect;
|
|
925
|
+
* type NewProduct = typeof products.$inferInsert;
|
|
926
|
+
*
|
|
781
927
|
* @Injectable()
|
|
782
|
-
* export class ProductRepository extends TenantBaseRepository<
|
|
783
|
-
* Product,
|
|
784
|
-
* CreateProductDto,
|
|
785
|
-
* UpdateProductDto
|
|
786
|
-
* > {
|
|
928
|
+
* export class ProductRepository extends TenantBaseRepository<typeof products> {
|
|
787
929
|
* constructor(database: TenantDatabaseService) {
|
|
788
|
-
* super(database,
|
|
930
|
+
* super(database, products);
|
|
789
931
|
* }
|
|
790
932
|
*
|
|
791
|
-
* //
|
|
933
|
+
* // Use SQL-builder syntax
|
|
792
934
|
* async findBySku(sku: string): Promise<Product | null> {
|
|
793
|
-
*
|
|
935
|
+
* const [result] = await this.db
|
|
936
|
+
* .select()
|
|
937
|
+
* .from(this.table)
|
|
938
|
+
* .where(eq(products.sku, sku))
|
|
939
|
+
* .limit(1);
|
|
940
|
+
* return result ?? null;
|
|
794
941
|
* }
|
|
795
|
-
* }
|
|
796
942
|
*
|
|
797
|
-
*
|
|
798
|
-
*
|
|
799
|
-
*
|
|
800
|
-
*
|
|
801
|
-
*
|
|
802
|
-
*
|
|
803
|
-
* }
|
|
804
|
-
*
|
|
805
|
-
* // Works with complex model names
|
|
806
|
-
* @Injectable()
|
|
807
|
-
* export class InventoryItemRepository extends TenantBaseRepository<InventoryItem> {
|
|
808
|
-
* constructor(database: TenantDatabaseService) {
|
|
809
|
-
* super(database, (p) => p.inventoryItem); // ✅ Matches Prisma naming
|
|
943
|
+
* // Use Prisma-like relational query syntax
|
|
944
|
+
* async findWithRelations(id: string): Promise<Product | null> {
|
|
945
|
+
* return await this.model.findFirst({
|
|
946
|
+
* where: eq(products.id, id),
|
|
947
|
+
* with: { category: true, variants: true }
|
|
948
|
+
* });
|
|
810
949
|
* }
|
|
811
950
|
* }
|
|
812
951
|
* ```
|
|
813
952
|
*/
|
|
814
|
-
declare abstract class TenantBaseRepository<
|
|
953
|
+
declare abstract class TenantBaseRepository<TTable extends PgTable, TInsert = InferInsertModel<TTable>, TSelect = InferSelectModel<TTable>> {
|
|
815
954
|
protected readonly database: TenantDatabaseService;
|
|
955
|
+
protected readonly table: TTable;
|
|
816
956
|
protected readonly logger: Logger;
|
|
817
|
-
private readonly modelGetter;
|
|
818
957
|
/**
|
|
819
|
-
*
|
|
958
|
+
* The table name extracted from the Drizzle table at runtime.
|
|
959
|
+
* Used to access the query API for this repository's table.
|
|
960
|
+
*/
|
|
961
|
+
private readonly tableName;
|
|
962
|
+
/**
|
|
963
|
+
* Lazy getter for Drizzle client.
|
|
820
964
|
* Accesses the client from the database service only when needed,
|
|
821
965
|
* avoiding initialization timing issues with NestJS lifecycle.
|
|
822
966
|
*/
|
|
823
|
-
protected get
|
|
967
|
+
protected get db(): TypedDrizzleClient;
|
|
824
968
|
/**
|
|
825
|
-
*
|
|
826
|
-
*
|
|
969
|
+
* Model query API for THIS repository's table (Prisma-like syntax)
|
|
970
|
+
* Scoped to only the table this repository manages
|
|
971
|
+
*
|
|
972
|
+
* @example
|
|
973
|
+
* ```typescript
|
|
974
|
+
* // Use relational queries with type safety
|
|
975
|
+
* const product = await this.model.findFirst({
|
|
976
|
+
* where: eq(products.id, id),
|
|
977
|
+
* with: { category: true, variants: true }
|
|
978
|
+
* });
|
|
979
|
+
* ```
|
|
827
980
|
*/
|
|
828
|
-
protected get model():
|
|
981
|
+
protected get model(): TypedDrizzleClient['query'][ExtractTableName<TTable> & keyof TypedDrizzleClient['query']];
|
|
829
982
|
/**
|
|
830
983
|
* Create a new repository instance
|
|
831
984
|
*
|
|
832
985
|
* @param database - The tenant database service
|
|
833
|
-
* @param
|
|
986
|
+
* @param table - The Drizzle table schema object
|
|
834
987
|
*
|
|
835
988
|
* @example
|
|
836
989
|
* ```typescript
|
|
837
|
-
*
|
|
838
|
-
* constructor(database: TenantDatabaseService) {
|
|
839
|
-
* super(database, (prisma) => prisma.product);
|
|
840
|
-
* }
|
|
841
|
-
*
|
|
842
|
-
* // Short syntax
|
|
843
|
-
* constructor(database: TenantDatabaseService) {
|
|
844
|
-
* super(database, (p) => p.product);
|
|
845
|
-
* }
|
|
990
|
+
* import { products } from '@/db/schema';
|
|
846
991
|
*
|
|
847
|
-
* // Complex model names
|
|
848
992
|
* constructor(database: TenantDatabaseService) {
|
|
849
|
-
* super(database,
|
|
993
|
+
* super(database, products);
|
|
850
994
|
* }
|
|
851
995
|
* ```
|
|
852
996
|
*/
|
|
853
|
-
constructor(database: TenantDatabaseService,
|
|
997
|
+
constructor(database: TenantDatabaseService, table: TTable);
|
|
854
998
|
/**
|
|
855
999
|
* Create a new record
|
|
856
1000
|
*
|
|
@@ -866,7 +1010,7 @@ declare abstract class TenantBaseRepository<TModel, TCreateDTO = any, TUpdateDTO
|
|
|
866
1010
|
* });
|
|
867
1011
|
* ```
|
|
868
1012
|
*/
|
|
869
|
-
create(data:
|
|
1013
|
+
create(data: TInsert): Promise<TSelect>;
|
|
870
1014
|
/**
|
|
871
1015
|
* Find a single record by ID
|
|
872
1016
|
*
|
|
@@ -878,47 +1022,48 @@ declare abstract class TenantBaseRepository<TModel, TCreateDTO = any, TUpdateDTO
|
|
|
878
1022
|
* const product = await productRepository.findById('product-id-123');
|
|
879
1023
|
* ```
|
|
880
1024
|
*/
|
|
881
|
-
findById(id: string): Promise<
|
|
1025
|
+
findById(id: string): Promise<TSelect | null>;
|
|
882
1026
|
/**
|
|
883
1027
|
* Find a single record with custom where clause
|
|
884
1028
|
*
|
|
885
|
-
* @param where -
|
|
1029
|
+
* @param where - SQL condition
|
|
886
1030
|
* @returns Promise resolving to the record or null if not found
|
|
887
1031
|
*
|
|
888
1032
|
* @example
|
|
889
1033
|
* ```typescript
|
|
890
|
-
*
|
|
891
|
-
* const product = await productRepository.findOne(
|
|
892
|
-
*
|
|
893
|
-
* // With include
|
|
894
|
-
* const product = await productRepository.findOne({
|
|
895
|
-
* where: { sku: 'WDG-001' },
|
|
896
|
-
* include: { category: true }
|
|
897
|
-
* });
|
|
1034
|
+
* import { eq } from 'drizzle-orm';
|
|
1035
|
+
* const product = await productRepository.findOne(eq(products.sku, 'WDG-001'));
|
|
898
1036
|
* ```
|
|
899
1037
|
*/
|
|
900
|
-
findOne(where:
|
|
1038
|
+
findOne(where: SQL): Promise<TSelect | null>;
|
|
901
1039
|
/**
|
|
902
1040
|
* Find multiple records
|
|
903
1041
|
*
|
|
904
|
-
* @param
|
|
1042
|
+
* @param options - Query options (where, orderBy, limit, offset)
|
|
905
1043
|
* @returns Promise resolving to an array of records
|
|
906
1044
|
*
|
|
907
1045
|
* @example
|
|
908
1046
|
* ```typescript
|
|
1047
|
+
* import { eq, desc } from 'drizzle-orm';
|
|
1048
|
+
*
|
|
909
1049
|
* // Find all products
|
|
910
1050
|
* const products = await productRepository.findMany();
|
|
911
1051
|
*
|
|
912
1052
|
* // Find with filtering and pagination
|
|
913
1053
|
* const products = await productRepository.findMany({
|
|
914
|
-
* where:
|
|
915
|
-
* orderBy:
|
|
916
|
-
*
|
|
917
|
-
*
|
|
1054
|
+
* where: eq(products.status, 'ACTIVE'),
|
|
1055
|
+
* orderBy: desc(products.createdAt),
|
|
1056
|
+
* limit: 10,
|
|
1057
|
+
* offset: 0
|
|
918
1058
|
* });
|
|
919
1059
|
* ```
|
|
920
1060
|
*/
|
|
921
|
-
findMany(
|
|
1061
|
+
findMany(options?: {
|
|
1062
|
+
where?: SQL;
|
|
1063
|
+
orderBy?: SQL;
|
|
1064
|
+
limit?: number;
|
|
1065
|
+
offset?: number;
|
|
1066
|
+
}): Promise<TSelect[]>;
|
|
922
1067
|
/**
|
|
923
1068
|
* Update a record by ID
|
|
924
1069
|
*
|
|
@@ -933,24 +1078,26 @@ declare abstract class TenantBaseRepository<TModel, TCreateDTO = any, TUpdateDTO
|
|
|
933
1078
|
* });
|
|
934
1079
|
* ```
|
|
935
1080
|
*/
|
|
936
|
-
update(id: string, data:
|
|
1081
|
+
update(id: string, data: Partial<TInsert>): Promise<TSelect>;
|
|
937
1082
|
/**
|
|
938
1083
|
* Update multiple records
|
|
939
1084
|
*
|
|
940
|
-
* @param where -
|
|
1085
|
+
* @param where - SQL condition to match records
|
|
941
1086
|
* @param data - The data to update
|
|
942
1087
|
* @returns Promise resolving to the count of updated records
|
|
943
1088
|
*
|
|
944
1089
|
* @example
|
|
945
1090
|
* ```typescript
|
|
1091
|
+
* import { eq } from 'drizzle-orm';
|
|
1092
|
+
*
|
|
946
1093
|
* const result = await productRepository.updateMany(
|
|
947
|
-
*
|
|
1094
|
+
* eq(products.status, 'PENDING'),
|
|
948
1095
|
* { status: 'ACTIVE' }
|
|
949
1096
|
* );
|
|
950
1097
|
* console.log(`Updated ${result.count} products`);
|
|
951
1098
|
* ```
|
|
952
1099
|
*/
|
|
953
|
-
updateMany(where:
|
|
1100
|
+
updateMany(where: SQL, data: Partial<TInsert>): Promise<{
|
|
954
1101
|
count: number;
|
|
955
1102
|
}>;
|
|
956
1103
|
/**
|
|
@@ -964,55 +1111,62 @@ declare abstract class TenantBaseRepository<TModel, TCreateDTO = any, TUpdateDTO
|
|
|
964
1111
|
* const product = await productRepository.delete('product-id-123');
|
|
965
1112
|
* ```
|
|
966
1113
|
*/
|
|
967
|
-
delete(id: string): Promise<
|
|
1114
|
+
delete(id: string): Promise<TSelect>;
|
|
968
1115
|
/**
|
|
969
1116
|
* Delete multiple records
|
|
970
1117
|
*
|
|
971
|
-
* @param where -
|
|
1118
|
+
* @param where - SQL condition to match records
|
|
972
1119
|
* @returns Promise resolving to the count of deleted records
|
|
973
1120
|
*
|
|
974
1121
|
* @example
|
|
975
1122
|
* ```typescript
|
|
976
|
-
*
|
|
977
|
-
*
|
|
978
|
-
*
|
|
979
|
-
*
|
|
1123
|
+
* import { lt } from 'drizzle-orm';
|
|
1124
|
+
*
|
|
1125
|
+
* const result = await productRepository.deleteMany(
|
|
1126
|
+
* lt(products.createdAt, new Date('2020-01-01'))
|
|
1127
|
+
* );
|
|
980
1128
|
* console.log(`Deleted ${result.count} products`);
|
|
981
1129
|
* ```
|
|
982
1130
|
*/
|
|
983
|
-
deleteMany(where:
|
|
1131
|
+
deleteMany(where: SQL): Promise<{
|
|
984
1132
|
count: number;
|
|
985
1133
|
}>;
|
|
986
1134
|
/**
|
|
987
1135
|
* Count records
|
|
988
1136
|
*
|
|
989
|
-
* @param where - Optional
|
|
1137
|
+
* @param where - Optional SQL condition to filter records
|
|
990
1138
|
* @returns Promise resolving to the count of records
|
|
991
1139
|
*
|
|
992
1140
|
* @example
|
|
993
1141
|
* ```typescript
|
|
1142
|
+
* import { eq } from 'drizzle-orm';
|
|
1143
|
+
*
|
|
994
1144
|
* // Count all products
|
|
995
1145
|
* const total = await productRepository.count();
|
|
996
1146
|
*
|
|
997
1147
|
* // Count active products
|
|
998
|
-
* const activeCount = await productRepository.count(
|
|
1148
|
+
* const activeCount = await productRepository.count(
|
|
1149
|
+
* eq(products.status, 'ACTIVE')
|
|
1150
|
+
* );
|
|
999
1151
|
* ```
|
|
1000
1152
|
*/
|
|
1001
|
-
count(where?:
|
|
1153
|
+
count(where?: SQL): Promise<number>;
|
|
1002
1154
|
/**
|
|
1003
1155
|
* Check if a record exists
|
|
1004
1156
|
*
|
|
1005
|
-
* @param where -
|
|
1157
|
+
* @param where - SQL condition to match records
|
|
1006
1158
|
* @returns Promise resolving to true if at least one record exists, false otherwise
|
|
1007
1159
|
*
|
|
1008
1160
|
* @example
|
|
1009
1161
|
* ```typescript
|
|
1010
|
-
*
|
|
1011
|
-
*
|
|
1012
|
-
*
|
|
1162
|
+
* import { eq } from 'drizzle-orm';
|
|
1163
|
+
*
|
|
1164
|
+
* const skuExists = await productRepository.exists(
|
|
1165
|
+
* eq(products.sku, 'WDG-001')
|
|
1166
|
+
* );
|
|
1013
1167
|
* ```
|
|
1014
1168
|
*/
|
|
1015
|
-
exists(where:
|
|
1169
|
+
exists(where: SQL): Promise<boolean>;
|
|
1016
1170
|
}
|
|
1017
1171
|
|
|
1018
1172
|
declare class RequestService {
|
|
@@ -1336,7 +1490,7 @@ declare class HttpExceptionFilter implements ExceptionFilter {
|
|
|
1336
1490
|
catch(exception: unknown, host: ArgumentsHost): void;
|
|
1337
1491
|
}
|
|
1338
1492
|
|
|
1339
|
-
interface FieldError
|
|
1493
|
+
interface FieldError {
|
|
1340
1494
|
field?: string;
|
|
1341
1495
|
message: string;
|
|
1342
1496
|
}
|
|
@@ -1346,13 +1500,9 @@ interface ProblemDetails {
|
|
|
1346
1500
|
detail: string;
|
|
1347
1501
|
}
|
|
1348
1502
|
interface ApiErrorResponse extends ProblemDetails {
|
|
1349
|
-
errors: FieldError
|
|
1503
|
+
errors: FieldError[];
|
|
1350
1504
|
}
|
|
1351
1505
|
|
|
1352
|
-
interface FieldError {
|
|
1353
|
-
field?: string;
|
|
1354
|
-
message: string;
|
|
1355
|
-
}
|
|
1356
1506
|
declare abstract class BaseFieldException extends HttpException {
|
|
1357
1507
|
constructor(statusOrMessageOrErrors: HttpStatus | string | FieldError[], messageOrStatus?: string | HttpStatus, statusOrDetail?: HttpStatus | string, detail?: string);
|
|
1358
1508
|
}
|
|
@@ -2167,4 +2317,4 @@ declare function generateCorrelationId(): string;
|
|
|
2167
2317
|
*/
|
|
2168
2318
|
declare function addCorrelationIdToResponse(reply: FastifyReply, correlationId: string, headerName?: string): void;
|
|
2169
2319
|
|
|
2170
|
-
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, RequestTimeoutException, ServiceUnavailableException, Tenant, TenantBaseRepository, TenantContextService, TenantDatabaseService, type TenantInfo, TooManyRequestsException, UnauthorizedException, UnprocessableEntityException, UnsupportedMediaTypeException, ValidationException, VrittiAuthGuard, addCorrelationIdToResponse, correlationStorage, generateCorrelationId, getCorrelationContext, getHttpStatusTitle, runWithCorrelationContext, updateCorrelationContext };
|
|
2320
|
+
export { type ApiErrorResponse, AuthConfigModule, BadGatewayException, BadRequestException, BaseFieldException, ConflictException, type CorrelationContext, CorrelationIdMiddleware, CsrfGuard, DEFAULT_CORRELATION_HEADER, DatabaseModule, type DatabaseModuleOptions, type FieldError, ForbiddenException, GoneException, HttpExceptionFilter, HttpLoggerInterceptor, type HttpLoggerOptions, HttpModule, InternalServerErrorException, LOGGER_MODULE_OPTIONS, type LogFormat, type LogLevel, type LogMetadata, LoggerModule, type LoggerModuleAsyncOptions, type LoggerModuleOptions, type LoggerOptionsFactory, LoggerService, MethodNotAllowedException, NotAcceptableException, NotFoundException, NotImplementedException, Onboarding, PayloadTooLargeException, PrimaryBaseRepository, PrimaryDatabaseService, type PrimaryDbConfig, type ProblemDetails, Public, type RegisteredSchema, RequestTimeoutException, type SchemaRegistry, ServiceUnavailableException, Tenant, TenantBaseRepository, TenantContextService, TenantDatabaseService, type TenantInfo, TooManyRequestsException, type TypedDrizzleClient, UnauthorizedException, UnprocessableEntityException, UnsupportedMediaTypeException, ValidationException, VrittiAuthGuard, addCorrelationIdToResponse, correlationStorage, generateCorrelationId, getCorrelationContext, getHttpStatusTitle, runWithCorrelationContext, updateCorrelationContext };
|