@vritti/api-sdk 0.0.8 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +267 -0
- package/dist/drizzle-orm.cjs +25 -0
- package/dist/drizzle-orm.cjs.map +1 -0
- package/dist/drizzle-orm.d.cts +2 -0
- package/dist/drizzle-orm.d.ts +2 -0
- package/dist/drizzle-orm.js +3 -0
- package/dist/drizzle-orm.js.map +1 -0
- package/dist/drizzle-pg-core.cjs +25 -0
- package/dist/drizzle-pg-core.cjs.map +1 -0
- package/dist/drizzle-pg-core.d.cts +1 -0
- package/dist/drizzle-pg-core.d.ts +1 -0
- package/dist/drizzle-pg-core.js +3 -0
- package/dist/drizzle-pg-core.js.map +1 -0
- package/dist/index.cjs +1222 -280
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +723 -217
- package/dist/index.d.ts +723 -217
- package/dist/index.js +1209 -279
- package/dist/index.js.map +1 -1
- package/package.json +33 -6
package/dist/index.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,24 @@ 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.db = drizzle({
|
|
205
|
+
client: this.pool,
|
|
206
|
+
schema: this.options.drizzleSchema
|
|
207
|
+
});
|
|
208
|
+
await this.pool.query("SELECT 1");
|
|
208
209
|
this.logger.log("Connected to primary database (tenant registry)");
|
|
209
210
|
} catch (error) {
|
|
210
211
|
this.logger.error("Failed to connect to primary database", error);
|
|
@@ -219,7 +220,7 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
219
220
|
throw new Error("Primary database configuration not provided");
|
|
220
221
|
}
|
|
221
222
|
const { host, port = 5432, username, password, database, schema = "public", sslMode = "require" } = this.options.primaryDb;
|
|
222
|
-
let url = `postgresql://${username}:${password}@${host}:${port}/${database}`;
|
|
223
|
+
let url = `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;
|
|
223
224
|
const params = new URLSearchParams();
|
|
224
225
|
if (schema) {
|
|
225
226
|
params.set("schema", schema);
|
|
@@ -239,9 +240,9 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
239
240
|
return url.replace(/:([^@]+)@/, ":****@");
|
|
240
241
|
}
|
|
241
242
|
/**
|
|
242
|
-
* Get tenant configuration by identifier (ID or
|
|
243
|
+
* Get tenant configuration by identifier (ID or subdomain)
|
|
243
244
|
*
|
|
244
|
-
* @param tenantIdentifier Tenant ID or
|
|
245
|
+
* @param tenantIdentifier Tenant ID or subdomain
|
|
245
246
|
* @returns Tenant configuration or null if not found
|
|
246
247
|
*/
|
|
247
248
|
async getTenantInfo(tenantIdentifier) {
|
|
@@ -251,45 +252,39 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
251
252
|
return cached;
|
|
252
253
|
}
|
|
253
254
|
try {
|
|
254
|
-
if (!this.
|
|
255
|
+
if (!this.db) {
|
|
255
256
|
throw new Error("Primary database client not initialized");
|
|
256
257
|
}
|
|
257
258
|
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) {
|
|
259
|
+
const schema = this.options.drizzleSchema;
|
|
260
|
+
const { tenants, tenantDatabaseConfigs } = schema;
|
|
261
|
+
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);
|
|
262
|
+
if (!result.length) {
|
|
275
263
|
this.logger.warn(`Tenant not found: ${tenantIdentifier}`);
|
|
276
264
|
return null;
|
|
277
265
|
}
|
|
266
|
+
const row = result[0];
|
|
267
|
+
const tenant = row.tenants;
|
|
268
|
+
const config = row.tenant_database_configs;
|
|
269
|
+
if (tenant.status !== "ACTIVE") {
|
|
270
|
+
this.logger.warn(`Tenant not active: ${tenantIdentifier}`);
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
278
273
|
const info = {
|
|
279
274
|
id: tenant.id,
|
|
280
275
|
subdomain: tenant.subdomain,
|
|
281
276
|
type: tenant.dbType,
|
|
282
277
|
status: tenant.status,
|
|
283
278
|
// For SHARED tenants: schema name
|
|
284
|
-
schemaName:
|
|
279
|
+
schemaName: config?.dbSchema || void 0,
|
|
285
280
|
// For DEDICATED tenants: database configuration from TenantDatabaseConfig table
|
|
286
|
-
databaseName:
|
|
287
|
-
databaseHost:
|
|
288
|
-
databasePort:
|
|
289
|
-
databaseUsername:
|
|
290
|
-
databasePassword:
|
|
291
|
-
databaseSslMode:
|
|
292
|
-
connectionPoolSize:
|
|
281
|
+
databaseName: config?.dbName || void 0,
|
|
282
|
+
databaseHost: config?.dbHost || void 0,
|
|
283
|
+
databasePort: config?.dbPort || void 0,
|
|
284
|
+
databaseUsername: config?.dbUsername ? this.decrypt(config.dbUsername) : void 0,
|
|
285
|
+
databasePassword: config?.dbPassword ? this.decrypt(config.dbPassword) : void 0,
|
|
286
|
+
databaseSslMode: config?.dbSslMode || void 0,
|
|
287
|
+
connectionPoolSize: config?.connectionPoolSize || void 0
|
|
293
288
|
};
|
|
294
289
|
this.cacheInfo(info);
|
|
295
290
|
return info;
|
|
@@ -315,7 +310,7 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
315
310
|
*
|
|
316
311
|
* Useful when tenant settings are updated and cache needs to be invalidated
|
|
317
312
|
*
|
|
318
|
-
* @param tenantIdentifier Tenant ID or
|
|
313
|
+
* @param tenantIdentifier Tenant ID or subdomain
|
|
319
314
|
*/
|
|
320
315
|
clearTenantCache(tenantIdentifier) {
|
|
321
316
|
const config = this.tenantConfigCache.get(tenantIdentifier);
|
|
@@ -334,17 +329,23 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
334
329
|
this.logger.log(`Cleared ${size} cached tenant configs`);
|
|
335
330
|
}
|
|
336
331
|
/**
|
|
337
|
-
* Get the
|
|
338
|
-
* This is a synchronous property that returns the initialized
|
|
332
|
+
* Get the Drizzle database instance for the primary database.
|
|
333
|
+
* This is a synchronous property that returns the initialized Drizzle client.
|
|
339
334
|
*
|
|
340
|
-
* @returns Primary database
|
|
335
|
+
* @returns Primary database Drizzle instance
|
|
341
336
|
* @throws Error if primary database client is not initialized
|
|
342
337
|
*/
|
|
343
|
-
get
|
|
344
|
-
if (!this.
|
|
338
|
+
get drizzleClient() {
|
|
339
|
+
if (!this.db) {
|
|
345
340
|
throw new Error("Primary database client not initialized");
|
|
346
341
|
}
|
|
347
|
-
return this.
|
|
342
|
+
return this.db;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Get the Drizzle schema
|
|
346
|
+
*/
|
|
347
|
+
get schema() {
|
|
348
|
+
return this.options.drizzleSchema;
|
|
348
349
|
}
|
|
349
350
|
/**
|
|
350
351
|
* Decrypt database credentials
|
|
@@ -358,8 +359,8 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
358
359
|
return encrypted;
|
|
359
360
|
}
|
|
360
361
|
async onModuleDestroy() {
|
|
361
|
-
if (this.
|
|
362
|
-
await this.
|
|
362
|
+
if (this.pool) {
|
|
363
|
+
await this.pool.end();
|
|
363
364
|
this.logger.log("Disconnected from primary database");
|
|
364
365
|
}
|
|
365
366
|
}
|
|
@@ -904,6 +905,8 @@ TenantContextInterceptor = _ts_decorate8([
|
|
|
904
905
|
|
|
905
906
|
// src/database/services/tenant-database.service.ts
|
|
906
907
|
import { Inject as Inject3, Injectable as Injectable7, InternalServerErrorException as InternalServerErrorException2, Logger as Logger5 } from "@nestjs/common";
|
|
908
|
+
import { Pool as Pool2 } from "pg";
|
|
909
|
+
import { drizzle as drizzle2 } from "drizzle-orm/node-postgres";
|
|
907
910
|
function _ts_decorate9(decorators, target, key, desc) {
|
|
908
911
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
909
912
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -928,7 +931,7 @@ var TenantDatabaseService = class _TenantDatabaseService {
|
|
|
928
931
|
options;
|
|
929
932
|
tenantContext;
|
|
930
933
|
logger = new Logger5(_TenantDatabaseService.name);
|
|
931
|
-
/** Connection pool: Map<cacheKey,
|
|
934
|
+
/** Connection pool: Map<cacheKey, TenantConnection> */
|
|
932
935
|
clients = /* @__PURE__ */ new Map();
|
|
933
936
|
/** Track last usage time for idle connection cleanup */
|
|
934
937
|
clientLastUsed = /* @__PURE__ */ new Map();
|
|
@@ -940,17 +943,23 @@ var TenantDatabaseService = class _TenantDatabaseService {
|
|
|
940
943
|
this.startConnectionCleaner();
|
|
941
944
|
}
|
|
942
945
|
/**
|
|
943
|
-
* Get the
|
|
946
|
+
* Get the Drizzle client for the current tenant's database.
|
|
944
947
|
* This returns the tenant-scoped database client.
|
|
945
948
|
*
|
|
946
|
-
* @returns Tenant-scoped database
|
|
949
|
+
* @returns Tenant-scoped Drizzle database instance
|
|
947
950
|
* @throws UnauthorizedException if tenant context not set
|
|
948
951
|
* @throws InternalServerErrorException if connection fails
|
|
949
952
|
*/
|
|
950
|
-
get
|
|
953
|
+
get drizzleClient() {
|
|
951
954
|
return this.getDbClient();
|
|
952
955
|
}
|
|
953
956
|
/**
|
|
957
|
+
* Get the Drizzle schema
|
|
958
|
+
*/
|
|
959
|
+
get schema() {
|
|
960
|
+
return this.options.drizzleSchema;
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
954
963
|
* Get tenant-scoped database client for the current request/message
|
|
955
964
|
*
|
|
956
965
|
* This method:
|
|
@@ -958,66 +967,61 @@ var TenantDatabaseService = class _TenantDatabaseService {
|
|
|
958
967
|
* 2. Builds a connection URL based on tenant type
|
|
959
968
|
* 3. Returns cached client if exists, otherwise creates new one
|
|
960
969
|
*
|
|
961
|
-
* @returns
|
|
970
|
+
* @returns Drizzle database instance
|
|
962
971
|
* @throws UnauthorizedException if tenant context not set
|
|
963
972
|
* @throws InternalServerErrorException if connection fails
|
|
964
|
-
*
|
|
965
|
-
* @example
|
|
966
|
-
* const dbClient = await tenantDatabase.getDbClient<PrismaClient>();
|
|
967
|
-
* const users = await dbClient.user.findMany();
|
|
968
973
|
*/
|
|
969
|
-
|
|
974
|
+
getDbClient() {
|
|
970
975
|
const tenant = this.tenantContext.getTenant();
|
|
971
976
|
const cacheKey = this.buildCacheKey(tenant);
|
|
972
|
-
|
|
977
|
+
const existing = this.clients.get(cacheKey);
|
|
978
|
+
if (existing) {
|
|
973
979
|
this.clientLastUsed.set(cacheKey, Date.now());
|
|
974
980
|
this.logger.debug(`Reusing cached connection: ${cacheKey}`);
|
|
975
|
-
return
|
|
981
|
+
return existing.db;
|
|
976
982
|
}
|
|
977
983
|
this.logger.log(`Creating new database connection: ${cacheKey}`);
|
|
978
|
-
const
|
|
979
|
-
this.clients.set(cacheKey,
|
|
984
|
+
const connection = this.createDbClientSync(tenant);
|
|
985
|
+
this.clients.set(cacheKey, connection);
|
|
980
986
|
this.clientLastUsed.set(cacheKey, Date.now());
|
|
981
|
-
return
|
|
987
|
+
return connection.db;
|
|
982
988
|
}
|
|
983
989
|
/**
|
|
984
|
-
* Create a new database client for the given tenant
|
|
990
|
+
* Create a new database client for the given tenant (synchronous)
|
|
985
991
|
*/
|
|
986
|
-
|
|
992
|
+
createDbClientSync(tenant) {
|
|
987
993
|
try {
|
|
988
994
|
const databaseUrl = this.buildTenantDbUrl(tenant);
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
log: [
|
|
997
|
-
"error",
|
|
998
|
-
"warn"
|
|
999
|
-
]
|
|
995
|
+
const pool = new Pool2({
|
|
996
|
+
connectionString: databaseUrl,
|
|
997
|
+
max: tenant.connectionPoolSize || this.options.maxConnections || 10
|
|
998
|
+
});
|
|
999
|
+
const db = drizzle2({
|
|
1000
|
+
client: pool,
|
|
1001
|
+
schema: this.options.drizzleSchema
|
|
1000
1002
|
});
|
|
1001
|
-
await client.$connect();
|
|
1002
1003
|
this.logger.log(`Connected to database for tenant: ${tenant.subdomain}`);
|
|
1003
|
-
return
|
|
1004
|
+
return {
|
|
1005
|
+
pool,
|
|
1006
|
+
db
|
|
1007
|
+
};
|
|
1004
1008
|
} catch (error) {
|
|
1005
1009
|
this.logger.error(`Failed to create database connection for tenant: ${tenant.subdomain}`, error);
|
|
1006
1010
|
throw new InternalServerErrorException2("Failed to connect to tenant database");
|
|
1007
1011
|
}
|
|
1008
1012
|
}
|
|
1009
1013
|
/**
|
|
1010
|
-
* Build connection URL for
|
|
1014
|
+
* Build connection URL for tenant (dedicated database)
|
|
1011
1015
|
*/
|
|
1012
1016
|
buildTenantDbUrl(tenant) {
|
|
1013
1017
|
const { databaseHost, databasePort, databaseName, databaseUsername, databasePassword, databaseSslMode } = tenant;
|
|
1014
1018
|
if (!databaseHost || !databaseName || !databaseUsername) {
|
|
1015
|
-
throw new Error(`
|
|
1019
|
+
throw new Error(`Tenant ${tenant.subdomain} missing database configuration`);
|
|
1016
1020
|
}
|
|
1017
1021
|
const port = databasePort || 5432;
|
|
1018
1022
|
const sslMode = databaseSslMode || "require";
|
|
1019
|
-
const connectionUrl = `postgresql://${databaseUsername}:${databasePassword}@${databaseHost}:${port}/${databaseName}?sslmode=${sslMode}`;
|
|
1020
|
-
this.logger.debug(`
|
|
1023
|
+
const connectionUrl = `postgresql://${databaseUsername}:${encodeURIComponent(databasePassword || "")}@${databaseHost}:${port}/${databaseName}?sslmode=${sslMode}`;
|
|
1024
|
+
this.logger.debug(`Tenant connection URL: ${this.maskPassword(connectionUrl)}`);
|
|
1021
1025
|
return connectionUrl;
|
|
1022
1026
|
}
|
|
1023
1027
|
/**
|
|
@@ -1039,19 +1043,20 @@ var TenantDatabaseService = class _TenantDatabaseService {
|
|
|
1039
1043
|
/**
|
|
1040
1044
|
* Clean up idle connections that haven't been used recently
|
|
1041
1045
|
*/
|
|
1042
|
-
cleanupIdleConnections() {
|
|
1046
|
+
async cleanupIdleConnections() {
|
|
1043
1047
|
const now = Date.now();
|
|
1044
1048
|
const maxIdle = this.options.connectionCacheTTL || 3e5;
|
|
1045
1049
|
let cleaned = 0;
|
|
1046
1050
|
for (const [key, lastUsed] of this.clientLastUsed.entries()) {
|
|
1047
1051
|
if (now - lastUsed > maxIdle) {
|
|
1048
|
-
const
|
|
1049
|
-
if (
|
|
1050
|
-
|
|
1052
|
+
const connection = this.clients.get(key);
|
|
1053
|
+
if (connection) {
|
|
1054
|
+
try {
|
|
1055
|
+
await connection.pool.end();
|
|
1051
1056
|
this.logger.debug(`Cleaned up idle connection: ${key}`);
|
|
1052
|
-
}
|
|
1057
|
+
} catch (error) {
|
|
1053
1058
|
this.logger.error(`Error disconnecting idle client: ${key}`, error);
|
|
1054
|
-
}
|
|
1059
|
+
}
|
|
1055
1060
|
this.clients.delete(key);
|
|
1056
1061
|
this.clientLastUsed.delete(key);
|
|
1057
1062
|
cleaned++;
|
|
@@ -1082,9 +1087,9 @@ var TenantDatabaseService = class _TenantDatabaseService {
|
|
|
1082
1087
|
clearInterval(this.cleanupInterval);
|
|
1083
1088
|
}
|
|
1084
1089
|
this.logger.log(`Disconnecting ${this.clients.size} database connections`);
|
|
1085
|
-
const disconnectPromises = Array.from(this.clients.entries()).map(async ([key,
|
|
1090
|
+
const disconnectPromises = Array.from(this.clients.entries()).map(async ([key, connection]) => {
|
|
1086
1091
|
try {
|
|
1087
|
-
await
|
|
1092
|
+
await connection.pool.end();
|
|
1088
1093
|
this.logger.debug(`Disconnected: ${key}`);
|
|
1089
1094
|
} catch (error) {
|
|
1090
1095
|
this.logger.error(`Error disconnecting client: ${key}`, error);
|
|
@@ -1220,56 +1225,64 @@ DatabaseModule = _ts_decorate10([
|
|
|
1220
1225
|
|
|
1221
1226
|
// src/database/repositories/primary-base.repository.ts
|
|
1222
1227
|
import { Logger as Logger6 } from "@nestjs/common";
|
|
1228
|
+
import { eq as eq2, sql, getTableName } from "drizzle-orm";
|
|
1223
1229
|
var PrimaryBaseRepository = class {
|
|
1224
1230
|
static {
|
|
1225
1231
|
__name(this, "PrimaryBaseRepository");
|
|
1226
1232
|
}
|
|
1227
1233
|
database;
|
|
1234
|
+
table;
|
|
1228
1235
|
logger;
|
|
1229
|
-
modelGetter;
|
|
1230
1236
|
/**
|
|
1231
|
-
*
|
|
1237
|
+
* The table name extracted from the Drizzle table at runtime.
|
|
1238
|
+
* Used to access the query API for this repository's table.
|
|
1239
|
+
*/
|
|
1240
|
+
tableName;
|
|
1241
|
+
/**
|
|
1242
|
+
* Lazy getter for Drizzle client.
|
|
1232
1243
|
* Accesses the client from the database service only when needed,
|
|
1233
1244
|
* avoiding initialization timing issues with NestJS lifecycle.
|
|
1234
1245
|
*/
|
|
1235
|
-
get
|
|
1236
|
-
return this.database.
|
|
1246
|
+
get db() {
|
|
1247
|
+
return this.database.drizzleClient;
|
|
1237
1248
|
}
|
|
1238
1249
|
/**
|
|
1239
|
-
*
|
|
1240
|
-
*
|
|
1250
|
+
* Model query API for THIS repository's table (Prisma-like syntax)
|
|
1251
|
+
* Scoped to only the table this repository manages.
|
|
1252
|
+
* Returns a type-safe wrapper around Drizzle's RelationalQueryBuilder.
|
|
1253
|
+
*
|
|
1254
|
+
* @example
|
|
1255
|
+
* ```typescript
|
|
1256
|
+
* // Use relational queries with type safety
|
|
1257
|
+
* const user = await this.model.findFirst({
|
|
1258
|
+
* where: eq(users.id, id),
|
|
1259
|
+
* with: { posts: true, profile: true }
|
|
1260
|
+
* });
|
|
1261
|
+
* ```
|
|
1241
1262
|
*/
|
|
1242
1263
|
get model() {
|
|
1243
|
-
return this.
|
|
1264
|
+
return this.database.drizzleClient.query[this.tableName];
|
|
1244
1265
|
}
|
|
1245
1266
|
/**
|
|
1246
1267
|
* Create a new repository instance
|
|
1247
1268
|
*
|
|
1248
1269
|
* @param database - The primary database service
|
|
1249
|
-
* @param
|
|
1270
|
+
* @param table - The Drizzle table schema object
|
|
1250
1271
|
*
|
|
1251
1272
|
* @example
|
|
1252
1273
|
* ```typescript
|
|
1253
|
-
*
|
|
1254
|
-
* constructor(database: PrimaryDatabaseService) {
|
|
1255
|
-
* super(database, (prisma) => prisma.user);
|
|
1256
|
-
* }
|
|
1257
|
-
*
|
|
1258
|
-
* // Short syntax
|
|
1259
|
-
* constructor(database: PrimaryDatabaseService) {
|
|
1260
|
-
* super(database, (p) => p.user);
|
|
1261
|
-
* }
|
|
1274
|
+
* import { users } from '@/db/schema';
|
|
1262
1275
|
*
|
|
1263
|
-
* // Complex model names
|
|
1264
1276
|
* constructor(database: PrimaryDatabaseService) {
|
|
1265
|
-
* super(database,
|
|
1277
|
+
* super(database, users);
|
|
1266
1278
|
* }
|
|
1267
1279
|
* ```
|
|
1268
1280
|
*/
|
|
1269
|
-
constructor(database,
|
|
1281
|
+
constructor(database, table) {
|
|
1270
1282
|
this.database = database;
|
|
1283
|
+
this.table = table;
|
|
1284
|
+
this.tableName = getTableName(table);
|
|
1271
1285
|
this.logger = new Logger6(this.constructor.name);
|
|
1272
|
-
this.modelGetter = getModel;
|
|
1273
1286
|
this.logger.debug(`Initialized ${this.constructor.name}`);
|
|
1274
1287
|
}
|
|
1275
1288
|
/**
|
|
@@ -1282,21 +1295,20 @@ var PrimaryBaseRepository = class {
|
|
|
1282
1295
|
* ```typescript
|
|
1283
1296
|
* const user = await userRepository.create({
|
|
1284
1297
|
* email: 'user@example.com',
|
|
1285
|
-
*
|
|
1298
|
+
* firstName: 'John'
|
|
1286
1299
|
* });
|
|
1287
1300
|
* ```
|
|
1288
1301
|
*/
|
|
1289
1302
|
async create(data) {
|
|
1290
1303
|
this.logger.log("Creating record");
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
});
|
|
1304
|
+
const results = await this.db.insert(this.table).values(data).returning();
|
|
1305
|
+
return results[0];
|
|
1294
1306
|
}
|
|
1295
1307
|
/**
|
|
1296
1308
|
* Find a single record by ID
|
|
1297
1309
|
*
|
|
1298
1310
|
* @param id - The record ID
|
|
1299
|
-
* @returns Promise resolving to the record or
|
|
1311
|
+
* @returns Promise resolving to the record or undefined if not found
|
|
1300
1312
|
*
|
|
1301
1313
|
* @example
|
|
1302
1314
|
* ```typescript
|
|
@@ -1305,59 +1317,54 @@ var PrimaryBaseRepository = class {
|
|
|
1305
1317
|
*/
|
|
1306
1318
|
async findById(id) {
|
|
1307
1319
|
this.logger.debug(`Finding record by ID: ${id}`);
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
}
|
|
1320
|
+
const idColumn = this.table.id;
|
|
1321
|
+
return this.model.findFirst({
|
|
1322
|
+
where: eq2(idColumn, id)
|
|
1312
1323
|
});
|
|
1313
1324
|
}
|
|
1314
1325
|
/**
|
|
1315
1326
|
* Find a single record with custom where clause
|
|
1316
1327
|
*
|
|
1317
|
-
* @param where -
|
|
1318
|
-
* @returns Promise resolving to the record or
|
|
1328
|
+
* @param where - SQL condition
|
|
1329
|
+
* @returns Promise resolving to the record or undefined if not found
|
|
1319
1330
|
*
|
|
1320
1331
|
* @example
|
|
1321
1332
|
* ```typescript
|
|
1322
|
-
*
|
|
1323
|
-
* const user = await userRepository.findOne(
|
|
1324
|
-
*
|
|
1325
|
-
* // With include
|
|
1326
|
-
* const user = await userRepository.findOne({
|
|
1327
|
-
* where: { email: 'user@example.com' },
|
|
1328
|
-
* include: { posts: true }
|
|
1329
|
-
* });
|
|
1333
|
+
* import { eq } from 'drizzle-orm';
|
|
1334
|
+
* const user = await userRepository.findOne(eq(users.email, 'user@example.com'));
|
|
1330
1335
|
* ```
|
|
1331
1336
|
*/
|
|
1332
1337
|
async findOne(where) {
|
|
1333
1338
|
this.logger.debug("Finding record with custom query");
|
|
1334
|
-
return
|
|
1339
|
+
return this.model.findFirst({
|
|
1335
1340
|
where
|
|
1336
1341
|
});
|
|
1337
1342
|
}
|
|
1338
1343
|
/**
|
|
1339
1344
|
* Find multiple records
|
|
1340
1345
|
*
|
|
1341
|
-
* @param
|
|
1346
|
+
* @param options - Query options (where, orderBy, limit, offset)
|
|
1342
1347
|
* @returns Promise resolving to an array of records
|
|
1343
1348
|
*
|
|
1344
1349
|
* @example
|
|
1345
1350
|
* ```typescript
|
|
1351
|
+
* import { eq, desc } from 'drizzle-orm';
|
|
1352
|
+
*
|
|
1346
1353
|
* // Find all users
|
|
1347
1354
|
* const users = await userRepository.findMany();
|
|
1348
1355
|
*
|
|
1349
1356
|
* // Find with filtering and pagination
|
|
1350
1357
|
* const users = await userRepository.findMany({
|
|
1351
|
-
* where:
|
|
1352
|
-
* orderBy:
|
|
1353
|
-
*
|
|
1354
|
-
*
|
|
1358
|
+
* where: eq(users.accountStatus, 'ACTIVE'),
|
|
1359
|
+
* orderBy: desc(users.createdAt),
|
|
1360
|
+
* limit: 10,
|
|
1361
|
+
* offset: 0
|
|
1355
1362
|
* });
|
|
1356
1363
|
* ```
|
|
1357
1364
|
*/
|
|
1358
|
-
async findMany(
|
|
1365
|
+
async findMany(options) {
|
|
1359
1366
|
this.logger.debug("Finding multiple records");
|
|
1360
|
-
return
|
|
1367
|
+
return this.model.findMany(options);
|
|
1361
1368
|
}
|
|
1362
1369
|
/**
|
|
1363
1370
|
* Update a record by ID
|
|
@@ -1369,41 +1376,40 @@ var PrimaryBaseRepository = class {
|
|
|
1369
1376
|
* @example
|
|
1370
1377
|
* ```typescript
|
|
1371
1378
|
* const user = await userRepository.update('user-id-123', {
|
|
1372
|
-
*
|
|
1379
|
+
* firstName: 'Jane'
|
|
1373
1380
|
* });
|
|
1374
1381
|
* ```
|
|
1375
1382
|
*/
|
|
1376
1383
|
async update(id, data) {
|
|
1377
1384
|
this.logger.log(`Updating record with ID: ${id}`);
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
},
|
|
1382
|
-
data
|
|
1383
|
-
});
|
|
1385
|
+
const idColumn = this.table.id;
|
|
1386
|
+
const results = await this.db.update(this.table).set(data).where(eq2(idColumn, id)).returning();
|
|
1387
|
+
return results[0];
|
|
1384
1388
|
}
|
|
1385
1389
|
/**
|
|
1386
1390
|
* Update multiple records
|
|
1387
1391
|
*
|
|
1388
|
-
* @param where -
|
|
1392
|
+
* @param where - SQL condition to match records
|
|
1389
1393
|
* @param data - The data to update
|
|
1390
1394
|
* @returns Promise resolving to the count of updated records
|
|
1391
1395
|
*
|
|
1392
1396
|
* @example
|
|
1393
1397
|
* ```typescript
|
|
1398
|
+
* import { eq } from 'drizzle-orm';
|
|
1399
|
+
*
|
|
1394
1400
|
* const result = await userRepository.updateMany(
|
|
1395
|
-
*
|
|
1396
|
-
* {
|
|
1401
|
+
* eq(users.accountStatus, 'PENDING'),
|
|
1402
|
+
* { accountStatus: 'ACTIVE' }
|
|
1397
1403
|
* );
|
|
1398
1404
|
* console.log(`Updated ${result.count} users`);
|
|
1399
1405
|
* ```
|
|
1400
1406
|
*/
|
|
1401
1407
|
async updateMany(where, data) {
|
|
1402
1408
|
this.logger.log("Updating multiple records");
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
}
|
|
1409
|
+
const result = await this.db.update(this.table).set(data).where(where);
|
|
1410
|
+
return {
|
|
1411
|
+
count: result.rowCount ?? 0
|
|
1412
|
+
};
|
|
1407
1413
|
}
|
|
1408
1414
|
/**
|
|
1409
1415
|
* Delete a record by ID
|
|
@@ -1418,127 +1424,143 @@ var PrimaryBaseRepository = class {
|
|
|
1418
1424
|
*/
|
|
1419
1425
|
async delete(id) {
|
|
1420
1426
|
this.logger.log(`Deleting record with ID: ${id}`);
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
}
|
|
1425
|
-
});
|
|
1427
|
+
const idColumn = this.table.id;
|
|
1428
|
+
const results = await this.db.delete(this.table).where(eq2(idColumn, id)).returning();
|
|
1429
|
+
return results[0];
|
|
1426
1430
|
}
|
|
1427
1431
|
/**
|
|
1428
1432
|
* Delete multiple records
|
|
1429
1433
|
*
|
|
1430
|
-
* @param where -
|
|
1434
|
+
* @param where - SQL condition to match records
|
|
1431
1435
|
* @returns Promise resolving to the count of deleted records
|
|
1432
1436
|
*
|
|
1433
1437
|
* @example
|
|
1434
1438
|
* ```typescript
|
|
1435
|
-
*
|
|
1436
|
-
*
|
|
1437
|
-
*
|
|
1438
|
-
*
|
|
1439
|
+
* import { lt } from 'drizzle-orm';
|
|
1440
|
+
*
|
|
1441
|
+
* const result = await userRepository.deleteMany(
|
|
1442
|
+
* lt(users.createdAt, new Date('2020-01-01'))
|
|
1443
|
+
* );
|
|
1439
1444
|
* console.log(`Deleted ${result.count} users`);
|
|
1440
1445
|
* ```
|
|
1441
1446
|
*/
|
|
1442
1447
|
async deleteMany(where) {
|
|
1443
1448
|
this.logger.log("Deleting multiple records");
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1449
|
+
const result = await this.db.delete(this.table).where(where);
|
|
1450
|
+
return {
|
|
1451
|
+
count: result.rowCount ?? 0
|
|
1452
|
+
};
|
|
1447
1453
|
}
|
|
1448
1454
|
/**
|
|
1449
1455
|
* Count records
|
|
1450
1456
|
*
|
|
1451
|
-
* @param where - Optional
|
|
1457
|
+
* @param where - Optional SQL condition to filter records
|
|
1452
1458
|
* @returns Promise resolving to the count of records
|
|
1453
1459
|
*
|
|
1454
1460
|
* @example
|
|
1455
1461
|
* ```typescript
|
|
1462
|
+
* import { eq } from 'drizzle-orm';
|
|
1463
|
+
*
|
|
1456
1464
|
* // Count all users
|
|
1457
1465
|
* const total = await userRepository.count();
|
|
1458
1466
|
*
|
|
1459
1467
|
* // Count active users
|
|
1460
|
-
* const activeCount = await userRepository.count(
|
|
1468
|
+
* const activeCount = await userRepository.count(
|
|
1469
|
+
* eq(users.accountStatus, 'ACTIVE')
|
|
1470
|
+
* );
|
|
1461
1471
|
* ```
|
|
1462
1472
|
*/
|
|
1463
1473
|
async count(where) {
|
|
1464
1474
|
this.logger.debug("Counting records");
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
});
|
|
1475
|
+
let query = this.db.select({
|
|
1476
|
+
count: sql`count(*)::int`
|
|
1477
|
+
}).from(this.table).$dynamic();
|
|
1478
|
+
if (where) {
|
|
1479
|
+
query = query.where(where);
|
|
1480
|
+
}
|
|
1481
|
+
const results = await query;
|
|
1482
|
+
return results[0].count;
|
|
1468
1483
|
}
|
|
1469
1484
|
/**
|
|
1470
1485
|
* Check if a record exists
|
|
1471
1486
|
*
|
|
1472
|
-
* @param where -
|
|
1487
|
+
* @param where - SQL condition to match records
|
|
1473
1488
|
* @returns Promise resolving to true if at least one record exists, false otherwise
|
|
1474
1489
|
*
|
|
1475
1490
|
* @example
|
|
1476
1491
|
* ```typescript
|
|
1477
|
-
*
|
|
1478
|
-
*
|
|
1479
|
-
*
|
|
1492
|
+
* import { eq } from 'drizzle-orm';
|
|
1493
|
+
*
|
|
1494
|
+
* const emailExists = await userRepository.exists(
|
|
1495
|
+
* eq(users.email, 'user@example.com')
|
|
1496
|
+
* );
|
|
1480
1497
|
* ```
|
|
1481
1498
|
*/
|
|
1482
1499
|
async exists(where) {
|
|
1483
|
-
const count = await this.
|
|
1484
|
-
where
|
|
1485
|
-
});
|
|
1500
|
+
const count = await this.count(where);
|
|
1486
1501
|
return count > 0;
|
|
1487
1502
|
}
|
|
1488
1503
|
};
|
|
1489
1504
|
|
|
1490
1505
|
// src/database/repositories/tenant-base.repository.ts
|
|
1491
1506
|
import { Logger as Logger7 } from "@nestjs/common";
|
|
1507
|
+
import { eq as eq3, sql as sql2, getTableName as getTableName2 } from "drizzle-orm";
|
|
1492
1508
|
var TenantBaseRepository = class {
|
|
1493
1509
|
static {
|
|
1494
1510
|
__name(this, "TenantBaseRepository");
|
|
1495
1511
|
}
|
|
1496
1512
|
database;
|
|
1513
|
+
table;
|
|
1497
1514
|
logger;
|
|
1498
|
-
modelGetter;
|
|
1499
1515
|
/**
|
|
1500
|
-
*
|
|
1516
|
+
* The table name extracted from the Drizzle table at runtime.
|
|
1517
|
+
* Used to access the query API for this repository's table.
|
|
1518
|
+
*/
|
|
1519
|
+
tableName;
|
|
1520
|
+
/**
|
|
1521
|
+
* Lazy getter for Drizzle client.
|
|
1501
1522
|
* Accesses the client from the database service only when needed,
|
|
1502
1523
|
* avoiding initialization timing issues with NestJS lifecycle.
|
|
1503
1524
|
*/
|
|
1504
|
-
get
|
|
1505
|
-
return this.database.
|
|
1525
|
+
get db() {
|
|
1526
|
+
return this.database.drizzleClient;
|
|
1506
1527
|
}
|
|
1507
1528
|
/**
|
|
1508
|
-
*
|
|
1509
|
-
*
|
|
1529
|
+
* Model query API for THIS repository's table (Prisma-like syntax)
|
|
1530
|
+
* Scoped to only the table this repository manages
|
|
1531
|
+
*
|
|
1532
|
+
* @example
|
|
1533
|
+
* ```typescript
|
|
1534
|
+
* // Use relational queries with type safety
|
|
1535
|
+
* const product = await this.model.findFirst({
|
|
1536
|
+
* where: eq(products.id, id),
|
|
1537
|
+
* with: { category: true, variants: true }
|
|
1538
|
+
* });
|
|
1539
|
+
* ```
|
|
1510
1540
|
*/
|
|
1511
1541
|
get model() {
|
|
1512
|
-
return this.
|
|
1542
|
+
return this.database.drizzleClient.query[this.tableName];
|
|
1513
1543
|
}
|
|
1514
1544
|
/**
|
|
1515
1545
|
* Create a new repository instance
|
|
1516
1546
|
*
|
|
1517
1547
|
* @param database - The tenant database service
|
|
1518
|
-
* @param
|
|
1548
|
+
* @param table - The Drizzle table schema object
|
|
1519
1549
|
*
|
|
1520
1550
|
* @example
|
|
1521
1551
|
* ```typescript
|
|
1522
|
-
*
|
|
1523
|
-
* constructor(database: TenantDatabaseService) {
|
|
1524
|
-
* super(database, (prisma) => prisma.product);
|
|
1525
|
-
* }
|
|
1526
|
-
*
|
|
1527
|
-
* // Short syntax
|
|
1528
|
-
* constructor(database: TenantDatabaseService) {
|
|
1529
|
-
* super(database, (p) => p.product);
|
|
1530
|
-
* }
|
|
1552
|
+
* import { products } from '@/db/schema';
|
|
1531
1553
|
*
|
|
1532
|
-
* // Complex model names
|
|
1533
1554
|
* constructor(database: TenantDatabaseService) {
|
|
1534
|
-
* super(database,
|
|
1555
|
+
* super(database, products);
|
|
1535
1556
|
* }
|
|
1536
1557
|
* ```
|
|
1537
1558
|
*/
|
|
1538
|
-
constructor(database,
|
|
1559
|
+
constructor(database, table) {
|
|
1539
1560
|
this.database = database;
|
|
1561
|
+
this.table = table;
|
|
1562
|
+
this.tableName = getTableName2(table);
|
|
1540
1563
|
this.logger = new Logger7(this.constructor.name);
|
|
1541
|
-
this.modelGetter = getModel;
|
|
1542
1564
|
this.logger.debug(`Initialized ${this.constructor.name}`);
|
|
1543
1565
|
}
|
|
1544
1566
|
/**
|
|
@@ -1558,9 +1580,8 @@ var TenantBaseRepository = class {
|
|
|
1558
1580
|
*/
|
|
1559
1581
|
async create(data) {
|
|
1560
1582
|
this.logger.log("Creating record");
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
});
|
|
1583
|
+
const results = await this.db.insert(this.table).values(data).returning();
|
|
1584
|
+
return results[0];
|
|
1564
1585
|
}
|
|
1565
1586
|
/**
|
|
1566
1587
|
* Find a single record by ID
|
|
@@ -1575,59 +1596,65 @@ var TenantBaseRepository = class {
|
|
|
1575
1596
|
*/
|
|
1576
1597
|
async findById(id) {
|
|
1577
1598
|
this.logger.debug(`Finding record by ID: ${id}`);
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
}
|
|
1582
|
-
});
|
|
1599
|
+
const idColumn = this.table.id;
|
|
1600
|
+
const results = await this.db.select().from(this.table).where(eq3(idColumn, id)).limit(1);
|
|
1601
|
+
return results[0] ?? null;
|
|
1583
1602
|
}
|
|
1584
1603
|
/**
|
|
1585
1604
|
* Find a single record with custom where clause
|
|
1586
1605
|
*
|
|
1587
|
-
* @param where -
|
|
1606
|
+
* @param where - SQL condition
|
|
1588
1607
|
* @returns Promise resolving to the record or null if not found
|
|
1589
1608
|
*
|
|
1590
1609
|
* @example
|
|
1591
1610
|
* ```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
|
-
* });
|
|
1611
|
+
* import { eq } from 'drizzle-orm';
|
|
1612
|
+
* const product = await productRepository.findOne(eq(products.sku, 'WDG-001'));
|
|
1600
1613
|
* ```
|
|
1601
1614
|
*/
|
|
1602
1615
|
async findOne(where) {
|
|
1603
1616
|
this.logger.debug("Finding record with custom query");
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
});
|
|
1617
|
+
const results = await this.db.select().from(this.table).where(where).limit(1);
|
|
1618
|
+
return results[0] ?? null;
|
|
1607
1619
|
}
|
|
1608
1620
|
/**
|
|
1609
1621
|
* Find multiple records
|
|
1610
1622
|
*
|
|
1611
|
-
* @param
|
|
1623
|
+
* @param options - Query options (where, orderBy, limit, offset)
|
|
1612
1624
|
* @returns Promise resolving to an array of records
|
|
1613
1625
|
*
|
|
1614
1626
|
* @example
|
|
1615
1627
|
* ```typescript
|
|
1628
|
+
* import { eq, desc } from 'drizzle-orm';
|
|
1629
|
+
*
|
|
1616
1630
|
* // Find all products
|
|
1617
1631
|
* const products = await productRepository.findMany();
|
|
1618
1632
|
*
|
|
1619
1633
|
* // Find with filtering and pagination
|
|
1620
1634
|
* const products = await productRepository.findMany({
|
|
1621
|
-
* where:
|
|
1622
|
-
* orderBy:
|
|
1623
|
-
*
|
|
1624
|
-
*
|
|
1635
|
+
* where: eq(products.status, 'ACTIVE'),
|
|
1636
|
+
* orderBy: desc(products.createdAt),
|
|
1637
|
+
* limit: 10,
|
|
1638
|
+
* offset: 0
|
|
1625
1639
|
* });
|
|
1626
1640
|
* ```
|
|
1627
1641
|
*/
|
|
1628
|
-
async findMany(
|
|
1642
|
+
async findMany(options) {
|
|
1629
1643
|
this.logger.debug("Finding multiple records");
|
|
1630
|
-
|
|
1644
|
+
let query = this.db.select().from(this.table).$dynamic();
|
|
1645
|
+
if (options?.where) {
|
|
1646
|
+
query = query.where(options.where);
|
|
1647
|
+
}
|
|
1648
|
+
if (options?.orderBy) {
|
|
1649
|
+
query = query.orderBy(options.orderBy);
|
|
1650
|
+
}
|
|
1651
|
+
if (options?.limit) {
|
|
1652
|
+
query = query.limit(options.limit);
|
|
1653
|
+
}
|
|
1654
|
+
if (options?.offset) {
|
|
1655
|
+
query = query.offset(options.offset);
|
|
1656
|
+
}
|
|
1657
|
+
return await query;
|
|
1631
1658
|
}
|
|
1632
1659
|
/**
|
|
1633
1660
|
* Update a record by ID
|
|
@@ -1645,24 +1672,23 @@ var TenantBaseRepository = class {
|
|
|
1645
1672
|
*/
|
|
1646
1673
|
async update(id, data) {
|
|
1647
1674
|
this.logger.log(`Updating record with ID: ${id}`);
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
},
|
|
1652
|
-
data
|
|
1653
|
-
});
|
|
1675
|
+
const idColumn = this.table.id;
|
|
1676
|
+
const results = await this.db.update(this.table).set(data).where(eq3(idColumn, id)).returning();
|
|
1677
|
+
return results[0];
|
|
1654
1678
|
}
|
|
1655
1679
|
/**
|
|
1656
1680
|
* Update multiple records
|
|
1657
1681
|
*
|
|
1658
|
-
* @param where -
|
|
1682
|
+
* @param where - SQL condition to match records
|
|
1659
1683
|
* @param data - The data to update
|
|
1660
1684
|
* @returns Promise resolving to the count of updated records
|
|
1661
1685
|
*
|
|
1662
1686
|
* @example
|
|
1663
1687
|
* ```typescript
|
|
1688
|
+
* import { eq } from 'drizzle-orm';
|
|
1689
|
+
*
|
|
1664
1690
|
* const result = await productRepository.updateMany(
|
|
1665
|
-
*
|
|
1691
|
+
* eq(products.status, 'PENDING'),
|
|
1666
1692
|
* { status: 'ACTIVE' }
|
|
1667
1693
|
* );
|
|
1668
1694
|
* console.log(`Updated ${result.count} products`);
|
|
@@ -1670,10 +1696,10 @@ var TenantBaseRepository = class {
|
|
|
1670
1696
|
*/
|
|
1671
1697
|
async updateMany(where, data) {
|
|
1672
1698
|
this.logger.log("Updating multiple records");
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
}
|
|
1699
|
+
const result = await this.db.update(this.table).set(data).where(where);
|
|
1700
|
+
return {
|
|
1701
|
+
count: result.rowCount ?? 0
|
|
1702
|
+
};
|
|
1677
1703
|
}
|
|
1678
1704
|
/**
|
|
1679
1705
|
* Delete a record by ID
|
|
@@ -1688,71 +1714,80 @@ var TenantBaseRepository = class {
|
|
|
1688
1714
|
*/
|
|
1689
1715
|
async delete(id) {
|
|
1690
1716
|
this.logger.log(`Deleting record with ID: ${id}`);
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
}
|
|
1695
|
-
});
|
|
1717
|
+
const idColumn = this.table.id;
|
|
1718
|
+
const results = await this.db.delete(this.table).where(eq3(idColumn, id)).returning();
|
|
1719
|
+
return results[0];
|
|
1696
1720
|
}
|
|
1697
1721
|
/**
|
|
1698
1722
|
* Delete multiple records
|
|
1699
1723
|
*
|
|
1700
|
-
* @param where -
|
|
1724
|
+
* @param where - SQL condition to match records
|
|
1701
1725
|
* @returns Promise resolving to the count of deleted records
|
|
1702
1726
|
*
|
|
1703
1727
|
* @example
|
|
1704
1728
|
* ```typescript
|
|
1705
|
-
*
|
|
1706
|
-
*
|
|
1707
|
-
*
|
|
1708
|
-
*
|
|
1729
|
+
* import { lt } from 'drizzle-orm';
|
|
1730
|
+
*
|
|
1731
|
+
* const result = await productRepository.deleteMany(
|
|
1732
|
+
* lt(products.createdAt, new Date('2020-01-01'))
|
|
1733
|
+
* );
|
|
1709
1734
|
* console.log(`Deleted ${result.count} products`);
|
|
1710
1735
|
* ```
|
|
1711
1736
|
*/
|
|
1712
1737
|
async deleteMany(where) {
|
|
1713
1738
|
this.logger.log("Deleting multiple records");
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1739
|
+
const result = await this.db.delete(this.table).where(where);
|
|
1740
|
+
return {
|
|
1741
|
+
count: result.rowCount ?? 0
|
|
1742
|
+
};
|
|
1717
1743
|
}
|
|
1718
1744
|
/**
|
|
1719
1745
|
* Count records
|
|
1720
1746
|
*
|
|
1721
|
-
* @param where - Optional
|
|
1747
|
+
* @param where - Optional SQL condition to filter records
|
|
1722
1748
|
* @returns Promise resolving to the count of records
|
|
1723
1749
|
*
|
|
1724
1750
|
* @example
|
|
1725
1751
|
* ```typescript
|
|
1752
|
+
* import { eq } from 'drizzle-orm';
|
|
1753
|
+
*
|
|
1726
1754
|
* // Count all products
|
|
1727
1755
|
* const total = await productRepository.count();
|
|
1728
1756
|
*
|
|
1729
1757
|
* // Count active products
|
|
1730
|
-
* const activeCount = await productRepository.count(
|
|
1758
|
+
* const activeCount = await productRepository.count(
|
|
1759
|
+
* eq(products.status, 'ACTIVE')
|
|
1760
|
+
* );
|
|
1731
1761
|
* ```
|
|
1732
1762
|
*/
|
|
1733
1763
|
async count(where) {
|
|
1734
1764
|
this.logger.debug("Counting records");
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
});
|
|
1765
|
+
let query = this.db.select({
|
|
1766
|
+
count: sql2`count(*)::int`
|
|
1767
|
+
}).from(this.table).$dynamic();
|
|
1768
|
+
if (where) {
|
|
1769
|
+
query = query.where(where);
|
|
1770
|
+
}
|
|
1771
|
+
const results = await query;
|
|
1772
|
+
return results[0].count;
|
|
1738
1773
|
}
|
|
1739
1774
|
/**
|
|
1740
1775
|
* Check if a record exists
|
|
1741
1776
|
*
|
|
1742
|
-
* @param where -
|
|
1777
|
+
* @param where - SQL condition to match records
|
|
1743
1778
|
* @returns Promise resolving to true if at least one record exists, false otherwise
|
|
1744
1779
|
*
|
|
1745
1780
|
* @example
|
|
1746
1781
|
* ```typescript
|
|
1747
|
-
*
|
|
1748
|
-
*
|
|
1749
|
-
*
|
|
1782
|
+
* import { eq } from 'drizzle-orm';
|
|
1783
|
+
*
|
|
1784
|
+
* const skuExists = await productRepository.exists(
|
|
1785
|
+
* eq(products.sku, 'WDG-001')
|
|
1786
|
+
* );
|
|
1750
1787
|
* ```
|
|
1751
1788
|
*/
|
|
1752
1789
|
async exists(where) {
|
|
1753
|
-
const count = await this.
|
|
1754
|
-
where
|
|
1755
|
-
});
|
|
1790
|
+
const count = await this.count(where);
|
|
1756
1791
|
return count > 0;
|
|
1757
1792
|
}
|
|
1758
1793
|
};
|
|
@@ -1909,15 +1944,16 @@ var HttpExceptionFilter = class _HttpExceptionFilter {
|
|
|
1909
1944
|
const exceptionResponse = exception.getResponse();
|
|
1910
1945
|
if (typeof exceptionResponse === "object" && exceptionResponse !== null) {
|
|
1911
1946
|
const responseObj = exceptionResponse;
|
|
1912
|
-
if (
|
|
1947
|
+
if ("errors" in responseObj && Array.isArray(responseObj.errors)) {
|
|
1913
1948
|
errors = responseObj.errors;
|
|
1914
|
-
detail = responseObj.detail || exception.message;
|
|
1915
|
-
} else if (
|
|
1949
|
+
detail = ("detail" in responseObj ? responseObj.detail : void 0) || exception.message;
|
|
1950
|
+
} else if ("message" in responseObj && Array.isArray(responseObj.message)) {
|
|
1916
1951
|
errors = responseObj.message.map((msg) => {
|
|
1917
|
-
if (typeof msg === "object" &&
|
|
1952
|
+
if (typeof msg === "object" && "property" in msg && "constraints" in msg) {
|
|
1953
|
+
const constraintValues = Object.values(msg.constraints);
|
|
1918
1954
|
return {
|
|
1919
1955
|
field: msg.property,
|
|
1920
|
-
message:
|
|
1956
|
+
message: constraintValues[0] ?? "Validation failed"
|
|
1921
1957
|
};
|
|
1922
1958
|
}
|
|
1923
1959
|
return {
|
|
@@ -1925,13 +1961,14 @@ var HttpExceptionFilter = class _HttpExceptionFilter {
|
|
|
1925
1961
|
};
|
|
1926
1962
|
});
|
|
1927
1963
|
detail = "Validation failed";
|
|
1928
|
-
} else if (responseObj
|
|
1964
|
+
} else if ("message" in responseObj) {
|
|
1965
|
+
const message = responseObj.message;
|
|
1929
1966
|
errors = [
|
|
1930
1967
|
{
|
|
1931
|
-
message: Array.isArray(
|
|
1968
|
+
message: Array.isArray(message) ? message.join(", ") : message
|
|
1932
1969
|
}
|
|
1933
1970
|
];
|
|
1934
|
-
detail = responseObj.error || exception.message;
|
|
1971
|
+
detail = ("error" in responseObj ? responseObj.error : void 0) || exception.message;
|
|
1935
1972
|
}
|
|
1936
1973
|
} else if (typeof exceptionResponse === "string") {
|
|
1937
1974
|
errors = [
|
|
@@ -1942,7 +1979,9 @@ var HttpExceptionFilter = class _HttpExceptionFilter {
|
|
|
1942
1979
|
detail = exceptionResponse;
|
|
1943
1980
|
}
|
|
1944
1981
|
} else {
|
|
1945
|
-
|
|
1982
|
+
const errorMessage = exception instanceof Error ? exception.message : "Unknown error";
|
|
1983
|
+
const stack = exception instanceof Error ? exception.stack : void 0;
|
|
1984
|
+
this.logger.error(`Unexpected error: ${errorMessage}`, stack);
|
|
1946
1985
|
errors = [
|
|
1947
1986
|
{
|
|
1948
1987
|
message: "An unexpected error occurred"
|
|
@@ -2345,19 +2384,904 @@ var BadGatewayException = class extends BaseFieldException {
|
|
|
2345
2384
|
}
|
|
2346
2385
|
}
|
|
2347
2386
|
};
|
|
2387
|
+
|
|
2388
|
+
// src/logger/logger.module.ts
|
|
2389
|
+
import { Global as Global4, Module as Module5, Logger as Logger11 } from "@nestjs/common";
|
|
2390
|
+
|
|
2391
|
+
// src/logger/services/logger.service.ts
|
|
2392
|
+
import { Injectable as Injectable9, Logger as Logger10, Optional } from "@nestjs/common";
|
|
2393
|
+
import { createLogger, format, transports } from "winston";
|
|
2394
|
+
import DailyRotateFile from "winston-daily-rotate-file";
|
|
2395
|
+
|
|
2396
|
+
// src/logger/utils/index.ts
|
|
2397
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
2398
|
+
import { randomUUID } from "crypto";
|
|
2399
|
+
var correlationStorage = new AsyncLocalStorage();
|
|
2400
|
+
function getCorrelationContext() {
|
|
2401
|
+
return correlationStorage.getStore();
|
|
2402
|
+
}
|
|
2403
|
+
__name(getCorrelationContext, "getCorrelationContext");
|
|
2404
|
+
function runWithCorrelationContext(context, callback) {
|
|
2405
|
+
return correlationStorage.run(context, callback);
|
|
2406
|
+
}
|
|
2407
|
+
__name(runWithCorrelationContext, "runWithCorrelationContext");
|
|
2408
|
+
function updateCorrelationContext(updates) {
|
|
2409
|
+
const context = correlationStorage.getStore();
|
|
2410
|
+
if (context) {
|
|
2411
|
+
Object.assign(context, updates);
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
__name(updateCorrelationContext, "updateCorrelationContext");
|
|
2415
|
+
var DEFAULT_CORRELATION_HEADER = "x-correlation-id";
|
|
2416
|
+
function generateCorrelationId() {
|
|
2417
|
+
return randomUUID();
|
|
2418
|
+
}
|
|
2419
|
+
__name(generateCorrelationId, "generateCorrelationId");
|
|
2420
|
+
function addCorrelationIdToResponse(reply, correlationId, headerName = DEFAULT_CORRELATION_HEADER) {
|
|
2421
|
+
if (typeof reply.header === "function") {
|
|
2422
|
+
reply.header(headerName, correlationId);
|
|
2423
|
+
} else if (reply.raw && typeof reply.raw.setHeader === "function") {
|
|
2424
|
+
reply.raw.setHeader(headerName, correlationId);
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
__name(addCorrelationIdToResponse, "addCorrelationIdToResponse");
|
|
2428
|
+
|
|
2429
|
+
// src/logger/services/logger.service.ts
|
|
2430
|
+
function _ts_decorate14(decorators, target, key, desc) {
|
|
2431
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2432
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2433
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
2434
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2435
|
+
}
|
|
2436
|
+
__name(_ts_decorate14, "_ts_decorate");
|
|
2437
|
+
function _ts_metadata8(k, v) {
|
|
2438
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2439
|
+
}
|
|
2440
|
+
__name(_ts_metadata8, "_ts_metadata");
|
|
2441
|
+
function _ts_param4(paramIndex, decorator) {
|
|
2442
|
+
return function(target, key) {
|
|
2443
|
+
decorator(target, key, paramIndex);
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
__name(_ts_param4, "_ts_param");
|
|
2447
|
+
var LoggerService2 = class _LoggerService {
|
|
2448
|
+
static {
|
|
2449
|
+
__name(this, "LoggerService");
|
|
2450
|
+
}
|
|
2451
|
+
defaultLogger;
|
|
2452
|
+
activeLogger;
|
|
2453
|
+
options;
|
|
2454
|
+
context;
|
|
2455
|
+
constructor(options = {}, defaultLogger) {
|
|
2456
|
+
this.defaultLogger = defaultLogger;
|
|
2457
|
+
this.options = options;
|
|
2458
|
+
const provider = options.provider ?? "winston";
|
|
2459
|
+
if (provider === "default") {
|
|
2460
|
+
if (!this.defaultLogger) {
|
|
2461
|
+
throw new Error("LoggerService: Default Logger not provided");
|
|
2462
|
+
}
|
|
2463
|
+
this.activeLogger = this.defaultLogger;
|
|
2464
|
+
} else {
|
|
2465
|
+
this.activeLogger = this.createWinstonLogger(options);
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
/**
|
|
2469
|
+
* Creates a Winston logger instance with inline configuration.
|
|
2470
|
+
* Consolidates winston-config.factory.ts logic.
|
|
2471
|
+
*/
|
|
2472
|
+
createWinstonLogger(opts) {
|
|
2473
|
+
const level = opts.level ?? "debug";
|
|
2474
|
+
const logFormat = opts.format ?? "text";
|
|
2475
|
+
const baseFormatters = [
|
|
2476
|
+
format.timestamp({
|
|
2477
|
+
format: "YYYY-MM-DDTHH:mm:ss.SSSZ"
|
|
2478
|
+
}),
|
|
2479
|
+
format.errors({
|
|
2480
|
+
stack: true
|
|
2481
|
+
})
|
|
2482
|
+
];
|
|
2483
|
+
const consoleTransport = logFormat === "json" ? new transports.Console({
|
|
2484
|
+
level,
|
|
2485
|
+
format: format.combine(...baseFormatters, format.json())
|
|
2486
|
+
}) : new transports.Console({
|
|
2487
|
+
level,
|
|
2488
|
+
format: format.combine(...baseFormatters, format.printf((info) => {
|
|
2489
|
+
const { timestamp, level: level2, message, context, correlationId, trace } = info;
|
|
2490
|
+
const parts = [
|
|
2491
|
+
timestamp,
|
|
2492
|
+
level2.toUpperCase().padEnd(7),
|
|
2493
|
+
correlationId ? `[${correlationId.toString().slice(-6)}]` : "",
|
|
2494
|
+
context ? `[${context}]` : "",
|
|
2495
|
+
message
|
|
2496
|
+
].filter(Boolean);
|
|
2497
|
+
let output = parts.join(" ");
|
|
2498
|
+
if (trace) {
|
|
2499
|
+
output += "\n" + trace;
|
|
2500
|
+
}
|
|
2501
|
+
return output;
|
|
2502
|
+
}), format.colorize({
|
|
2503
|
+
all: true
|
|
2504
|
+
}))
|
|
2505
|
+
});
|
|
2506
|
+
const winstonTransports = [
|
|
2507
|
+
consoleTransport
|
|
2508
|
+
];
|
|
2509
|
+
if (opts.enableFileLogger) {
|
|
2510
|
+
const filePath = opts.filePath ?? "./logs";
|
|
2511
|
+
const maxFiles = opts.maxFiles ?? "14d";
|
|
2512
|
+
winstonTransports.push(new DailyRotateFile({
|
|
2513
|
+
level,
|
|
2514
|
+
filename: `${filePath}/%DATE%-combined.log`,
|
|
2515
|
+
datePattern: "YYYY-MM-DD",
|
|
2516
|
+
maxSize: "20m",
|
|
2517
|
+
maxFiles,
|
|
2518
|
+
format: format.combine(format.timestamp(), format.json())
|
|
2519
|
+
}), new DailyRotateFile({
|
|
2520
|
+
level: "error",
|
|
2521
|
+
filename: `${filePath}/%DATE%-error.log`,
|
|
2522
|
+
datePattern: "YYYY-MM-DD",
|
|
2523
|
+
maxSize: "20m",
|
|
2524
|
+
maxFiles,
|
|
2525
|
+
format: format.combine(format.timestamp(), format.json())
|
|
2526
|
+
}));
|
|
2527
|
+
}
|
|
2528
|
+
const config = {
|
|
2529
|
+
level,
|
|
2530
|
+
transports: winstonTransports,
|
|
2531
|
+
exitOnError: false
|
|
2532
|
+
};
|
|
2533
|
+
if (opts.defaultMeta || opts.appName) {
|
|
2534
|
+
config.defaultMeta = {
|
|
2535
|
+
...opts.defaultMeta,
|
|
2536
|
+
appName: opts.appName,
|
|
2537
|
+
environment: opts.environment
|
|
2538
|
+
};
|
|
2539
|
+
}
|
|
2540
|
+
return createLogger(config);
|
|
2541
|
+
}
|
|
2542
|
+
// NestJS LoggerService interface methods
|
|
2543
|
+
log(message, context) {
|
|
2544
|
+
this._log("log", message, context);
|
|
2545
|
+
}
|
|
2546
|
+
error(message, trace, context) {
|
|
2547
|
+
this._log("error", message, context, trace);
|
|
2548
|
+
}
|
|
2549
|
+
warn(message, context) {
|
|
2550
|
+
this._log("warn", message, context);
|
|
2551
|
+
}
|
|
2552
|
+
debug(message, context) {
|
|
2553
|
+
this._log("debug", message, context);
|
|
2554
|
+
}
|
|
2555
|
+
verbose(message, context) {
|
|
2556
|
+
this._log("verbose", message, context);
|
|
2557
|
+
}
|
|
2558
|
+
setContext(context) {
|
|
2559
|
+
this.context = context;
|
|
2560
|
+
}
|
|
2561
|
+
/**
|
|
2562
|
+
* Unified internal logging method that handles both Winston and NestJS Logger.
|
|
2563
|
+
*/
|
|
2564
|
+
_log(level, message, context, trace) {
|
|
2565
|
+
const ctx = context ?? this.context;
|
|
2566
|
+
if ("format" in this.activeLogger && "transports" in this.activeLogger) {
|
|
2567
|
+
const winstonLogger = this.activeLogger;
|
|
2568
|
+
const winstonLevel = level === "log" ? "info" : level;
|
|
2569
|
+
const formattedMessage = this.formatMessage(message);
|
|
2570
|
+
const metadata = this.enrichMetadata({}, ctx, trace);
|
|
2571
|
+
winstonLogger.log({
|
|
2572
|
+
level: winstonLevel,
|
|
2573
|
+
message: formattedMessage,
|
|
2574
|
+
...metadata
|
|
2575
|
+
});
|
|
2576
|
+
} else {
|
|
2577
|
+
const nestLogger = this.activeLogger;
|
|
2578
|
+
if (level === "error" && trace) {
|
|
2579
|
+
ctx ? nestLogger.error(message, trace, ctx) : nestLogger.error(message, trace);
|
|
2580
|
+
} else if (level === "log") {
|
|
2581
|
+
ctx ? nestLogger.log(message, ctx) : nestLogger.log(message);
|
|
2582
|
+
} else if (level === "warn") {
|
|
2583
|
+
ctx ? nestLogger.warn(message, ctx) : nestLogger.warn(message);
|
|
2584
|
+
} else if (level === "debug" && nestLogger.debug) {
|
|
2585
|
+
ctx ? nestLogger.debug(message, ctx) : nestLogger.debug(message);
|
|
2586
|
+
} else if (level === "verbose" && nestLogger.verbose) {
|
|
2587
|
+
ctx ? nestLogger.verbose(message, ctx) : nestLogger.verbose(message);
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
/**
|
|
2592
|
+
* Logs with custom metadata (Winston only).
|
|
2593
|
+
*/
|
|
2594
|
+
logWithMetadata(level, message, metadata, context) {
|
|
2595
|
+
const ctx = context ?? this.context;
|
|
2596
|
+
if ("format" in this.activeLogger && "transports" in this.activeLogger) {
|
|
2597
|
+
const winstonLogger = this.activeLogger;
|
|
2598
|
+
const winstonLevel = level === "log" ? "info" : level;
|
|
2599
|
+
winstonLogger.log({
|
|
2600
|
+
level: winstonLevel,
|
|
2601
|
+
message: this.formatMessage(message),
|
|
2602
|
+
...metadata
|
|
2603
|
+
});
|
|
2604
|
+
} else {
|
|
2605
|
+
const messageWithMeta = metadata ? `${message} ${JSON.stringify(metadata)}` : message;
|
|
2606
|
+
this[level](messageWithMeta, ctx);
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
formatMessage(message) {
|
|
2610
|
+
if (message instanceof Error) return message.message;
|
|
2611
|
+
if (typeof message === "object" && message !== null) {
|
|
2612
|
+
try {
|
|
2613
|
+
return JSON.stringify(message);
|
|
2614
|
+
} catch {
|
|
2615
|
+
return String(message);
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
return String(message);
|
|
2619
|
+
}
|
|
2620
|
+
/**
|
|
2621
|
+
* Enriches metadata with correlation context from AsyncLocalStorage.
|
|
2622
|
+
* Inline from winston-logger.service.ts
|
|
2623
|
+
*/
|
|
2624
|
+
enrichMetadata(metadata = {}, context, trace) {
|
|
2625
|
+
const enriched = {
|
|
2626
|
+
...metadata
|
|
2627
|
+
};
|
|
2628
|
+
if (context) enriched.context = context;
|
|
2629
|
+
const correlationContext = getCorrelationContext();
|
|
2630
|
+
if (correlationContext) {
|
|
2631
|
+
if (correlationContext.correlationId) enriched.correlationId = correlationContext.correlationId;
|
|
2632
|
+
for (const [key, value] of Object.entries(correlationContext)) {
|
|
2633
|
+
if (key !== "correlationId") {
|
|
2634
|
+
enriched[key] = value;
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
if (trace) enriched.trace = trace;
|
|
2639
|
+
return enriched;
|
|
2640
|
+
}
|
|
2641
|
+
child(context) {
|
|
2642
|
+
const childLogger = new _LoggerService(this.options, this.defaultLogger);
|
|
2643
|
+
childLogger.setContext(context);
|
|
2644
|
+
return childLogger;
|
|
2645
|
+
}
|
|
2646
|
+
};
|
|
2647
|
+
LoggerService2 = _ts_decorate14([
|
|
2648
|
+
Injectable9(),
|
|
2649
|
+
_ts_param4(0, Optional()),
|
|
2650
|
+
_ts_param4(1, Optional()),
|
|
2651
|
+
_ts_metadata8("design:type", Function),
|
|
2652
|
+
_ts_metadata8("design:paramtypes", [
|
|
2653
|
+
typeof LoggerModuleOptions === "undefined" ? Object : LoggerModuleOptions,
|
|
2654
|
+
typeof Logger10 === "undefined" ? Object : Logger10
|
|
2655
|
+
])
|
|
2656
|
+
], LoggerService2);
|
|
2657
|
+
|
|
2658
|
+
// src/logger/middleware/correlation-id.middleware.ts
|
|
2659
|
+
import { Injectable as Injectable10 } from "@nestjs/common";
|
|
2660
|
+
function _ts_decorate15(decorators, target, key, desc) {
|
|
2661
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2662
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2663
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
2664
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2665
|
+
}
|
|
2666
|
+
__name(_ts_decorate15, "_ts_decorate");
|
|
2667
|
+
function _ts_metadata9(k, v) {
|
|
2668
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2669
|
+
}
|
|
2670
|
+
__name(_ts_metadata9, "_ts_metadata");
|
|
2671
|
+
var CorrelationIdMiddleware = class {
|
|
2672
|
+
static {
|
|
2673
|
+
__name(this, "CorrelationIdMiddleware");
|
|
2674
|
+
}
|
|
2675
|
+
includeInResponse;
|
|
2676
|
+
responseHeader;
|
|
2677
|
+
constructor(options = {}) {
|
|
2678
|
+
this.includeInResponse = options.includeInResponse ?? true;
|
|
2679
|
+
this.responseHeader = options.responseHeader ?? DEFAULT_CORRELATION_HEADER;
|
|
2680
|
+
}
|
|
2681
|
+
/**
|
|
2682
|
+
* Middleware handler for processing requests.
|
|
2683
|
+
*/
|
|
2684
|
+
use(req, reply, next) {
|
|
2685
|
+
const correlationId = generateCorrelationId();
|
|
2686
|
+
if (this.includeInResponse) {
|
|
2687
|
+
addCorrelationIdToResponse(reply, correlationId, this.responseHeader);
|
|
2688
|
+
}
|
|
2689
|
+
runWithCorrelationContext({
|
|
2690
|
+
correlationId
|
|
2691
|
+
}, () => {
|
|
2692
|
+
next();
|
|
2693
|
+
});
|
|
2694
|
+
}
|
|
2695
|
+
/**
|
|
2696
|
+
* Fastify hook handler for onRequest.
|
|
2697
|
+
* This is an async function that returns a Promise, ensuring the AsyncLocalStorage
|
|
2698
|
+
* context persists throughout the entire request lifecycle.
|
|
2699
|
+
*/
|
|
2700
|
+
async onRequest(req, reply) {
|
|
2701
|
+
const correlationId = generateCorrelationId();
|
|
2702
|
+
if (this.includeInResponse) {
|
|
2703
|
+
addCorrelationIdToResponse(reply, correlationId, this.responseHeader);
|
|
2704
|
+
}
|
|
2705
|
+
const store = correlationStorage.getStore();
|
|
2706
|
+
if (!store) {
|
|
2707
|
+
correlationStorage.enterWith({
|
|
2708
|
+
correlationId
|
|
2709
|
+
});
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
};
|
|
2713
|
+
CorrelationIdMiddleware = _ts_decorate15([
|
|
2714
|
+
Injectable10(),
|
|
2715
|
+
_ts_metadata9("design:type", Function),
|
|
2716
|
+
_ts_metadata9("design:paramtypes", [
|
|
2717
|
+
typeof CorrelationIdMiddlewareOptions === "undefined" ? Object : CorrelationIdMiddlewareOptions
|
|
2718
|
+
])
|
|
2719
|
+
], CorrelationIdMiddleware);
|
|
2720
|
+
|
|
2721
|
+
// src/logger/interceptors/http-logger.interceptor.ts
|
|
2722
|
+
import { Injectable as Injectable11, Optional as Optional2 } from "@nestjs/common";
|
|
2723
|
+
import { tap as tap2, catchError } from "rxjs/operators";
|
|
2724
|
+
function _ts_decorate16(decorators, target, key, desc) {
|
|
2725
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2726
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2727
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
2728
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2729
|
+
}
|
|
2730
|
+
__name(_ts_decorate16, "_ts_decorate");
|
|
2731
|
+
function _ts_metadata10(k, v) {
|
|
2732
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2733
|
+
}
|
|
2734
|
+
__name(_ts_metadata10, "_ts_metadata");
|
|
2735
|
+
function _ts_param5(paramIndex, decorator) {
|
|
2736
|
+
return function(target, key) {
|
|
2737
|
+
decorator(target, key, paramIndex);
|
|
2738
|
+
};
|
|
2739
|
+
}
|
|
2740
|
+
__name(_ts_param5, "_ts_param");
|
|
2741
|
+
var HttpLoggerInterceptor = class {
|
|
2742
|
+
static {
|
|
2743
|
+
__name(this, "HttpLoggerInterceptor");
|
|
2744
|
+
}
|
|
2745
|
+
logger;
|
|
2746
|
+
enableRequestLog;
|
|
2747
|
+
enableResponseLog;
|
|
2748
|
+
slowRequestThreshold;
|
|
2749
|
+
constructor(logger, options) {
|
|
2750
|
+
this.logger = logger;
|
|
2751
|
+
this.enableRequestLog = options?.enableRequestLog ?? true;
|
|
2752
|
+
this.enableResponseLog = options?.enableResponseLog ?? true;
|
|
2753
|
+
this.slowRequestThreshold = options?.slowRequestThreshold ?? 3e3;
|
|
2754
|
+
}
|
|
2755
|
+
intercept(context, next) {
|
|
2756
|
+
if (context.getType() !== "http") {
|
|
2757
|
+
return next.handle();
|
|
2758
|
+
}
|
|
2759
|
+
const httpContext = context.switchToHttp();
|
|
2760
|
+
const request = httpContext.getRequest();
|
|
2761
|
+
const response = httpContext.getResponse();
|
|
2762
|
+
const startTime = Date.now();
|
|
2763
|
+
if (this.enableRequestLog) {
|
|
2764
|
+
this.logRequest(request);
|
|
2765
|
+
}
|
|
2766
|
+
return next.handle().pipe(tap2(() => {
|
|
2767
|
+
if (this.enableResponseLog) {
|
|
2768
|
+
const duration = Date.now() - startTime;
|
|
2769
|
+
this.logResponse(request, response, duration);
|
|
2770
|
+
}
|
|
2771
|
+
}), catchError((error) => {
|
|
2772
|
+
const duration = Date.now() - startTime;
|
|
2773
|
+
this.logError(request, response, duration, error);
|
|
2774
|
+
throw error;
|
|
2775
|
+
}));
|
|
2776
|
+
}
|
|
2777
|
+
logRequest(request) {
|
|
2778
|
+
try {
|
|
2779
|
+
const correlationContext = getCorrelationContext();
|
|
2780
|
+
const metadata = {
|
|
2781
|
+
type: "http_request",
|
|
2782
|
+
method: request.method,
|
|
2783
|
+
url: request.url,
|
|
2784
|
+
correlationId: correlationContext?.correlationId,
|
|
2785
|
+
ip: request.ip,
|
|
2786
|
+
userAgent: request.headers["user-agent"]
|
|
2787
|
+
};
|
|
2788
|
+
this.logger.logWithMetadata("log", `Incoming ${request.method} ${request.url}`, metadata);
|
|
2789
|
+
} catch (error) {
|
|
2790
|
+
this.logger.error("Failed to log HTTP request", error.stack);
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
logResponse(request, response, duration) {
|
|
2794
|
+
try {
|
|
2795
|
+
const correlationContext = getCorrelationContext();
|
|
2796
|
+
const statusCode = response.statusCode;
|
|
2797
|
+
const logLevel = statusCode >= 500 ? "error" : statusCode >= 400 ? "warn" : "log";
|
|
2798
|
+
const metadata = {
|
|
2799
|
+
type: "http_response",
|
|
2800
|
+
method: request.method,
|
|
2801
|
+
url: request.url,
|
|
2802
|
+
statusCode,
|
|
2803
|
+
duration,
|
|
2804
|
+
correlationId: correlationContext?.correlationId
|
|
2805
|
+
};
|
|
2806
|
+
if (duration > this.slowRequestThreshold) {
|
|
2807
|
+
metadata.slowRequest = true;
|
|
2808
|
+
}
|
|
2809
|
+
const message = metadata.slowRequest ? `SLOW ${request.method} ${request.url} ${statusCode} - ${duration}ms` : `${request.method} ${request.url} ${statusCode} - ${duration}ms`;
|
|
2810
|
+
this.logger.logWithMetadata(logLevel, message, metadata);
|
|
2811
|
+
} catch (error) {
|
|
2812
|
+
this.logger.error("Failed to log HTTP response", error.stack);
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
logError(request, response, duration, error) {
|
|
2816
|
+
try {
|
|
2817
|
+
const correlationContext = getCorrelationContext();
|
|
2818
|
+
const statusCode = response.statusCode || 500;
|
|
2819
|
+
const metadata = {
|
|
2820
|
+
type: "http_error",
|
|
2821
|
+
method: request.method,
|
|
2822
|
+
url: request.url,
|
|
2823
|
+
statusCode,
|
|
2824
|
+
duration,
|
|
2825
|
+
correlationId: correlationContext?.correlationId,
|
|
2826
|
+
errorName: error?.name || "Error",
|
|
2827
|
+
errorMessage: error?.message || "Unknown error"
|
|
2828
|
+
};
|
|
2829
|
+
if (error?.stack) {
|
|
2830
|
+
metadata.trace = error.stack;
|
|
2831
|
+
}
|
|
2832
|
+
if (error?.response) {
|
|
2833
|
+
metadata.errorDetails = error.response;
|
|
2834
|
+
}
|
|
2835
|
+
const message = `ERROR ${request.method} ${request.url} ${statusCode} - ${error?.message || "Unknown error"}`;
|
|
2836
|
+
this.logger.logWithMetadata("error", message, metadata);
|
|
2837
|
+
} catch (loggingError) {
|
|
2838
|
+
this.logger.error("Failed to log HTTP error", loggingError.stack);
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
};
|
|
2842
|
+
HttpLoggerInterceptor = _ts_decorate16([
|
|
2843
|
+
Injectable11(),
|
|
2844
|
+
_ts_param5(1, Optional2()),
|
|
2845
|
+
_ts_metadata10("design:type", Function),
|
|
2846
|
+
_ts_metadata10("design:paramtypes", [
|
|
2847
|
+
typeof LoggerService === "undefined" ? Object : LoggerService,
|
|
2848
|
+
typeof HttpLoggerOptions === "undefined" ? Object : HttpLoggerOptions
|
|
2849
|
+
])
|
|
2850
|
+
], HttpLoggerInterceptor);
|
|
2851
|
+
|
|
2852
|
+
// src/logger/logger.module.ts
|
|
2853
|
+
function _ts_decorate17(decorators, target, key, desc) {
|
|
2854
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2855
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2856
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
2857
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2858
|
+
}
|
|
2859
|
+
__name(_ts_decorate17, "_ts_decorate");
|
|
2860
|
+
var LOGGER_MODULE_OPTIONS = Symbol("LOGGER_MODULE_OPTIONS");
|
|
2861
|
+
var DEFAULT_LOGGER_OPTIONS = {
|
|
2862
|
+
provider: "winston",
|
|
2863
|
+
enableCorrelationId: true,
|
|
2864
|
+
enableHttpLogger: true,
|
|
2865
|
+
filePath: "./logs",
|
|
2866
|
+
maxFiles: "14d"
|
|
2867
|
+
};
|
|
2868
|
+
var ENVIRONMENT_PRESETS = {
|
|
2869
|
+
/**
|
|
2870
|
+
* Development preset - maximum verbosity for local development
|
|
2871
|
+
*/
|
|
2872
|
+
development: {
|
|
2873
|
+
provider: "winston",
|
|
2874
|
+
level: "debug",
|
|
2875
|
+
format: "text",
|
|
2876
|
+
enableFileLogger: false,
|
|
2877
|
+
enableCorrelationId: true,
|
|
2878
|
+
enableHttpLogger: true,
|
|
2879
|
+
httpLogger: {
|
|
2880
|
+
enableRequestLog: true,
|
|
2881
|
+
enableResponseLog: true,
|
|
2882
|
+
slowRequestThreshold: 1e3
|
|
2883
|
+
}
|
|
2884
|
+
},
|
|
2885
|
+
/**
|
|
2886
|
+
* Staging preset - moderate verbosity with file logging
|
|
2887
|
+
*/
|
|
2888
|
+
staging: {
|
|
2889
|
+
provider: "winston",
|
|
2890
|
+
level: "log",
|
|
2891
|
+
format: "json",
|
|
2892
|
+
enableFileLogger: true,
|
|
2893
|
+
enableCorrelationId: true,
|
|
2894
|
+
enableHttpLogger: true,
|
|
2895
|
+
httpLogger: {
|
|
2896
|
+
enableRequestLog: true,
|
|
2897
|
+
enableResponseLog: true,
|
|
2898
|
+
slowRequestThreshold: 3e3
|
|
2899
|
+
}
|
|
2900
|
+
},
|
|
2901
|
+
/**
|
|
2902
|
+
* Production preset - minimal verbosity with all safety features enabled
|
|
2903
|
+
*/
|
|
2904
|
+
production: {
|
|
2905
|
+
provider: "winston",
|
|
2906
|
+
level: "warn",
|
|
2907
|
+
format: "json",
|
|
2908
|
+
enableFileLogger: true,
|
|
2909
|
+
enableCorrelationId: true,
|
|
2910
|
+
enableHttpLogger: true,
|
|
2911
|
+
httpLogger: {
|
|
2912
|
+
enableRequestLog: false,
|
|
2913
|
+
enableResponseLog: true,
|
|
2914
|
+
slowRequestThreshold: 5e3
|
|
2915
|
+
}
|
|
2916
|
+
},
|
|
2917
|
+
/**
|
|
2918
|
+
* Test preset - errors only, minimal features for faster test execution
|
|
2919
|
+
*/
|
|
2920
|
+
test: {
|
|
2921
|
+
provider: "winston",
|
|
2922
|
+
level: "error",
|
|
2923
|
+
format: "json",
|
|
2924
|
+
enableFileLogger: false,
|
|
2925
|
+
enableCorrelationId: false,
|
|
2926
|
+
enableHttpLogger: false
|
|
2927
|
+
}
|
|
2928
|
+
};
|
|
2929
|
+
function mergeWithDefaults(options = {}) {
|
|
2930
|
+
const preset = options.environment ? ENVIRONMENT_PRESETS[options.environment] ?? ENVIRONMENT_PRESETS.development : ENVIRONMENT_PRESETS.development;
|
|
2931
|
+
const filteredOptions = Object.fromEntries(Object.entries(options).filter(([_, value]) => value !== void 0));
|
|
2932
|
+
if (filteredOptions.httpLogger && preset?.httpLogger) {
|
|
2933
|
+
filteredOptions.httpLogger = {
|
|
2934
|
+
...preset.httpLogger,
|
|
2935
|
+
...Object.fromEntries(Object.entries(filteredOptions.httpLogger).filter(([_, value]) => value !== void 0))
|
|
2936
|
+
};
|
|
2937
|
+
}
|
|
2938
|
+
const merged = {
|
|
2939
|
+
...DEFAULT_LOGGER_OPTIONS,
|
|
2940
|
+
...preset,
|
|
2941
|
+
...filteredOptions
|
|
2942
|
+
};
|
|
2943
|
+
return merged;
|
|
2944
|
+
}
|
|
2945
|
+
__name(mergeWithDefaults, "mergeWithDefaults");
|
|
2946
|
+
function createDefaultLoggerProvider(options) {
|
|
2947
|
+
return {
|
|
2948
|
+
provide: Logger11,
|
|
2949
|
+
useFactory: /* @__PURE__ */ __name(() => {
|
|
2950
|
+
const logger = new Logger11();
|
|
2951
|
+
if (options.level && typeof logger.setLogLevels === "function") {
|
|
2952
|
+
const levels = getLevelsUpTo(options.level);
|
|
2953
|
+
logger.setLogLevels(levels);
|
|
2954
|
+
}
|
|
2955
|
+
return logger;
|
|
2956
|
+
}, "useFactory")
|
|
2957
|
+
};
|
|
2958
|
+
}
|
|
2959
|
+
__name(createDefaultLoggerProvider, "createDefaultLoggerProvider");
|
|
2960
|
+
function createLoggerProviders(options = {}) {
|
|
2961
|
+
const mergedOptions = mergeWithDefaults(options);
|
|
2962
|
+
const providers = [
|
|
2963
|
+
// Options provider
|
|
2964
|
+
{
|
|
2965
|
+
provide: LOGGER_MODULE_OPTIONS,
|
|
2966
|
+
useValue: mergedOptions
|
|
2967
|
+
}
|
|
2968
|
+
];
|
|
2969
|
+
if (mergedOptions.provider === "default") {
|
|
2970
|
+
providers.push(createDefaultLoggerProvider(mergedOptions));
|
|
2971
|
+
}
|
|
2972
|
+
providers.push({
|
|
2973
|
+
provide: LoggerService2,
|
|
2974
|
+
useFactory: /* @__PURE__ */ __name((opts, defaultLogger) => {
|
|
2975
|
+
return new LoggerService2(opts, defaultLogger);
|
|
2976
|
+
}, "useFactory"),
|
|
2977
|
+
inject: [
|
|
2978
|
+
LOGGER_MODULE_OPTIONS,
|
|
2979
|
+
{
|
|
2980
|
+
token: Logger11,
|
|
2981
|
+
optional: true
|
|
2982
|
+
}
|
|
2983
|
+
]
|
|
2984
|
+
});
|
|
2985
|
+
providers.push({
|
|
2986
|
+
provide: CorrelationIdMiddleware,
|
|
2987
|
+
useFactory: /* @__PURE__ */ __name(() => {
|
|
2988
|
+
return new CorrelationIdMiddleware({
|
|
2989
|
+
includeInResponse: true,
|
|
2990
|
+
responseHeader: "x-correlation-id"
|
|
2991
|
+
});
|
|
2992
|
+
}, "useFactory")
|
|
2993
|
+
});
|
|
2994
|
+
providers.push({
|
|
2995
|
+
provide: HttpLoggerInterceptor,
|
|
2996
|
+
useFactory: /* @__PURE__ */ __name((logger, opts) => {
|
|
2997
|
+
const httpLoggerOptions = opts.httpLogger ?? {
|
|
2998
|
+
enableRequestLog: opts.enableHttpLogger,
|
|
2999
|
+
enableResponseLog: opts.enableHttpLogger
|
|
3000
|
+
};
|
|
3001
|
+
return new HttpLoggerInterceptor(logger, httpLoggerOptions);
|
|
3002
|
+
}, "useFactory"),
|
|
3003
|
+
inject: [
|
|
3004
|
+
LoggerService2,
|
|
3005
|
+
LOGGER_MODULE_OPTIONS
|
|
3006
|
+
]
|
|
3007
|
+
});
|
|
3008
|
+
return providers;
|
|
3009
|
+
}
|
|
3010
|
+
__name(createLoggerProviders, "createLoggerProviders");
|
|
3011
|
+
function getLevelsUpTo(level) {
|
|
3012
|
+
const allLevels = [
|
|
3013
|
+
"error",
|
|
3014
|
+
"warn",
|
|
3015
|
+
"log",
|
|
3016
|
+
"debug",
|
|
3017
|
+
"verbose"
|
|
3018
|
+
];
|
|
3019
|
+
const isValidLevel = /* @__PURE__ */ __name((l) => allLevels.includes(l), "isValidLevel");
|
|
3020
|
+
if (!isValidLevel(level)) {
|
|
3021
|
+
return [
|
|
3022
|
+
"error",
|
|
3023
|
+
"warn",
|
|
3024
|
+
"log"
|
|
3025
|
+
];
|
|
3026
|
+
}
|
|
3027
|
+
const levelIndex = allLevels.indexOf(level);
|
|
3028
|
+
return allLevels.slice(0, levelIndex + 1);
|
|
3029
|
+
}
|
|
3030
|
+
__name(getLevelsUpTo, "getLevelsUpTo");
|
|
3031
|
+
var LoggerModule = class _LoggerModule {
|
|
3032
|
+
static {
|
|
3033
|
+
__name(this, "LoggerModule");
|
|
3034
|
+
}
|
|
3035
|
+
/**
|
|
3036
|
+
* Configures the logger module with static options.
|
|
3037
|
+
*
|
|
3038
|
+
* Users must explicitly pass `environment` to select a preset.
|
|
3039
|
+
* All preset values can be overridden by passing explicit options.
|
|
3040
|
+
*
|
|
3041
|
+
* @param options - Logger configuration options
|
|
3042
|
+
* @returns Dynamic module configuration
|
|
3043
|
+
*
|
|
3044
|
+
* @example
|
|
3045
|
+
* ```typescript
|
|
3046
|
+
* // Production preset with app name
|
|
3047
|
+
* LoggerModule.forRoot({
|
|
3048
|
+
* environment: 'production',
|
|
3049
|
+
* appName: 'my-service'
|
|
3050
|
+
* })
|
|
3051
|
+
*
|
|
3052
|
+
* // Development preset with custom level
|
|
3053
|
+
* LoggerModule.forRoot({
|
|
3054
|
+
* environment: 'development',
|
|
3055
|
+
* level: 'verbose',
|
|
3056
|
+
* enableFileLogger: true
|
|
3057
|
+
* })
|
|
3058
|
+
*
|
|
3059
|
+
* // Use default NestJS logger
|
|
3060
|
+
* LoggerModule.forRoot({
|
|
3061
|
+
* provider: 'default',
|
|
3062
|
+
* environment: 'development'
|
|
3063
|
+
* })
|
|
3064
|
+
* ```
|
|
3065
|
+
*/
|
|
3066
|
+
static forRoot(options = {}) {
|
|
3067
|
+
const providers = createLoggerProviders(options);
|
|
3068
|
+
return {
|
|
3069
|
+
module: _LoggerModule,
|
|
3070
|
+
providers,
|
|
3071
|
+
exports: [
|
|
3072
|
+
LoggerService2,
|
|
3073
|
+
CorrelationIdMiddleware,
|
|
3074
|
+
HttpLoggerInterceptor,
|
|
3075
|
+
LOGGER_MODULE_OPTIONS
|
|
3076
|
+
]
|
|
3077
|
+
};
|
|
3078
|
+
}
|
|
3079
|
+
/**
|
|
3080
|
+
* Configures the logger module with async options.
|
|
3081
|
+
*
|
|
3082
|
+
* Supports dynamic configuration using:
|
|
3083
|
+
* - `useFactory`: Factory function with dependency injection
|
|
3084
|
+
* - `useClass`: Class implementing `LoggerOptionsFactory`
|
|
3085
|
+
* - `useExisting`: Existing provider implementing `LoggerOptionsFactory`
|
|
3086
|
+
*
|
|
3087
|
+
* Options from the factory/class are merged with environment preset defaults.
|
|
3088
|
+
*
|
|
3089
|
+
* @param options - Async configuration options
|
|
3090
|
+
* @returns Dynamic module configuration
|
|
3091
|
+
*
|
|
3092
|
+
* @example
|
|
3093
|
+
* ```typescript
|
|
3094
|
+
* // Factory with ConfigService
|
|
3095
|
+
* LoggerModule.forRootAsync({
|
|
3096
|
+
* imports: [ConfigModule],
|
|
3097
|
+
* useFactory: (config: ConfigService) => ({
|
|
3098
|
+
* environment: config.get('NODE_ENV', 'development'),
|
|
3099
|
+
* provider: config.get('LOG_PROVIDER', 'winston'),
|
|
3100
|
+
* level: config.get('LOG_LEVEL'),
|
|
3101
|
+
* appName: config.get('APP_NAME'),
|
|
3102
|
+
* }),
|
|
3103
|
+
* inject: [ConfigService]
|
|
3104
|
+
* })
|
|
3105
|
+
*
|
|
3106
|
+
* // Factory class
|
|
3107
|
+
* @Injectable()
|
|
3108
|
+
* class LoggerConfigService implements LoggerOptionsFactory {
|
|
3109
|
+
* createLoggerOptions(): LoggerModuleOptions {
|
|
3110
|
+
* return {
|
|
3111
|
+
* environment: 'production',
|
|
3112
|
+
* appName: 'my-service'
|
|
3113
|
+
* };
|
|
3114
|
+
* }
|
|
3115
|
+
* }
|
|
3116
|
+
*
|
|
3117
|
+
* LoggerModule.forRootAsync({
|
|
3118
|
+
* useClass: LoggerConfigService
|
|
3119
|
+
* })
|
|
3120
|
+
* ```
|
|
3121
|
+
*/
|
|
3122
|
+
static forRootAsync(options) {
|
|
3123
|
+
const asyncProviders = this.createAsyncProviders(options);
|
|
3124
|
+
return {
|
|
3125
|
+
module: _LoggerModule,
|
|
3126
|
+
imports: options.imports || [],
|
|
3127
|
+
providers: [
|
|
3128
|
+
...asyncProviders,
|
|
3129
|
+
// Default logger provider
|
|
3130
|
+
{
|
|
3131
|
+
provide: Logger11,
|
|
3132
|
+
useFactory: /* @__PURE__ */ __name((opts) => {
|
|
3133
|
+
if (opts.provider === "default") {
|
|
3134
|
+
const logger = new Logger11();
|
|
3135
|
+
if (opts.level && typeof logger.setLogLevels === "function") {
|
|
3136
|
+
const levels = getLevelsUpTo(opts.level);
|
|
3137
|
+
logger.setLogLevels(levels);
|
|
3138
|
+
}
|
|
3139
|
+
return logger;
|
|
3140
|
+
}
|
|
3141
|
+
return null;
|
|
3142
|
+
}, "useFactory"),
|
|
3143
|
+
inject: [
|
|
3144
|
+
LOGGER_MODULE_OPTIONS
|
|
3145
|
+
]
|
|
3146
|
+
},
|
|
3147
|
+
// Unified logger service
|
|
3148
|
+
{
|
|
3149
|
+
provide: LoggerService2,
|
|
3150
|
+
useFactory: /* @__PURE__ */ __name((opts, defaultLogger) => {
|
|
3151
|
+
return new LoggerService2(opts, defaultLogger);
|
|
3152
|
+
}, "useFactory"),
|
|
3153
|
+
inject: [
|
|
3154
|
+
LOGGER_MODULE_OPTIONS,
|
|
3155
|
+
{
|
|
3156
|
+
token: Logger11,
|
|
3157
|
+
optional: true
|
|
3158
|
+
}
|
|
3159
|
+
]
|
|
3160
|
+
},
|
|
3161
|
+
// Correlation ID middleware
|
|
3162
|
+
{
|
|
3163
|
+
provide: CorrelationIdMiddleware,
|
|
3164
|
+
useFactory: /* @__PURE__ */ __name(() => {
|
|
3165
|
+
return new CorrelationIdMiddleware({
|
|
3166
|
+
includeInResponse: true,
|
|
3167
|
+
responseHeader: "x-correlation-id"
|
|
3168
|
+
});
|
|
3169
|
+
}, "useFactory")
|
|
3170
|
+
},
|
|
3171
|
+
// HTTP logger interceptor
|
|
3172
|
+
{
|
|
3173
|
+
provide: HttpLoggerInterceptor,
|
|
3174
|
+
useFactory: /* @__PURE__ */ __name((logger, opts) => {
|
|
3175
|
+
const httpLoggerOptions = opts.httpLogger ?? {
|
|
3176
|
+
enableRequestLog: opts.enableHttpLogger,
|
|
3177
|
+
enableResponseLog: opts.enableHttpLogger
|
|
3178
|
+
};
|
|
3179
|
+
return new HttpLoggerInterceptor(logger, httpLoggerOptions);
|
|
3180
|
+
}, "useFactory"),
|
|
3181
|
+
inject: [
|
|
3182
|
+
LoggerService2,
|
|
3183
|
+
LOGGER_MODULE_OPTIONS
|
|
3184
|
+
]
|
|
3185
|
+
}
|
|
3186
|
+
],
|
|
3187
|
+
exports: [
|
|
3188
|
+
LoggerService2,
|
|
3189
|
+
CorrelationIdMiddleware,
|
|
3190
|
+
HttpLoggerInterceptor,
|
|
3191
|
+
LOGGER_MODULE_OPTIONS
|
|
3192
|
+
]
|
|
3193
|
+
};
|
|
3194
|
+
}
|
|
3195
|
+
/**
|
|
3196
|
+
* Configures middleware for the module.
|
|
3197
|
+
* Middleware is registered globally in main.ts using Fastify hooks.
|
|
3198
|
+
*/
|
|
3199
|
+
configure(consumer) {
|
|
3200
|
+
}
|
|
3201
|
+
/**
|
|
3202
|
+
* Creates async providers for dynamic module configuration.
|
|
3203
|
+
*/
|
|
3204
|
+
static createAsyncProviders(options) {
|
|
3205
|
+
if (options.useFactory) {
|
|
3206
|
+
return [
|
|
3207
|
+
this.createAsyncOptionsProvider(options)
|
|
3208
|
+
];
|
|
3209
|
+
}
|
|
3210
|
+
const providers = [
|
|
3211
|
+
this.createAsyncOptionsProvider(options)
|
|
3212
|
+
];
|
|
3213
|
+
if (options.useClass) {
|
|
3214
|
+
providers.push({
|
|
3215
|
+
provide: options.useClass,
|
|
3216
|
+
useClass: options.useClass
|
|
3217
|
+
});
|
|
3218
|
+
}
|
|
3219
|
+
return providers;
|
|
3220
|
+
}
|
|
3221
|
+
/**
|
|
3222
|
+
* Creates the async options provider.
|
|
3223
|
+
*/
|
|
3224
|
+
static createAsyncOptionsProvider(options) {
|
|
3225
|
+
if (options.useFactory) {
|
|
3226
|
+
return {
|
|
3227
|
+
provide: LOGGER_MODULE_OPTIONS,
|
|
3228
|
+
useFactory: /* @__PURE__ */ __name(async (...args) => {
|
|
3229
|
+
const userOptions = await options.useFactory(...args);
|
|
3230
|
+
return mergeWithDefaults(userOptions);
|
|
3231
|
+
}, "useFactory"),
|
|
3232
|
+
inject: options.inject || []
|
|
3233
|
+
};
|
|
3234
|
+
}
|
|
3235
|
+
if (options.useClass) {
|
|
3236
|
+
return {
|
|
3237
|
+
provide: LOGGER_MODULE_OPTIONS,
|
|
3238
|
+
useFactory: /* @__PURE__ */ __name(async (optionsFactory) => {
|
|
3239
|
+
const userOptions = await optionsFactory.createLoggerOptions();
|
|
3240
|
+
return mergeWithDefaults(userOptions);
|
|
3241
|
+
}, "useFactory"),
|
|
3242
|
+
inject: [
|
|
3243
|
+
options.useClass
|
|
3244
|
+
]
|
|
3245
|
+
};
|
|
3246
|
+
}
|
|
3247
|
+
if (options.useExisting) {
|
|
3248
|
+
return {
|
|
3249
|
+
provide: LOGGER_MODULE_OPTIONS,
|
|
3250
|
+
useFactory: /* @__PURE__ */ __name(async (optionsFactory) => {
|
|
3251
|
+
const userOptions = await optionsFactory.createLoggerOptions();
|
|
3252
|
+
return mergeWithDefaults(userOptions);
|
|
3253
|
+
}, "useFactory"),
|
|
3254
|
+
inject: [
|
|
3255
|
+
options.useExisting
|
|
3256
|
+
]
|
|
3257
|
+
};
|
|
3258
|
+
}
|
|
3259
|
+
throw new Error("LoggerModule.forRootAsync() requires one of: useFactory, useClass, or useExisting");
|
|
3260
|
+
}
|
|
3261
|
+
};
|
|
3262
|
+
LoggerModule = _ts_decorate17([
|
|
3263
|
+
Global4(),
|
|
3264
|
+
Module5({})
|
|
3265
|
+
], LoggerModule);
|
|
2348
3266
|
export {
|
|
2349
3267
|
AuthConfigModule,
|
|
2350
3268
|
BadGatewayException,
|
|
2351
3269
|
BadRequestException,
|
|
2352
3270
|
BaseFieldException,
|
|
2353
3271
|
ConflictException,
|
|
3272
|
+
CorrelationIdMiddleware,
|
|
2354
3273
|
CsrfGuard,
|
|
3274
|
+
DEFAULT_CORRELATION_HEADER,
|
|
2355
3275
|
DatabaseModule,
|
|
2356
3276
|
ForbiddenException2 as ForbiddenException,
|
|
2357
3277
|
GoneException,
|
|
2358
3278
|
HttpExceptionFilter,
|
|
3279
|
+
HttpLoggerInterceptor,
|
|
2359
3280
|
HttpModule,
|
|
2360
3281
|
InternalServerErrorException3 as InternalServerErrorException,
|
|
3282
|
+
LOGGER_MODULE_OPTIONS,
|
|
3283
|
+
LoggerModule,
|
|
3284
|
+
LoggerService2 as LoggerService,
|
|
2361
3285
|
MethodNotAllowedException,
|
|
2362
3286
|
NotAcceptableException,
|
|
2363
3287
|
NotFoundException,
|
|
@@ -2379,6 +3303,12 @@ export {
|
|
|
2379
3303
|
UnsupportedMediaTypeException,
|
|
2380
3304
|
ValidationException,
|
|
2381
3305
|
VrittiAuthGuard,
|
|
2382
|
-
|
|
3306
|
+
addCorrelationIdToResponse,
|
|
3307
|
+
correlationStorage,
|
|
3308
|
+
generateCorrelationId,
|
|
3309
|
+
getCorrelationContext,
|
|
3310
|
+
getHttpStatusTitle,
|
|
3311
|
+
runWithCorrelationContext,
|
|
3312
|
+
updateCorrelationContext
|
|
2383
3313
|
};
|
|
2384
3314
|
//# sourceMappingURL=index.js.map
|