@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.js
CHANGED
|
@@ -143,6 +143,9 @@ import * as jwt from "jsonwebtoken";
|
|
|
143
143
|
|
|
144
144
|
// src/database/services/primary-database.service.ts
|
|
145
145
|
import { Inject as Inject2, Injectable as Injectable2, InternalServerErrorException, Logger } from "@nestjs/common";
|
|
146
|
+
import { Pool } from "pg";
|
|
147
|
+
import { drizzle } from "drizzle-orm/node-postgres";
|
|
148
|
+
import { eq, or } from "drizzle-orm";
|
|
146
149
|
|
|
147
150
|
// src/database/constants.ts
|
|
148
151
|
var DATABASE_MODULE_OPTIONS = Symbol("DATABASE_MODULE_OPTIONS");
|
|
@@ -171,8 +174,10 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
171
174
|
}
|
|
172
175
|
options;
|
|
173
176
|
logger = new Logger(_PrimaryDatabaseService.name);
|
|
174
|
-
/**
|
|
175
|
-
|
|
177
|
+
/** PostgreSQL connection pool */
|
|
178
|
+
pool = null;
|
|
179
|
+
/** Drizzle database instance */
|
|
180
|
+
db = null;
|
|
176
181
|
/** In-memory cache: Map<tenantIdentifier, TenantConfig> */
|
|
177
182
|
tenantConfigCache = /* @__PURE__ */ new Map();
|
|
178
183
|
/** Cache TTL in milliseconds */
|
|
@@ -183,28 +188,28 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
183
188
|
}
|
|
184
189
|
async onModuleInit() {
|
|
185
190
|
if (this.options.primaryDb) {
|
|
186
|
-
await this.
|
|
191
|
+
await this.initializeDrizzleClient();
|
|
187
192
|
}
|
|
188
193
|
}
|
|
189
194
|
/**
|
|
190
|
-
* Initialize connection to primary database
|
|
195
|
+
* Initialize connection to primary database using Drizzle
|
|
191
196
|
*/
|
|
192
|
-
async
|
|
197
|
+
async initializeDrizzleClient() {
|
|
193
198
|
try {
|
|
194
|
-
const PrimaryDbClient = this.options.prismaClientConstructor;
|
|
195
199
|
const databaseUrl = this.buildPrimaryDbUrl();
|
|
196
|
-
this.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
url: databaseUrl
|
|
200
|
-
}
|
|
201
|
-
},
|
|
202
|
-
log: [
|
|
203
|
-
"error",
|
|
204
|
-
"warn"
|
|
205
|
-
]
|
|
200
|
+
this.pool = new Pool({
|
|
201
|
+
connectionString: databaseUrl,
|
|
202
|
+
max: this.options.maxConnections || 10
|
|
206
203
|
});
|
|
207
|
-
|
|
204
|
+
this.logger.debug(`Schema keys passed to drizzle: [${Object.keys(this.options.drizzleSchema || {}).join(", ")}]`);
|
|
205
|
+
this.logger.debug(`Relations keys passed to drizzle: [${Object.keys(this.options.drizzleRelations || {}).join(", ")}]`);
|
|
206
|
+
this.db = drizzle({
|
|
207
|
+
client: this.pool,
|
|
208
|
+
schema: this.options.drizzleSchema,
|
|
209
|
+
relations: this.options.drizzleRelations
|
|
210
|
+
});
|
|
211
|
+
this.logger.debug(`Drizzle query keys after init: [${Object.keys(this.db.query || {}).join(", ")}]`);
|
|
212
|
+
await this.pool.query("SELECT 1");
|
|
208
213
|
this.logger.log("Connected to primary database (tenant registry)");
|
|
209
214
|
} catch (error) {
|
|
210
215
|
this.logger.error("Failed to connect to primary database", error);
|
|
@@ -219,7 +224,7 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
219
224
|
throw new Error("Primary database configuration not provided");
|
|
220
225
|
}
|
|
221
226
|
const { host, port = 5432, username, password, database, schema = "public", sslMode = "require" } = this.options.primaryDb;
|
|
222
|
-
let url = `postgresql://${username}:${password}@${host}:${port}/${database}`;
|
|
227
|
+
let url = `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;
|
|
223
228
|
const params = new URLSearchParams();
|
|
224
229
|
if (schema) {
|
|
225
230
|
params.set("schema", schema);
|
|
@@ -239,9 +244,9 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
239
244
|
return url.replace(/:([^@]+)@/, ":****@");
|
|
240
245
|
}
|
|
241
246
|
/**
|
|
242
|
-
* Get tenant configuration by identifier (ID or
|
|
247
|
+
* Get tenant configuration by identifier (ID or subdomain)
|
|
243
248
|
*
|
|
244
|
-
* @param tenantIdentifier Tenant ID or
|
|
249
|
+
* @param tenantIdentifier Tenant ID or subdomain
|
|
245
250
|
* @returns Tenant configuration or null if not found
|
|
246
251
|
*/
|
|
247
252
|
async getTenantInfo(tenantIdentifier) {
|
|
@@ -251,45 +256,39 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
251
256
|
return cached;
|
|
252
257
|
}
|
|
253
258
|
try {
|
|
254
|
-
if (!this.
|
|
259
|
+
if (!this.db) {
|
|
255
260
|
throw new Error("Primary database client not initialized");
|
|
256
261
|
}
|
|
257
262
|
this.logger.debug(`Querying primary database for tenant: ${tenantIdentifier}`);
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
id: tenantIdentifier
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
subdomain: tenantIdentifier
|
|
266
|
-
}
|
|
267
|
-
],
|
|
268
|
-
status: "ACTIVE"
|
|
269
|
-
},
|
|
270
|
-
include: {
|
|
271
|
-
databaseConfig: true
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
if (!tenant) {
|
|
263
|
+
const schema = this.options.drizzleSchema;
|
|
264
|
+
const { tenants, tenantDatabaseConfigs } = schema;
|
|
265
|
+
const result = await this.db.select().from(tenants).leftJoin(tenantDatabaseConfigs, eq(tenants.id, tenantDatabaseConfigs.tenantId)).where(or(eq(tenants.id, tenantIdentifier), eq(tenants.subdomain, tenantIdentifier))).limit(1);
|
|
266
|
+
if (!result.length) {
|
|
275
267
|
this.logger.warn(`Tenant not found: ${tenantIdentifier}`);
|
|
276
268
|
return null;
|
|
277
269
|
}
|
|
270
|
+
const row = result[0];
|
|
271
|
+
const tenant = row.tenants;
|
|
272
|
+
const config = row.tenant_database_configs;
|
|
273
|
+
if (tenant.status !== "ACTIVE") {
|
|
274
|
+
this.logger.warn(`Tenant not active: ${tenantIdentifier}`);
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
278
277
|
const info = {
|
|
279
278
|
id: tenant.id,
|
|
280
279
|
subdomain: tenant.subdomain,
|
|
281
280
|
type: tenant.dbType,
|
|
282
281
|
status: tenant.status,
|
|
283
282
|
// For SHARED tenants: schema name
|
|
284
|
-
schemaName:
|
|
283
|
+
schemaName: config?.dbSchema || void 0,
|
|
285
284
|
// For DEDICATED tenants: database configuration from TenantDatabaseConfig table
|
|
286
|
-
databaseName:
|
|
287
|
-
databaseHost:
|
|
288
|
-
databasePort:
|
|
289
|
-
databaseUsername:
|
|
290
|
-
databasePassword:
|
|
291
|
-
databaseSslMode:
|
|
292
|
-
connectionPoolSize:
|
|
285
|
+
databaseName: config?.dbName || void 0,
|
|
286
|
+
databaseHost: config?.dbHost || void 0,
|
|
287
|
+
databasePort: config?.dbPort || void 0,
|
|
288
|
+
databaseUsername: config?.dbUsername ? this.decrypt(config.dbUsername) : void 0,
|
|
289
|
+
databasePassword: config?.dbPassword ? this.decrypt(config.dbPassword) : void 0,
|
|
290
|
+
databaseSslMode: config?.dbSslMode || void 0,
|
|
291
|
+
connectionPoolSize: config?.connectionPoolSize || void 0
|
|
293
292
|
};
|
|
294
293
|
this.cacheInfo(info);
|
|
295
294
|
return info;
|
|
@@ -315,7 +314,7 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
315
314
|
*
|
|
316
315
|
* Useful when tenant settings are updated and cache needs to be invalidated
|
|
317
316
|
*
|
|
318
|
-
* @param tenantIdentifier Tenant ID or
|
|
317
|
+
* @param tenantIdentifier Tenant ID or subdomain
|
|
319
318
|
*/
|
|
320
319
|
clearTenantCache(tenantIdentifier) {
|
|
321
320
|
const config = this.tenantConfigCache.get(tenantIdentifier);
|
|
@@ -334,17 +333,23 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
334
333
|
this.logger.log(`Cleared ${size} cached tenant configs`);
|
|
335
334
|
}
|
|
336
335
|
/**
|
|
337
|
-
* Get the
|
|
338
|
-
* This is a synchronous property that returns the initialized
|
|
336
|
+
* Get the Drizzle database instance for the primary database.
|
|
337
|
+
* This is a synchronous property that returns the initialized Drizzle client.
|
|
339
338
|
*
|
|
340
|
-
* @returns Primary database
|
|
339
|
+
* @returns Primary database Drizzle instance
|
|
341
340
|
* @throws Error if primary database client is not initialized
|
|
342
341
|
*/
|
|
343
|
-
get
|
|
344
|
-
if (!this.
|
|
342
|
+
get drizzleClient() {
|
|
343
|
+
if (!this.db) {
|
|
345
344
|
throw new Error("Primary database client not initialized");
|
|
346
345
|
}
|
|
347
|
-
return this.
|
|
346
|
+
return this.db;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Get the Drizzle schema
|
|
350
|
+
*/
|
|
351
|
+
get schema() {
|
|
352
|
+
return this.options.drizzleSchema;
|
|
348
353
|
}
|
|
349
354
|
/**
|
|
350
355
|
* Decrypt database credentials
|
|
@@ -358,8 +363,8 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
358
363
|
return encrypted;
|
|
359
364
|
}
|
|
360
365
|
async onModuleDestroy() {
|
|
361
|
-
if (this.
|
|
362
|
-
await this.
|
|
366
|
+
if (this.pool) {
|
|
367
|
+
await this.pool.end();
|
|
363
368
|
this.logger.log("Disconnected from primary database");
|
|
364
369
|
}
|
|
365
370
|
}
|
|
@@ -904,6 +909,8 @@ TenantContextInterceptor = _ts_decorate8([
|
|
|
904
909
|
|
|
905
910
|
// src/database/services/tenant-database.service.ts
|
|
906
911
|
import { Inject as Inject3, Injectable as Injectable7, InternalServerErrorException as InternalServerErrorException2, Logger as Logger5 } from "@nestjs/common";
|
|
912
|
+
import { Pool as Pool2 } from "pg";
|
|
913
|
+
import { drizzle as drizzle2 } from "drizzle-orm/node-postgres";
|
|
907
914
|
function _ts_decorate9(decorators, target, key, desc) {
|
|
908
915
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
909
916
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -928,7 +935,7 @@ var TenantDatabaseService = class _TenantDatabaseService {
|
|
|
928
935
|
options;
|
|
929
936
|
tenantContext;
|
|
930
937
|
logger = new Logger5(_TenantDatabaseService.name);
|
|
931
|
-
/** Connection pool: Map<cacheKey,
|
|
938
|
+
/** Connection pool: Map<cacheKey, TenantConnection> */
|
|
932
939
|
clients = /* @__PURE__ */ new Map();
|
|
933
940
|
/** Track last usage time for idle connection cleanup */
|
|
934
941
|
clientLastUsed = /* @__PURE__ */ new Map();
|
|
@@ -940,17 +947,23 @@ var TenantDatabaseService = class _TenantDatabaseService {
|
|
|
940
947
|
this.startConnectionCleaner();
|
|
941
948
|
}
|
|
942
949
|
/**
|
|
943
|
-
* Get the
|
|
950
|
+
* Get the Drizzle client for the current tenant's database.
|
|
944
951
|
* This returns the tenant-scoped database client.
|
|
945
952
|
*
|
|
946
|
-
* @returns Tenant-scoped database
|
|
953
|
+
* @returns Tenant-scoped Drizzle database instance
|
|
947
954
|
* @throws UnauthorizedException if tenant context not set
|
|
948
955
|
* @throws InternalServerErrorException if connection fails
|
|
949
956
|
*/
|
|
950
|
-
get
|
|
957
|
+
get drizzleClient() {
|
|
951
958
|
return this.getDbClient();
|
|
952
959
|
}
|
|
953
960
|
/**
|
|
961
|
+
* Get the Drizzle schema
|
|
962
|
+
*/
|
|
963
|
+
get schema() {
|
|
964
|
+
return this.options.drizzleSchema;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
954
967
|
* Get tenant-scoped database client for the current request/message
|
|
955
968
|
*
|
|
956
969
|
* This method:
|
|
@@ -958,66 +971,61 @@ var TenantDatabaseService = class _TenantDatabaseService {
|
|
|
958
971
|
* 2. Builds a connection URL based on tenant type
|
|
959
972
|
* 3. Returns cached client if exists, otherwise creates new one
|
|
960
973
|
*
|
|
961
|
-
* @returns
|
|
974
|
+
* @returns Drizzle database instance
|
|
962
975
|
* @throws UnauthorizedException if tenant context not set
|
|
963
976
|
* @throws InternalServerErrorException if connection fails
|
|
964
|
-
*
|
|
965
|
-
* @example
|
|
966
|
-
* const dbClient = await tenantDatabase.getDbClient<PrismaClient>();
|
|
967
|
-
* const users = await dbClient.user.findMany();
|
|
968
977
|
*/
|
|
969
|
-
|
|
978
|
+
getDbClient() {
|
|
970
979
|
const tenant = this.tenantContext.getTenant();
|
|
971
980
|
const cacheKey = this.buildCacheKey(tenant);
|
|
972
|
-
|
|
981
|
+
const existing = this.clients.get(cacheKey);
|
|
982
|
+
if (existing) {
|
|
973
983
|
this.clientLastUsed.set(cacheKey, Date.now());
|
|
974
984
|
this.logger.debug(`Reusing cached connection: ${cacheKey}`);
|
|
975
|
-
return
|
|
985
|
+
return existing.db;
|
|
976
986
|
}
|
|
977
987
|
this.logger.log(`Creating new database connection: ${cacheKey}`);
|
|
978
|
-
const
|
|
979
|
-
this.clients.set(cacheKey,
|
|
988
|
+
const connection = this.createDbClientSync(tenant);
|
|
989
|
+
this.clients.set(cacheKey, connection);
|
|
980
990
|
this.clientLastUsed.set(cacheKey, Date.now());
|
|
981
|
-
return
|
|
991
|
+
return connection.db;
|
|
982
992
|
}
|
|
983
993
|
/**
|
|
984
|
-
* Create a new database client for the given tenant
|
|
994
|
+
* Create a new database client for the given tenant (synchronous)
|
|
985
995
|
*/
|
|
986
|
-
|
|
996
|
+
createDbClientSync(tenant) {
|
|
987
997
|
try {
|
|
988
998
|
const databaseUrl = this.buildTenantDbUrl(tenant);
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
log: [
|
|
997
|
-
"error",
|
|
998
|
-
"warn"
|
|
999
|
-
]
|
|
999
|
+
const pool = new Pool2({
|
|
1000
|
+
connectionString: databaseUrl,
|
|
1001
|
+
max: tenant.connectionPoolSize || this.options.maxConnections || 10
|
|
1002
|
+
});
|
|
1003
|
+
const db = drizzle2({
|
|
1004
|
+
client: pool,
|
|
1005
|
+
schema: this.options.drizzleSchema
|
|
1000
1006
|
});
|
|
1001
|
-
await client.$connect();
|
|
1002
1007
|
this.logger.log(`Connected to database for tenant: ${tenant.subdomain}`);
|
|
1003
|
-
return
|
|
1008
|
+
return {
|
|
1009
|
+
pool,
|
|
1010
|
+
db
|
|
1011
|
+
};
|
|
1004
1012
|
} catch (error) {
|
|
1005
1013
|
this.logger.error(`Failed to create database connection for tenant: ${tenant.subdomain}`, error);
|
|
1006
1014
|
throw new InternalServerErrorException2("Failed to connect to tenant database");
|
|
1007
1015
|
}
|
|
1008
1016
|
}
|
|
1009
1017
|
/**
|
|
1010
|
-
* Build connection URL for
|
|
1018
|
+
* Build connection URL for tenant (dedicated database)
|
|
1011
1019
|
*/
|
|
1012
1020
|
buildTenantDbUrl(tenant) {
|
|
1013
1021
|
const { databaseHost, databasePort, databaseName, databaseUsername, databasePassword, databaseSslMode } = tenant;
|
|
1014
1022
|
if (!databaseHost || !databaseName || !databaseUsername) {
|
|
1015
|
-
throw new Error(`
|
|
1023
|
+
throw new Error(`Tenant ${tenant.subdomain} missing database configuration`);
|
|
1016
1024
|
}
|
|
1017
1025
|
const port = databasePort || 5432;
|
|
1018
1026
|
const sslMode = databaseSslMode || "require";
|
|
1019
|
-
const connectionUrl = `postgresql://${databaseUsername}:${databasePassword}@${databaseHost}:${port}/${databaseName}?sslmode=${sslMode}`;
|
|
1020
|
-
this.logger.debug(`
|
|
1027
|
+
const connectionUrl = `postgresql://${databaseUsername}:${encodeURIComponent(databasePassword || "")}@${databaseHost}:${port}/${databaseName}?sslmode=${sslMode}`;
|
|
1028
|
+
this.logger.debug(`Tenant connection URL: ${this.maskPassword(connectionUrl)}`);
|
|
1021
1029
|
return connectionUrl;
|
|
1022
1030
|
}
|
|
1023
1031
|
/**
|
|
@@ -1039,19 +1047,20 @@ var TenantDatabaseService = class _TenantDatabaseService {
|
|
|
1039
1047
|
/**
|
|
1040
1048
|
* Clean up idle connections that haven't been used recently
|
|
1041
1049
|
*/
|
|
1042
|
-
cleanupIdleConnections() {
|
|
1050
|
+
async cleanupIdleConnections() {
|
|
1043
1051
|
const now = Date.now();
|
|
1044
1052
|
const maxIdle = this.options.connectionCacheTTL || 3e5;
|
|
1045
1053
|
let cleaned = 0;
|
|
1046
1054
|
for (const [key, lastUsed] of this.clientLastUsed.entries()) {
|
|
1047
1055
|
if (now - lastUsed > maxIdle) {
|
|
1048
|
-
const
|
|
1049
|
-
if (
|
|
1050
|
-
|
|
1056
|
+
const connection = this.clients.get(key);
|
|
1057
|
+
if (connection) {
|
|
1058
|
+
try {
|
|
1059
|
+
await connection.pool.end();
|
|
1051
1060
|
this.logger.debug(`Cleaned up idle connection: ${key}`);
|
|
1052
|
-
}
|
|
1061
|
+
} catch (error) {
|
|
1053
1062
|
this.logger.error(`Error disconnecting idle client: ${key}`, error);
|
|
1054
|
-
}
|
|
1063
|
+
}
|
|
1055
1064
|
this.clients.delete(key);
|
|
1056
1065
|
this.clientLastUsed.delete(key);
|
|
1057
1066
|
cleaned++;
|
|
@@ -1082,9 +1091,9 @@ var TenantDatabaseService = class _TenantDatabaseService {
|
|
|
1082
1091
|
clearInterval(this.cleanupInterval);
|
|
1083
1092
|
}
|
|
1084
1093
|
this.logger.log(`Disconnecting ${this.clients.size} database connections`);
|
|
1085
|
-
const disconnectPromises = Array.from(this.clients.entries()).map(async ([key,
|
|
1094
|
+
const disconnectPromises = Array.from(this.clients.entries()).map(async ([key, connection]) => {
|
|
1086
1095
|
try {
|
|
1087
|
-
await
|
|
1096
|
+
await connection.pool.end();
|
|
1088
1097
|
this.logger.debug(`Disconnected: ${key}`);
|
|
1089
1098
|
} catch (error) {
|
|
1090
1099
|
this.logger.error(`Error disconnecting client: ${key}`, error);
|
|
@@ -1220,57 +1229,73 @@ DatabaseModule = _ts_decorate10([
|
|
|
1220
1229
|
|
|
1221
1230
|
// src/database/repositories/primary-base.repository.ts
|
|
1222
1231
|
import { Logger as Logger6 } from "@nestjs/common";
|
|
1232
|
+
import { eq as eq2, sql, getTableName } from "drizzle-orm";
|
|
1223
1233
|
var PrimaryBaseRepository = class {
|
|
1224
1234
|
static {
|
|
1225
1235
|
__name(this, "PrimaryBaseRepository");
|
|
1226
1236
|
}
|
|
1227
1237
|
database;
|
|
1238
|
+
table;
|
|
1228
1239
|
logger;
|
|
1229
|
-
modelGetter;
|
|
1230
1240
|
/**
|
|
1231
|
-
*
|
|
1241
|
+
* The table name extracted from the Drizzle table at runtime.
|
|
1242
|
+
* Used to access the query API for this repository's table.
|
|
1243
|
+
*/
|
|
1244
|
+
tableName;
|
|
1245
|
+
/**
|
|
1246
|
+
* Lazy getter for Drizzle client.
|
|
1232
1247
|
* Accesses the client from the database service only when needed,
|
|
1233
1248
|
* avoiding initialization timing issues with NestJS lifecycle.
|
|
1234
1249
|
*/
|
|
1235
|
-
get
|
|
1236
|
-
return this.database.
|
|
1250
|
+
get db() {
|
|
1251
|
+
return this.database.drizzleClient;
|
|
1237
1252
|
}
|
|
1238
1253
|
/**
|
|
1239
|
-
*
|
|
1240
|
-
*
|
|
1254
|
+
* Model query API for THIS repository's table (Drizzle v2 relational queries)
|
|
1255
|
+
* Scoped to only the table this repository manages.
|
|
1256
|
+
* Returns a type-safe wrapper around Drizzle's RelationalQueryBuilder.
|
|
1257
|
+
*
|
|
1258
|
+
* @example
|
|
1259
|
+
* ```typescript
|
|
1260
|
+
* // Use relational queries with v2 object-based where syntax
|
|
1261
|
+
* const user = await this.model.findFirst({
|
|
1262
|
+
* where: { id },
|
|
1263
|
+
* with: { posts: true, profile: true }
|
|
1264
|
+
* });
|
|
1265
|
+
* ```
|
|
1241
1266
|
*/
|
|
1242
1267
|
get model() {
|
|
1243
|
-
|
|
1268
|
+
const query = this.database.drizzleClient.query;
|
|
1269
|
+
const queryKeys = Object.keys(query || {});
|
|
1270
|
+
this.logger.debug(`Looking for '${this.tableName}' in query keys: [${queryKeys.join(", ")}]`);
|
|
1271
|
+
const model = query[this.tableName];
|
|
1272
|
+
if (!model) {
|
|
1273
|
+
this.logger.error(`Table '${this.tableName}' not found in query object. Available: [${queryKeys.join(", ")}]`);
|
|
1274
|
+
}
|
|
1275
|
+
return model;
|
|
1244
1276
|
}
|
|
1245
1277
|
/**
|
|
1246
1278
|
* Create a new repository instance
|
|
1247
1279
|
*
|
|
1248
1280
|
* @param database - The primary database service
|
|
1249
|
-
* @param
|
|
1281
|
+
* @param table - The Drizzle table schema object
|
|
1250
1282
|
*
|
|
1251
1283
|
* @example
|
|
1252
1284
|
* ```typescript
|
|
1253
|
-
*
|
|
1254
|
-
* constructor(database: PrimaryDatabaseService) {
|
|
1255
|
-
* super(database, (prisma) => prisma.user);
|
|
1256
|
-
* }
|
|
1285
|
+
* import { users } from '@/db/schema';
|
|
1257
1286
|
*
|
|
1258
|
-
* // Short syntax
|
|
1259
1287
|
* constructor(database: PrimaryDatabaseService) {
|
|
1260
|
-
* super(database,
|
|
1261
|
-
* }
|
|
1262
|
-
*
|
|
1263
|
-
* // Complex model names
|
|
1264
|
-
* constructor(database: PrimaryDatabaseService) {
|
|
1265
|
-
* super(database, (p) => p.emailVerification);
|
|
1288
|
+
* super(database, users);
|
|
1266
1289
|
* }
|
|
1267
1290
|
* ```
|
|
1268
1291
|
*/
|
|
1269
|
-
constructor(database,
|
|
1292
|
+
constructor(database, table) {
|
|
1270
1293
|
this.database = database;
|
|
1294
|
+
this.table = table;
|
|
1295
|
+
this.tableName = getTableName(table);
|
|
1271
1296
|
this.logger = new Logger6(this.constructor.name);
|
|
1272
|
-
this.modelGetter = getModel;
|
|
1273
1297
|
this.logger.debug(`Initialized ${this.constructor.name}`);
|
|
1298
|
+
this.logger.debug(`Table name from getTableName: '${this.tableName}'`);
|
|
1274
1299
|
}
|
|
1275
1300
|
/**
|
|
1276
1301
|
* Create a new record
|
|
@@ -1282,21 +1307,20 @@ var PrimaryBaseRepository = class {
|
|
|
1282
1307
|
* ```typescript
|
|
1283
1308
|
* const user = await userRepository.create({
|
|
1284
1309
|
* email: 'user@example.com',
|
|
1285
|
-
*
|
|
1310
|
+
* firstName: 'John'
|
|
1286
1311
|
* });
|
|
1287
1312
|
* ```
|
|
1288
1313
|
*/
|
|
1289
1314
|
async create(data) {
|
|
1290
1315
|
this.logger.log("Creating record");
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
});
|
|
1316
|
+
const results = await this.db.insert(this.table).values(data).returning();
|
|
1317
|
+
return results[0];
|
|
1294
1318
|
}
|
|
1295
1319
|
/**
|
|
1296
1320
|
* Find a single record by ID
|
|
1297
1321
|
*
|
|
1298
1322
|
* @param id - The record ID
|
|
1299
|
-
* @returns Promise resolving to the record or
|
|
1323
|
+
* @returns Promise resolving to the record or undefined if not found
|
|
1300
1324
|
*
|
|
1301
1325
|
* @example
|
|
1302
1326
|
* ```typescript
|
|
@@ -1305,40 +1329,43 @@ var PrimaryBaseRepository = class {
|
|
|
1305
1329
|
*/
|
|
1306
1330
|
async findById(id) {
|
|
1307
1331
|
this.logger.debug(`Finding record by ID: ${id}`);
|
|
1308
|
-
return
|
|
1332
|
+
return this.model.findFirst({
|
|
1309
1333
|
where: {
|
|
1310
1334
|
id
|
|
1311
1335
|
}
|
|
1312
1336
|
});
|
|
1313
1337
|
}
|
|
1314
1338
|
/**
|
|
1315
|
-
* Find a single record with custom where clause
|
|
1339
|
+
* Find a single record with custom where clause (Drizzle v2 object-based syntax)
|
|
1316
1340
|
*
|
|
1317
|
-
* @param where -
|
|
1318
|
-
* @returns Promise resolving to the record or
|
|
1341
|
+
* @param where - Object-based filter condition
|
|
1342
|
+
* @returns Promise resolving to the record or undefined if not found
|
|
1319
1343
|
*
|
|
1320
1344
|
* @example
|
|
1321
1345
|
* ```typescript
|
|
1322
|
-
* // Simple
|
|
1346
|
+
* // Simple equality
|
|
1323
1347
|
* const user = await userRepository.findOne({ email: 'user@example.com' });
|
|
1324
1348
|
*
|
|
1325
|
-
* // With
|
|
1349
|
+
* // With operators
|
|
1350
|
+
* const user = await userRepository.findOne({ age: { gte: 18 } });
|
|
1351
|
+
*
|
|
1352
|
+
* // Multiple conditions (AND)
|
|
1326
1353
|
* const user = await userRepository.findOne({
|
|
1327
|
-
*
|
|
1328
|
-
*
|
|
1354
|
+
* email: 'user@example.com',
|
|
1355
|
+
* status: 'ACTIVE'
|
|
1329
1356
|
* });
|
|
1330
1357
|
* ```
|
|
1331
1358
|
*/
|
|
1332
1359
|
async findOne(where) {
|
|
1333
1360
|
this.logger.debug("Finding record with custom query");
|
|
1334
|
-
return
|
|
1361
|
+
return this.model.findFirst({
|
|
1335
1362
|
where
|
|
1336
1363
|
});
|
|
1337
1364
|
}
|
|
1338
1365
|
/**
|
|
1339
|
-
* Find multiple records
|
|
1366
|
+
* Find multiple records (Drizzle v2 object-based syntax)
|
|
1340
1367
|
*
|
|
1341
|
-
* @param
|
|
1368
|
+
* @param options - Query options (where, orderBy, limit, offset)
|
|
1342
1369
|
* @returns Promise resolving to an array of records
|
|
1343
1370
|
*
|
|
1344
1371
|
* @example
|
|
@@ -1346,18 +1373,28 @@ var PrimaryBaseRepository = class {
|
|
|
1346
1373
|
* // Find all users
|
|
1347
1374
|
* const users = await userRepository.findMany();
|
|
1348
1375
|
*
|
|
1349
|
-
* // Find with filtering and pagination
|
|
1376
|
+
* // Find with filtering and pagination (v2 object syntax)
|
|
1350
1377
|
* const users = await userRepository.findMany({
|
|
1351
|
-
* where: {
|
|
1378
|
+
* where: { accountStatus: 'ACTIVE' },
|
|
1352
1379
|
* orderBy: { createdAt: 'desc' },
|
|
1353
|
-
*
|
|
1354
|
-
*
|
|
1380
|
+
* limit: 10,
|
|
1381
|
+
* offset: 0
|
|
1382
|
+
* });
|
|
1383
|
+
*
|
|
1384
|
+
* // Multiple conditions
|
|
1385
|
+
* const users = await userRepository.findMany({
|
|
1386
|
+
* where: {
|
|
1387
|
+
* AND: [
|
|
1388
|
+
* { status: 'ACTIVE' },
|
|
1389
|
+
* { age: { gte: 18 } }
|
|
1390
|
+
* ]
|
|
1391
|
+
* }
|
|
1355
1392
|
* });
|
|
1356
1393
|
* ```
|
|
1357
1394
|
*/
|
|
1358
|
-
async findMany(
|
|
1395
|
+
async findMany(options) {
|
|
1359
1396
|
this.logger.debug("Finding multiple records");
|
|
1360
|
-
return
|
|
1397
|
+
return this.model.findMany(options);
|
|
1361
1398
|
}
|
|
1362
1399
|
/**
|
|
1363
1400
|
* Update a record by ID
|
|
@@ -1369,41 +1406,40 @@ var PrimaryBaseRepository = class {
|
|
|
1369
1406
|
* @example
|
|
1370
1407
|
* ```typescript
|
|
1371
1408
|
* const user = await userRepository.update('user-id-123', {
|
|
1372
|
-
*
|
|
1409
|
+
* firstName: 'Jane'
|
|
1373
1410
|
* });
|
|
1374
1411
|
* ```
|
|
1375
1412
|
*/
|
|
1376
1413
|
async update(id, data) {
|
|
1377
1414
|
this.logger.log(`Updating record with ID: ${id}`);
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
},
|
|
1382
|
-
data
|
|
1383
|
-
});
|
|
1415
|
+
const idColumn = this.table.id;
|
|
1416
|
+
const results = await this.db.update(this.table).set(data).where(eq2(idColumn, id)).returning();
|
|
1417
|
+
return results[0];
|
|
1384
1418
|
}
|
|
1385
1419
|
/**
|
|
1386
1420
|
* Update multiple records
|
|
1387
1421
|
*
|
|
1388
|
-
* @param where -
|
|
1422
|
+
* @param where - SQL condition to match records
|
|
1389
1423
|
* @param data - The data to update
|
|
1390
1424
|
* @returns Promise resolving to the count of updated records
|
|
1391
1425
|
*
|
|
1392
1426
|
* @example
|
|
1393
1427
|
* ```typescript
|
|
1428
|
+
* import { eq } from 'drizzle-orm';
|
|
1429
|
+
*
|
|
1394
1430
|
* const result = await userRepository.updateMany(
|
|
1395
|
-
*
|
|
1396
|
-
* {
|
|
1431
|
+
* eq(users.accountStatus, 'PENDING'),
|
|
1432
|
+
* { accountStatus: 'ACTIVE' }
|
|
1397
1433
|
* );
|
|
1398
1434
|
* console.log(`Updated ${result.count} users`);
|
|
1399
1435
|
* ```
|
|
1400
1436
|
*/
|
|
1401
1437
|
async updateMany(where, data) {
|
|
1402
1438
|
this.logger.log("Updating multiple records");
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
}
|
|
1439
|
+
const result = await this.db.update(this.table).set(data).where(where);
|
|
1440
|
+
return {
|
|
1441
|
+
count: result.rowCount ?? 0
|
|
1442
|
+
};
|
|
1407
1443
|
}
|
|
1408
1444
|
/**
|
|
1409
1445
|
* Delete a record by ID
|
|
@@ -1418,127 +1454,143 @@ var PrimaryBaseRepository = class {
|
|
|
1418
1454
|
*/
|
|
1419
1455
|
async delete(id) {
|
|
1420
1456
|
this.logger.log(`Deleting record with ID: ${id}`);
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
}
|
|
1425
|
-
});
|
|
1457
|
+
const idColumn = this.table.id;
|
|
1458
|
+
const results = await this.db.delete(this.table).where(eq2(idColumn, id)).returning();
|
|
1459
|
+
return results[0];
|
|
1426
1460
|
}
|
|
1427
1461
|
/**
|
|
1428
1462
|
* Delete multiple records
|
|
1429
1463
|
*
|
|
1430
|
-
* @param where -
|
|
1464
|
+
* @param where - SQL condition to match records
|
|
1431
1465
|
* @returns Promise resolving to the count of deleted records
|
|
1432
1466
|
*
|
|
1433
1467
|
* @example
|
|
1434
1468
|
* ```typescript
|
|
1435
|
-
*
|
|
1436
|
-
*
|
|
1437
|
-
*
|
|
1438
|
-
*
|
|
1469
|
+
* import { lt } from 'drizzle-orm';
|
|
1470
|
+
*
|
|
1471
|
+
* const result = await userRepository.deleteMany(
|
|
1472
|
+
* lt(users.createdAt, new Date('2020-01-01'))
|
|
1473
|
+
* );
|
|
1439
1474
|
* console.log(`Deleted ${result.count} users`);
|
|
1440
1475
|
* ```
|
|
1441
1476
|
*/
|
|
1442
1477
|
async deleteMany(where) {
|
|
1443
1478
|
this.logger.log("Deleting multiple records");
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1479
|
+
const result = await this.db.delete(this.table).where(where);
|
|
1480
|
+
return {
|
|
1481
|
+
count: result.rowCount ?? 0
|
|
1482
|
+
};
|
|
1447
1483
|
}
|
|
1448
1484
|
/**
|
|
1449
1485
|
* Count records
|
|
1450
1486
|
*
|
|
1451
|
-
* @param where - Optional
|
|
1487
|
+
* @param where - Optional SQL condition to filter records
|
|
1452
1488
|
* @returns Promise resolving to the count of records
|
|
1453
1489
|
*
|
|
1454
1490
|
* @example
|
|
1455
1491
|
* ```typescript
|
|
1492
|
+
* import { eq } from 'drizzle-orm';
|
|
1493
|
+
*
|
|
1456
1494
|
* // Count all users
|
|
1457
1495
|
* const total = await userRepository.count();
|
|
1458
1496
|
*
|
|
1459
1497
|
* // Count active users
|
|
1460
|
-
* const activeCount = await userRepository.count(
|
|
1498
|
+
* const activeCount = await userRepository.count(
|
|
1499
|
+
* eq(users.accountStatus, 'ACTIVE')
|
|
1500
|
+
* );
|
|
1461
1501
|
* ```
|
|
1462
1502
|
*/
|
|
1463
1503
|
async count(where) {
|
|
1464
1504
|
this.logger.debug("Counting records");
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
});
|
|
1505
|
+
let query = this.db.select({
|
|
1506
|
+
count: sql`count(*)::int`
|
|
1507
|
+
}).from(this.table).$dynamic();
|
|
1508
|
+
if (where) {
|
|
1509
|
+
query = query.where(where);
|
|
1510
|
+
}
|
|
1511
|
+
const results = await query;
|
|
1512
|
+
return results[0].count;
|
|
1468
1513
|
}
|
|
1469
1514
|
/**
|
|
1470
1515
|
* Check if a record exists
|
|
1471
1516
|
*
|
|
1472
|
-
* @param where -
|
|
1517
|
+
* @param where - SQL condition to match records
|
|
1473
1518
|
* @returns Promise resolving to true if at least one record exists, false otherwise
|
|
1474
1519
|
*
|
|
1475
1520
|
* @example
|
|
1476
1521
|
* ```typescript
|
|
1477
|
-
*
|
|
1478
|
-
*
|
|
1479
|
-
*
|
|
1522
|
+
* import { eq } from 'drizzle-orm';
|
|
1523
|
+
*
|
|
1524
|
+
* const emailExists = await userRepository.exists(
|
|
1525
|
+
* eq(users.email, 'user@example.com')
|
|
1526
|
+
* );
|
|
1480
1527
|
* ```
|
|
1481
1528
|
*/
|
|
1482
1529
|
async exists(where) {
|
|
1483
|
-
const count = await this.
|
|
1484
|
-
where
|
|
1485
|
-
});
|
|
1530
|
+
const count = await this.count(where);
|
|
1486
1531
|
return count > 0;
|
|
1487
1532
|
}
|
|
1488
1533
|
};
|
|
1489
1534
|
|
|
1490
1535
|
// src/database/repositories/tenant-base.repository.ts
|
|
1491
1536
|
import { Logger as Logger7 } from "@nestjs/common";
|
|
1537
|
+
import { eq as eq3, sql as sql2, getTableName as getTableName2 } from "drizzle-orm";
|
|
1492
1538
|
var TenantBaseRepository = class {
|
|
1493
1539
|
static {
|
|
1494
1540
|
__name(this, "TenantBaseRepository");
|
|
1495
1541
|
}
|
|
1496
1542
|
database;
|
|
1543
|
+
table;
|
|
1497
1544
|
logger;
|
|
1498
|
-
modelGetter;
|
|
1499
1545
|
/**
|
|
1500
|
-
*
|
|
1546
|
+
* The table name extracted from the Drizzle table at runtime.
|
|
1547
|
+
* Used to access the query API for this repository's table.
|
|
1548
|
+
*/
|
|
1549
|
+
tableName;
|
|
1550
|
+
/**
|
|
1551
|
+
* Lazy getter for Drizzle client.
|
|
1501
1552
|
* Accesses the client from the database service only when needed,
|
|
1502
1553
|
* avoiding initialization timing issues with NestJS lifecycle.
|
|
1503
1554
|
*/
|
|
1504
|
-
get
|
|
1505
|
-
return this.database.
|
|
1555
|
+
get db() {
|
|
1556
|
+
return this.database.drizzleClient;
|
|
1506
1557
|
}
|
|
1507
1558
|
/**
|
|
1508
|
-
*
|
|
1509
|
-
*
|
|
1559
|
+
* Model query API for THIS repository's table (Prisma-like syntax)
|
|
1560
|
+
* Scoped to only the table this repository manages
|
|
1561
|
+
*
|
|
1562
|
+
* @example
|
|
1563
|
+
* ```typescript
|
|
1564
|
+
* // Use relational queries with type safety
|
|
1565
|
+
* const product = await this.model.findFirst({
|
|
1566
|
+
* where: eq(products.id, id),
|
|
1567
|
+
* with: { category: true, variants: true }
|
|
1568
|
+
* });
|
|
1569
|
+
* ```
|
|
1510
1570
|
*/
|
|
1511
1571
|
get model() {
|
|
1512
|
-
return this.
|
|
1572
|
+
return this.database.drizzleClient.query[this.tableName];
|
|
1513
1573
|
}
|
|
1514
1574
|
/**
|
|
1515
1575
|
* Create a new repository instance
|
|
1516
1576
|
*
|
|
1517
1577
|
* @param database - The tenant database service
|
|
1518
|
-
* @param
|
|
1578
|
+
* @param table - The Drizzle table schema object
|
|
1519
1579
|
*
|
|
1520
1580
|
* @example
|
|
1521
1581
|
* ```typescript
|
|
1522
|
-
*
|
|
1523
|
-
* constructor(database: TenantDatabaseService) {
|
|
1524
|
-
* super(database, (prisma) => prisma.product);
|
|
1525
|
-
* }
|
|
1582
|
+
* import { products } from '@/db/schema';
|
|
1526
1583
|
*
|
|
1527
|
-
* // Short syntax
|
|
1528
1584
|
* constructor(database: TenantDatabaseService) {
|
|
1529
|
-
* super(database,
|
|
1530
|
-
* }
|
|
1531
|
-
*
|
|
1532
|
-
* // Complex model names
|
|
1533
|
-
* constructor(database: TenantDatabaseService) {
|
|
1534
|
-
* super(database, (p) => p.inventoryItem);
|
|
1585
|
+
* super(database, products);
|
|
1535
1586
|
* }
|
|
1536
1587
|
* ```
|
|
1537
1588
|
*/
|
|
1538
|
-
constructor(database,
|
|
1589
|
+
constructor(database, table) {
|
|
1539
1590
|
this.database = database;
|
|
1591
|
+
this.table = table;
|
|
1592
|
+
this.tableName = getTableName2(table);
|
|
1540
1593
|
this.logger = new Logger7(this.constructor.name);
|
|
1541
|
-
this.modelGetter = getModel;
|
|
1542
1594
|
this.logger.debug(`Initialized ${this.constructor.name}`);
|
|
1543
1595
|
}
|
|
1544
1596
|
/**
|
|
@@ -1558,9 +1610,8 @@ var TenantBaseRepository = class {
|
|
|
1558
1610
|
*/
|
|
1559
1611
|
async create(data) {
|
|
1560
1612
|
this.logger.log("Creating record");
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
});
|
|
1613
|
+
const results = await this.db.insert(this.table).values(data).returning();
|
|
1614
|
+
return results[0];
|
|
1564
1615
|
}
|
|
1565
1616
|
/**
|
|
1566
1617
|
* Find a single record by ID
|
|
@@ -1575,59 +1626,65 @@ var TenantBaseRepository = class {
|
|
|
1575
1626
|
*/
|
|
1576
1627
|
async findById(id) {
|
|
1577
1628
|
this.logger.debug(`Finding record by ID: ${id}`);
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
}
|
|
1582
|
-
});
|
|
1629
|
+
const idColumn = this.table.id;
|
|
1630
|
+
const results = await this.db.select().from(this.table).where(eq3(idColumn, id)).limit(1);
|
|
1631
|
+
return results[0] ?? null;
|
|
1583
1632
|
}
|
|
1584
1633
|
/**
|
|
1585
1634
|
* Find a single record with custom where clause
|
|
1586
1635
|
*
|
|
1587
|
-
* @param where -
|
|
1636
|
+
* @param where - SQL condition
|
|
1588
1637
|
* @returns Promise resolving to the record or null if not found
|
|
1589
1638
|
*
|
|
1590
1639
|
* @example
|
|
1591
1640
|
* ```typescript
|
|
1592
|
-
*
|
|
1593
|
-
* const product = await productRepository.findOne(
|
|
1594
|
-
*
|
|
1595
|
-
* // With include
|
|
1596
|
-
* const product = await productRepository.findOne({
|
|
1597
|
-
* where: { sku: 'WDG-001' },
|
|
1598
|
-
* include: { category: true }
|
|
1599
|
-
* });
|
|
1641
|
+
* import { eq } from 'drizzle-orm';
|
|
1642
|
+
* const product = await productRepository.findOne(eq(products.sku, 'WDG-001'));
|
|
1600
1643
|
* ```
|
|
1601
1644
|
*/
|
|
1602
1645
|
async findOne(where) {
|
|
1603
1646
|
this.logger.debug("Finding record with custom query");
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
});
|
|
1647
|
+
const results = await this.db.select().from(this.table).where(where).limit(1);
|
|
1648
|
+
return results[0] ?? null;
|
|
1607
1649
|
}
|
|
1608
1650
|
/**
|
|
1609
1651
|
* Find multiple records
|
|
1610
1652
|
*
|
|
1611
|
-
* @param
|
|
1653
|
+
* @param options - Query options (where, orderBy, limit, offset)
|
|
1612
1654
|
* @returns Promise resolving to an array of records
|
|
1613
1655
|
*
|
|
1614
1656
|
* @example
|
|
1615
1657
|
* ```typescript
|
|
1658
|
+
* import { eq, desc } from 'drizzle-orm';
|
|
1659
|
+
*
|
|
1616
1660
|
* // Find all products
|
|
1617
1661
|
* const products = await productRepository.findMany();
|
|
1618
1662
|
*
|
|
1619
1663
|
* // Find with filtering and pagination
|
|
1620
1664
|
* const products = await productRepository.findMany({
|
|
1621
|
-
* where:
|
|
1622
|
-
* orderBy:
|
|
1623
|
-
*
|
|
1624
|
-
*
|
|
1665
|
+
* where: eq(products.status, 'ACTIVE'),
|
|
1666
|
+
* orderBy: desc(products.createdAt),
|
|
1667
|
+
* limit: 10,
|
|
1668
|
+
* offset: 0
|
|
1625
1669
|
* });
|
|
1626
1670
|
* ```
|
|
1627
1671
|
*/
|
|
1628
|
-
async findMany(
|
|
1672
|
+
async findMany(options) {
|
|
1629
1673
|
this.logger.debug("Finding multiple records");
|
|
1630
|
-
|
|
1674
|
+
let query = this.db.select().from(this.table).$dynamic();
|
|
1675
|
+
if (options?.where) {
|
|
1676
|
+
query = query.where(options.where);
|
|
1677
|
+
}
|
|
1678
|
+
if (options?.orderBy) {
|
|
1679
|
+
query = query.orderBy(options.orderBy);
|
|
1680
|
+
}
|
|
1681
|
+
if (options?.limit) {
|
|
1682
|
+
query = query.limit(options.limit);
|
|
1683
|
+
}
|
|
1684
|
+
if (options?.offset) {
|
|
1685
|
+
query = query.offset(options.offset);
|
|
1686
|
+
}
|
|
1687
|
+
return await query;
|
|
1631
1688
|
}
|
|
1632
1689
|
/**
|
|
1633
1690
|
* Update a record by ID
|
|
@@ -1645,24 +1702,23 @@ var TenantBaseRepository = class {
|
|
|
1645
1702
|
*/
|
|
1646
1703
|
async update(id, data) {
|
|
1647
1704
|
this.logger.log(`Updating record with ID: ${id}`);
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
},
|
|
1652
|
-
data
|
|
1653
|
-
});
|
|
1705
|
+
const idColumn = this.table.id;
|
|
1706
|
+
const results = await this.db.update(this.table).set(data).where(eq3(idColumn, id)).returning();
|
|
1707
|
+
return results[0];
|
|
1654
1708
|
}
|
|
1655
1709
|
/**
|
|
1656
1710
|
* Update multiple records
|
|
1657
1711
|
*
|
|
1658
|
-
* @param where -
|
|
1712
|
+
* @param where - SQL condition to match records
|
|
1659
1713
|
* @param data - The data to update
|
|
1660
1714
|
* @returns Promise resolving to the count of updated records
|
|
1661
1715
|
*
|
|
1662
1716
|
* @example
|
|
1663
1717
|
* ```typescript
|
|
1718
|
+
* import { eq } from 'drizzle-orm';
|
|
1719
|
+
*
|
|
1664
1720
|
* const result = await productRepository.updateMany(
|
|
1665
|
-
*
|
|
1721
|
+
* eq(products.status, 'PENDING'),
|
|
1666
1722
|
* { status: 'ACTIVE' }
|
|
1667
1723
|
* );
|
|
1668
1724
|
* console.log(`Updated ${result.count} products`);
|
|
@@ -1670,10 +1726,10 @@ var TenantBaseRepository = class {
|
|
|
1670
1726
|
*/
|
|
1671
1727
|
async updateMany(where, data) {
|
|
1672
1728
|
this.logger.log("Updating multiple records");
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
}
|
|
1729
|
+
const result = await this.db.update(this.table).set(data).where(where);
|
|
1730
|
+
return {
|
|
1731
|
+
count: result.rowCount ?? 0
|
|
1732
|
+
};
|
|
1677
1733
|
}
|
|
1678
1734
|
/**
|
|
1679
1735
|
* Delete a record by ID
|
|
@@ -1688,71 +1744,80 @@ var TenantBaseRepository = class {
|
|
|
1688
1744
|
*/
|
|
1689
1745
|
async delete(id) {
|
|
1690
1746
|
this.logger.log(`Deleting record with ID: ${id}`);
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
}
|
|
1695
|
-
});
|
|
1747
|
+
const idColumn = this.table.id;
|
|
1748
|
+
const results = await this.db.delete(this.table).where(eq3(idColumn, id)).returning();
|
|
1749
|
+
return results[0];
|
|
1696
1750
|
}
|
|
1697
1751
|
/**
|
|
1698
1752
|
* Delete multiple records
|
|
1699
1753
|
*
|
|
1700
|
-
* @param where -
|
|
1754
|
+
* @param where - SQL condition to match records
|
|
1701
1755
|
* @returns Promise resolving to the count of deleted records
|
|
1702
1756
|
*
|
|
1703
1757
|
* @example
|
|
1704
1758
|
* ```typescript
|
|
1705
|
-
*
|
|
1706
|
-
*
|
|
1707
|
-
*
|
|
1708
|
-
*
|
|
1759
|
+
* import { lt } from 'drizzle-orm';
|
|
1760
|
+
*
|
|
1761
|
+
* const result = await productRepository.deleteMany(
|
|
1762
|
+
* lt(products.createdAt, new Date('2020-01-01'))
|
|
1763
|
+
* );
|
|
1709
1764
|
* console.log(`Deleted ${result.count} products`);
|
|
1710
1765
|
* ```
|
|
1711
1766
|
*/
|
|
1712
1767
|
async deleteMany(where) {
|
|
1713
1768
|
this.logger.log("Deleting multiple records");
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1769
|
+
const result = await this.db.delete(this.table).where(where);
|
|
1770
|
+
return {
|
|
1771
|
+
count: result.rowCount ?? 0
|
|
1772
|
+
};
|
|
1717
1773
|
}
|
|
1718
1774
|
/**
|
|
1719
1775
|
* Count records
|
|
1720
1776
|
*
|
|
1721
|
-
* @param where - Optional
|
|
1777
|
+
* @param where - Optional SQL condition to filter records
|
|
1722
1778
|
* @returns Promise resolving to the count of records
|
|
1723
1779
|
*
|
|
1724
1780
|
* @example
|
|
1725
1781
|
* ```typescript
|
|
1782
|
+
* import { eq } from 'drizzle-orm';
|
|
1783
|
+
*
|
|
1726
1784
|
* // Count all products
|
|
1727
1785
|
* const total = await productRepository.count();
|
|
1728
1786
|
*
|
|
1729
1787
|
* // Count active products
|
|
1730
|
-
* const activeCount = await productRepository.count(
|
|
1788
|
+
* const activeCount = await productRepository.count(
|
|
1789
|
+
* eq(products.status, 'ACTIVE')
|
|
1790
|
+
* );
|
|
1731
1791
|
* ```
|
|
1732
1792
|
*/
|
|
1733
1793
|
async count(where) {
|
|
1734
1794
|
this.logger.debug("Counting records");
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
});
|
|
1795
|
+
let query = this.db.select({
|
|
1796
|
+
count: sql2`count(*)::int`
|
|
1797
|
+
}).from(this.table).$dynamic();
|
|
1798
|
+
if (where) {
|
|
1799
|
+
query = query.where(where);
|
|
1800
|
+
}
|
|
1801
|
+
const results = await query;
|
|
1802
|
+
return results[0].count;
|
|
1738
1803
|
}
|
|
1739
1804
|
/**
|
|
1740
1805
|
* Check if a record exists
|
|
1741
1806
|
*
|
|
1742
|
-
* @param where -
|
|
1807
|
+
* @param where - SQL condition to match records
|
|
1743
1808
|
* @returns Promise resolving to true if at least one record exists, false otherwise
|
|
1744
1809
|
*
|
|
1745
1810
|
* @example
|
|
1746
1811
|
* ```typescript
|
|
1747
|
-
*
|
|
1748
|
-
*
|
|
1749
|
-
*
|
|
1812
|
+
* import { eq } from 'drizzle-orm';
|
|
1813
|
+
*
|
|
1814
|
+
* const skuExists = await productRepository.exists(
|
|
1815
|
+
* eq(products.sku, 'WDG-001')
|
|
1816
|
+
* );
|
|
1750
1817
|
* ```
|
|
1751
1818
|
*/
|
|
1752
1819
|
async exists(where) {
|
|
1753
|
-
const count = await this.
|
|
1754
|
-
where
|
|
1755
|
-
});
|
|
1820
|
+
const count = await this.count(where);
|
|
1756
1821
|
return count > 0;
|
|
1757
1822
|
}
|
|
1758
1823
|
};
|
|
@@ -1909,15 +1974,16 @@ var HttpExceptionFilter = class _HttpExceptionFilter {
|
|
|
1909
1974
|
const exceptionResponse = exception.getResponse();
|
|
1910
1975
|
if (typeof exceptionResponse === "object" && exceptionResponse !== null) {
|
|
1911
1976
|
const responseObj = exceptionResponse;
|
|
1912
|
-
if (
|
|
1977
|
+
if ("errors" in responseObj && Array.isArray(responseObj.errors)) {
|
|
1913
1978
|
errors = responseObj.errors;
|
|
1914
|
-
detail = responseObj.detail || exception.message;
|
|
1915
|
-
} else if (
|
|
1979
|
+
detail = ("detail" in responseObj ? responseObj.detail : void 0) || exception.message;
|
|
1980
|
+
} else if ("message" in responseObj && Array.isArray(responseObj.message)) {
|
|
1916
1981
|
errors = responseObj.message.map((msg) => {
|
|
1917
|
-
if (typeof msg === "object" &&
|
|
1982
|
+
if (typeof msg === "object" && "property" in msg && "constraints" in msg) {
|
|
1983
|
+
const constraintValues = Object.values(msg.constraints);
|
|
1918
1984
|
return {
|
|
1919
1985
|
field: msg.property,
|
|
1920
|
-
message:
|
|
1986
|
+
message: constraintValues[0] ?? "Validation failed"
|
|
1921
1987
|
};
|
|
1922
1988
|
}
|
|
1923
1989
|
return {
|
|
@@ -1925,13 +1991,14 @@ var HttpExceptionFilter = class _HttpExceptionFilter {
|
|
|
1925
1991
|
};
|
|
1926
1992
|
});
|
|
1927
1993
|
detail = "Validation failed";
|
|
1928
|
-
} else if (responseObj
|
|
1994
|
+
} else if ("message" in responseObj) {
|
|
1995
|
+
const message = responseObj.message;
|
|
1929
1996
|
errors = [
|
|
1930
1997
|
{
|
|
1931
|
-
message: Array.isArray(
|
|
1998
|
+
message: Array.isArray(message) ? message.join(", ") : message
|
|
1932
1999
|
}
|
|
1933
2000
|
];
|
|
1934
|
-
detail = responseObj.error || exception.message;
|
|
2001
|
+
detail = ("error" in responseObj ? responseObj.error : void 0) || exception.message;
|
|
1935
2002
|
}
|
|
1936
2003
|
} else if (typeof exceptionResponse === "string") {
|
|
1937
2004
|
errors = [
|
|
@@ -2979,14 +3046,15 @@ function getLevelsUpTo(level) {
|
|
|
2979
3046
|
"debug",
|
|
2980
3047
|
"verbose"
|
|
2981
3048
|
];
|
|
2982
|
-
const
|
|
2983
|
-
if (
|
|
3049
|
+
const isValidLevel = /* @__PURE__ */ __name((l) => allLevels.includes(l), "isValidLevel");
|
|
3050
|
+
if (!isValidLevel(level)) {
|
|
2984
3051
|
return [
|
|
2985
3052
|
"error",
|
|
2986
3053
|
"warn",
|
|
2987
3054
|
"log"
|
|
2988
3055
|
];
|
|
2989
3056
|
}
|
|
3057
|
+
const levelIndex = allLevels.indexOf(level);
|
|
2990
3058
|
return allLevels.slice(0, levelIndex + 1);
|
|
2991
3059
|
}
|
|
2992
3060
|
__name(getLevelsUpTo, "getLevelsUpTo");
|