@flusys/nestjs-shared 3.0.0 → 4.0.0-rc

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.
Files changed (103) hide show
  1. package/README.md +160 -80
  2. package/cjs/classes/api-controller.class.js +26 -8
  3. package/cjs/classes/api-service.class.js +100 -17
  4. package/cjs/classes/winston-logger-adapter.class.js +15 -20
  5. package/cjs/classes/winston.logger.class.js +103 -70
  6. package/cjs/constants/index.js +1 -0
  7. package/cjs/constants/message-keys.js +80 -0
  8. package/cjs/constants/permissions.js +65 -11
  9. package/cjs/decorators/index.js +1 -0
  10. package/cjs/decorators/log-action.decorator.js +149 -0
  11. package/cjs/dtos/response-payload.dto.js +72 -0
  12. package/cjs/enums/index.js +20 -0
  13. package/cjs/enums/notification-type.enum.js +17 -0
  14. package/cjs/enums/participant-status.enum.js +17 -0
  15. package/cjs/enums/recurrence-type.enum.js +18 -0
  16. package/cjs/exceptions/base-app.exception.js +145 -0
  17. package/cjs/exceptions/index.js +1 -0
  18. package/cjs/exceptions/permission.exception.js +12 -8
  19. package/cjs/filters/global-exception.filter.js +167 -0
  20. package/cjs/filters/index.js +18 -0
  21. package/cjs/guards/jwt-auth.guard.js +4 -1
  22. package/cjs/guards/permission.guard.js +6 -13
  23. package/cjs/index.js +2 -0
  24. package/cjs/interceptors/idempotency.interceptor.js +1 -1
  25. package/cjs/interceptors/index.js +0 -1
  26. package/cjs/interfaces/event-manager-adapter.interface.js +11 -0
  27. package/cjs/interfaces/index.js +2 -0
  28. package/cjs/interfaces/logger.interface.js +1 -4
  29. package/cjs/interfaces/notification-adapter.interface.js +11 -0
  30. package/cjs/middlewares/logger.middleware.js +83 -26
  31. package/cjs/modules/datasource/multi-tenant-datasource.service.js +33 -11
  32. package/cjs/modules/utils/utils.service.js +4 -20
  33. package/cjs/utils/index.js +0 -1
  34. package/cjs/utils/query-helpers.util.js +8 -1
  35. package/classes/api-controller.class.d.ts +1 -0
  36. package/classes/api-service.class.d.ts +5 -10
  37. package/classes/winston-logger-adapter.class.d.ts +12 -11
  38. package/classes/winston.logger.class.d.ts +1 -0
  39. package/constants/index.d.ts +1 -0
  40. package/constants/message-keys.d.ts +81 -0
  41. package/constants/permissions.d.ts +72 -0
  42. package/decorators/index.d.ts +1 -0
  43. package/decorators/log-action.decorator.d.ts +8 -0
  44. package/dtos/response-payload.dto.d.ts +8 -0
  45. package/enums/index.d.ts +3 -0
  46. package/enums/notification-type.enum.d.ts +6 -0
  47. package/enums/participant-status.enum.d.ts +6 -0
  48. package/enums/recurrence-type.enum.d.ts +7 -0
  49. package/exceptions/base-app.exception.d.ts +41 -0
  50. package/exceptions/index.d.ts +1 -0
  51. package/exceptions/permission.exception.d.ts +1 -1
  52. package/fesm/classes/api-controller.class.js +26 -8
  53. package/fesm/classes/api-service.class.js +101 -18
  54. package/fesm/classes/winston-logger-adapter.class.js +18 -44
  55. package/fesm/classes/winston.logger.class.js +100 -68
  56. package/fesm/constants/index.js +2 -0
  57. package/fesm/constants/message-keys.js +59 -0
  58. package/fesm/constants/permissions.js +51 -14
  59. package/fesm/decorators/index.js +1 -0
  60. package/fesm/decorators/log-action.decorator.js +139 -0
  61. package/fesm/dtos/response-payload.dto.js +72 -0
  62. package/fesm/enums/index.js +3 -0
  63. package/fesm/enums/notification-type.enum.js +7 -0
  64. package/fesm/enums/participant-status.enum.js +7 -0
  65. package/fesm/enums/recurrence-type.enum.js +8 -0
  66. package/fesm/exceptions/base-app.exception.js +109 -0
  67. package/fesm/exceptions/index.js +1 -0
  68. package/fesm/exceptions/permission.exception.js +15 -17
  69. package/fesm/filters/global-exception.filter.js +157 -0
  70. package/fesm/filters/index.js +1 -0
  71. package/fesm/guards/jwt-auth.guard.js +5 -2
  72. package/fesm/guards/permission.guard.js +8 -15
  73. package/fesm/index.js +2 -0
  74. package/fesm/interceptors/idempotency.interceptor.js +2 -2
  75. package/fesm/interceptors/index.js +0 -1
  76. package/fesm/interfaces/event-manager-adapter.interface.js +1 -0
  77. package/fesm/interfaces/index.js +2 -0
  78. package/fesm/interfaces/logger.interface.js +1 -4
  79. package/fesm/interfaces/notification-adapter.interface.js +1 -0
  80. package/fesm/middlewares/logger.middleware.js +83 -26
  81. package/fesm/modules/datasource/multi-tenant-datasource.service.js +34 -12
  82. package/fesm/modules/utils/utils.service.js +5 -21
  83. package/fesm/utils/index.js +0 -1
  84. package/fesm/utils/query-helpers.util.js +8 -1
  85. package/filters/global-exception.filter.d.ts +10 -0
  86. package/filters/index.d.ts +1 -0
  87. package/guards/permission.guard.d.ts +1 -3
  88. package/index.d.ts +2 -0
  89. package/interceptors/index.d.ts +0 -1
  90. package/interfaces/event-manager-adapter.interface.d.ts +43 -0
  91. package/interfaces/index.d.ts +2 -0
  92. package/interfaces/logger.interface.d.ts +5 -5
  93. package/interfaces/notification-adapter.interface.d.ts +22 -0
  94. package/modules/datasource/multi-tenant-datasource.service.d.ts +1 -2
  95. package/modules/utils/utils.service.d.ts +0 -1
  96. package/package.json +7 -2
  97. package/utils/index.d.ts +0 -1
  98. package/cjs/interceptors/query-performance.interceptor.js +0 -66
  99. package/cjs/utils/error-handler.util.js +0 -90
  100. package/fesm/interceptors/query-performance.interceptor.js +0 -56
  101. package/fesm/utils/error-handler.util.js +0 -82
  102. package/interceptors/query-performance.interceptor.d.ts +0 -8
  103. package/utils/error-handler.util.d.ts +0 -19
@@ -0,0 +1,8 @@
1
+ export var RecurrenceType = /*#__PURE__*/ function(RecurrenceType) {
2
+ RecurrenceType["NONE"] = "none";
3
+ RecurrenceType["DAILY"] = "daily";
4
+ RecurrenceType["WEEKLY"] = "weekly";
5
+ RecurrenceType["BIWEEKLY"] = "biweekly";
6
+ RecurrenceType["MONTHLY"] = "monthly";
7
+ return RecurrenceType;
8
+ }({});
@@ -0,0 +1,109 @@
1
+ function _define_property(obj, key, value) {
2
+ if (key in obj) {
3
+ Object.defineProperty(obj, key, {
4
+ value: value,
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true
8
+ });
9
+ } else {
10
+ obj[key] = value;
11
+ }
12
+ return obj;
13
+ }
14
+ import { HttpException, HttpStatus } from '@nestjs/common';
15
+ import { ERROR_MESSAGES } from '../constants/message-keys';
16
+ export class BaseAppException extends HttpException {
17
+ constructor(options){
18
+ const status = options.status || HttpStatus.BAD_REQUEST;
19
+ super({
20
+ success: false,
21
+ message: options.message,
22
+ messageKey: options.messageKey || ERROR_MESSAGES.GENERIC,
23
+ messageParams: options.messageParams,
24
+ errors: options.errors
25
+ }, status), _define_property(this, "messageKey", void 0), _define_property(this, "messageParams", void 0), _define_property(this, "errors", void 0), _define_property(this, "metadata", void 0);
26
+ this.messageKey = options.messageKey || ERROR_MESSAGES.GENERIC;
27
+ this.messageParams = options.messageParams;
28
+ this.errors = options.errors;
29
+ this.metadata = options.metadata;
30
+ }
31
+ }
32
+ export class NotFoundException extends BaseAppException {
33
+ constructor(entity, id){
34
+ super({
35
+ message: id ? `${entity} with id "${id}" not found` : `${entity} not found`,
36
+ messageKey: ERROR_MESSAGES.NOT_FOUND,
37
+ status: HttpStatus.NOT_FOUND,
38
+ metadata: {
39
+ entity,
40
+ id
41
+ }
42
+ });
43
+ }
44
+ }
45
+ export class ValidationException extends BaseAppException {
46
+ constructor(errors){
47
+ super({
48
+ message: 'Validation failed',
49
+ messageKey: ERROR_MESSAGES.VALIDATION,
50
+ status: HttpStatus.BAD_REQUEST,
51
+ errors
52
+ });
53
+ }
54
+ }
55
+ export class UnauthorizedException extends BaseAppException {
56
+ constructor(message = 'Unauthorized access'){
57
+ super({
58
+ message,
59
+ messageKey: ERROR_MESSAGES.UNAUTHORIZED,
60
+ status: HttpStatus.UNAUTHORIZED
61
+ });
62
+ }
63
+ }
64
+ export class ForbiddenException extends BaseAppException {
65
+ constructor(message = 'Access forbidden'){
66
+ super({
67
+ message,
68
+ messageKey: ERROR_MESSAGES.FORBIDDEN,
69
+ status: HttpStatus.FORBIDDEN
70
+ });
71
+ }
72
+ }
73
+ export class ConflictException extends BaseAppException {
74
+ constructor(entity, field){
75
+ super({
76
+ message: field ? `${entity} with this ${field} already exists` : `${entity} already exists`,
77
+ messageKey: ERROR_MESSAGES.CONFLICT,
78
+ status: HttpStatus.CONFLICT,
79
+ metadata: {
80
+ entity,
81
+ field
82
+ }
83
+ });
84
+ }
85
+ }
86
+ export class InternalServerException extends BaseAppException {
87
+ constructor(message = 'Internal server error'){
88
+ super({
89
+ message,
90
+ messageKey: ERROR_MESSAGES.INTERNAL,
91
+ status: HttpStatus.INTERNAL_SERVER_ERROR
92
+ });
93
+ }
94
+ }
95
+ export class ServiceUnavailableException extends BaseAppException {
96
+ constructor(service){
97
+ super({
98
+ message: `${service} is temporarily unavailable`,
99
+ messageKey: ERROR_MESSAGES.SERVICE_UNAVAILABLE,
100
+ messageParams: {
101
+ service
102
+ },
103
+ status: HttpStatus.SERVICE_UNAVAILABLE,
104
+ metadata: {
105
+ service
106
+ }
107
+ });
108
+ }
109
+ }
@@ -1 +1,2 @@
1
+ export * from './base-app.exception';
1
2
  export * from './permission.exception';
@@ -1,37 +1,35 @@
1
1
  import { ForbiddenException, InternalServerErrorException } from '@nestjs/common';
2
- /**
3
- * Exception thrown when permission system is unavailable
4
- */ export class PermissionSystemUnavailableException extends InternalServerErrorException {
5
- constructor(message = 'Permission system temporarily unavailable. Please try again later.'){
2
+ import { ERROR_MESSAGES } from '../constants/message-keys';
3
+ export class PermissionSystemUnavailableException extends InternalServerErrorException {
4
+ constructor(){
6
5
  super({
7
6
  success: false,
8
- message,
9
- code: 'PERMISSION_SYSTEM_UNAVAILABLE'
7
+ message: 'Permission system temporarily unavailable',
8
+ messageKey: ERROR_MESSAGES.PERMISSION_SYSTEM_UNAVAILABLE
10
9
  });
11
10
  }
12
11
  }
13
- /**
14
- * Exception thrown when user lacks required permissions
15
- */ export class InsufficientPermissionsException extends ForbiddenException {
12
+ export class InsufficientPermissionsException extends ForbiddenException {
16
13
  constructor(missingPermissions, operator = 'AND'){
17
- const message = operator === 'OR' ? `Requires at least one of: ${missingPermissions.join(', ')}` : `Missing required permissions: ${missingPermissions.join(', ')}`;
14
+ const messageKey = operator === 'OR' ? ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS_OR : ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS;
18
15
  super({
19
16
  success: false,
20
- message,
21
- code: 'INSUFFICIENT_PERMISSIONS',
17
+ message: 'Insufficient permissions',
18
+ messageKey,
19
+ messageParams: {
20
+ permissions: missingPermissions.join(', ')
21
+ },
22
22
  missingPermissions,
23
23
  operator
24
24
  });
25
25
  }
26
26
  }
27
- /**
28
- * Exception thrown when no permissions found for user
29
- */ export class NoPermissionsFoundException extends ForbiddenException {
27
+ export class NoPermissionsFoundException extends ForbiddenException {
30
28
  constructor(){
31
29
  super({
32
30
  success: false,
33
- message: 'No permissions found. Please contact administrator.',
34
- code: 'NO_PERMISSIONS_FOUND'
31
+ message: 'No permissions found',
32
+ messageKey: ERROR_MESSAGES.NO_PERMISSIONS_FOUND
35
33
  });
36
34
  }
37
35
  }
@@ -0,0 +1,157 @@
1
+ function _define_property(obj, key, value) {
2
+ if (key in obj) {
3
+ Object.defineProperty(obj, key, {
4
+ value: value,
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true
8
+ });
9
+ } else {
10
+ obj[key] = value;
11
+ }
12
+ return obj;
13
+ }
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
+ import { Catch, HttpException, HttpStatus, Logger } from '@nestjs/common';
21
+ import { ERROR_MESSAGES } from '../constants/message-keys';
22
+ import { BaseAppException } from '../exceptions/base-app.exception';
23
+ import { getRequestId, getUserId, getTenantId, getCompanyId } from '../middlewares/logger.middleware';
24
+ export class GlobalExceptionFilter {
25
+ catch(exception, host) {
26
+ const ctx = host.switchToHttp();
27
+ const request = ctx.getRequest();
28
+ const response = ctx.getResponse();
29
+ const startTime = request._startTime || Date.now();
30
+ const responseTime = Date.now() - startTime;
31
+ const requestId = getRequestId() || request.headers['x-request-id'] || 'no-request-id';
32
+ const userId = getUserId();
33
+ const tenantId = getTenantId();
34
+ const companyId = getCompanyId();
35
+ const errorResponse = this.buildErrorResponse(exception, requestId, responseTime);
36
+ const statusCode = this.getStatusCode(exception);
37
+ const logContext = {
38
+ requestId,
39
+ userId,
40
+ tenantId,
41
+ companyId,
42
+ method: request.method,
43
+ url: request.originalUrl,
44
+ path: request.path,
45
+ statusCode,
46
+ duration: `${responseTime}ms`,
47
+ messageKey: errorResponse.messageKey
48
+ };
49
+ const logMessage = this.buildLogMessage(request, statusCode, responseTime, errorResponse);
50
+ if (statusCode >= 500) {
51
+ this.logger.error(logMessage, this.getStack(exception), logContext);
52
+ } else if (statusCode >= 400) {
53
+ this.logger.warn(logMessage, logContext);
54
+ }
55
+ response.status(statusCode).json(errorResponse);
56
+ }
57
+ buildErrorResponse(exception, requestId, responseTime) {
58
+ const timestamp = new Date().toISOString();
59
+ if (exception instanceof BaseAppException) {
60
+ return {
61
+ success: false,
62
+ message: exception.message,
63
+ messageKey: exception.messageKey,
64
+ messageParams: exception.messageParams,
65
+ errors: exception.errors,
66
+ _meta: {
67
+ requestId,
68
+ timestamp,
69
+ responseTime
70
+ }
71
+ };
72
+ }
73
+ if (exception instanceof HttpException) {
74
+ const response = exception.getResponse();
75
+ const { message, messageKey, messageParams, errors } = this.extractHttpExceptionDetails(response);
76
+ return {
77
+ success: false,
78
+ message,
79
+ messageKey,
80
+ messageParams,
81
+ errors,
82
+ _meta: {
83
+ requestId,
84
+ timestamp,
85
+ responseTime
86
+ }
87
+ };
88
+ }
89
+ return {
90
+ success: false,
91
+ message: 'Internal server error',
92
+ messageKey: ERROR_MESSAGES.INTERNAL,
93
+ _meta: {
94
+ requestId,
95
+ timestamp,
96
+ responseTime
97
+ }
98
+ };
99
+ }
100
+ extractHttpExceptionDetails(response) {
101
+ if (typeof response === 'string') {
102
+ return {
103
+ message: response,
104
+ messageKey: ERROR_MESSAGES.HTTP
105
+ };
106
+ }
107
+ if (typeof response === 'object' && response !== null) {
108
+ const obj = response;
109
+ // Handle class-validator validation errors
110
+ if (Array.isArray(obj.message)) {
111
+ const errors = obj.message.map((msg)=>{
112
+ const [field, ...rest] = msg.split(' ');
113
+ return {
114
+ field: field || 'unknown',
115
+ message: rest.join(' ') || msg
116
+ };
117
+ });
118
+ return {
119
+ message: 'Validation failed',
120
+ messageKey: ERROR_MESSAGES.VALIDATION,
121
+ errors
122
+ };
123
+ }
124
+ return {
125
+ message: String(obj.message || 'Unknown error'),
126
+ messageKey: String(obj.messageKey || ERROR_MESSAGES.HTTP),
127
+ messageParams: obj.messageParams,
128
+ errors: obj.errors
129
+ };
130
+ }
131
+ return {
132
+ message: 'Unknown error',
133
+ messageKey: ERROR_MESSAGES.UNKNOWN
134
+ };
135
+ }
136
+ getStatusCode(exception) {
137
+ if (exception instanceof HttpException) {
138
+ return exception.getStatus();
139
+ }
140
+ return HttpStatus.INTERNAL_SERVER_ERROR;
141
+ }
142
+ getStack(exception) {
143
+ if (exception instanceof Error) {
144
+ return exception.stack;
145
+ }
146
+ return undefined;
147
+ }
148
+ buildLogMessage(request, statusCode, responseTime, errorResponse) {
149
+ return `Exception caught "HTTP/1.1" "${request.method}" "${request.originalUrl}" - ${statusCode} "${errorResponse.messageKey}" ${responseTime}ms | ${errorResponse.message}`;
150
+ }
151
+ constructor(){
152
+ _define_property(this, "logger", new Logger('ExceptionFilter'));
153
+ }
154
+ }
155
+ GlobalExceptionFilter = _ts_decorate([
156
+ Catch()
157
+ ], GlobalExceptionFilter);
@@ -0,0 +1 @@
1
+ export * from './global-exception.filter';
@@ -25,7 +25,7 @@ function _ts_param(paramIndex, decorator) {
25
25
  decorator(target, key, paramIndex);
26
26
  };
27
27
  }
28
- import { IS_PUBLIC_KEY } from '../constants';
28
+ import { AUTH_MESSAGES, IS_PUBLIC_KEY } from '../constants';
29
29
  import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
30
30
  import { Reflector } from '@nestjs/core';
31
31
  import { AuthGuard } from '@nestjs/passport';
@@ -43,7 +43,10 @@ export class JwtAuthGuard extends AuthGuard('jwt') {
43
43
  }
44
44
  handleRequest(err, user) {
45
45
  if (err || !user) {
46
- throw err || new UnauthorizedException('Invalid or expired token');
46
+ throw err || new UnauthorizedException({
47
+ message: 'Invalid or expired token',
48
+ messageKey: AUTH_MESSAGES.TOKEN_INVALID
49
+ });
47
50
  }
48
51
  return user;
49
52
  }
@@ -25,13 +25,11 @@ function _ts_param(paramIndex, decorator) {
25
25
  decorator(target, key, paramIndex);
26
26
  };
27
27
  }
28
- import { Inject, Injectable, Logger, Optional, UnauthorizedException } from '@nestjs/common';
28
+ import { Inject, Injectable, Optional, UnauthorizedException } from '@nestjs/common';
29
29
  import { Reflector } from '@nestjs/core';
30
30
  import { HybridCache } from '../classes/hybrid-cache.class';
31
- import { NestLoggerAdapter } from '../classes/winston-logger-adapter.class';
32
- import { CACHE_INSTANCE, IS_PUBLIC_KEY, LOGGER_INSTANCE, PERMISSION_GUARD_CONFIG, PERMISSIONS_CACHE_PREFIX, PERMISSIONS_KEY } from '../constants';
31
+ import { AUTH_MESSAGES, CACHE_INSTANCE, IS_PUBLIC_KEY, PERMISSION_GUARD_CONFIG, PERMISSIONS_CACHE_PREFIX, PERMISSIONS_KEY } from '../constants';
33
32
  import { InsufficientPermissionsException, NoPermissionsFoundException, PermissionSystemUnavailableException } from '../exceptions/permission.exception';
34
- import { ILogger } from '../interfaces/logger.interface';
35
33
  import { PermissionGuardConfig } from '../interfaces/permission.interface';
36
34
  export class PermissionGuard {
37
35
  async canActivate(context) {
@@ -47,21 +45,21 @@ export class PermissionGuard {
47
45
  if (!permissionConfig) return true;
48
46
  const request = context.switchToHttp().getRequest();
49
47
  const user = request.user;
50
- if (!user) throw new UnauthorizedException('Authentication required');
48
+ if (!user) throw new UnauthorizedException({
49
+ message: 'Authentication required',
50
+ messageKey: AUTH_MESSAGES.TOKEN_REQUIRED
51
+ });
51
52
  if (!this.cache) {
52
- this.logger.error(`Cache not available (userId: ${user.id})`, undefined, 'PermissionGuard');
53
53
  throw new PermissionSystemUnavailableException();
54
54
  }
55
55
  const userPermissions = await this.getUserPermissions(user);
56
56
  if (!userPermissions || userPermissions.length === 0) {
57
- this.logger.warn(`No permissions found (userId: ${user.id})`, 'PermissionGuard');
58
57
  throw new NoPermissionsFoundException();
59
58
  }
60
59
  const logicNode = this.normalizeToLogicNode(permissionConfig);
61
60
  if (!logicNode) return true;
62
61
  const result = this.evaluateLogicNode(logicNode, userPermissions);
63
62
  if (!result.passed) {
64
- this.logger.warn(`Permission denied (userId: ${user.id})`, 'PermissionGuard');
65
63
  throw new InsufficientPermissionsException(result.missingPermissions, result.operator);
66
64
  }
67
65
  return true;
@@ -168,11 +166,10 @@ export class PermissionGuard {
168
166
  }
169
167
  return false;
170
168
  }
171
- constructor(reflector, cache, config, logger){
169
+ constructor(reflector, cache, config){
172
170
  _define_property(this, "reflector", void 0);
173
171
  _define_property(this, "cache", void 0);
174
172
  _define_property(this, "config", void 0);
175
- _define_property(this, "logger", void 0);
176
173
  this.reflector = reflector;
177
174
  this.cache = cache;
178
175
  this.config = {
@@ -181,7 +178,6 @@ export class PermissionGuard {
181
178
  companyPermissionKeyFormat: `${PERMISSIONS_CACHE_PREFIX}:company:{companyId}:branch:{branchId}:user:{userId}`,
182
179
  ...config
183
180
  };
184
- this.logger = logger || new NestLoggerAdapter(new Logger(PermissionGuard.name));
185
181
  }
186
182
  }
187
183
  PermissionGuard = _ts_decorate([
@@ -191,13 +187,10 @@ PermissionGuard = _ts_decorate([
191
187
  _ts_param(1, Inject(CACHE_INSTANCE)),
192
188
  _ts_param(2, Optional()),
193
189
  _ts_param(2, Inject(PERMISSION_GUARD_CONFIG)),
194
- _ts_param(3, Optional()),
195
- _ts_param(3, Inject(LOGGER_INSTANCE)),
196
190
  _ts_metadata("design:type", Function),
197
191
  _ts_metadata("design:paramtypes", [
198
192
  typeof Reflector === "undefined" ? Object : Reflector,
199
193
  typeof HybridCache === "undefined" ? Object : HybridCache,
200
- typeof PermissionGuardConfig === "undefined" ? Object : PermissionGuardConfig,
201
- typeof ILogger === "undefined" ? Object : ILogger
194
+ typeof PermissionGuardConfig === "undefined" ? Object : PermissionGuardConfig
202
195
  ])
203
196
  ], PermissionGuard);
package/fesm/index.js CHANGED
@@ -3,7 +3,9 @@ export * from './constants';
3
3
  export * from './decorators';
4
4
  export * from './dtos';
5
5
  export * from './entities';
6
+ export * from './enums';
6
7
  export * from './exceptions';
8
+ export * from './filters';
7
9
  export * from './guards';
8
10
  export * from './interceptors';
9
11
  export * from './interfaces';
@@ -29,7 +29,7 @@ import { ConflictException, Inject, Injectable, Optional } from '@nestjs/common'
29
29
  import { of } from 'rxjs';
30
30
  import { tap } from 'rxjs/operators';
31
31
  import { HybridCache } from '../classes/hybrid-cache.class';
32
- import { CACHE_INSTANCE, IDEMPOTENCY_CACHE_PREFIX, IDEMPOTENCY_KEY_HEADER } from '../constants';
32
+ import { CACHE_INSTANCE, IDEMPOTENCY_CACHE_PREFIX, IDEMPOTENCY_KEY_HEADER, SYSTEM_MESSAGES } from '../constants';
33
33
  export class IdempotencyInterceptor {
34
34
  async intercept(context, next) {
35
35
  // Skip if no cache available
@@ -51,7 +51,7 @@ export class IdempotencyInterceptor {
51
51
  throw new ConflictException({
52
52
  success: false,
53
53
  message: 'Request is already being processed',
54
- code: 'DUPLICATE_REQUEST_IN_PROGRESS'
54
+ messageKey: SYSTEM_MESSAGES.DUPLICATE_REQUEST
55
55
  });
56
56
  }
57
57
  // Return cached response
@@ -1,6 +1,5 @@
1
1
  export * from './delete-empty-id-from-body.interceptor';
2
2
  export * from './idempotency.interceptor';
3
- export * from './query-performance.interceptor';
4
3
  export * from './response-meta.interceptor';
5
4
  export * from './set-user-field-on-body.interceptor';
6
5
  export * from './slug.interceptor';
@@ -0,0 +1 @@
1
+ export const EVENT_MANAGER_ADAPTER = 'EVENT_MANAGER_ADAPTER';
@@ -1,7 +1,9 @@
1
1
  export * from './api.interface';
2
2
  export * from './datasource.interface';
3
+ export * from './event-manager-adapter.interface';
3
4
  export * from './identity.interface';
4
5
  export * from './logged-user-info.interface';
5
6
  export * from './logger.interface';
6
7
  export * from './module-config.interface';
8
+ export * from './notification-adapter.interface';
7
9
  export * from './permission.interface';
@@ -1,4 +1 @@
1
- /**
2
- * Logger interface for dependency injection
3
- * Compatible with Winston, NestJS Logger, and console
4
- */ export { };
1
+ export { };
@@ -0,0 +1 @@
1
+ export const NOTIFICATION_ADAPTER = 'NOTIFICATION_ADAPTER';
@@ -24,18 +24,15 @@ import { v4 as uuidv4 } from 'uuid';
24
24
  import { instance as winstonLogger } from '../classes/winston.logger.class';
25
25
  import { REQUEST_ID_HEADER } from '../constants';
26
26
  // Configuration
27
- const IS_DEBUG = envConfig.getLogConfig().level === 'debug';
27
+ const logConfig = envConfig.getLogConfig();
28
+ const IS_DEBUG = logConfig.level === 'debug';
29
+ const DISABLE_HTTP_LOGGING = logConfig.disableHttpLogging;
28
30
  const TENANT_ID_HEADER = 'x-tenant-id';
29
31
  const EXCLUDED_PATHS = [
30
32
  '/health',
31
33
  '/metrics',
32
34
  '/favicon.ico'
33
35
  ];
34
- const EXCLUDED_HEADERS = [
35
- 'authorization',
36
- 'cookie',
37
- 'x-api-key'
38
- ];
39
36
  const MAX_BODY_LOG_SIZE = 1000;
40
37
  export const requestContext = new AsyncLocalStorage();
41
38
  // Context accessors
@@ -44,17 +41,71 @@ export const getTenantId = ()=>requestContext.getStore()?.tenantId;
44
41
  export const getUserId = ()=>requestContext.getStore()?.userId;
45
42
  export const getCompanyId = ()=>requestContext.getStore()?.companyId;
46
43
  // Helper Functions
47
- function sanitizeHeaders(headers) {
48
- const sanitized = {};
49
- for (const [key, value] of Object.entries(headers)){
50
- sanitized[key] = EXCLUDED_HEADERS.includes(key.toLowerCase()) ? '[REDACTED]' : value;
44
+ function summarizeRequestBody(body) {
45
+ if (!body) return undefined;
46
+ const parsed = typeof body === 'string' ? tryParseJson(body) : body;
47
+ if (!parsed || typeof parsed !== 'object') {
48
+ const str = String(body);
49
+ return str.length > MAX_BODY_LOG_SIZE ? str.substring(0, MAX_BODY_LOG_SIZE) + '...' : str;
50
+ }
51
+ // For API request bodies, provide a concise summary
52
+ const summary = {};
53
+ // Pagination info
54
+ if (parsed.pagination) {
55
+ summary.page = parsed.pagination.currentPage;
56
+ summary.pageSize = parsed.pagination.pageSize;
57
+ }
58
+ // Filter keys only (not values - may contain sensitive data)
59
+ if (parsed.filter && typeof parsed.filter === 'object') {
60
+ const filterKeys = Object.keys(parsed.filter);
61
+ if (filterKeys.length > 0) summary.filterKeys = filterKeys;
62
+ }
63
+ // Select fields count
64
+ if (Array.isArray(parsed.select) && parsed.select.length > 0) {
65
+ summary.selectCount = parsed.select.length;
66
+ }
67
+ // Sort keys
68
+ if (parsed.sort && typeof parsed.sort === 'object') {
69
+ const sortKeys = Object.keys(parsed.sort);
70
+ if (sortKeys.length > 0) summary.sortKeys = sortKeys;
51
71
  }
52
- return sanitized;
72
+ // ID for single item operations
73
+ if (parsed.id) summary.id = parsed.id;
74
+ // IDs for bulk operations
75
+ if (Array.isArray(parsed.ids)) summary.idsCount = parsed.ids.length;
76
+ return Object.keys(summary).length > 0 ? JSON.stringify(summary) : '{}';
53
77
  }
54
- function truncateBody(body) {
78
+ function summarizeResponseBody(body) {
55
79
  if (!body) return undefined;
56
- const str = typeof body === 'string' ? body : JSON.stringify(body);
57
- return str.length > MAX_BODY_LOG_SIZE ? str.substring(0, MAX_BODY_LOG_SIZE) + '...' : body;
80
+ const parsed = typeof body === 'string' ? tryParseJson(body) : body;
81
+ if (!parsed || typeof parsed !== 'object') {
82
+ const str = String(body);
83
+ return str.length > MAX_BODY_LOG_SIZE ? str.substring(0, MAX_BODY_LOG_SIZE) + '...' : str;
84
+ }
85
+ const summary = {};
86
+ if ('success' in parsed) summary.success = parsed.success;
87
+ if ('message' in parsed) summary.message = parsed.message;
88
+ if ('code' in parsed) summary.code = parsed.code;
89
+ if (Array.isArray(parsed.data)) {
90
+ summary.dataCount = parsed.data.length;
91
+ } else if (parsed.data && typeof parsed.data === 'object') {
92
+ summary.hasData = true;
93
+ }
94
+ if (parsed.meta) {
95
+ summary.total = parsed.meta.total;
96
+ summary.page = parsed.meta.page;
97
+ }
98
+ if (parsed.errors) {
99
+ summary.errorsCount = Array.isArray(parsed.errors) ? parsed.errors.length : 1;
100
+ }
101
+ return JSON.stringify(summary);
102
+ }
103
+ function tryParseJson(str) {
104
+ try {
105
+ return JSON.parse(str);
106
+ } catch {
107
+ return null;
108
+ }
58
109
  }
59
110
  function getClientIp(req) {
60
111
  return req.headers['x-forwarded-for']?.split(',')[0] || req.socket?.remoteAddress || 'unknown';
@@ -71,7 +122,7 @@ export class LoggerMiddleware {
71
122
  startTime
72
123
  };
73
124
  requestContext.run(context, ()=>{
74
- const shouldSkipLogging = EXCLUDED_PATHS.some((path)=>req.originalUrl.startsWith(path));
125
+ const shouldSkipLogging = DISABLE_HTTP_LOGGING || EXCLUDED_PATHS.some((path)=>req.originalUrl.startsWith(path));
75
126
  if (!shouldSkipLogging) {
76
127
  this.logRequest(req, requestId, tenantId);
77
128
  this.setupResponseLogging(req, res, startTime, requestId, tenantId);
@@ -112,20 +163,22 @@ export class LoggerMiddleware {
112
163
  });
113
164
  }
114
165
  logRequest(req, requestId, tenantId) {
166
+ const ip = getClientIp(req);
167
+ const contentType = req.headers['content-type'] || 'none';
168
+ // Build descriptive message like: Started processing request "POST" "/api/users" from "192.168.1.1"
169
+ const message = `Started processing request "${req.method}" "${req.originalUrl}" from "${ip}"`;
115
170
  const logData = {
116
171
  ...this.buildBaseLogData(req, requestId, tenantId),
117
172
  query: Object.keys(req.query).length > 0 ? req.query : undefined,
118
- ip: getClientIp(req),
173
+ ip,
119
174
  userAgent: req.headers['user-agent'],
120
- contentType: req.headers['content-type'],
175
+ contentType,
121
176
  contentLength: req.headers['content-length']
122
177
  };
123
178
  if (IS_DEBUG) {
124
- logData.headers = sanitizeHeaders(req.headers);
125
- logData.body = truncateBody(req.body);
126
- logData.params = Object.keys(req.params).length > 0 ? req.params : undefined;
179
+ logData.body = summarizeRequestBody(req.body);
127
180
  }
128
- this.logger.info('Incoming request', logData);
181
+ this.logger.info(message, logData);
129
182
  }
130
183
  logResponse(req, res, startTime, body, requestId, tenantId) {
131
184
  const duration = Date.now() - startTime;
@@ -133,6 +186,10 @@ export class LoggerMiddleware {
133
186
  const level = statusCode >= 500 ? 'error' : statusCode >= 400 ? 'warn' : 'info';
134
187
  const userId = getUserId();
135
188
  const companyId = getCompanyId();
189
+ const contentType = res.getHeader('content-type') || 'none';
190
+ const contentLength = res.getHeader('content-length') || 'null';
191
+ // Build descriptive message like: Request finished "HTTP/1.1" "POST" "/api/users" - 200 "application/json" 534.29ms
192
+ const message = `Request finished "HTTP/1.1" "${req.method}" "${req.originalUrl}" - ${statusCode} ${contentLength} "${contentType}" ${duration.toFixed(2)}ms`;
136
193
  const logData = {
137
194
  ...this.buildBaseLogData(req, requestId, tenantId),
138
195
  userId,
@@ -141,15 +198,15 @@ export class LoggerMiddleware {
141
198
  statusMessage: res.statusMessage,
142
199
  duration: `${duration}ms`,
143
200
  durationMs: duration,
144
- contentType: res.getHeader('content-type'),
145
- contentLength: res.getHeader('content-length')
201
+ contentType,
202
+ contentLength
146
203
  };
147
204
  if (statusCode >= 400 || IS_DEBUG) {
148
- logData.responseBody = truncateBody(body);
205
+ logData.responseBody = summarizeResponseBody(body);
149
206
  }
150
- this.logger.log(level, `Response [${statusCode}]`, logData);
207
+ this.logger.log(level, message, logData);
151
208
  if (duration > 3000 && statusCode < 400) {
152
- this.logger.warn('Slow request detected', {
209
+ this.logger.warn(`Slow request detected: "${req.method}" "${req.originalUrl}" took ${duration}ms (threshold: 3000ms)`, {
153
210
  ...this.buildBaseLogData(req, requestId, tenantId),
154
211
  userId,
155
212
  companyId,