@flusys/nestjs-shared 1.0.0-rc → 2.0.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.
Files changed (63) hide show
  1. package/README.md +493 -658
  2. package/cjs/classes/api-service.class.js +59 -92
  3. package/cjs/classes/winston-logger-adapter.class.js +23 -40
  4. package/cjs/constants/permissions.js +11 -1
  5. package/cjs/dtos/delete.dto.js +10 -0
  6. package/cjs/dtos/response-payload.dto.js +0 -75
  7. package/cjs/guards/permission.guard.js +19 -18
  8. package/cjs/interceptors/index.js +0 -3
  9. package/cjs/interceptors/set-user-field-on-body.interceptor.js +20 -3
  10. package/cjs/middlewares/logger.middleware.js +50 -89
  11. package/cjs/modules/datasource/datasource.module.js +11 -14
  12. package/cjs/modules/datasource/multi-tenant-datasource.service.js +0 -4
  13. package/cjs/modules/utils/utils.service.js +22 -103
  14. package/cjs/utils/error-handler.util.js +12 -67
  15. package/cjs/utils/html-sanitizer.util.js +1 -11
  16. package/cjs/utils/index.js +2 -0
  17. package/cjs/utils/request.util.js +70 -0
  18. package/cjs/utils/string.util.js +63 -0
  19. package/classes/api-service.class.d.ts +2 -0
  20. package/classes/winston-logger-adapter.class.d.ts +2 -0
  21. package/constants/permissions.d.ts +12 -0
  22. package/dtos/delete.dto.d.ts +1 -0
  23. package/dtos/response-payload.dto.d.ts +0 -13
  24. package/fesm/classes/api-service.class.js +59 -92
  25. package/fesm/classes/winston-logger-adapter.class.js +23 -40
  26. package/fesm/constants/permissions.js +8 -1
  27. package/fesm/dtos/delete.dto.js +12 -2
  28. package/fesm/dtos/response-payload.dto.js +0 -69
  29. package/fesm/guards/permission.guard.js +19 -18
  30. package/fesm/interceptors/index.js +0 -3
  31. package/fesm/interceptors/set-user-field-on-body.interceptor.js +3 -0
  32. package/fesm/middlewares/logger.middleware.js +50 -83
  33. package/fesm/modules/datasource/datasource.module.js +11 -14
  34. package/fesm/modules/datasource/multi-tenant-datasource.service.js +0 -4
  35. package/fesm/modules/utils/utils.service.js +19 -89
  36. package/fesm/utils/error-handler.util.js +12 -68
  37. package/fesm/utils/html-sanitizer.util.js +1 -14
  38. package/fesm/utils/index.js +2 -0
  39. package/fesm/utils/request.util.js +58 -0
  40. package/fesm/utils/string.util.js +71 -0
  41. package/guards/permission.guard.d.ts +2 -0
  42. package/interceptors/index.d.ts +0 -3
  43. package/interceptors/set-user-field-on-body.interceptor.d.ts +3 -0
  44. package/interfaces/logged-user-info.interface.d.ts +0 -2
  45. package/middlewares/logger.middleware.d.ts +2 -2
  46. package/modules/datasource/datasource.module.d.ts +1 -0
  47. package/modules/datasource/multi-tenant-datasource.service.d.ts +0 -1
  48. package/modules/utils/utils.service.d.ts +2 -18
  49. package/package.json +2 -2
  50. package/utils/error-handler.util.d.ts +3 -18
  51. package/utils/html-sanitizer.util.d.ts +0 -1
  52. package/utils/index.d.ts +2 -0
  53. package/utils/request.util.d.ts +4 -0
  54. package/utils/string.util.d.ts +2 -0
  55. package/cjs/interceptors/set-create-by-on-body.interceptor.js +0 -12
  56. package/cjs/interceptors/set-delete-by-on-body.interceptor.js +0 -12
  57. package/cjs/interceptors/set-update-by-on-body.interceptor.js +0 -12
  58. package/fesm/interceptors/set-create-by-on-body.interceptor.js +0 -4
  59. package/fesm/interceptors/set-delete-by-on-body.interceptor.js +0 -4
  60. package/fesm/interceptors/set-update-by-on-body.interceptor.js +0 -4
  61. package/interceptors/set-create-by-on-body.interceptor.d.ts +0 -1
  62. package/interceptors/set-delete-by-on-body.interceptor.d.ts +0 -1
  63. package/interceptors/set-update-by-on-body.interceptor.d.ts +0 -1
@@ -26,12 +26,6 @@ _export(exports, {
26
26
  },
27
27
  get requestContext () {
28
28
  return requestContext;
29
- },
30
- get setCompanyId () {
31
- return setCompanyId;
32
- },
33
- get setUserId () {
34
- return setUserId;
35
29
  }
36
30
  });
37
31
  const _config = require("@flusys/nestjs-core/config");
@@ -78,14 +72,6 @@ const getRequestId = ()=>requestContext.getStore()?.requestId;
78
72
  const getTenantId = ()=>requestContext.getStore()?.tenantId;
79
73
  const getUserId = ()=>requestContext.getStore()?.userId;
80
74
  const getCompanyId = ()=>requestContext.getStore()?.companyId;
81
- const setUserId = (userId)=>{
82
- const store = requestContext.getStore();
83
- if (store) store.userId = userId;
84
- };
85
- const setCompanyId = (companyId)=>{
86
- const store = requestContext.getStore();
87
- if (store) store.companyId = companyId;
88
- };
89
75
  // Helper Functions
90
76
  function sanitizeHeaders(headers) {
91
77
  const sanitized = {};
@@ -107,84 +93,62 @@ let LoggerMiddleware = class LoggerMiddleware {
107
93
  const requestId = req.headers[_constants.REQUEST_ID_HEADER] || (0, _uuid.v4)();
108
94
  const tenantId = req.headers[TENANT_ID_HEADER];
109
95
  const startTime = Date.now();
110
- // Set response header
111
96
  res.setHeader(_constants.REQUEST_ID_HEADER, requestId);
112
- // Create context
113
97
  const context = {
114
98
  requestId,
115
99
  tenantId,
116
100
  startTime
117
101
  };
118
- // Run in AsyncLocalStorage context
119
102
  requestContext.run(context, ()=>{
120
- // Check if we should skip logging
121
103
  const shouldSkipLogging = EXCLUDED_PATHS.some((path)=>req.originalUrl.startsWith(path));
122
104
  if (!shouldSkipLogging) {
123
105
  this.logRequest(req, requestId, tenantId);
124
- // Track if response was already logged
125
- let responseLogged = false;
126
- // Capture response using multiple hooks to ensure we catch it
127
- const originalSend = res.send;
128
- const originalJson = res.json;
129
- const originalEnd = res.end;
130
- // Store reference to this for use in callbacks
131
- const self = this;
132
- // Override res.send
133
- res.send = function(body) {
134
- if (!responseLogged) {
135
- responseLogged = true;
136
- self.logResponse(req, res, startTime, body, requestId, tenantId);
137
- }
138
- return originalSend.call(this, body);
139
- };
140
- // Override res.json
141
- res.json = function(body) {
142
- if (!responseLogged) {
143
- responseLogged = true;
144
- self.logResponse(req, res, startTime, body, requestId, tenantId);
145
- }
146
- return originalJson.call(this, body);
147
- };
148
- // Override res.end as fallback
149
- res.end = function(...args) {
150
- if (!responseLogged) {
151
- responseLogged = true;
152
- self.logResponse(req, res, startTime, args[0], requestId, tenantId);
153
- }
154
- return originalEnd.apply(this, args);
155
- };
156
- // Handle errors
157
- res.on('error', (error)=>{
158
- this.logger.error('Response error', {
159
- context: 'HTTP',
160
- requestId,
161
- tenantId,
162
- method: req.method,
163
- url: req.originalUrl,
164
- path: req.path,
165
- error: error.message,
166
- stack: error.stack
167
- });
168
- });
106
+ this.setupResponseLogging(req, res, startTime, requestId, tenantId);
169
107
  }
170
108
  next();
171
109
  });
172
110
  }
111
+ setupResponseLogging(req, res, startTime, requestId, tenantId) {
112
+ let responseLogged = false;
113
+ const originalSend = res.send;
114
+ const originalJson = res.json;
115
+ const originalEnd = res.end;
116
+ const self = this;
117
+ const logOnce = (body)=>{
118
+ if (!responseLogged) {
119
+ responseLogged = true;
120
+ self.logResponse(req, res, startTime, body, requestId, tenantId);
121
+ }
122
+ };
123
+ res.send = function(body) {
124
+ logOnce(body);
125
+ return originalSend.call(this, body);
126
+ };
127
+ res.json = function(body) {
128
+ logOnce(body);
129
+ return originalJson.call(this, body);
130
+ };
131
+ res.end = function(...args) {
132
+ logOnce(args[0]);
133
+ return originalEnd.apply(this, args);
134
+ };
135
+ res.on('error', (error)=>{
136
+ this.logger.error('Response error', {
137
+ ...this.buildBaseLogData(req, requestId, tenantId),
138
+ error: error.message,
139
+ stack: error.stack
140
+ });
141
+ });
142
+ }
173
143
  logRequest(req, requestId, tenantId) {
174
144
  const logData = {
175
- context: 'HTTP',
176
- requestId,
177
- tenantId,
178
- method: req.method,
179
- url: req.originalUrl,
180
- path: req.path,
145
+ ...this.buildBaseLogData(req, requestId, tenantId),
181
146
  query: Object.keys(req.query).length > 0 ? req.query : undefined,
182
147
  ip: getClientIp(req),
183
148
  userAgent: req.headers['user-agent'],
184
149
  contentType: req.headers['content-type'],
185
150
  contentLength: req.headers['content-length']
186
151
  };
187
- // Add debug details if enabled
188
152
  if (IS_DEBUG) {
189
153
  logData.headers = sanitizeHeaders(req.headers);
190
154
  logData.body = truncateBody(req.body);
@@ -196,13 +160,12 @@ let LoggerMiddleware = class LoggerMiddleware {
196
160
  const duration = Date.now() - startTime;
197
161
  const statusCode = res.statusCode;
198
162
  const level = statusCode >= 500 ? 'error' : statusCode >= 400 ? 'warn' : 'info';
163
+ const userId = getUserId();
164
+ const companyId = getCompanyId();
199
165
  const logData = {
200
- context: 'HTTP',
201
- requestId,
202
- tenantId,
203
- method: req.method,
204
- url: req.originalUrl,
205
- path: req.path,
166
+ ...this.buildBaseLogData(req, requestId, tenantId),
167
+ userId,
168
+ companyId,
206
169
  statusCode,
207
170
  statusMessage: res.statusMessage,
208
171
  duration: `${duration}ms`,
@@ -210,33 +173,31 @@ let LoggerMiddleware = class LoggerMiddleware {
210
173
  contentType: res.getHeader('content-type'),
211
174
  contentLength: res.getHeader('content-length')
212
175
  };
213
- // Add user context if available
214
- const userId = getUserId();
215
- const companyId = getCompanyId();
216
- if (userId) logData.userId = userId;
217
- if (companyId) logData.companyId = companyId;
218
- // Add response body for errors or debug mode
219
176
  if (statusCode >= 400 || IS_DEBUG) {
220
177
  logData.responseBody = truncateBody(body);
221
178
  }
222
179
  this.logger.log(level, `Response [${statusCode}]`, logData);
223
- // Log slow requests separately
224
180
  if (duration > 3000 && statusCode < 400) {
225
181
  this.logger.warn('Slow request detected', {
226
- context: 'HTTP',
227
- requestId,
228
- tenantId,
182
+ ...this.buildBaseLogData(req, requestId, tenantId),
229
183
  userId,
230
184
  companyId,
231
- method: req.method,
232
- url: req.originalUrl,
233
- path: req.path,
234
185
  duration: `${duration}ms`,
235
186
  durationMs: duration,
236
187
  threshold: '3000ms'
237
188
  });
238
189
  }
239
190
  }
191
+ buildBaseLogData(req, requestId, tenantId) {
192
+ return {
193
+ context: 'HTTP',
194
+ requestId,
195
+ tenantId,
196
+ method: req.method,
197
+ url: req.originalUrl,
198
+ path: req.path
199
+ };
200
+ }
240
201
  constructor(){
241
202
  _define_property(this, "logger", _winstonloggerclass.instance);
242
203
  }
@@ -87,24 +87,12 @@ let DataSourceModule = class DataSourceModule {
87
87
  provide: options.useClass,
88
88
  useClass: options.useClass
89
89
  },
90
- {
91
- provide: _nestjscore.MODULE_OPTIONS,
92
- useFactory: async (factory)=>factory.createOptions(),
93
- inject: [
94
- options.useClass
95
- ]
96
- }
90
+ this.createFactoryProvider(options.useClass)
97
91
  ];
98
92
  }
99
93
  if (options.useExisting) {
100
94
  return [
101
- {
102
- provide: _nestjscore.MODULE_OPTIONS,
103
- useFactory: async (factory)=>factory.createOptions(),
104
- inject: [
105
- options.useExisting
106
- ]
107
- }
95
+ this.createFactoryProvider(options.useExisting)
108
96
  ];
109
97
  }
110
98
  return [
@@ -114,6 +102,15 @@ let DataSourceModule = class DataSourceModule {
114
102
  }
115
103
  ];
116
104
  }
105
+ static createFactoryProvider(factoryClass) {
106
+ return {
107
+ provide: _nestjscore.MODULE_OPTIONS,
108
+ useFactory: async (factory)=>factory.createOptions(),
109
+ inject: [
110
+ factoryClass
111
+ ]
112
+ };
113
+ }
117
114
  };
118
115
  DataSourceModule = _ts_decorate([
119
116
  (0, _common.Module)({})
@@ -100,10 +100,6 @@ let MultiTenantDataSourceService = class MultiTenantDataSourceService {
100
100
  const dataSource = await this.getDataSource();
101
101
  return dataSource.getRepository(entity);
102
102
  }
103
- async getRepositoryForTenant(entity, tenantId) {
104
- const dataSource = await this.getDataSourceForTenant(tenantId);
105
- return dataSource.getRepository(entity);
106
- }
107
103
  async withTenant(tenantId, callback) {
108
104
  const dataSource = await this.getDataSourceForTenant(tenantId);
109
105
  return callback(dataSource);
@@ -2,25 +2,13 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
- function _export(target, all) {
6
- for(var name in all)Object.defineProperty(target, name, {
7
- enumerable: true,
8
- get: Object.getOwnPropertyDescriptor(all, name).get
9
- });
10
- }
11
- _export(exports, {
12
- get DEFAULT_PHONE_COUNTRY_CODE () {
13
- return DEFAULT_PHONE_COUNTRY_CODE;
14
- },
15
- get DEFAULT_PHONE_REGEX () {
16
- return DEFAULT_PHONE_REGEX;
17
- },
18
- get UtilsService () {
5
+ Object.defineProperty(exports, "UtilsService", {
6
+ enumerable: true,
7
+ get: function() {
19
8
  return UtilsService;
20
9
  }
21
10
  });
22
11
  const _common = require("@nestjs/common");
23
- const _classvalidator = require("class-validator");
24
12
  function _define_property(obj, key, value) {
25
13
  if (key in obj) {
26
14
  Object.defineProperty(obj, key, {
@@ -40,38 +28,25 @@ function _ts_decorate(decorators, target, key, desc) {
40
28
  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;
41
29
  return c > 3 && r && Object.defineProperty(target, key, r), r;
42
30
  }
43
- const DEFAULT_PHONE_REGEX = /^((\+880)|0)?(13|15|16|17|18|19)\d{8}$/;
44
- const DEFAULT_PHONE_COUNTRY_CODE = '+88';
45
31
  let UtilsService = class UtilsService {
46
- /**
47
- * Configure phone validation regex and country code
48
- */ setPhoneValidationConfig(config) {
49
- this.phoneConfig = config;
50
- }
51
32
  // ---------------- CACHE HELPERS ----------------
52
33
  /**
53
34
  * Generate cache key with optional tenant prefix for multi-tenant isolation
54
35
  */ getCacheKey(entityName, params, entityId, tenantId) {
55
- const tenantPrefix = tenantId ? `tenant_${tenantId}_` : '';
56
- if (entityId) return `${tenantPrefix}entity_${entityName}_id_${entityId}${params ? '_select_' + JSON.stringify(params) : ''}`;
57
- return `${tenantPrefix}entity_${entityName}_all_${JSON.stringify(params)}`;
36
+ const prefix = this.buildTenantPrefix(tenantId);
37
+ if (entityId) {
38
+ return `${prefix}entity_${entityName}_id_${entityId}${params ? '_select_' + JSON.stringify(params) : ''}`;
39
+ }
40
+ return `${prefix}entity_${entityName}_all_${JSON.stringify(params)}`;
58
41
  }
59
42
  /**
60
43
  * Track cache key for later invalidation with optional tenant prefix
61
44
  */ async trackCacheKey(cacheKey, entityName, cacheManager, entityId, tenantId) {
62
- const tenantPrefix = tenantId ? `tenant_${tenantId}_` : '';
63
45
  try {
64
- if (entityId) {
65
- const trackingKey = `${tenantPrefix}entity_${entityName}_id_${entityId}_keys`;
66
- const idKeys = await cacheManager.get(trackingKey) || [];
67
- if (!idKeys.includes(cacheKey)) idKeys.push(cacheKey);
68
- await cacheManager.set(trackingKey, idKeys);
69
- } else {
70
- const trackingKey = `${tenantPrefix}entity_${entityName}_keys`;
71
- const allKeys = await cacheManager.get(trackingKey) || [];
72
- if (!allKeys.includes(cacheKey)) allKeys.push(cacheKey);
73
- await cacheManager.set(trackingKey, allKeys);
74
- }
46
+ const trackingKey = this.buildTrackingKey(entityName, entityId, tenantId);
47
+ const keys = await cacheManager.get(trackingKey) || [];
48
+ if (!keys.includes(cacheKey)) keys.push(cacheKey);
49
+ await cacheManager.set(trackingKey, keys);
75
50
  } catch (error) {
76
51
  this.logger.error(`Cache tracking failed for ${entityName}`, error instanceof Error ? error.stack : String(error));
77
52
  }
@@ -79,49 +54,15 @@ let UtilsService = class UtilsService {
79
54
  /**
80
55
  * Clear cache for entity with optional tenant prefix
81
56
  */ async clearCache(entityName, cacheManager, entityId, tenantId) {
82
- const tenantPrefix = tenantId ? `tenant_${tenantId}_` : '';
83
57
  try {
84
- if (entityId) {
85
- const trackingKey = `${tenantPrefix}entity_${entityName}_id_${entityId}_keys`;
86
- const idKeys = await cacheManager.get(trackingKey) || [];
87
- await Promise.allSettled(idKeys.map((key)=>cacheManager.del(key)));
88
- await cacheManager.del(trackingKey);
89
- } else {
90
- const trackingKey = `${tenantPrefix}entity_${entityName}_keys`;
91
- const keySet = await cacheManager.get(trackingKey) || [];
92
- await Promise.allSettled(keySet.map((key)=>cacheManager.del(key)));
93
- await cacheManager.del(trackingKey);
94
- }
58
+ const trackingKey = this.buildTrackingKey(entityName, entityId, tenantId);
59
+ const keys = await cacheManager.get(trackingKey) || [];
60
+ await Promise.allSettled(keys.map((key)=>cacheManager.del(key)));
61
+ await cacheManager.del(trackingKey);
95
62
  } catch (error) {
96
63
  this.logger.error(`Cache invalidation failed for ${entityName}`, error instanceof Error ? error.stack : String(error));
97
64
  }
98
65
  }
99
- // ---------------- VALIDATION HELPERS ----------------
100
- /**
101
- * Check if value is phone or email and normalize phone number
102
- */ checkPhoneOrEmail(value) {
103
- if ((0, _classvalidator.isEmail)(value)) {
104
- return {
105
- value,
106
- type: 'email'
107
- };
108
- }
109
- const phoneMatch = value.match(this.phoneConfig.regex);
110
- if (phoneMatch) {
111
- let phone = phoneMatch[0];
112
- if (!phone.startsWith(this.phoneConfig.countryCode)) {
113
- phone = this.phoneConfig.countryCode + phone;
114
- }
115
- return {
116
- value: phone,
117
- type: 'phone'
118
- };
119
- }
120
- return {
121
- value: null,
122
- type: null
123
- };
124
- }
125
66
  // ---------------- STRING HELPERS ----------------
126
67
  /**
127
68
  * Transform string to URL-friendly slug
@@ -129,43 +70,21 @@ let UtilsService = class UtilsService {
129
70
  const slug = value?.trim().replace(/[^A-Z0-9]+/gi, '-').toLowerCase();
130
71
  return salt ? `${slug}-${this.getRandomInt(1, 100)}` : slug;
131
72
  }
132
- // ---------------- RANDOM HELPERS ----------------
133
73
  /**
134
74
  * Generate random integer between min and max (inclusive)
135
75
  */ getRandomInt(min, max) {
136
76
  return Math.floor(Math.random() * (max - min + 1)) + min;
137
77
  }
138
- /**
139
- * Generate random alphanumeric string
140
- */ generateRandomId(length) {
141
- const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
142
- let result = '';
143
- for(let i = 0; i < length; i++){
144
- result += characters.charAt(Math.floor(Math.random() * characters.length));
145
- }
146
- return result;
78
+ // ---------------- PRIVATE HELPERS ----------------
79
+ buildTenantPrefix(tenantId) {
80
+ return tenantId ? `tenant_${tenantId}_` : '';
147
81
  }
148
- /**
149
- * Generate 4-digit OTP code
150
- */ getRandomOtpCode() {
151
- return Math.floor(Math.random() * (9999 - 1000 + 1)) + 1000;
152
- }
153
- // ---------------- ERROR HELPERS ----------------
154
- /**
155
- * Extract column name and value from PostgreSQL error detail
156
- */ extractColumnNameFromError(detail) {
157
- const match = detail.match(/\((.*?)\)=\((.*?)\)/);
158
- return {
159
- columnName: match ? match[1] : 'unknown',
160
- value: match ? match[2] : 'unknown'
161
- };
82
+ buildTrackingKey(entityName, entityId, tenantId) {
83
+ const prefix = this.buildTenantPrefix(tenantId);
84
+ return entityId ? `${prefix}entity_${entityName}_id_${entityId}_keys` : `${prefix}entity_${entityName}_keys`;
162
85
  }
163
86
  constructor(){
164
87
  _define_property(this, "logger", new _common.Logger(UtilsService.name));
165
- _define_property(this, "phoneConfig", {
166
- regex: DEFAULT_PHONE_REGEX,
167
- countryCode: DEFAULT_PHONE_COUNTRY_CODE
168
- });
169
88
  }
170
89
  };
171
90
  UtilsService = _ts_decorate([
@@ -8,8 +8,6 @@ Object.defineProperty(exports, "ErrorHandler", {
8
8
  return ErrorHandler;
9
9
  }
10
10
  });
11
- const _common = require("@nestjs/common");
12
- /** Check if running in production environment */ const IS_PRODUCTION = process.env.NODE_ENV === 'production';
13
11
  /** Sensitive keys that should be redacted from logs */ const SENSITIVE_KEYS = [
14
12
  'password',
15
13
  'secret',
@@ -18,78 +16,23 @@ const _common = require("@nestjs/common");
18
16
  'credential',
19
17
  'authorization'
20
18
  ];
21
- /** Patterns that indicate sensitive data in error messages */ const SENSITIVE_PATTERNS = [
22
- /password/i,
23
- /secret/i,
24
- /token/i,
25
- /key/i,
26
- /credential/i,
27
- /authorization/i,
28
- /bearer/i
29
- ];
30
19
  let ErrorHandler = class ErrorHandler {
31
20
  /**
32
21
  * Safely extract error message from unknown error.
33
- * @param error - The error to extract message from
34
- * @param sanitizeForClient - If true, redacts potentially sensitive info in production
35
- */ static getErrorMessage(error, sanitizeForClient = false) {
36
- let message = 'Unknown error occurred';
37
- if (error instanceof Error) {
38
- message = error.message;
39
- } else if (typeof error === 'string') {
40
- message = error;
41
- }
42
- // Sanitize for client responses in production
43
- if (sanitizeForClient && IS_PRODUCTION) {
44
- if (this.containsSensitiveData(message)) {
45
- return 'An unexpected error occurred. Please try again later.';
46
- }
47
- }
48
- return message;
49
- }
50
- /**
51
- * Check if a string contains potentially sensitive data.
52
- */ static containsSensitiveData(text) {
53
- return SENSITIVE_PATTERNS.some((pattern)=>pattern.test(text));
54
- }
55
- /**
56
- * Safely extract error stack from unknown error.
57
- * Returns undefined in production for client responses.
58
- * @param error - The error to extract stack from
59
- * @param forClient - If true, never returns stack in production
60
- */ static getErrorStack(error, forClient = false) {
61
- // Never expose stack traces to clients in production
62
- if (forClient && IS_PRODUCTION) {
63
- return undefined;
64
- }
22
+ */ static getErrorMessage(error) {
65
23
  if (error instanceof Error) {
66
- return error.stack;
67
- }
68
- return undefined;
69
- }
70
- /**
71
- * Create a sanitized error response for clients.
72
- * In production, sensitive data is redacted and stack traces removed.
73
- */ static createClientError(error, statusCode = _common.HttpStatus.INTERNAL_SERVER_ERROR, code) {
74
- const sanitizedError = {
75
- message: this.getErrorMessage(error, true),
76
- statusCode
77
- };
78
- if (code) {
79
- sanitizedError.code = code;
24
+ return error.message;
80
25
  }
81
- // Include stack only in development
82
- if (!IS_PRODUCTION) {
83
- sanitizedError.stack = this.getErrorStack(error);
26
+ if (typeof error === 'string') {
27
+ return error;
84
28
  }
85
- return sanitizedError;
29
+ return 'Unknown error occurred';
86
30
  }
87
31
  /**
88
32
  * Sanitize context data to redact sensitive fields from logs.
89
33
  */ static sanitizeContextForLogging(context) {
90
34
  const sanitized = {};
91
35
  for (const [key, value] of Object.entries(context)){
92
- // Check if key contains sensitive words
93
36
  const isSensitive = SENSITIVE_KEYS.some((sk)=>key.toLowerCase().includes(sk.toLowerCase()));
94
37
  if (isSensitive) {
95
38
  sanitized[key] = '[REDACTED]';
@@ -105,7 +48,6 @@ let ErrorHandler = class ErrorHandler {
105
48
  }
106
49
  /**
107
50
  * Create error context object for internal logging.
108
- * Context data is sanitized to redact sensitive fields.
109
51
  */ static createErrorContext(error, context) {
110
52
  const errorContext = {
111
53
  error: {
@@ -117,29 +59,32 @@ let ErrorHandler = class ErrorHandler {
117
59
  errorContext.error.name = error.name;
118
60
  }
119
61
  if (context && Object.keys(context).length > 0) {
120
- // Sanitize context to redact sensitive fields
121
62
  errorContext.context = this.sanitizeContextForLogging(context);
122
63
  }
123
64
  return errorContext;
124
65
  }
125
66
  /**
126
67
  * Log error with consistent format.
127
- * Sensitive data in context is automatically redacted.
128
68
  */ static logError(logger, error, operation, context) {
129
69
  const errorContext = this.createErrorContext(error, {
130
70
  operation,
131
71
  ...context
132
72
  });
133
73
  const errorMessage = `Failed to ${operation}: ${errorContext.error.message}`;
134
- // Log full details internally (stack traces are fine for internal logs)
135
74
  logger.error(errorMessage, errorContext.error.stack, errorContext);
136
75
  }
137
76
  /**
138
- * Re-throw error with proper type checking
77
+ * Re-throw error with proper type checking.
139
78
  */ static rethrowError(error) {
140
79
  if (error instanceof Error) {
141
80
  throw error;
142
81
  }
143
82
  throw new Error(`Unexpected error: ${String(error)}`);
144
83
  }
84
+ /**
85
+ * Log error and re-throw (common pattern).
86
+ */ static logAndRethrow(logger, error, operation, context) {
87
+ this.logError(logger, error, operation, context);
88
+ this.rethrowError(error);
89
+ }
145
90
  };
@@ -16,9 +16,6 @@ function _export(target, all) {
16
16
  });
17
17
  }
18
18
  _export(exports, {
19
- get containsHtmlContent () {
20
- return containsHtmlContent;
21
- },
22
19
  get escapeHtml () {
23
20
  return escapeHtml;
24
21
  },
@@ -43,7 +40,7 @@ function escapeHtml(str) {
43
40
  if (!str || typeof str !== 'string') {
44
41
  return str ?? '';
45
42
  }
46
- return str.replace(HTML_ESCAPE_REGEX, (char)=>HTML_ESCAPE_MAP[char] || char);
43
+ return str.replace(HTML_ESCAPE_REGEX, (char)=>HTML_ESCAPE_MAP[char]);
47
44
  }
48
45
  function escapeHtmlVariables(variables) {
49
46
  if (!variables || typeof variables !== 'object') {
@@ -65,10 +62,3 @@ function escapeHtmlVariables(variables) {
65
62
  }
66
63
  return escaped;
67
64
  }
68
- function containsHtmlContent(str) {
69
- if (!str || typeof str !== 'string') {
70
- return false;
71
- }
72
- // Check for common HTML patterns
73
- return /<[a-z][\s\S]*>/i.test(str) || /javascript:/i.test(str) || /on\w+=/i.test(str);
74
- }
@@ -5,6 +5,8 @@ Object.defineProperty(exports, "__esModule", {
5
5
  _export_star(require("./error-handler.util"), exports);
6
6
  _export_star(require("./html-sanitizer.util"), exports);
7
7
  _export_star(require("./query-helpers.util"), exports);
8
+ _export_star(require("./request.util"), exports);
9
+ _export_star(require("./string.util"), exports);
8
10
  function _export_star(from, to) {
9
11
  Object.keys(from).forEach(function(k) {
10
12
  if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {