@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.cjs
CHANGED
|
@@ -222,6 +222,9 @@ var jwt = __toESM(require("jsonwebtoken"), 1);
|
|
|
222
222
|
|
|
223
223
|
// src/database/services/primary-database.service.ts
|
|
224
224
|
var import_common3 = require("@nestjs/common");
|
|
225
|
+
var import_pg = require("pg");
|
|
226
|
+
var import_node_postgres = require("drizzle-orm/node-postgres");
|
|
227
|
+
var import_drizzle_orm = require("drizzle-orm");
|
|
225
228
|
|
|
226
229
|
// src/database/constants.ts
|
|
227
230
|
var DATABASE_MODULE_OPTIONS = Symbol("DATABASE_MODULE_OPTIONS");
|
|
@@ -250,8 +253,10 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
250
253
|
}
|
|
251
254
|
options;
|
|
252
255
|
logger = new import_common3.Logger(_PrimaryDatabaseService.name);
|
|
253
|
-
/**
|
|
254
|
-
|
|
256
|
+
/** PostgreSQL connection pool */
|
|
257
|
+
pool = null;
|
|
258
|
+
/** Drizzle database instance */
|
|
259
|
+
db = null;
|
|
255
260
|
/** In-memory cache: Map<tenantIdentifier, TenantConfig> */
|
|
256
261
|
tenantConfigCache = /* @__PURE__ */ new Map();
|
|
257
262
|
/** Cache TTL in milliseconds */
|
|
@@ -262,28 +267,28 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
262
267
|
}
|
|
263
268
|
async onModuleInit() {
|
|
264
269
|
if (this.options.primaryDb) {
|
|
265
|
-
await this.
|
|
270
|
+
await this.initializeDrizzleClient();
|
|
266
271
|
}
|
|
267
272
|
}
|
|
268
273
|
/**
|
|
269
|
-
* Initialize connection to primary database
|
|
274
|
+
* Initialize connection to primary database using Drizzle
|
|
270
275
|
*/
|
|
271
|
-
async
|
|
276
|
+
async initializeDrizzleClient() {
|
|
272
277
|
try {
|
|
273
|
-
const PrimaryDbClient = this.options.prismaClientConstructor;
|
|
274
278
|
const databaseUrl = this.buildPrimaryDbUrl();
|
|
275
|
-
this.
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
url: databaseUrl
|
|
279
|
-
}
|
|
280
|
-
},
|
|
281
|
-
log: [
|
|
282
|
-
"error",
|
|
283
|
-
"warn"
|
|
284
|
-
]
|
|
279
|
+
this.pool = new import_pg.Pool({
|
|
280
|
+
connectionString: databaseUrl,
|
|
281
|
+
max: this.options.maxConnections || 10
|
|
285
282
|
});
|
|
286
|
-
|
|
283
|
+
this.logger.debug(`Schema keys passed to drizzle: [${Object.keys(this.options.drizzleSchema || {}).join(", ")}]`);
|
|
284
|
+
this.logger.debug(`Relations keys passed to drizzle: [${Object.keys(this.options.drizzleRelations || {}).join(", ")}]`);
|
|
285
|
+
this.db = (0, import_node_postgres.drizzle)({
|
|
286
|
+
client: this.pool,
|
|
287
|
+
schema: this.options.drizzleSchema,
|
|
288
|
+
relations: this.options.drizzleRelations
|
|
289
|
+
});
|
|
290
|
+
this.logger.debug(`Drizzle query keys after init: [${Object.keys(this.db.query || {}).join(", ")}]`);
|
|
291
|
+
await this.pool.query("SELECT 1");
|
|
287
292
|
this.logger.log("Connected to primary database (tenant registry)");
|
|
288
293
|
} catch (error) {
|
|
289
294
|
this.logger.error("Failed to connect to primary database", error);
|
|
@@ -298,7 +303,7 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
298
303
|
throw new Error("Primary database configuration not provided");
|
|
299
304
|
}
|
|
300
305
|
const { host, port = 5432, username, password, database, schema = "public", sslMode = "require" } = this.options.primaryDb;
|
|
301
|
-
let url = `postgresql://${username}:${password}@${host}:${port}/${database}`;
|
|
306
|
+
let url = `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;
|
|
302
307
|
const params = new URLSearchParams();
|
|
303
308
|
if (schema) {
|
|
304
309
|
params.set("schema", schema);
|
|
@@ -318,9 +323,9 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
318
323
|
return url.replace(/:([^@]+)@/, ":****@");
|
|
319
324
|
}
|
|
320
325
|
/**
|
|
321
|
-
* Get tenant configuration by identifier (ID or
|
|
326
|
+
* Get tenant configuration by identifier (ID or subdomain)
|
|
322
327
|
*
|
|
323
|
-
* @param tenantIdentifier Tenant ID or
|
|
328
|
+
* @param tenantIdentifier Tenant ID or subdomain
|
|
324
329
|
* @returns Tenant configuration or null if not found
|
|
325
330
|
*/
|
|
326
331
|
async getTenantInfo(tenantIdentifier) {
|
|
@@ -330,45 +335,39 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
330
335
|
return cached;
|
|
331
336
|
}
|
|
332
337
|
try {
|
|
333
|
-
if (!this.
|
|
338
|
+
if (!this.db) {
|
|
334
339
|
throw new Error("Primary database client not initialized");
|
|
335
340
|
}
|
|
336
341
|
this.logger.debug(`Querying primary database for tenant: ${tenantIdentifier}`);
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
id: tenantIdentifier
|
|
342
|
-
},
|
|
343
|
-
{
|
|
344
|
-
subdomain: tenantIdentifier
|
|
345
|
-
}
|
|
346
|
-
],
|
|
347
|
-
status: "ACTIVE"
|
|
348
|
-
},
|
|
349
|
-
include: {
|
|
350
|
-
databaseConfig: true
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
if (!tenant) {
|
|
342
|
+
const schema = this.options.drizzleSchema;
|
|
343
|
+
const { tenants, tenantDatabaseConfigs } = schema;
|
|
344
|
+
const result = await this.db.select().from(tenants).leftJoin(tenantDatabaseConfigs, (0, import_drizzle_orm.eq)(tenants.id, tenantDatabaseConfigs.tenantId)).where((0, import_drizzle_orm.or)((0, import_drizzle_orm.eq)(tenants.id, tenantIdentifier), (0, import_drizzle_orm.eq)(tenants.subdomain, tenantIdentifier))).limit(1);
|
|
345
|
+
if (!result.length) {
|
|
354
346
|
this.logger.warn(`Tenant not found: ${tenantIdentifier}`);
|
|
355
347
|
return null;
|
|
356
348
|
}
|
|
349
|
+
const row = result[0];
|
|
350
|
+
const tenant = row.tenants;
|
|
351
|
+
const config = row.tenant_database_configs;
|
|
352
|
+
if (tenant.status !== "ACTIVE") {
|
|
353
|
+
this.logger.warn(`Tenant not active: ${tenantIdentifier}`);
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
357
356
|
const info = {
|
|
358
357
|
id: tenant.id,
|
|
359
358
|
subdomain: tenant.subdomain,
|
|
360
359
|
type: tenant.dbType,
|
|
361
360
|
status: tenant.status,
|
|
362
361
|
// For SHARED tenants: schema name
|
|
363
|
-
schemaName:
|
|
362
|
+
schemaName: config?.dbSchema || void 0,
|
|
364
363
|
// For DEDICATED tenants: database configuration from TenantDatabaseConfig table
|
|
365
|
-
databaseName:
|
|
366
|
-
databaseHost:
|
|
367
|
-
databasePort:
|
|
368
|
-
databaseUsername:
|
|
369
|
-
databasePassword:
|
|
370
|
-
databaseSslMode:
|
|
371
|
-
connectionPoolSize:
|
|
364
|
+
databaseName: config?.dbName || void 0,
|
|
365
|
+
databaseHost: config?.dbHost || void 0,
|
|
366
|
+
databasePort: config?.dbPort || void 0,
|
|
367
|
+
databaseUsername: config?.dbUsername ? this.decrypt(config.dbUsername) : void 0,
|
|
368
|
+
databasePassword: config?.dbPassword ? this.decrypt(config.dbPassword) : void 0,
|
|
369
|
+
databaseSslMode: config?.dbSslMode || void 0,
|
|
370
|
+
connectionPoolSize: config?.connectionPoolSize || void 0
|
|
372
371
|
};
|
|
373
372
|
this.cacheInfo(info);
|
|
374
373
|
return info;
|
|
@@ -394,7 +393,7 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
394
393
|
*
|
|
395
394
|
* Useful when tenant settings are updated and cache needs to be invalidated
|
|
396
395
|
*
|
|
397
|
-
* @param tenantIdentifier Tenant ID or
|
|
396
|
+
* @param tenantIdentifier Tenant ID or subdomain
|
|
398
397
|
*/
|
|
399
398
|
clearTenantCache(tenantIdentifier) {
|
|
400
399
|
const config = this.tenantConfigCache.get(tenantIdentifier);
|
|
@@ -413,17 +412,23 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
413
412
|
this.logger.log(`Cleared ${size} cached tenant configs`);
|
|
414
413
|
}
|
|
415
414
|
/**
|
|
416
|
-
* Get the
|
|
417
|
-
* This is a synchronous property that returns the initialized
|
|
415
|
+
* Get the Drizzle database instance for the primary database.
|
|
416
|
+
* This is a synchronous property that returns the initialized Drizzle client.
|
|
418
417
|
*
|
|
419
|
-
* @returns Primary database
|
|
418
|
+
* @returns Primary database Drizzle instance
|
|
420
419
|
* @throws Error if primary database client is not initialized
|
|
421
420
|
*/
|
|
422
|
-
get
|
|
423
|
-
if (!this.
|
|
421
|
+
get drizzleClient() {
|
|
422
|
+
if (!this.db) {
|
|
424
423
|
throw new Error("Primary database client not initialized");
|
|
425
424
|
}
|
|
426
|
-
return this.
|
|
425
|
+
return this.db;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Get the Drizzle schema
|
|
429
|
+
*/
|
|
430
|
+
get schema() {
|
|
431
|
+
return this.options.drizzleSchema;
|
|
427
432
|
}
|
|
428
433
|
/**
|
|
429
434
|
* Decrypt database credentials
|
|
@@ -437,8 +442,8 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
437
442
|
return encrypted;
|
|
438
443
|
}
|
|
439
444
|
async onModuleDestroy() {
|
|
440
|
-
if (this.
|
|
441
|
-
await this.
|
|
445
|
+
if (this.pool) {
|
|
446
|
+
await this.pool.end();
|
|
442
447
|
this.logger.log("Disconnected from primary database");
|
|
443
448
|
}
|
|
444
449
|
}
|
|
@@ -983,6 +988,8 @@ TenantContextInterceptor = _ts_decorate8([
|
|
|
983
988
|
|
|
984
989
|
// src/database/services/tenant-database.service.ts
|
|
985
990
|
var import_common9 = require("@nestjs/common");
|
|
991
|
+
var import_pg2 = require("pg");
|
|
992
|
+
var import_node_postgres2 = require("drizzle-orm/node-postgres");
|
|
986
993
|
function _ts_decorate9(decorators, target, key, desc) {
|
|
987
994
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
988
995
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -1007,7 +1014,7 @@ var TenantDatabaseService = class _TenantDatabaseService {
|
|
|
1007
1014
|
options;
|
|
1008
1015
|
tenantContext;
|
|
1009
1016
|
logger = new import_common9.Logger(_TenantDatabaseService.name);
|
|
1010
|
-
/** Connection pool: Map<cacheKey,
|
|
1017
|
+
/** Connection pool: Map<cacheKey, TenantConnection> */
|
|
1011
1018
|
clients = /* @__PURE__ */ new Map();
|
|
1012
1019
|
/** Track last usage time for idle connection cleanup */
|
|
1013
1020
|
clientLastUsed = /* @__PURE__ */ new Map();
|
|
@@ -1019,17 +1026,23 @@ var TenantDatabaseService = class _TenantDatabaseService {
|
|
|
1019
1026
|
this.startConnectionCleaner();
|
|
1020
1027
|
}
|
|
1021
1028
|
/**
|
|
1022
|
-
* Get the
|
|
1029
|
+
* Get the Drizzle client for the current tenant's database.
|
|
1023
1030
|
* This returns the tenant-scoped database client.
|
|
1024
1031
|
*
|
|
1025
|
-
* @returns Tenant-scoped database
|
|
1032
|
+
* @returns Tenant-scoped Drizzle database instance
|
|
1026
1033
|
* @throws UnauthorizedException if tenant context not set
|
|
1027
1034
|
* @throws InternalServerErrorException if connection fails
|
|
1028
1035
|
*/
|
|
1029
|
-
get
|
|
1036
|
+
get drizzleClient() {
|
|
1030
1037
|
return this.getDbClient();
|
|
1031
1038
|
}
|
|
1032
1039
|
/**
|
|
1040
|
+
* Get the Drizzle schema
|
|
1041
|
+
*/
|
|
1042
|
+
get schema() {
|
|
1043
|
+
return this.options.drizzleSchema;
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1033
1046
|
* Get tenant-scoped database client for the current request/message
|
|
1034
1047
|
*
|
|
1035
1048
|
* This method:
|
|
@@ -1037,66 +1050,61 @@ var TenantDatabaseService = class _TenantDatabaseService {
|
|
|
1037
1050
|
* 2. Builds a connection URL based on tenant type
|
|
1038
1051
|
* 3. Returns cached client if exists, otherwise creates new one
|
|
1039
1052
|
*
|
|
1040
|
-
* @returns
|
|
1053
|
+
* @returns Drizzle database instance
|
|
1041
1054
|
* @throws UnauthorizedException if tenant context not set
|
|
1042
1055
|
* @throws InternalServerErrorException if connection fails
|
|
1043
|
-
*
|
|
1044
|
-
* @example
|
|
1045
|
-
* const dbClient = await tenantDatabase.getDbClient<PrismaClient>();
|
|
1046
|
-
* const users = await dbClient.user.findMany();
|
|
1047
1056
|
*/
|
|
1048
|
-
|
|
1057
|
+
getDbClient() {
|
|
1049
1058
|
const tenant = this.tenantContext.getTenant();
|
|
1050
1059
|
const cacheKey = this.buildCacheKey(tenant);
|
|
1051
|
-
|
|
1060
|
+
const existing = this.clients.get(cacheKey);
|
|
1061
|
+
if (existing) {
|
|
1052
1062
|
this.clientLastUsed.set(cacheKey, Date.now());
|
|
1053
1063
|
this.logger.debug(`Reusing cached connection: ${cacheKey}`);
|
|
1054
|
-
return
|
|
1064
|
+
return existing.db;
|
|
1055
1065
|
}
|
|
1056
1066
|
this.logger.log(`Creating new database connection: ${cacheKey}`);
|
|
1057
|
-
const
|
|
1058
|
-
this.clients.set(cacheKey,
|
|
1067
|
+
const connection = this.createDbClientSync(tenant);
|
|
1068
|
+
this.clients.set(cacheKey, connection);
|
|
1059
1069
|
this.clientLastUsed.set(cacheKey, Date.now());
|
|
1060
|
-
return
|
|
1070
|
+
return connection.db;
|
|
1061
1071
|
}
|
|
1062
1072
|
/**
|
|
1063
|
-
* Create a new database client for the given tenant
|
|
1073
|
+
* Create a new database client for the given tenant (synchronous)
|
|
1064
1074
|
*/
|
|
1065
|
-
|
|
1075
|
+
createDbClientSync(tenant) {
|
|
1066
1076
|
try {
|
|
1067
1077
|
const databaseUrl = this.buildTenantDbUrl(tenant);
|
|
1068
|
-
const
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
log: [
|
|
1076
|
-
"error",
|
|
1077
|
-
"warn"
|
|
1078
|
-
]
|
|
1078
|
+
const pool = new import_pg2.Pool({
|
|
1079
|
+
connectionString: databaseUrl,
|
|
1080
|
+
max: tenant.connectionPoolSize || this.options.maxConnections || 10
|
|
1081
|
+
});
|
|
1082
|
+
const db = (0, import_node_postgres2.drizzle)({
|
|
1083
|
+
client: pool,
|
|
1084
|
+
schema: this.options.drizzleSchema
|
|
1079
1085
|
});
|
|
1080
|
-
await client.$connect();
|
|
1081
1086
|
this.logger.log(`Connected to database for tenant: ${tenant.subdomain}`);
|
|
1082
|
-
return
|
|
1087
|
+
return {
|
|
1088
|
+
pool,
|
|
1089
|
+
db
|
|
1090
|
+
};
|
|
1083
1091
|
} catch (error) {
|
|
1084
1092
|
this.logger.error(`Failed to create database connection for tenant: ${tenant.subdomain}`, error);
|
|
1085
1093
|
throw new import_common9.InternalServerErrorException("Failed to connect to tenant database");
|
|
1086
1094
|
}
|
|
1087
1095
|
}
|
|
1088
1096
|
/**
|
|
1089
|
-
* Build connection URL for
|
|
1097
|
+
* Build connection URL for tenant (dedicated database)
|
|
1090
1098
|
*/
|
|
1091
1099
|
buildTenantDbUrl(tenant) {
|
|
1092
1100
|
const { databaseHost, databasePort, databaseName, databaseUsername, databasePassword, databaseSslMode } = tenant;
|
|
1093
1101
|
if (!databaseHost || !databaseName || !databaseUsername) {
|
|
1094
|
-
throw new Error(`
|
|
1102
|
+
throw new Error(`Tenant ${tenant.subdomain} missing database configuration`);
|
|
1095
1103
|
}
|
|
1096
1104
|
const port = databasePort || 5432;
|
|
1097
1105
|
const sslMode = databaseSslMode || "require";
|
|
1098
|
-
const connectionUrl = `postgresql://${databaseUsername}:${databasePassword}@${databaseHost}:${port}/${databaseName}?sslmode=${sslMode}`;
|
|
1099
|
-
this.logger.debug(`
|
|
1106
|
+
const connectionUrl = `postgresql://${databaseUsername}:${encodeURIComponent(databasePassword || "")}@${databaseHost}:${port}/${databaseName}?sslmode=${sslMode}`;
|
|
1107
|
+
this.logger.debug(`Tenant connection URL: ${this.maskPassword(connectionUrl)}`);
|
|
1100
1108
|
return connectionUrl;
|
|
1101
1109
|
}
|
|
1102
1110
|
/**
|
|
@@ -1118,19 +1126,20 @@ var TenantDatabaseService = class _TenantDatabaseService {
|
|
|
1118
1126
|
/**
|
|
1119
1127
|
* Clean up idle connections that haven't been used recently
|
|
1120
1128
|
*/
|
|
1121
|
-
cleanupIdleConnections() {
|
|
1129
|
+
async cleanupIdleConnections() {
|
|
1122
1130
|
const now = Date.now();
|
|
1123
1131
|
const maxIdle = this.options.connectionCacheTTL || 3e5;
|
|
1124
1132
|
let cleaned = 0;
|
|
1125
1133
|
for (const [key, lastUsed] of this.clientLastUsed.entries()) {
|
|
1126
1134
|
if (now - lastUsed > maxIdle) {
|
|
1127
|
-
const
|
|
1128
|
-
if (
|
|
1129
|
-
|
|
1135
|
+
const connection = this.clients.get(key);
|
|
1136
|
+
if (connection) {
|
|
1137
|
+
try {
|
|
1138
|
+
await connection.pool.end();
|
|
1130
1139
|
this.logger.debug(`Cleaned up idle connection: ${key}`);
|
|
1131
|
-
}
|
|
1140
|
+
} catch (error) {
|
|
1132
1141
|
this.logger.error(`Error disconnecting idle client: ${key}`, error);
|
|
1133
|
-
}
|
|
1142
|
+
}
|
|
1134
1143
|
this.clients.delete(key);
|
|
1135
1144
|
this.clientLastUsed.delete(key);
|
|
1136
1145
|
cleaned++;
|
|
@@ -1161,9 +1170,9 @@ var TenantDatabaseService = class _TenantDatabaseService {
|
|
|
1161
1170
|
clearInterval(this.cleanupInterval);
|
|
1162
1171
|
}
|
|
1163
1172
|
this.logger.log(`Disconnecting ${this.clients.size} database connections`);
|
|
1164
|
-
const disconnectPromises = Array.from(this.clients.entries()).map(async ([key,
|
|
1173
|
+
const disconnectPromises = Array.from(this.clients.entries()).map(async ([key, connection]) => {
|
|
1165
1174
|
try {
|
|
1166
|
-
await
|
|
1175
|
+
await connection.pool.end();
|
|
1167
1176
|
this.logger.debug(`Disconnected: ${key}`);
|
|
1168
1177
|
} catch (error) {
|
|
1169
1178
|
this.logger.error(`Error disconnecting client: ${key}`, error);
|
|
@@ -1299,57 +1308,73 @@ DatabaseModule = _ts_decorate10([
|
|
|
1299
1308
|
|
|
1300
1309
|
// src/database/repositories/primary-base.repository.ts
|
|
1301
1310
|
var import_common11 = require("@nestjs/common");
|
|
1311
|
+
var import_drizzle_orm2 = require("drizzle-orm");
|
|
1302
1312
|
var PrimaryBaseRepository = class {
|
|
1303
1313
|
static {
|
|
1304
1314
|
__name(this, "PrimaryBaseRepository");
|
|
1305
1315
|
}
|
|
1306
1316
|
database;
|
|
1317
|
+
table;
|
|
1307
1318
|
logger;
|
|
1308
|
-
modelGetter;
|
|
1309
1319
|
/**
|
|
1310
|
-
*
|
|
1320
|
+
* The table name extracted from the Drizzle table at runtime.
|
|
1321
|
+
* Used to access the query API for this repository's table.
|
|
1322
|
+
*/
|
|
1323
|
+
tableName;
|
|
1324
|
+
/**
|
|
1325
|
+
* Lazy getter for Drizzle client.
|
|
1311
1326
|
* Accesses the client from the database service only when needed,
|
|
1312
1327
|
* avoiding initialization timing issues with NestJS lifecycle.
|
|
1313
1328
|
*/
|
|
1314
|
-
get
|
|
1315
|
-
return this.database.
|
|
1329
|
+
get db() {
|
|
1330
|
+
return this.database.drizzleClient;
|
|
1316
1331
|
}
|
|
1317
1332
|
/**
|
|
1318
|
-
*
|
|
1319
|
-
*
|
|
1333
|
+
* Model query API for THIS repository's table (Drizzle v2 relational queries)
|
|
1334
|
+
* Scoped to only the table this repository manages.
|
|
1335
|
+
* Returns a type-safe wrapper around Drizzle's RelationalQueryBuilder.
|
|
1336
|
+
*
|
|
1337
|
+
* @example
|
|
1338
|
+
* ```typescript
|
|
1339
|
+
* // Use relational queries with v2 object-based where syntax
|
|
1340
|
+
* const user = await this.model.findFirst({
|
|
1341
|
+
* where: { id },
|
|
1342
|
+
* with: { posts: true, profile: true }
|
|
1343
|
+
* });
|
|
1344
|
+
* ```
|
|
1320
1345
|
*/
|
|
1321
1346
|
get model() {
|
|
1322
|
-
|
|
1347
|
+
const query = this.database.drizzleClient.query;
|
|
1348
|
+
const queryKeys = Object.keys(query || {});
|
|
1349
|
+
this.logger.debug(`Looking for '${this.tableName}' in query keys: [${queryKeys.join(", ")}]`);
|
|
1350
|
+
const model = query[this.tableName];
|
|
1351
|
+
if (!model) {
|
|
1352
|
+
this.logger.error(`Table '${this.tableName}' not found in query object. Available: [${queryKeys.join(", ")}]`);
|
|
1353
|
+
}
|
|
1354
|
+
return model;
|
|
1323
1355
|
}
|
|
1324
1356
|
/**
|
|
1325
1357
|
* Create a new repository instance
|
|
1326
1358
|
*
|
|
1327
1359
|
* @param database - The primary database service
|
|
1328
|
-
* @param
|
|
1360
|
+
* @param table - The Drizzle table schema object
|
|
1329
1361
|
*
|
|
1330
1362
|
* @example
|
|
1331
1363
|
* ```typescript
|
|
1332
|
-
*
|
|
1333
|
-
* constructor(database: PrimaryDatabaseService) {
|
|
1334
|
-
* super(database, (prisma) => prisma.user);
|
|
1335
|
-
* }
|
|
1364
|
+
* import { users } from '@/db/schema';
|
|
1336
1365
|
*
|
|
1337
|
-
* // Short syntax
|
|
1338
1366
|
* constructor(database: PrimaryDatabaseService) {
|
|
1339
|
-
* super(database,
|
|
1340
|
-
* }
|
|
1341
|
-
*
|
|
1342
|
-
* // Complex model names
|
|
1343
|
-
* constructor(database: PrimaryDatabaseService) {
|
|
1344
|
-
* super(database, (p) => p.emailVerification);
|
|
1367
|
+
* super(database, users);
|
|
1345
1368
|
* }
|
|
1346
1369
|
* ```
|
|
1347
1370
|
*/
|
|
1348
|
-
constructor(database,
|
|
1371
|
+
constructor(database, table) {
|
|
1349
1372
|
this.database = database;
|
|
1373
|
+
this.table = table;
|
|
1374
|
+
this.tableName = (0, import_drizzle_orm2.getTableName)(table);
|
|
1350
1375
|
this.logger = new import_common11.Logger(this.constructor.name);
|
|
1351
|
-
this.modelGetter = getModel;
|
|
1352
1376
|
this.logger.debug(`Initialized ${this.constructor.name}`);
|
|
1377
|
+
this.logger.debug(`Table name from getTableName: '${this.tableName}'`);
|
|
1353
1378
|
}
|
|
1354
1379
|
/**
|
|
1355
1380
|
* Create a new record
|
|
@@ -1361,21 +1386,20 @@ var PrimaryBaseRepository = class {
|
|
|
1361
1386
|
* ```typescript
|
|
1362
1387
|
* const user = await userRepository.create({
|
|
1363
1388
|
* email: 'user@example.com',
|
|
1364
|
-
*
|
|
1389
|
+
* firstName: 'John'
|
|
1365
1390
|
* });
|
|
1366
1391
|
* ```
|
|
1367
1392
|
*/
|
|
1368
1393
|
async create(data) {
|
|
1369
1394
|
this.logger.log("Creating record");
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
});
|
|
1395
|
+
const results = await this.db.insert(this.table).values(data).returning();
|
|
1396
|
+
return results[0];
|
|
1373
1397
|
}
|
|
1374
1398
|
/**
|
|
1375
1399
|
* Find a single record by ID
|
|
1376
1400
|
*
|
|
1377
1401
|
* @param id - The record ID
|
|
1378
|
-
* @returns Promise resolving to the record or
|
|
1402
|
+
* @returns Promise resolving to the record or undefined if not found
|
|
1379
1403
|
*
|
|
1380
1404
|
* @example
|
|
1381
1405
|
* ```typescript
|
|
@@ -1384,40 +1408,43 @@ var PrimaryBaseRepository = class {
|
|
|
1384
1408
|
*/
|
|
1385
1409
|
async findById(id) {
|
|
1386
1410
|
this.logger.debug(`Finding record by ID: ${id}`);
|
|
1387
|
-
return
|
|
1411
|
+
return this.model.findFirst({
|
|
1388
1412
|
where: {
|
|
1389
1413
|
id
|
|
1390
1414
|
}
|
|
1391
1415
|
});
|
|
1392
1416
|
}
|
|
1393
1417
|
/**
|
|
1394
|
-
* Find a single record with custom where clause
|
|
1418
|
+
* Find a single record with custom where clause (Drizzle v2 object-based syntax)
|
|
1395
1419
|
*
|
|
1396
|
-
* @param where -
|
|
1397
|
-
* @returns Promise resolving to the record or
|
|
1420
|
+
* @param where - Object-based filter condition
|
|
1421
|
+
* @returns Promise resolving to the record or undefined if not found
|
|
1398
1422
|
*
|
|
1399
1423
|
* @example
|
|
1400
1424
|
* ```typescript
|
|
1401
|
-
* // Simple
|
|
1425
|
+
* // Simple equality
|
|
1402
1426
|
* const user = await userRepository.findOne({ email: 'user@example.com' });
|
|
1403
1427
|
*
|
|
1404
|
-
* // With
|
|
1428
|
+
* // With operators
|
|
1429
|
+
* const user = await userRepository.findOne({ age: { gte: 18 } });
|
|
1430
|
+
*
|
|
1431
|
+
* // Multiple conditions (AND)
|
|
1405
1432
|
* const user = await userRepository.findOne({
|
|
1406
|
-
*
|
|
1407
|
-
*
|
|
1433
|
+
* email: 'user@example.com',
|
|
1434
|
+
* status: 'ACTIVE'
|
|
1408
1435
|
* });
|
|
1409
1436
|
* ```
|
|
1410
1437
|
*/
|
|
1411
1438
|
async findOne(where) {
|
|
1412
1439
|
this.logger.debug("Finding record with custom query");
|
|
1413
|
-
return
|
|
1440
|
+
return this.model.findFirst({
|
|
1414
1441
|
where
|
|
1415
1442
|
});
|
|
1416
1443
|
}
|
|
1417
1444
|
/**
|
|
1418
|
-
* Find multiple records
|
|
1445
|
+
* Find multiple records (Drizzle v2 object-based syntax)
|
|
1419
1446
|
*
|
|
1420
|
-
* @param
|
|
1447
|
+
* @param options - Query options (where, orderBy, limit, offset)
|
|
1421
1448
|
* @returns Promise resolving to an array of records
|
|
1422
1449
|
*
|
|
1423
1450
|
* @example
|
|
@@ -1425,18 +1452,28 @@ var PrimaryBaseRepository = class {
|
|
|
1425
1452
|
* // Find all users
|
|
1426
1453
|
* const users = await userRepository.findMany();
|
|
1427
1454
|
*
|
|
1428
|
-
* // Find with filtering and pagination
|
|
1455
|
+
* // Find with filtering and pagination (v2 object syntax)
|
|
1429
1456
|
* const users = await userRepository.findMany({
|
|
1430
|
-
* where: {
|
|
1457
|
+
* where: { accountStatus: 'ACTIVE' },
|
|
1431
1458
|
* orderBy: { createdAt: 'desc' },
|
|
1432
|
-
*
|
|
1433
|
-
*
|
|
1459
|
+
* limit: 10,
|
|
1460
|
+
* offset: 0
|
|
1461
|
+
* });
|
|
1462
|
+
*
|
|
1463
|
+
* // Multiple conditions
|
|
1464
|
+
* const users = await userRepository.findMany({
|
|
1465
|
+
* where: {
|
|
1466
|
+
* AND: [
|
|
1467
|
+
* { status: 'ACTIVE' },
|
|
1468
|
+
* { age: { gte: 18 } }
|
|
1469
|
+
* ]
|
|
1470
|
+
* }
|
|
1434
1471
|
* });
|
|
1435
1472
|
* ```
|
|
1436
1473
|
*/
|
|
1437
|
-
async findMany(
|
|
1474
|
+
async findMany(options) {
|
|
1438
1475
|
this.logger.debug("Finding multiple records");
|
|
1439
|
-
return
|
|
1476
|
+
return this.model.findMany(options);
|
|
1440
1477
|
}
|
|
1441
1478
|
/**
|
|
1442
1479
|
* Update a record by ID
|
|
@@ -1448,41 +1485,40 @@ var PrimaryBaseRepository = class {
|
|
|
1448
1485
|
* @example
|
|
1449
1486
|
* ```typescript
|
|
1450
1487
|
* const user = await userRepository.update('user-id-123', {
|
|
1451
|
-
*
|
|
1488
|
+
* firstName: 'Jane'
|
|
1452
1489
|
* });
|
|
1453
1490
|
* ```
|
|
1454
1491
|
*/
|
|
1455
1492
|
async update(id, data) {
|
|
1456
1493
|
this.logger.log(`Updating record with ID: ${id}`);
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
},
|
|
1461
|
-
data
|
|
1462
|
-
});
|
|
1494
|
+
const idColumn = this.table.id;
|
|
1495
|
+
const results = await this.db.update(this.table).set(data).where((0, import_drizzle_orm2.eq)(idColumn, id)).returning();
|
|
1496
|
+
return results[0];
|
|
1463
1497
|
}
|
|
1464
1498
|
/**
|
|
1465
1499
|
* Update multiple records
|
|
1466
1500
|
*
|
|
1467
|
-
* @param where -
|
|
1501
|
+
* @param where - SQL condition to match records
|
|
1468
1502
|
* @param data - The data to update
|
|
1469
1503
|
* @returns Promise resolving to the count of updated records
|
|
1470
1504
|
*
|
|
1471
1505
|
* @example
|
|
1472
1506
|
* ```typescript
|
|
1507
|
+
* import { eq } from 'drizzle-orm';
|
|
1508
|
+
*
|
|
1473
1509
|
* const result = await userRepository.updateMany(
|
|
1474
|
-
*
|
|
1475
|
-
* {
|
|
1510
|
+
* eq(users.accountStatus, 'PENDING'),
|
|
1511
|
+
* { accountStatus: 'ACTIVE' }
|
|
1476
1512
|
* );
|
|
1477
1513
|
* console.log(`Updated ${result.count} users`);
|
|
1478
1514
|
* ```
|
|
1479
1515
|
*/
|
|
1480
1516
|
async updateMany(where, data) {
|
|
1481
1517
|
this.logger.log("Updating multiple records");
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
}
|
|
1518
|
+
const result = await this.db.update(this.table).set(data).where(where);
|
|
1519
|
+
return {
|
|
1520
|
+
count: result.rowCount ?? 0
|
|
1521
|
+
};
|
|
1486
1522
|
}
|
|
1487
1523
|
/**
|
|
1488
1524
|
* Delete a record by ID
|
|
@@ -1497,127 +1533,143 @@ var PrimaryBaseRepository = class {
|
|
|
1497
1533
|
*/
|
|
1498
1534
|
async delete(id) {
|
|
1499
1535
|
this.logger.log(`Deleting record with ID: ${id}`);
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
}
|
|
1504
|
-
});
|
|
1536
|
+
const idColumn = this.table.id;
|
|
1537
|
+
const results = await this.db.delete(this.table).where((0, import_drizzle_orm2.eq)(idColumn, id)).returning();
|
|
1538
|
+
return results[0];
|
|
1505
1539
|
}
|
|
1506
1540
|
/**
|
|
1507
1541
|
* Delete multiple records
|
|
1508
1542
|
*
|
|
1509
|
-
* @param where -
|
|
1543
|
+
* @param where - SQL condition to match records
|
|
1510
1544
|
* @returns Promise resolving to the count of deleted records
|
|
1511
1545
|
*
|
|
1512
1546
|
* @example
|
|
1513
1547
|
* ```typescript
|
|
1514
|
-
*
|
|
1515
|
-
*
|
|
1516
|
-
*
|
|
1517
|
-
*
|
|
1548
|
+
* import { lt } from 'drizzle-orm';
|
|
1549
|
+
*
|
|
1550
|
+
* const result = await userRepository.deleteMany(
|
|
1551
|
+
* lt(users.createdAt, new Date('2020-01-01'))
|
|
1552
|
+
* );
|
|
1518
1553
|
* console.log(`Deleted ${result.count} users`);
|
|
1519
1554
|
* ```
|
|
1520
1555
|
*/
|
|
1521
1556
|
async deleteMany(where) {
|
|
1522
1557
|
this.logger.log("Deleting multiple records");
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1558
|
+
const result = await this.db.delete(this.table).where(where);
|
|
1559
|
+
return {
|
|
1560
|
+
count: result.rowCount ?? 0
|
|
1561
|
+
};
|
|
1526
1562
|
}
|
|
1527
1563
|
/**
|
|
1528
1564
|
* Count records
|
|
1529
1565
|
*
|
|
1530
|
-
* @param where - Optional
|
|
1566
|
+
* @param where - Optional SQL condition to filter records
|
|
1531
1567
|
* @returns Promise resolving to the count of records
|
|
1532
1568
|
*
|
|
1533
1569
|
* @example
|
|
1534
1570
|
* ```typescript
|
|
1571
|
+
* import { eq } from 'drizzle-orm';
|
|
1572
|
+
*
|
|
1535
1573
|
* // Count all users
|
|
1536
1574
|
* const total = await userRepository.count();
|
|
1537
1575
|
*
|
|
1538
1576
|
* // Count active users
|
|
1539
|
-
* const activeCount = await userRepository.count(
|
|
1577
|
+
* const activeCount = await userRepository.count(
|
|
1578
|
+
* eq(users.accountStatus, 'ACTIVE')
|
|
1579
|
+
* );
|
|
1540
1580
|
* ```
|
|
1541
1581
|
*/
|
|
1542
1582
|
async count(where) {
|
|
1543
1583
|
this.logger.debug("Counting records");
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
});
|
|
1584
|
+
let query = this.db.select({
|
|
1585
|
+
count: import_drizzle_orm2.sql`count(*)::int`
|
|
1586
|
+
}).from(this.table).$dynamic();
|
|
1587
|
+
if (where) {
|
|
1588
|
+
query = query.where(where);
|
|
1589
|
+
}
|
|
1590
|
+
const results = await query;
|
|
1591
|
+
return results[0].count;
|
|
1547
1592
|
}
|
|
1548
1593
|
/**
|
|
1549
1594
|
* Check if a record exists
|
|
1550
1595
|
*
|
|
1551
|
-
* @param where -
|
|
1596
|
+
* @param where - SQL condition to match records
|
|
1552
1597
|
* @returns Promise resolving to true if at least one record exists, false otherwise
|
|
1553
1598
|
*
|
|
1554
1599
|
* @example
|
|
1555
1600
|
* ```typescript
|
|
1556
|
-
*
|
|
1557
|
-
*
|
|
1558
|
-
*
|
|
1601
|
+
* import { eq } from 'drizzle-orm';
|
|
1602
|
+
*
|
|
1603
|
+
* const emailExists = await userRepository.exists(
|
|
1604
|
+
* eq(users.email, 'user@example.com')
|
|
1605
|
+
* );
|
|
1559
1606
|
* ```
|
|
1560
1607
|
*/
|
|
1561
1608
|
async exists(where) {
|
|
1562
|
-
const count = await this.
|
|
1563
|
-
where
|
|
1564
|
-
});
|
|
1609
|
+
const count = await this.count(where);
|
|
1565
1610
|
return count > 0;
|
|
1566
1611
|
}
|
|
1567
1612
|
};
|
|
1568
1613
|
|
|
1569
1614
|
// src/database/repositories/tenant-base.repository.ts
|
|
1570
1615
|
var import_common12 = require("@nestjs/common");
|
|
1616
|
+
var import_drizzle_orm3 = require("drizzle-orm");
|
|
1571
1617
|
var TenantBaseRepository = class {
|
|
1572
1618
|
static {
|
|
1573
1619
|
__name(this, "TenantBaseRepository");
|
|
1574
1620
|
}
|
|
1575
1621
|
database;
|
|
1622
|
+
table;
|
|
1576
1623
|
logger;
|
|
1577
|
-
modelGetter;
|
|
1578
1624
|
/**
|
|
1579
|
-
*
|
|
1625
|
+
* The table name extracted from the Drizzle table at runtime.
|
|
1626
|
+
* Used to access the query API for this repository's table.
|
|
1627
|
+
*/
|
|
1628
|
+
tableName;
|
|
1629
|
+
/**
|
|
1630
|
+
* Lazy getter for Drizzle client.
|
|
1580
1631
|
* Accesses the client from the database service only when needed,
|
|
1581
1632
|
* avoiding initialization timing issues with NestJS lifecycle.
|
|
1582
1633
|
*/
|
|
1583
|
-
get
|
|
1584
|
-
return this.database.
|
|
1634
|
+
get db() {
|
|
1635
|
+
return this.database.drizzleClient;
|
|
1585
1636
|
}
|
|
1586
1637
|
/**
|
|
1587
|
-
*
|
|
1588
|
-
*
|
|
1638
|
+
* Model query API for THIS repository's table (Prisma-like syntax)
|
|
1639
|
+
* Scoped to only the table this repository manages
|
|
1640
|
+
*
|
|
1641
|
+
* @example
|
|
1642
|
+
* ```typescript
|
|
1643
|
+
* // Use relational queries with type safety
|
|
1644
|
+
* const product = await this.model.findFirst({
|
|
1645
|
+
* where: eq(products.id, id),
|
|
1646
|
+
* with: { category: true, variants: true }
|
|
1647
|
+
* });
|
|
1648
|
+
* ```
|
|
1589
1649
|
*/
|
|
1590
1650
|
get model() {
|
|
1591
|
-
return this.
|
|
1651
|
+
return this.database.drizzleClient.query[this.tableName];
|
|
1592
1652
|
}
|
|
1593
1653
|
/**
|
|
1594
1654
|
* Create a new repository instance
|
|
1595
1655
|
*
|
|
1596
1656
|
* @param database - The tenant database service
|
|
1597
|
-
* @param
|
|
1657
|
+
* @param table - The Drizzle table schema object
|
|
1598
1658
|
*
|
|
1599
1659
|
* @example
|
|
1600
1660
|
* ```typescript
|
|
1601
|
-
*
|
|
1602
|
-
* constructor(database: TenantDatabaseService) {
|
|
1603
|
-
* super(database, (prisma) => prisma.product);
|
|
1604
|
-
* }
|
|
1661
|
+
* import { products } from '@/db/schema';
|
|
1605
1662
|
*
|
|
1606
|
-
* // Short syntax
|
|
1607
1663
|
* constructor(database: TenantDatabaseService) {
|
|
1608
|
-
* super(database,
|
|
1609
|
-
* }
|
|
1610
|
-
*
|
|
1611
|
-
* // Complex model names
|
|
1612
|
-
* constructor(database: TenantDatabaseService) {
|
|
1613
|
-
* super(database, (p) => p.inventoryItem);
|
|
1664
|
+
* super(database, products);
|
|
1614
1665
|
* }
|
|
1615
1666
|
* ```
|
|
1616
1667
|
*/
|
|
1617
|
-
constructor(database,
|
|
1668
|
+
constructor(database, table) {
|
|
1618
1669
|
this.database = database;
|
|
1670
|
+
this.table = table;
|
|
1671
|
+
this.tableName = (0, import_drizzle_orm3.getTableName)(table);
|
|
1619
1672
|
this.logger = new import_common12.Logger(this.constructor.name);
|
|
1620
|
-
this.modelGetter = getModel;
|
|
1621
1673
|
this.logger.debug(`Initialized ${this.constructor.name}`);
|
|
1622
1674
|
}
|
|
1623
1675
|
/**
|
|
@@ -1637,9 +1689,8 @@ var TenantBaseRepository = class {
|
|
|
1637
1689
|
*/
|
|
1638
1690
|
async create(data) {
|
|
1639
1691
|
this.logger.log("Creating record");
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
});
|
|
1692
|
+
const results = await this.db.insert(this.table).values(data).returning();
|
|
1693
|
+
return results[0];
|
|
1643
1694
|
}
|
|
1644
1695
|
/**
|
|
1645
1696
|
* Find a single record by ID
|
|
@@ -1654,59 +1705,65 @@ var TenantBaseRepository = class {
|
|
|
1654
1705
|
*/
|
|
1655
1706
|
async findById(id) {
|
|
1656
1707
|
this.logger.debug(`Finding record by ID: ${id}`);
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
}
|
|
1661
|
-
});
|
|
1708
|
+
const idColumn = this.table.id;
|
|
1709
|
+
const results = await this.db.select().from(this.table).where((0, import_drizzle_orm3.eq)(idColumn, id)).limit(1);
|
|
1710
|
+
return results[0] ?? null;
|
|
1662
1711
|
}
|
|
1663
1712
|
/**
|
|
1664
1713
|
* Find a single record with custom where clause
|
|
1665
1714
|
*
|
|
1666
|
-
* @param where -
|
|
1715
|
+
* @param where - SQL condition
|
|
1667
1716
|
* @returns Promise resolving to the record or null if not found
|
|
1668
1717
|
*
|
|
1669
1718
|
* @example
|
|
1670
1719
|
* ```typescript
|
|
1671
|
-
*
|
|
1672
|
-
* const product = await productRepository.findOne(
|
|
1673
|
-
*
|
|
1674
|
-
* // With include
|
|
1675
|
-
* const product = await productRepository.findOne({
|
|
1676
|
-
* where: { sku: 'WDG-001' },
|
|
1677
|
-
* include: { category: true }
|
|
1678
|
-
* });
|
|
1720
|
+
* import { eq } from 'drizzle-orm';
|
|
1721
|
+
* const product = await productRepository.findOne(eq(products.sku, 'WDG-001'));
|
|
1679
1722
|
* ```
|
|
1680
1723
|
*/
|
|
1681
1724
|
async findOne(where) {
|
|
1682
1725
|
this.logger.debug("Finding record with custom query");
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
});
|
|
1726
|
+
const results = await this.db.select().from(this.table).where(where).limit(1);
|
|
1727
|
+
return results[0] ?? null;
|
|
1686
1728
|
}
|
|
1687
1729
|
/**
|
|
1688
1730
|
* Find multiple records
|
|
1689
1731
|
*
|
|
1690
|
-
* @param
|
|
1732
|
+
* @param options - Query options (where, orderBy, limit, offset)
|
|
1691
1733
|
* @returns Promise resolving to an array of records
|
|
1692
1734
|
*
|
|
1693
1735
|
* @example
|
|
1694
1736
|
* ```typescript
|
|
1737
|
+
* import { eq, desc } from 'drizzle-orm';
|
|
1738
|
+
*
|
|
1695
1739
|
* // Find all products
|
|
1696
1740
|
* const products = await productRepository.findMany();
|
|
1697
1741
|
*
|
|
1698
1742
|
* // Find with filtering and pagination
|
|
1699
1743
|
* const products = await productRepository.findMany({
|
|
1700
|
-
* where:
|
|
1701
|
-
* orderBy:
|
|
1702
|
-
*
|
|
1703
|
-
*
|
|
1744
|
+
* where: eq(products.status, 'ACTIVE'),
|
|
1745
|
+
* orderBy: desc(products.createdAt),
|
|
1746
|
+
* limit: 10,
|
|
1747
|
+
* offset: 0
|
|
1704
1748
|
* });
|
|
1705
1749
|
* ```
|
|
1706
1750
|
*/
|
|
1707
|
-
async findMany(
|
|
1751
|
+
async findMany(options) {
|
|
1708
1752
|
this.logger.debug("Finding multiple records");
|
|
1709
|
-
|
|
1753
|
+
let query = this.db.select().from(this.table).$dynamic();
|
|
1754
|
+
if (options?.where) {
|
|
1755
|
+
query = query.where(options.where);
|
|
1756
|
+
}
|
|
1757
|
+
if (options?.orderBy) {
|
|
1758
|
+
query = query.orderBy(options.orderBy);
|
|
1759
|
+
}
|
|
1760
|
+
if (options?.limit) {
|
|
1761
|
+
query = query.limit(options.limit);
|
|
1762
|
+
}
|
|
1763
|
+
if (options?.offset) {
|
|
1764
|
+
query = query.offset(options.offset);
|
|
1765
|
+
}
|
|
1766
|
+
return await query;
|
|
1710
1767
|
}
|
|
1711
1768
|
/**
|
|
1712
1769
|
* Update a record by ID
|
|
@@ -1724,24 +1781,23 @@ var TenantBaseRepository = class {
|
|
|
1724
1781
|
*/
|
|
1725
1782
|
async update(id, data) {
|
|
1726
1783
|
this.logger.log(`Updating record with ID: ${id}`);
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
},
|
|
1731
|
-
data
|
|
1732
|
-
});
|
|
1784
|
+
const idColumn = this.table.id;
|
|
1785
|
+
const results = await this.db.update(this.table).set(data).where((0, import_drizzle_orm3.eq)(idColumn, id)).returning();
|
|
1786
|
+
return results[0];
|
|
1733
1787
|
}
|
|
1734
1788
|
/**
|
|
1735
1789
|
* Update multiple records
|
|
1736
1790
|
*
|
|
1737
|
-
* @param where -
|
|
1791
|
+
* @param where - SQL condition to match records
|
|
1738
1792
|
* @param data - The data to update
|
|
1739
1793
|
* @returns Promise resolving to the count of updated records
|
|
1740
1794
|
*
|
|
1741
1795
|
* @example
|
|
1742
1796
|
* ```typescript
|
|
1797
|
+
* import { eq } from 'drizzle-orm';
|
|
1798
|
+
*
|
|
1743
1799
|
* const result = await productRepository.updateMany(
|
|
1744
|
-
*
|
|
1800
|
+
* eq(products.status, 'PENDING'),
|
|
1745
1801
|
* { status: 'ACTIVE' }
|
|
1746
1802
|
* );
|
|
1747
1803
|
* console.log(`Updated ${result.count} products`);
|
|
@@ -1749,10 +1805,10 @@ var TenantBaseRepository = class {
|
|
|
1749
1805
|
*/
|
|
1750
1806
|
async updateMany(where, data) {
|
|
1751
1807
|
this.logger.log("Updating multiple records");
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
}
|
|
1808
|
+
const result = await this.db.update(this.table).set(data).where(where);
|
|
1809
|
+
return {
|
|
1810
|
+
count: result.rowCount ?? 0
|
|
1811
|
+
};
|
|
1756
1812
|
}
|
|
1757
1813
|
/**
|
|
1758
1814
|
* Delete a record by ID
|
|
@@ -1767,71 +1823,80 @@ var TenantBaseRepository = class {
|
|
|
1767
1823
|
*/
|
|
1768
1824
|
async delete(id) {
|
|
1769
1825
|
this.logger.log(`Deleting record with ID: ${id}`);
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
}
|
|
1774
|
-
});
|
|
1826
|
+
const idColumn = this.table.id;
|
|
1827
|
+
const results = await this.db.delete(this.table).where((0, import_drizzle_orm3.eq)(idColumn, id)).returning();
|
|
1828
|
+
return results[0];
|
|
1775
1829
|
}
|
|
1776
1830
|
/**
|
|
1777
1831
|
* Delete multiple records
|
|
1778
1832
|
*
|
|
1779
|
-
* @param where -
|
|
1833
|
+
* @param where - SQL condition to match records
|
|
1780
1834
|
* @returns Promise resolving to the count of deleted records
|
|
1781
1835
|
*
|
|
1782
1836
|
* @example
|
|
1783
1837
|
* ```typescript
|
|
1784
|
-
*
|
|
1785
|
-
*
|
|
1786
|
-
*
|
|
1787
|
-
*
|
|
1838
|
+
* import { lt } from 'drizzle-orm';
|
|
1839
|
+
*
|
|
1840
|
+
* const result = await productRepository.deleteMany(
|
|
1841
|
+
* lt(products.createdAt, new Date('2020-01-01'))
|
|
1842
|
+
* );
|
|
1788
1843
|
* console.log(`Deleted ${result.count} products`);
|
|
1789
1844
|
* ```
|
|
1790
1845
|
*/
|
|
1791
1846
|
async deleteMany(where) {
|
|
1792
1847
|
this.logger.log("Deleting multiple records");
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1848
|
+
const result = await this.db.delete(this.table).where(where);
|
|
1849
|
+
return {
|
|
1850
|
+
count: result.rowCount ?? 0
|
|
1851
|
+
};
|
|
1796
1852
|
}
|
|
1797
1853
|
/**
|
|
1798
1854
|
* Count records
|
|
1799
1855
|
*
|
|
1800
|
-
* @param where - Optional
|
|
1856
|
+
* @param where - Optional SQL condition to filter records
|
|
1801
1857
|
* @returns Promise resolving to the count of records
|
|
1802
1858
|
*
|
|
1803
1859
|
* @example
|
|
1804
1860
|
* ```typescript
|
|
1861
|
+
* import { eq } from 'drizzle-orm';
|
|
1862
|
+
*
|
|
1805
1863
|
* // Count all products
|
|
1806
1864
|
* const total = await productRepository.count();
|
|
1807
1865
|
*
|
|
1808
1866
|
* // Count active products
|
|
1809
|
-
* const activeCount = await productRepository.count(
|
|
1867
|
+
* const activeCount = await productRepository.count(
|
|
1868
|
+
* eq(products.status, 'ACTIVE')
|
|
1869
|
+
* );
|
|
1810
1870
|
* ```
|
|
1811
1871
|
*/
|
|
1812
1872
|
async count(where) {
|
|
1813
1873
|
this.logger.debug("Counting records");
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
});
|
|
1874
|
+
let query = this.db.select({
|
|
1875
|
+
count: import_drizzle_orm3.sql`count(*)::int`
|
|
1876
|
+
}).from(this.table).$dynamic();
|
|
1877
|
+
if (where) {
|
|
1878
|
+
query = query.where(where);
|
|
1879
|
+
}
|
|
1880
|
+
const results = await query;
|
|
1881
|
+
return results[0].count;
|
|
1817
1882
|
}
|
|
1818
1883
|
/**
|
|
1819
1884
|
* Check if a record exists
|
|
1820
1885
|
*
|
|
1821
|
-
* @param where -
|
|
1886
|
+
* @param where - SQL condition to match records
|
|
1822
1887
|
* @returns Promise resolving to true if at least one record exists, false otherwise
|
|
1823
1888
|
*
|
|
1824
1889
|
* @example
|
|
1825
1890
|
* ```typescript
|
|
1826
|
-
*
|
|
1827
|
-
*
|
|
1828
|
-
*
|
|
1891
|
+
* import { eq } from 'drizzle-orm';
|
|
1892
|
+
*
|
|
1893
|
+
* const skuExists = await productRepository.exists(
|
|
1894
|
+
* eq(products.sku, 'WDG-001')
|
|
1895
|
+
* );
|
|
1829
1896
|
* ```
|
|
1830
1897
|
*/
|
|
1831
1898
|
async exists(where) {
|
|
1832
|
-
const count = await this.
|
|
1833
|
-
where
|
|
1834
|
-
});
|
|
1899
|
+
const count = await this.count(where);
|
|
1835
1900
|
return count > 0;
|
|
1836
1901
|
}
|
|
1837
1902
|
};
|
|
@@ -1988,15 +2053,16 @@ var HttpExceptionFilter = class _HttpExceptionFilter {
|
|
|
1988
2053
|
const exceptionResponse = exception.getResponse();
|
|
1989
2054
|
if (typeof exceptionResponse === "object" && exceptionResponse !== null) {
|
|
1990
2055
|
const responseObj = exceptionResponse;
|
|
1991
|
-
if (
|
|
2056
|
+
if ("errors" in responseObj && Array.isArray(responseObj.errors)) {
|
|
1992
2057
|
errors = responseObj.errors;
|
|
1993
|
-
detail = responseObj.detail || exception.message;
|
|
1994
|
-
} else if (
|
|
2058
|
+
detail = ("detail" in responseObj ? responseObj.detail : void 0) || exception.message;
|
|
2059
|
+
} else if ("message" in responseObj && Array.isArray(responseObj.message)) {
|
|
1995
2060
|
errors = responseObj.message.map((msg) => {
|
|
1996
|
-
if (typeof msg === "object" &&
|
|
2061
|
+
if (typeof msg === "object" && "property" in msg && "constraints" in msg) {
|
|
2062
|
+
const constraintValues = Object.values(msg.constraints);
|
|
1997
2063
|
return {
|
|
1998
2064
|
field: msg.property,
|
|
1999
|
-
message:
|
|
2065
|
+
message: constraintValues[0] ?? "Validation failed"
|
|
2000
2066
|
};
|
|
2001
2067
|
}
|
|
2002
2068
|
return {
|
|
@@ -2004,13 +2070,14 @@ var HttpExceptionFilter = class _HttpExceptionFilter {
|
|
|
2004
2070
|
};
|
|
2005
2071
|
});
|
|
2006
2072
|
detail = "Validation failed";
|
|
2007
|
-
} else if (responseObj
|
|
2073
|
+
} else if ("message" in responseObj) {
|
|
2074
|
+
const message = responseObj.message;
|
|
2008
2075
|
errors = [
|
|
2009
2076
|
{
|
|
2010
|
-
message: Array.isArray(
|
|
2077
|
+
message: Array.isArray(message) ? message.join(", ") : message
|
|
2011
2078
|
}
|
|
2012
2079
|
];
|
|
2013
|
-
detail = responseObj.error || exception.message;
|
|
2080
|
+
detail = ("error" in responseObj ? responseObj.error : void 0) || exception.message;
|
|
2014
2081
|
}
|
|
2015
2082
|
} else if (typeof exceptionResponse === "string") {
|
|
2016
2083
|
errors = [
|
|
@@ -3058,14 +3125,15 @@ function getLevelsUpTo(level) {
|
|
|
3058
3125
|
"debug",
|
|
3059
3126
|
"verbose"
|
|
3060
3127
|
];
|
|
3061
|
-
const
|
|
3062
|
-
if (
|
|
3128
|
+
const isValidLevel = /* @__PURE__ */ __name((l) => allLevels.includes(l), "isValidLevel");
|
|
3129
|
+
if (!isValidLevel(level)) {
|
|
3063
3130
|
return [
|
|
3064
3131
|
"error",
|
|
3065
3132
|
"warn",
|
|
3066
3133
|
"log"
|
|
3067
3134
|
];
|
|
3068
3135
|
}
|
|
3136
|
+
const levelIndex = allLevels.indexOf(level);
|
|
3069
3137
|
return allLevels.slice(0, levelIndex + 1);
|
|
3070
3138
|
}
|
|
3071
3139
|
__name(getLevelsUpTo, "getLevelsUpTo");
|