@flusys/nestjs-shared 3.0.1 → 4.0.0-lts
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 +159 -79
- package/cjs/classes/api-controller.class.js +26 -8
- package/cjs/classes/api-service.class.js +100 -17
- package/cjs/classes/winston-logger-adapter.class.js +15 -20
- package/cjs/classes/winston.logger.class.js +103 -70
- package/cjs/constants/index.js +1 -0
- package/cjs/constants/message-keys.js +80 -0
- package/cjs/constants/permissions.js +32 -1
- package/cjs/decorators/index.js +1 -0
- package/cjs/decorators/log-action.decorator.js +149 -0
- package/cjs/dtos/response-payload.dto.js +72 -0
- package/cjs/exceptions/base-app.exception.js +145 -0
- package/cjs/exceptions/index.js +1 -0
- package/cjs/exceptions/permission.exception.js +12 -8
- package/cjs/filters/global-exception.filter.js +167 -0
- package/cjs/filters/index.js +18 -0
- package/cjs/guards/jwt-auth.guard.js +4 -1
- package/cjs/guards/permission.guard.js +6 -13
- package/cjs/index.js +1 -0
- package/cjs/interceptors/idempotency.interceptor.js +1 -1
- package/cjs/interceptors/index.js +0 -1
- package/cjs/interfaces/logger.interface.js +1 -4
- package/cjs/middlewares/logger.middleware.js +83 -26
- package/cjs/modules/datasource/multi-tenant-datasource.service.js +33 -11
- package/cjs/modules/utils/utils.service.js +4 -20
- package/cjs/utils/index.js +0 -1
- package/cjs/utils/query-helpers.util.js +8 -1
- package/classes/api-controller.class.d.ts +1 -0
- package/classes/api-service.class.d.ts +5 -10
- package/classes/winston-logger-adapter.class.d.ts +12 -11
- package/classes/winston.logger.class.d.ts +1 -0
- package/constants/index.d.ts +1 -0
- package/constants/message-keys.d.ts +81 -0
- package/constants/permissions.d.ts +36 -0
- package/decorators/index.d.ts +1 -0
- package/decorators/log-action.decorator.d.ts +8 -0
- package/dtos/response-payload.dto.d.ts +8 -0
- package/exceptions/base-app.exception.d.ts +41 -0
- package/exceptions/index.d.ts +1 -0
- package/exceptions/permission.exception.d.ts +1 -1
- package/fesm/classes/api-controller.class.js +26 -8
- package/fesm/classes/api-service.class.js +101 -18
- package/fesm/classes/winston-logger-adapter.class.js +18 -44
- package/fesm/classes/winston.logger.class.js +100 -68
- package/fesm/constants/index.js +2 -0
- package/fesm/constants/message-keys.js +59 -0
- package/fesm/constants/permissions.js +24 -1
- package/fesm/decorators/index.js +1 -0
- package/fesm/decorators/log-action.decorator.js +139 -0
- package/fesm/dtos/response-payload.dto.js +72 -0
- package/fesm/exceptions/base-app.exception.js +109 -0
- package/fesm/exceptions/index.js +1 -0
- package/fesm/exceptions/permission.exception.js +15 -17
- package/fesm/filters/global-exception.filter.js +157 -0
- package/fesm/filters/index.js +1 -0
- package/fesm/guards/jwt-auth.guard.js +5 -2
- package/fesm/guards/permission.guard.js +8 -15
- package/fesm/index.js +1 -0
- package/fesm/interceptors/idempotency.interceptor.js +2 -2
- package/fesm/interceptors/index.js +0 -1
- package/fesm/interfaces/logger.interface.js +1 -4
- package/fesm/middlewares/logger.middleware.js +83 -26
- package/fesm/modules/datasource/multi-tenant-datasource.service.js +34 -12
- package/fesm/modules/utils/utils.service.js +5 -21
- package/fesm/utils/index.js +0 -1
- package/fesm/utils/query-helpers.util.js +8 -1
- package/filters/global-exception.filter.d.ts +10 -0
- package/filters/index.d.ts +1 -0
- package/guards/permission.guard.d.ts +1 -3
- package/index.d.ts +1 -0
- package/interceptors/index.d.ts +0 -1
- package/interfaces/logger.interface.d.ts +5 -5
- package/modules/datasource/multi-tenant-datasource.service.d.ts +1 -2
- package/modules/utils/utils.service.d.ts +0 -1
- package/package.json +5 -3
- package/utils/index.d.ts +0 -1
- package/cjs/interceptors/query-performance.interceptor.js +0 -66
- package/cjs/utils/error-handler.util.js +0 -90
- package/fesm/interceptors/query-performance.interceptor.js +0 -56
- package/fesm/utils/error-handler.util.js +0 -82
- package/interceptors/query-performance.interceptor.d.ts +0 -8
- package/utils/error-handler.util.d.ts +0 -19
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { HttpException, HttpStatus } from '@nestjs/common';
|
|
2
|
+
export interface IValidationError {
|
|
3
|
+
field: string;
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
export interface IBaseAppExceptionOptions {
|
|
7
|
+
message: string;
|
|
8
|
+
messageKey?: string;
|
|
9
|
+
messageParams?: Record<string, unknown>;
|
|
10
|
+
status?: HttpStatus;
|
|
11
|
+
errors?: IValidationError[];
|
|
12
|
+
metadata?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
export declare class BaseAppException extends HttpException {
|
|
15
|
+
readonly messageKey: string;
|
|
16
|
+
readonly messageParams?: Record<string, unknown>;
|
|
17
|
+
readonly errors?: IValidationError[];
|
|
18
|
+
readonly metadata?: Record<string, unknown>;
|
|
19
|
+
constructor(options: IBaseAppExceptionOptions);
|
|
20
|
+
}
|
|
21
|
+
export declare class NotFoundException extends BaseAppException {
|
|
22
|
+
constructor(entity: string, id?: string);
|
|
23
|
+
}
|
|
24
|
+
export declare class ValidationException extends BaseAppException {
|
|
25
|
+
constructor(errors: IValidationError[]);
|
|
26
|
+
}
|
|
27
|
+
export declare class UnauthorizedException extends BaseAppException {
|
|
28
|
+
constructor(message?: string);
|
|
29
|
+
}
|
|
30
|
+
export declare class ForbiddenException extends BaseAppException {
|
|
31
|
+
constructor(message?: string);
|
|
32
|
+
}
|
|
33
|
+
export declare class ConflictException extends BaseAppException {
|
|
34
|
+
constructor(entity: string, field?: string);
|
|
35
|
+
}
|
|
36
|
+
export declare class InternalServerException extends BaseAppException {
|
|
37
|
+
constructor(message?: string);
|
|
38
|
+
}
|
|
39
|
+
export declare class ServiceUnavailableException extends BaseAppException {
|
|
40
|
+
constructor(service: string);
|
|
41
|
+
}
|
package/exceptions/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ForbiddenException, InternalServerErrorException } from '@nestjs/common';
|
|
2
2
|
export declare class PermissionSystemUnavailableException extends InternalServerErrorException {
|
|
3
|
-
constructor(
|
|
3
|
+
constructor();
|
|
4
4
|
}
|
|
5
5
|
export declare class InsufficientPermissionsException extends ForbiddenException {
|
|
6
6
|
constructor(missingPermissions: string[], operator?: 'AND' | 'OR');
|
|
@@ -78,6 +78,8 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
|
|
|
78
78
|
return applyDecorators(...decorators);
|
|
79
79
|
}
|
|
80
80
|
/** Creates an API Controller with standardized CRUD endpoints (POST-only RPC pattern) */ export function createApiController(createDtoClass, updateDtoClass, responseDtoClass, options = {}) {
|
|
81
|
+
// Entity name for message keys (defaults to 'item')
|
|
82
|
+
const entityName = options.entityName ?? 'item';
|
|
81
83
|
// Determine if security is global (applies to all) or per-endpoint
|
|
82
84
|
const securityConfig = options.security;
|
|
83
85
|
const endpointKeys = [
|
|
@@ -114,7 +116,8 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
|
|
|
114
116
|
const data = plainToInstance(responseDtoClass, entity);
|
|
115
117
|
return {
|
|
116
118
|
success: true,
|
|
117
|
-
message:
|
|
119
|
+
message: `${entityName.charAt(0).toUpperCase() + entityName.slice(1)} created successfully`,
|
|
120
|
+
messageKey: `${entityName}.create.success`,
|
|
118
121
|
data
|
|
119
122
|
};
|
|
120
123
|
}
|
|
@@ -123,7 +126,11 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
|
|
|
123
126
|
const data = entities.map((item)=>plainToInstance(responseDtoClass, item));
|
|
124
127
|
return {
|
|
125
128
|
success: true,
|
|
126
|
-
message: `${data.length}
|
|
129
|
+
message: `${data.length} ${entityName}s created successfully`,
|
|
130
|
+
messageKey: `${entityName}.create.many.success`,
|
|
131
|
+
messageVariables: {
|
|
132
|
+
count: data.length
|
|
133
|
+
},
|
|
127
134
|
data,
|
|
128
135
|
meta: {
|
|
129
136
|
count: data.length,
|
|
@@ -137,7 +144,8 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
|
|
|
137
144
|
const data = plainToInstance(responseDtoClass, entity);
|
|
138
145
|
return {
|
|
139
146
|
success: true,
|
|
140
|
-
message:
|
|
147
|
+
message: `${entityName.charAt(0).toUpperCase() + entityName.slice(1)} retrieved successfully`,
|
|
148
|
+
messageKey: `${entityName}.get.success`,
|
|
141
149
|
data
|
|
142
150
|
};
|
|
143
151
|
}
|
|
@@ -146,7 +154,8 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
|
|
|
146
154
|
const data = plainToInstance(responseDtoClass, entity);
|
|
147
155
|
return {
|
|
148
156
|
success: true,
|
|
149
|
-
message:
|
|
157
|
+
message: `${entityName.charAt(0).toUpperCase() + entityName.slice(1)} updated successfully`,
|
|
158
|
+
messageKey: `${entityName}.update.success`,
|
|
150
159
|
data
|
|
151
160
|
};
|
|
152
161
|
}
|
|
@@ -155,7 +164,11 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
|
|
|
155
164
|
const data = plainToInstance(responseDtoClass, entities);
|
|
156
165
|
return {
|
|
157
166
|
success: true,
|
|
158
|
-
message: `${data.length}
|
|
167
|
+
message: `${data.length} ${entityName}s updated successfully`,
|
|
168
|
+
messageKey: `${entityName}.update.many.success`,
|
|
169
|
+
messageVariables: {
|
|
170
|
+
count: data.length
|
|
171
|
+
},
|
|
159
172
|
data,
|
|
160
173
|
meta: {
|
|
161
174
|
count: data.length,
|
|
@@ -172,7 +185,8 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
|
|
|
172
185
|
const totalPages = pageSize > 0 ? Math.ceil(result.total / pageSize) : 1;
|
|
173
186
|
return {
|
|
174
187
|
success: true,
|
|
175
|
-
message:
|
|
188
|
+
message: `${entityName.charAt(0).toUpperCase() + entityName.slice(1)}s retrieved successfully`,
|
|
189
|
+
messageKey: `${entityName}.get.all.success`,
|
|
176
190
|
data,
|
|
177
191
|
meta: {
|
|
178
192
|
total: result.total,
|
|
@@ -187,10 +201,14 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
|
|
|
187
201
|
async delete(deleteDto, user) {
|
|
188
202
|
await this.service.delete(deleteDto, user);
|
|
189
203
|
const count = Array.isArray(deleteDto.id) ? deleteDto.id.length : 1;
|
|
190
|
-
const action = deleteDto.type === 'restore' ? '
|
|
204
|
+
const action = deleteDto.type === 'restore' ? 'restore' : 'delete';
|
|
191
205
|
return {
|
|
192
206
|
success: true,
|
|
193
|
-
message: `${count}
|
|
207
|
+
message: `${count} ${entityName}${count > 1 ? 's' : ''} ${action}d successfully`,
|
|
208
|
+
messageKey: `${entityName}.${action}.success`,
|
|
209
|
+
messageVariables: {
|
|
210
|
+
count
|
|
211
|
+
}
|
|
194
212
|
};
|
|
195
213
|
}
|
|
196
214
|
constructor(service){
|
|
@@ -11,9 +11,20 @@ function _define_property(obj, key, value) {
|
|
|
11
11
|
}
|
|
12
12
|
return obj;
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
15
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
16
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
17
|
+
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;
|
|
18
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
19
|
+
}
|
|
20
|
+
function _ts_metadata(k, v) {
|
|
21
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
22
|
+
}
|
|
23
|
+
import { DeleteDto } from '../dtos';
|
|
24
|
+
import { InternalServerErrorException, NotFoundException } from '@nestjs/common';
|
|
25
|
+
import { SYSTEM_MESSAGES } from '../constants';
|
|
16
26
|
import { In } from 'typeorm';
|
|
27
|
+
import { LogAction } from '../decorators/log-action.decorator';
|
|
17
28
|
/** Generic API service with CRUD operations and caching support */ export class ApiService {
|
|
18
29
|
async insert(dto, user) {
|
|
19
30
|
return this.executeInTransaction('insert', async (qr)=>{
|
|
@@ -78,7 +89,10 @@ import { In } from 'typeorm';
|
|
|
78
89
|
output = await finalQuery.getMany();
|
|
79
90
|
}
|
|
80
91
|
const result = this.convertEntityListToResponseListDto(output, isRaw);
|
|
81
|
-
if (!result || !result.length) throw new NotFoundException(
|
|
92
|
+
if (!result || !result.length) throw new NotFoundException({
|
|
93
|
+
message: 'No Data Found',
|
|
94
|
+
messageKey: SYSTEM_MESSAGES.NOT_FOUND
|
|
95
|
+
});
|
|
82
96
|
return result;
|
|
83
97
|
} catch (error) {
|
|
84
98
|
if (error instanceof NotFoundException) {
|
|
@@ -109,7 +123,10 @@ import { In } from 'typeorm';
|
|
|
109
123
|
if (convertResult.length) result = convertResult[0];
|
|
110
124
|
} else {
|
|
111
125
|
output = await finalQuery.getOne();
|
|
112
|
-
if (!output) throw new NotFoundException(
|
|
126
|
+
if (!output) throw new NotFoundException({
|
|
127
|
+
message: 'No Data Found',
|
|
128
|
+
messageKey: SYSTEM_MESSAGES.NOT_FOUND
|
|
129
|
+
});
|
|
113
130
|
result = this.convertEntityToResponseDto(output, false);
|
|
114
131
|
}
|
|
115
132
|
if (this.isCacheable) {
|
|
@@ -199,7 +216,15 @@ import { In } from 'typeorm';
|
|
|
199
216
|
const parameters = rawSubQuery.getParameters();
|
|
200
217
|
const orderedValues = [];
|
|
201
218
|
const convertedQuery = countSql.replace(/:(\w+)/g, (_, key)=>{
|
|
202
|
-
if (!(key in parameters))
|
|
219
|
+
if (!(key in parameters)) {
|
|
220
|
+
throw new InternalServerErrorException({
|
|
221
|
+
message: `Missing parameter value for: ${key}`,
|
|
222
|
+
messageKey: SYSTEM_MESSAGES.MISSING_PARAMETER,
|
|
223
|
+
messageParams: {
|
|
224
|
+
key
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
203
228
|
orderedValues.push(parameters[key]);
|
|
204
229
|
return '?';
|
|
205
230
|
});
|
|
@@ -272,13 +297,14 @@ import { In } from 'typeorm';
|
|
|
272
297
|
async clearCacheForId(entities) {
|
|
273
298
|
await Promise.all(entities.map((item)=>this.utilsService.clearCache(this.entityName, this.cacheManager, item.id)));
|
|
274
299
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
300
|
+
handleError(error, _operation) {
|
|
301
|
+
if (error instanceof Error) {
|
|
302
|
+
throw error;
|
|
303
|
+
}
|
|
304
|
+
throw new InternalServerErrorException({
|
|
305
|
+
message: typeof error === 'string' ? error : 'Unknown error',
|
|
306
|
+
messageKey: SYSTEM_MESSAGES.INTERNAL_ERROR
|
|
280
307
|
});
|
|
281
|
-
ErrorHandler.rethrowError(error);
|
|
282
308
|
}
|
|
283
309
|
/** Ensures value is always an array */ ensureArray(value) {
|
|
284
310
|
return Array.isArray(value) ? value : [
|
|
@@ -375,7 +401,10 @@ import { In } from 'typeorm';
|
|
|
375
401
|
id: d.id
|
|
376
402
|
}
|
|
377
403
|
});
|
|
378
|
-
if (!existing) throw new NotFoundException(
|
|
404
|
+
if (!existing) throw new NotFoundException({
|
|
405
|
+
message: 'No such entity data found for update! Please, Try Again.',
|
|
406
|
+
messageKey: SYSTEM_MESSAGES.NOT_FOUND
|
|
407
|
+
});
|
|
379
408
|
return Object.assign(existing, d);
|
|
380
409
|
}
|
|
381
410
|
delete d.id;
|
|
@@ -393,21 +422,75 @@ import { In } from 'typeorm';
|
|
|
393
422
|
async getEntityClass() {
|
|
394
423
|
return await this.repository.create();
|
|
395
424
|
}
|
|
396
|
-
constructor(entityName, repository, cacheManager, utilsService,
|
|
425
|
+
constructor(entityName, repository, cacheManager, utilsService, _loggerName, isCacheable = false, moduleName){
|
|
397
426
|
_define_property(this, "entityName", void 0);
|
|
398
427
|
_define_property(this, "repository", void 0);
|
|
399
428
|
_define_property(this, "cacheManager", void 0);
|
|
400
429
|
_define_property(this, "utilsService", void 0);
|
|
401
|
-
_define_property(this, "
|
|
430
|
+
_define_property(this, "_loggerName", void 0);
|
|
402
431
|
_define_property(this, "isCacheable", void 0);
|
|
403
|
-
_define_property(this, "
|
|
432
|
+
_define_property(this, "moduleName", void 0);
|
|
404
433
|
this.entityName = entityName;
|
|
405
434
|
this.repository = repository;
|
|
406
435
|
this.cacheManager = cacheManager;
|
|
407
436
|
this.utilsService = utilsService;
|
|
408
|
-
this.
|
|
437
|
+
this._loggerName = _loggerName;
|
|
409
438
|
this.isCacheable = isCacheable;
|
|
410
|
-
this.
|
|
411
|
-
this.logger = new Logger(loggerName);
|
|
439
|
+
this.moduleName = moduleName;
|
|
412
440
|
}
|
|
413
441
|
}
|
|
442
|
+
_ts_decorate([
|
|
443
|
+
LogAction({
|
|
444
|
+
action: 'create'
|
|
445
|
+
}),
|
|
446
|
+
_ts_metadata("design:type", Function),
|
|
447
|
+
_ts_metadata("design:paramtypes", [
|
|
448
|
+
typeof CreateDtoT === "undefined" ? Object : CreateDtoT,
|
|
449
|
+
Object
|
|
450
|
+
]),
|
|
451
|
+
_ts_metadata("design:returntype", Promise)
|
|
452
|
+
], ApiService.prototype, "insert", null);
|
|
453
|
+
_ts_decorate([
|
|
454
|
+
LogAction({
|
|
455
|
+
action: 'createMany'
|
|
456
|
+
}),
|
|
457
|
+
_ts_metadata("design:type", Function),
|
|
458
|
+
_ts_metadata("design:paramtypes", [
|
|
459
|
+
typeof Array === "undefined" ? Object : Array,
|
|
460
|
+
Object
|
|
461
|
+
]),
|
|
462
|
+
_ts_metadata("design:returntype", Promise)
|
|
463
|
+
], ApiService.prototype, "insertMany", null);
|
|
464
|
+
_ts_decorate([
|
|
465
|
+
LogAction({
|
|
466
|
+
action: 'update'
|
|
467
|
+
}),
|
|
468
|
+
_ts_metadata("design:type", Function),
|
|
469
|
+
_ts_metadata("design:paramtypes", [
|
|
470
|
+
typeof UpdateDtoT === "undefined" ? Object : UpdateDtoT,
|
|
471
|
+
Object
|
|
472
|
+
]),
|
|
473
|
+
_ts_metadata("design:returntype", Promise)
|
|
474
|
+
], ApiService.prototype, "update", null);
|
|
475
|
+
_ts_decorate([
|
|
476
|
+
LogAction({
|
|
477
|
+
action: 'updateMany'
|
|
478
|
+
}),
|
|
479
|
+
_ts_metadata("design:type", Function),
|
|
480
|
+
_ts_metadata("design:paramtypes", [
|
|
481
|
+
Array,
|
|
482
|
+
Object
|
|
483
|
+
]),
|
|
484
|
+
_ts_metadata("design:returntype", Promise)
|
|
485
|
+
], ApiService.prototype, "updateMany", null);
|
|
486
|
+
_ts_decorate([
|
|
487
|
+
LogAction({
|
|
488
|
+
action: 'delete'
|
|
489
|
+
}),
|
|
490
|
+
_ts_metadata("design:type", Function),
|
|
491
|
+
_ts_metadata("design:paramtypes", [
|
|
492
|
+
typeof DeleteDto === "undefined" ? Object : DeleteDto,
|
|
493
|
+
Object
|
|
494
|
+
]),
|
|
495
|
+
_ts_metadata("design:returntype", Promise)
|
|
496
|
+
], ApiService.prototype, "delete", null);
|
|
@@ -11,39 +11,17 @@ function _define_property(obj, key, value) {
|
|
|
11
11
|
}
|
|
12
12
|
return obj;
|
|
13
13
|
}
|
|
14
|
-
import { instance as winstonLogger } from './winston.logger.class';
|
|
14
|
+
import { instance as winstonLogger, createModuleLogger } from './winston.logger.class';
|
|
15
15
|
import { getRequestId, getUserId, getTenantId, getCompanyId } from '../middlewares/logger.middleware';
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const tenantId = getTenantId();
|
|
24
|
-
const companyId = getCompanyId();
|
|
25
|
-
if (requestId) meta.requestId = requestId;
|
|
26
|
-
if (userId) meta.userId = userId;
|
|
27
|
-
if (tenantId) meta.tenantId = tenantId;
|
|
28
|
-
if (companyId) meta.companyId = companyId;
|
|
29
|
-
return meta;
|
|
16
|
+
function getCorrelationMeta() {
|
|
17
|
+
return Object.fromEntries(Object.entries({
|
|
18
|
+
requestId: getRequestId(),
|
|
19
|
+
userId: getUserId(),
|
|
20
|
+
tenantId: getTenantId(),
|
|
21
|
+
companyId: getCompanyId()
|
|
22
|
+
}).filter(([, v])=>v != null));
|
|
30
23
|
}
|
|
31
|
-
|
|
32
|
-
* Winston Logger Adapter
|
|
33
|
-
* Adapts Winston logger to ILogger interface for dependency injection
|
|
34
|
-
*
|
|
35
|
-
* Features:
|
|
36
|
-
* - Automatic correlation ID injection
|
|
37
|
-
* - User ID tracking
|
|
38
|
-
* - Structured metadata logging
|
|
39
|
-
*
|
|
40
|
-
* Usage:
|
|
41
|
-
* ```typescript
|
|
42
|
-
* const logger = new WinstonLoggerAdapter('MyService');
|
|
43
|
-
* logger.log('Operation completed', undefined, { orderId: '123' });
|
|
44
|
-
* // Output: { requestId: 'uuid', userId: 'user-id', context: 'MyService', message: 'Operation completed', orderId: '123' }
|
|
45
|
-
* ```
|
|
46
|
-
*/ export class WinstonLoggerAdapter {
|
|
24
|
+
export class WinstonLoggerAdapter {
|
|
47
25
|
buildLogMeta(context, args, extra) {
|
|
48
26
|
const meta = args?.length && typeof args[0] === 'object' ? args[0] : {};
|
|
49
27
|
return {
|
|
@@ -54,34 +32,30 @@ import { getRequestId, getUserId, getTenantId, getCompanyId } from '../middlewar
|
|
|
54
32
|
};
|
|
55
33
|
}
|
|
56
34
|
log(message, context, ...args) {
|
|
57
|
-
|
|
35
|
+
this.logger.info(message, this.buildLogMeta(context, args));
|
|
58
36
|
}
|
|
59
37
|
error(message, trace, context, ...args) {
|
|
60
|
-
|
|
38
|
+
this.logger.error(message, this.buildLogMeta(context, args, {
|
|
61
39
|
stack: trace
|
|
62
40
|
}));
|
|
63
41
|
}
|
|
64
42
|
warn(message, context, ...args) {
|
|
65
|
-
|
|
43
|
+
this.logger.warn(message, this.buildLogMeta(context, args));
|
|
66
44
|
}
|
|
67
45
|
debug(message, context, ...args) {
|
|
68
|
-
|
|
46
|
+
this.logger.debug(message, this.buildLogMeta(context, args));
|
|
69
47
|
}
|
|
70
48
|
verbose(message, context, ...args) {
|
|
71
|
-
|
|
49
|
+
this.logger.verbose(message, this.buildLogMeta(context, args));
|
|
72
50
|
}
|
|
73
|
-
constructor(context){
|
|
51
|
+
constructor(context, moduleName){
|
|
74
52
|
_define_property(this, "context", void 0);
|
|
53
|
+
_define_property(this, "logger", void 0);
|
|
75
54
|
this.context = context;
|
|
55
|
+
this.logger = moduleName ? createModuleLogger(moduleName) : winstonLogger;
|
|
76
56
|
}
|
|
77
57
|
}
|
|
78
|
-
|
|
79
|
-
* NestJS Logger Adapter
|
|
80
|
-
* Adapts NestJS Logger to ILogger interface
|
|
81
|
-
*
|
|
82
|
-
* Use this when you need to use NestJS's built-in logger
|
|
83
|
-
* instead of Winston (e.g., for testing)
|
|
84
|
-
*/ export class NestLoggerAdapter {
|
|
58
|
+
export class NestLoggerAdapter {
|
|
85
59
|
formatMessage(message, args) {
|
|
86
60
|
return args?.length ? `${message} ${JSON.stringify(args)}` : message;
|
|
87
61
|
}
|
|
@@ -16,9 +16,9 @@ import { existsSync, mkdirSync } from 'fs';
|
|
|
16
16
|
import { join } from 'path';
|
|
17
17
|
import { createLogger, format, transports } from 'winston';
|
|
18
18
|
// Handle both webpack (no default export) and esbuild (default export)
|
|
19
|
+
import * as DailyRotateFileModule from 'winston-daily-rotate-file';
|
|
19
20
|
import * as TransportModule from 'winston-transport';
|
|
20
21
|
const Transport = TransportModule.default || TransportModule;
|
|
21
|
-
import * as DailyRotateFileModule from 'winston-daily-rotate-file';
|
|
22
22
|
const DailyRotateFile = DailyRotateFileModule.default || DailyRotateFileModule;
|
|
23
23
|
// Configuration
|
|
24
24
|
const logConfig = envConfig.getLogConfig();
|
|
@@ -35,33 +35,48 @@ if (!existsSync(LOG_DIR)) {
|
|
|
35
35
|
}
|
|
36
36
|
// Custom Formats
|
|
37
37
|
/**
|
|
38
|
-
* Custom format for structured logging
|
|
39
|
-
*
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
38
|
+
* Custom format for structured logging (production)
|
|
39
|
+
* Multi-line format:
|
|
40
|
+
* - Line 1: Timestamp
|
|
41
|
+
* - Line 2: Log Level
|
|
42
|
+
* - Line 3: [RequestId]
|
|
43
|
+
* - Line 4: Empty
|
|
44
|
+
* - Line 5+: Message with context
|
|
45
|
+
*/ const LEVEL_MAP = {
|
|
46
|
+
error: 'Error',
|
|
47
|
+
warn: 'Warning',
|
|
48
|
+
info: 'Information',
|
|
49
|
+
debug: 'Debug',
|
|
50
|
+
verbose: 'Verbose'
|
|
51
|
+
};
|
|
52
|
+
const LOG_SEPARATOR = '─'.repeat(80);
|
|
53
|
+
const structuredFormat = format.printf(({ timestamp, level, message, context, requestId, userId, tenantId, companyId, method, url, path, statusCode, duration, stack, ...metadata })=>{
|
|
54
|
+
const reqId = requestId || 'no-request-id';
|
|
55
|
+
const levelFormatted = LEVEL_MAP[level] || level.charAt(0).toUpperCase() + level.slice(1);
|
|
56
|
+
const contextParts = [];
|
|
57
|
+
if (context) contextParts.push(`Context: ${context}`);
|
|
58
|
+
if (method && (path || url)) contextParts.push(`Endpoint: ${method} ${path || url}`);
|
|
59
|
+
if (statusCode) contextParts.push(`Status: ${statusCode}`);
|
|
60
|
+
if (duration) contextParts.push(`Duration: ${duration}`);
|
|
61
|
+
if (userId) contextParts.push(`UserId: ${userId}`);
|
|
62
|
+
if (tenantId) contextParts.push(`TenantId: ${tenantId}`);
|
|
63
|
+
if (companyId) contextParts.push(`CompanyId: ${companyId}`);
|
|
64
|
+
let content = message;
|
|
65
|
+
if (contextParts.length > 0) {
|
|
66
|
+
content = `${contextParts.join(' | ')}\n${message}`;
|
|
67
|
+
}
|
|
68
|
+
const metaKeys = Object.keys(metadata).filter((k)=>k !== 'splat');
|
|
69
|
+
if (metaKeys.length > 0) {
|
|
70
|
+
const metaStr = JSON.stringify(metaKeys.reduce((acc, k)=>({
|
|
71
|
+
...acc,
|
|
72
|
+
[k]: metadata[k]
|
|
73
|
+
}), {}));
|
|
74
|
+
content += `\nMetadata: ${metaStr}`;
|
|
75
|
+
}
|
|
76
|
+
if (stack) {
|
|
77
|
+
content += `\nStack: ${stack}`;
|
|
78
|
+
}
|
|
79
|
+
return `${LOG_SEPARATOR}\n${timestamp} | ${levelFormatted} | [${reqId}]\n${content}`;
|
|
65
80
|
});
|
|
66
81
|
// Transports
|
|
67
82
|
/**
|
|
@@ -84,63 +99,44 @@ if (!existsSync(LOG_DIR)) {
|
|
|
84
99
|
maxFiles: LOG_MAX_FILES,
|
|
85
100
|
level: 'error'
|
|
86
101
|
});
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (tenantTransportCache.has(tenantId)) {
|
|
97
|
-
return tenantTransportCache.get(tenantId);
|
|
98
|
-
}
|
|
99
|
-
const tenantLogDir = join(LOG_DIR, tenantId);
|
|
100
|
-
if (!existsSync(tenantLogDir)) {
|
|
101
|
-
mkdirSync(tenantLogDir, {
|
|
102
|
+
// Transport Caches
|
|
103
|
+
const moduleTransportCache = new Map();
|
|
104
|
+
const tenantTransportCache = new Map();
|
|
105
|
+
function getCachedTransport(cache, name) {
|
|
106
|
+
const cached = cache.get(name);
|
|
107
|
+
if (cached) return cached;
|
|
108
|
+
const logDir = join(LOG_DIR, name);
|
|
109
|
+
if (!existsSync(logDir)) {
|
|
110
|
+
mkdirSync(logDir, {
|
|
102
111
|
recursive: true
|
|
103
112
|
});
|
|
104
113
|
}
|
|
105
114
|
const transport = new DailyRotateFile({
|
|
106
|
-
filename: `${
|
|
115
|
+
filename: `${logDir}/combined-%DATE%.log`,
|
|
107
116
|
datePattern: 'YYYY-MM-DD',
|
|
108
117
|
zippedArchive: true,
|
|
109
118
|
maxSize: LOG_MAX_SIZE,
|
|
110
119
|
maxFiles: LOG_MAX_FILES,
|
|
111
120
|
level: LOG_LEVEL
|
|
112
121
|
});
|
|
113
|
-
|
|
122
|
+
cache.set(name, transport);
|
|
114
123
|
return transport;
|
|
115
124
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
*/ let TenantAwareTransport = class TenantAwareTransport extends Transport {
|
|
125
|
+
const getModuleTransport = (name)=>getCachedTransport(moduleTransportCache, name);
|
|
126
|
+
const getTenantTransport = (name)=>getCachedTransport(tenantTransportCache, name);
|
|
127
|
+
let TenantAwareTransport = class TenantAwareTransport extends Transport {
|
|
120
128
|
log(info, callback) {
|
|
121
129
|
setImmediate(()=>this.emit('logged', info));
|
|
122
130
|
const tenantId = info.tenantId || info.metadata?.tenantId;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const tenantTransport = getTenantTransport(tenantId);
|
|
126
|
-
tenantTransport.log(info, callback);
|
|
127
|
-
} else {
|
|
128
|
-
// Write to default log folder
|
|
129
|
-
this.defaultTransport.log(info, callback);
|
|
130
|
-
}
|
|
131
|
+
const transport = USE_TENANT_MODE && tenantId ? getTenantTransport(tenantId) : this.defaultTransport;
|
|
132
|
+
transport.log(info, callback);
|
|
131
133
|
}
|
|
132
134
|
constructor(defaultTransport){
|
|
133
|
-
super(), _define_property(this, "defaultTransport", void 0);
|
|
134
|
-
this.defaultTransport = defaultTransport;
|
|
135
|
+
super(), _define_property(this, "defaultTransport", void 0), this.defaultTransport = defaultTransport;
|
|
135
136
|
}
|
|
136
137
|
};
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
* When USE_TENANT_MODE=true, routes to tenant folders (like database mode)
|
|
140
|
-
*/ const tenantAwareCombinedTransport = new TenantAwareTransport(combinedRotateTransport);
|
|
141
|
-
/**
|
|
142
|
-
* Console transport for development
|
|
143
|
-
*/ const consoleTransport = new transports.Console({
|
|
138
|
+
const tenantAwareCombinedTransport = new TenantAwareTransport(combinedRotateTransport);
|
|
139
|
+
const consoleTransport = new transports.Console({
|
|
144
140
|
level: LOG_LEVEL,
|
|
145
141
|
format: format.combine(format.colorize({
|
|
146
142
|
all: true
|
|
@@ -148,7 +144,7 @@ if (!existsSync(LOG_DIR)) {
|
|
|
148
144
|
format: 'YYYY-MM-DD HH:mm:ss'
|
|
149
145
|
}), format.errors({
|
|
150
146
|
stack: true
|
|
151
|
-
}),
|
|
147
|
+
}), structuredFormat)
|
|
152
148
|
});
|
|
153
149
|
// Logger Instances
|
|
154
150
|
/**
|
|
@@ -190,7 +186,43 @@ if (!existsSync(LOG_DIR)) {
|
|
|
190
186
|
});
|
|
191
187
|
// Exports
|
|
192
188
|
/**
|
|
193
|
-
* Winston logger instance
|
|
189
|
+
* Winston logger instance (global)
|
|
194
190
|
* - DEV: Console output with colors
|
|
195
191
|
* - PROD: File-based with daily rotation, tenant-aware routing
|
|
196
192
|
*/ export const instance = envConfig.isProduction() ? prodLogger : devLogger;
|
|
193
|
+
/**
|
|
194
|
+
* Module logger cache to avoid creating multiple loggers for the same module
|
|
195
|
+
*/ const moduleLoggerCache = new Map();
|
|
196
|
+
/**
|
|
197
|
+
* Create a module-specific logger instance
|
|
198
|
+
* - DEV: Console output (same as global)
|
|
199
|
+
* - PROD: File-based with module-specific folder + shared error log
|
|
200
|
+
*
|
|
201
|
+
* @param moduleName - The module name (e.g., 'auth', 'iam', 'storage')
|
|
202
|
+
* @returns Logger instance for the module
|
|
203
|
+
*/ export function createModuleLogger(moduleName) {
|
|
204
|
+
// In dev mode, use global console logger
|
|
205
|
+
if (!envConfig.isProduction()) {
|
|
206
|
+
return devLogger;
|
|
207
|
+
}
|
|
208
|
+
// Check cache first
|
|
209
|
+
if (moduleLoggerCache.has(moduleName)) {
|
|
210
|
+
return moduleLoggerCache.get(moduleName);
|
|
211
|
+
}
|
|
212
|
+
// Create module-specific logger for production
|
|
213
|
+
const moduleLogger = createLogger({
|
|
214
|
+
level: LOG_LEVEL,
|
|
215
|
+
format: format.combine(format.timestamp({
|
|
216
|
+
format: 'YYYY-MM-DD HH:mm:ss.SSS'
|
|
217
|
+
}), format.errors({
|
|
218
|
+
stack: true
|
|
219
|
+
}), structuredFormat),
|
|
220
|
+
transports: [
|
|
221
|
+
getModuleTransport(moduleName),
|
|
222
|
+
errorRotateTransport
|
|
223
|
+
],
|
|
224
|
+
exitOnError: false
|
|
225
|
+
});
|
|
226
|
+
moduleLoggerCache.set(moduleName, moduleLogger);
|
|
227
|
+
return moduleLogger;
|
|
228
|
+
}
|
package/fesm/constants/index.js
CHANGED