@catbee/utils 2.0.4 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/logger/index.mjs CHANGED
@@ -51,57 +51,140 @@ var defaultSensitiveFields = [
51
51
  "passphrase",
52
52
  "otp",
53
53
  "api_secret",
54
- "token_secret"
54
+ "token_secret",
55
+ "cookie",
56
+ "cookies",
57
+ "set-cookie",
58
+ "session_id",
59
+ "credit_card",
60
+ "card_number",
61
+ "cvv",
62
+ "cvc",
63
+ "ssn",
64
+ "pin"
55
65
  ];
56
- var defaultRedactPaths = [
57
- "req.authorization",
58
- "req.headers.authorization",
59
- "req.headers.cookie",
60
- 'req.headers["x-api-key"]',
61
- 'req.headers["x-auth-token"]',
62
- 'req.headers["x-access-token"]',
63
- "req.body.password",
64
- "req.body.token",
65
- "req.body.secret",
66
- "req.query.token",
67
- "req.query.api_key",
68
- "req.query.apiKey",
69
- "res.authorization",
70
- "res.headers.authorization",
71
- 'res.headers["set-cookie"]',
66
+ var requestSubPaths = [
67
+ "authorization",
72
68
  "headers.authorization",
69
+ "headers.cookie",
73
70
  "headers.cookies",
74
71
  'headers["set-cookie"]',
75
72
  'headers["x-api-key"]',
76
73
  'headers["x-auth-token"]',
77
74
  'headers["x-access-token"]',
75
+ "body.password",
76
+ "body.token",
77
+ "body.secret",
78
+ "query.token",
79
+ "query.api_key",
80
+ "query.apiKey",
81
+ "url",
82
+ "originalUrl"
83
+ ];
84
+ var responseSubPaths = [
85
+ "authorization",
86
+ "headers.authorization",
87
+ 'headers["set-cookie"]'
88
+ ];
89
+ var commonHeaderPaths = [
90
+ "headers.authorization",
91
+ "headers.cookie",
92
+ "headers.cookies",
93
+ 'headers["set-cookie"]',
94
+ 'headers["x-api-key"]',
95
+ 'headers["x-auth-token"]',
96
+ 'headers["x-access-token"]'
97
+ ];
98
+ var defaultRedactPaths = [
99
+ ...[
100
+ "req",
101
+ "request"
102
+ ].flatMap((p) => requestSubPaths.map((sp) => `${p}.${sp}`)),
103
+ ...[
104
+ "res",
105
+ "response"
106
+ ].flatMap((p) => responseSubPaths.map((sp) => `${p}.${sp}`)),
107
+ ...commonHeaderPaths,
78
108
  "url",
79
109
  "uri",
80
110
  "href",
81
111
  "redirect_uri",
82
112
  "redirectUri"
83
113
  ];
84
- var globalRedactCensor = /* @__PURE__ */ __name((value, path, sensitiveFields = getExpandedSensitiveFields()) => {
114
+ var escapeRegExp = /* @__PURE__ */ __name((value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "escapeRegExp");
115
+ function buildUrlRedactRegex(lowerSensitiveFields) {
116
+ if (lowerSensitiveFields.length === 0) return null;
117
+ const pattern = lowerSensitiveFields.map(escapeRegExp).join("|");
118
+ return new RegExp(`([?&](${pattern})=)[^&#]*`, "gi");
119
+ }
120
+ __name(buildUrlRedactRegex, "buildUrlRedactRegex");
121
+ var URL_PATH_KEYS = /* @__PURE__ */ new Set([
122
+ "url",
123
+ "uri",
124
+ "href",
125
+ "originalurl",
126
+ "redirect_uri",
127
+ "redirecturi"
128
+ ]);
129
+ var cachedExpandedFields = null;
130
+ var cachedLowerSensitiveFields = null;
131
+ var cachedLowerSensitiveSet = null;
132
+ var cachedNormalizedMultiWordFields = null;
133
+ var cachedUrlRegex = null;
134
+ function invalidateSensitiveFieldsCache() {
135
+ cachedExpandedFields = null;
136
+ cachedLowerSensitiveFields = null;
137
+ cachedLowerSensitiveSet = null;
138
+ cachedNormalizedMultiWordFields = null;
139
+ cachedUrlRegex = null;
140
+ }
141
+ __name(invalidateSensitiveFieldsCache, "invalidateSensitiveFieldsCache");
142
+ function extractNormalizedMultiWordFields(fields) {
143
+ return Array.from(new Set(fields.map((f) => f.replace(/[-]/g, "_")).filter((f) => f.includes("_")).map((f) => `_${f}_`)));
144
+ }
145
+ __name(extractNormalizedMultiWordFields, "extractNormalizedMultiWordFields");
146
+ function isSensitivePathSegment(segment, lowerFieldsSet, multiWordNormalizedList) {
147
+ if (!segment || typeof segment !== "string") return false;
148
+ const lower = segment.toLowerCase();
149
+ if (lowerFieldsSet.has(lower)) return true;
150
+ const tokens = segment.split(/[_-]+|(?<=[a-z0-9])(?=[A-Z])/g).filter(Boolean);
151
+ if (tokens.some((t) => lowerFieldsSet.has(t.toLowerCase()))) return true;
152
+ if (multiWordNormalizedList.length === 0) return false;
153
+ const normalized = `_${segment.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[-]/g, "_")}_`;
154
+ return multiWordNormalizedList.some((fNorm) => normalized.includes(fNorm));
155
+ }
156
+ __name(isSensitivePathSegment, "isSensitivePathSegment");
157
+ function getExpandedSensitiveFields() {
158
+ if (!cachedExpandedFields) {
159
+ cachedExpandedFields = expandSensitiveFields(defaultSensitiveFields);
160
+ cachedLowerSensitiveFields = cachedExpandedFields.map((f) => f.toLowerCase());
161
+ cachedLowerSensitiveSet = new Set(cachedLowerSensitiveFields);
162
+ cachedNormalizedMultiWordFields = extractNormalizedMultiWordFields(cachedLowerSensitiveFields);
163
+ cachedUrlRegex = buildUrlRedactRegex(cachedLowerSensitiveFields);
164
+ }
165
+ return cachedExpandedFields;
166
+ }
167
+ __name(getExpandedSensitiveFields, "getExpandedSensitiveFields");
168
+ var globalRedactCensor = /* @__PURE__ */ __name((value, path, sensitiveFields) => {
85
169
  if (typeof value !== "string") return "***";
86
- const lowerPath = path.filter((p) => typeof p === "string").map((p) => p.toLowerCase());
87
- const lowerSensitiveFields = sensitiveFields.map((f) => f.toLowerCase());
88
- if (lowerPath.some((p) => lowerSensitiveFields.includes(p))) return "***";
89
- if (lowerPath[0] === "url" || lowerPath[0] === "uri" || lowerPath[0] === "href" || lowerPath.includes("redirect_uri") || lowerPath.includes("redirecturi")) {
90
- return value.replace(new RegExp(`([?&](${lowerSensitiveFields.join("|")})=)[^&#]*`, "gi"), "$1***");
170
+ const stringPath = path.filter((p) => typeof p === "string");
171
+ const lowerPath = stringPath.map((p) => p.toLowerCase());
172
+ const isCustomFields = sensitiveFields !== void 0;
173
+ if (!isCustomFields) {
174
+ getExpandedSensitiveFields();
91
175
  }
92
- if (lowerPath.some((p) => p.includes("authorization") || p.includes("auth"))) {
93
- return value.replace(/^(\S+)\s+.+$/, "$1 ***") || "***";
176
+ const lowerFieldsList = isCustomFields ? sensitiveFields.map((f) => f.toLowerCase()) : cachedLowerSensitiveFields;
177
+ const lowerFieldsSet = isCustomFields ? new Set(lowerFieldsList) : cachedLowerSensitiveSet;
178
+ const multiWordList = isCustomFields ? extractNormalizedMultiWordFields(lowerFieldsList) : cachedNormalizedMultiWordFields;
179
+ if (lowerPath.some((p) => lowerFieldsSet.has(p))) return "***";
180
+ if (lowerPath.some((p) => URL_PATH_KEYS.has(p) || p.includes("redirect_uri") || p.includes("redirecturi"))) {
181
+ if (lowerFieldsList.length === 0) return value;
182
+ const urlRegex = isCustomFields ? buildUrlRedactRegex(lowerFieldsList) : cachedUrlRegex;
183
+ return urlRegex ? value.replace(urlRegex, "$1***") : value;
94
184
  }
95
- if (lowerPath.some((p) => lowerSensitiveFields.some((f) => p.includes(f)))) {
185
+ if (stringPath.some((p) => isSensitivePathSegment(p, lowerFieldsSet, multiWordList))) {
96
186
  return "***";
97
187
  }
98
- if (path.length > 0 && ![
99
- "req",
100
- "res",
101
- "headers"
102
- ].includes(lowerPath[0])) {
103
- return value;
104
- }
105
188
  return value;
106
189
  }, "globalRedactCensor");
107
190
  function setRedactCensor(fn) {
@@ -117,38 +200,36 @@ function redact(value, path, sensitiveFields) {
117
200
  }
118
201
  __name(redact, "redact");
119
202
  function addRedactFields(fields) {
203
+ addSensitiveFields(fields);
120
204
  const prev = globalRedactCensor;
121
205
  globalRedactCensor = /* @__PURE__ */ __name((value, path, sensitiveFields = getExpandedSensitiveFields()) => prev(value, path, [
122
206
  ...sensitiveFields || [],
123
207
  ...fields
124
208
  ]), "globalRedactCensor");
125
- cachedExpandedFields = null;
126
209
  }
127
210
  __name(addRedactFields, "addRedactFields");
128
- var cachedExpandedFields = null;
129
- function getExpandedSensitiveFields() {
130
- cachedExpandedFields ??= expandSensitiveFields(defaultSensitiveFields);
131
- return cachedExpandedFields;
132
- }
133
- __name(getExpandedSensitiveFields, "getExpandedSensitiveFields");
134
211
  function setSensitiveFields(fields) {
135
212
  defaultSensitiveFields.splice(0, defaultSensitiveFields.length, ...fields);
136
- cachedExpandedFields = null;
213
+ invalidateSensitiveFieldsCache();
137
214
  }
138
215
  __name(setSensitiveFields, "setSensitiveFields");
139
216
  function addSensitiveFields(fields) {
140
217
  defaultSensitiveFields.push(...fields);
141
- cachedExpandedFields = null;
218
+ invalidateSensitiveFieldsCache();
142
219
  }
143
220
  __name(addSensitiveFields, "addSensitiveFields");
144
221
  var _globalThis = typeof globalThis === "object" ? globalThis : global;
145
222
  var _global = _globalThis;
223
+ function resetLogger() {
224
+ delete _global[GLOBAL_LOGGER_KEY];
225
+ }
226
+ __name(resetLogger, "resetLogger");
146
227
  function setupLogger(isGlobal = true) {
147
228
  const sensitiveFields = getExpandedSensitiveFields();
148
229
  const { logger: loggerConfig } = getCatbeeGlobalConfig();
149
230
  const paths = /* @__PURE__ */ new Set([
150
231
  ...defaultRedactPaths,
151
- ...sensitiveFields.flatMap((field) => generateDeepPaths(field, 2))
232
+ ...sensitiveFields.flatMap((field) => generateDeepPaths(field, 5))
152
233
  ]);
153
234
  const logParams = {
154
235
  name: loggerConfig?.name ?? "@catbee/utils",
@@ -170,56 +251,50 @@ function setupLogger(isGlobal = true) {
170
251
  let logger;
171
252
  const logDir = loggerConfig?.dir?.trim();
172
253
  const hasFileLogging = Boolean(logDir);
254
+ const destination = logDir ? `${logDir.replace(/[/\\]+$/, "")}/app.log` : "app.log";
255
+ const prettyOptions = {
256
+ colorize: loggerConfig?.colorize,
257
+ translateTime: "SYS:standard",
258
+ ignore: "pid,hostname",
259
+ singleLine: loggerConfig?.singleLine,
260
+ levelFirst: true
261
+ };
262
+ const fileOptions = {
263
+ destination,
264
+ mkdir: true
265
+ };
173
266
  if (hasFileLogging && loggerConfig?.pretty) {
174
267
  logger = pino(logParams, pino.transport({
175
268
  targets: [
176
269
  {
177
270
  target: "pino-pretty",
178
271
  level: loggerConfig?.level ?? "info",
179
- options: {
180
- colorize: loggerConfig?.colorize,
181
- translateTime: "SYS:standard",
182
- ignore: "pid,hostname",
183
- singleLine: loggerConfig?.singleLine,
184
- levelFirst: true
185
- }
272
+ options: prettyOptions
186
273
  },
187
274
  {
188
275
  target: "pino/file",
189
276
  level: loggerConfig?.level ?? "info",
190
- options: {
191
- destination: `${logDir}/app.log`,
192
- mkdir: true
193
- }
277
+ options: fileOptions
194
278
  }
195
279
  ]
196
280
  }));
197
281
  } else if (hasFileLogging) {
198
282
  logger = pino(logParams, pino.transport({
199
283
  target: "pino/file",
200
- options: {
201
- destination: `${logDir}/app.log`,
202
- mkdir: true
203
- }
284
+ options: fileOptions
204
285
  }));
205
286
  } else if (loggerConfig?.pretty) {
206
287
  logger = pino(logParams, pino.transport({
207
288
  target: "pino-pretty",
208
- options: {
209
- colorize: loggerConfig?.colorize,
210
- translateTime: "SYS:standard",
211
- ignore: "pid,hostname",
212
- singleLine: loggerConfig?.singleLine,
213
- levelFirst: true
214
- }
289
+ options: prettyOptions
215
290
  }));
216
291
  } else {
217
292
  logger = pino(logParams);
218
293
  }
219
294
  if (isGlobal) {
220
295
  _global[GLOBAL_LOGGER_KEY] = logger;
221
- _global[GLOBAL_LOGGER_KEY]?.debug({
222
- logDir
296
+ logger.debug({
297
+ logDir: logDir || "none"
223
298
  }, "Global Logger Initialized");
224
299
  }
225
300
  return logger;
@@ -247,9 +322,15 @@ function createRequestLogger(requestId, additionalContext = {}) {
247
322
  requestId,
248
323
  ...additionalContext
249
324
  });
325
+ let stored = false;
250
326
  try {
251
- ContextStore.set(StoreKeys.LOGGER, logger);
327
+ if (typeof ContextStore.getAll !== "function" || ContextStore.getAll() !== void 0) {
328
+ ContextStore.set(StoreKeys.LOGGER, logger);
329
+ stored = true;
330
+ }
252
331
  } catch {
332
+ }
333
+ if (!stored) {
253
334
  logger.debug("Failed to store logger in context - AsyncLocalStorage not initialized");
254
335
  }
255
336
  return logger;
@@ -276,13 +357,29 @@ function expandSensitiveFields(fields) {
276
357
  }
277
358
  __name(expandSensitiveFields, "expandSensitiveFields");
278
359
  function expandSensitiveField(field) {
279
- const parts = field.split(/[_-]/g).filter(Boolean);
280
- const camel = parts[0].toLowerCase() + parts.slice(1).map((p) => p.charAt(0).toUpperCase() + p.slice(1).toLowerCase()).join("");
281
- const mergedLower = parts.join("").toLowerCase();
282
- const mergedUpper = parts.join("").toUpperCase();
283
- const kebab = parts.map((p) => p.toLowerCase()).join("-");
284
- const snakeLower = parts.map((p) => p.toLowerCase()).join("_");
285
- const snakeUpper = parts.map((p) => p.toUpperCase()).join("_");
360
+ if (!field || typeof field !== "string") {
361
+ return [];
362
+ }
363
+ const trimmed = field.trim();
364
+ if (!trimmed) {
365
+ return [
366
+ field
367
+ ];
368
+ }
369
+ const parts = trimmed.split(/[_-]+|(?<=[a-z0-9])(?=[A-Z])/g).filter(Boolean);
370
+ if (parts.length === 0) {
371
+ return [
372
+ field
373
+ ];
374
+ }
375
+ const lowerParts = parts.map((p) => p.toLowerCase());
376
+ const upperParts = parts.map((p) => p.toUpperCase());
377
+ const camel = lowerParts[0] + lowerParts.slice(1).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
378
+ const mergedLower = lowerParts.join("");
379
+ const mergedUpper = upperParts.join("");
380
+ const kebab = lowerParts.join("-");
381
+ const snakeLower = lowerParts.join("_");
382
+ const snakeUpper = upperParts.join("_");
286
383
  return Array.from(/* @__PURE__ */ new Set([
287
384
  field,
288
385
  camel,
@@ -294,7 +391,7 @@ function expandSensitiveField(field) {
294
391
  ]));
295
392
  }
296
393
  __name(expandSensitiveField, "expandSensitiveField");
297
- function generateDeepPaths(field, depth) {
394
+ function generateDeepPaths(field, depth = 5) {
298
395
  const set = /* @__PURE__ */ new Set([
299
396
  field
300
397
  ]);
@@ -309,4 +406,4 @@ function generateDeepPaths(field, depth) {
309
406
  }
310
407
  __name(generateDeepPaths, "generateDeepPaths");
311
408
 
312
- export { _globalThis, addRedactFields, addSensitiveFields, createChildLogger, createRequestLogger, defaultSensitiveFields, expandSensitiveField, expandSensitiveFields, generateDeepPaths, getExpandedSensitiveFields, getLogger, getRedactCensor, logError, redact, setRedactCensor, setSensitiveFields };
409
+ export { _globalThis, addRedactFields, addSensitiveFields, createChildLogger, createRequestLogger, defaultSensitiveFields, expandSensitiveField, expandSensitiveFields, generateDeepPaths, getExpandedSensitiveFields, getLogger, getRedactCensor, logError, redact, resetLogger, setRedactCensor, setSensitiveFields };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@catbee/utils",
3
- "version": "2.0.4",
3
+ "version": "2.1.1",
4
4
  "description": "A modular, production-grade utility toolkit for Node.js and TypeScript, designed for robust, scalable applications (including Express-based services). All utilities are tree-shakable and can be imported independently.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -20,17 +20,17 @@
20
20
  },
21
21
  "license": "MIT",
22
22
  "dependencies": {
23
- "pino": "^10.3.1"
23
+ "pino": "^10.4.0"
24
24
  },
25
25
  "optionalDependencies": {
26
- "@scalar/express-api-reference": "^0.9.6",
26
+ "@scalar/express-api-reference": "^0.10.26",
27
27
  "abort-controller": "^3.0.0",
28
- "compression": "^1.8.1",
28
+ "compression": "^1.8.2",
29
29
  "cookie-parser": "^1.4.7",
30
30
  "cors": "^2.8.6",
31
- "dotenv": "^17.4.1",
32
- "express-rate-limit": "^8.3.2",
33
- "helmet": "^8.1.0",
31
+ "dotenv": "^18.0.5",
32
+ "express-rate-limit": "^8.7.0",
33
+ "helmet": "^8.3.0",
34
34
  "pino-pretty": "^13.1.3",
35
35
  "reflect-metadata": "^0.2.2"
36
36
  },
@@ -145,6 +145,11 @@
145
145
  "require": "./fs/index.cjs",
146
146
  "types": "./fs/index.d.ts"
147
147
  },
148
+ "./healthz-server": {
149
+ "import": "./healthz-server/index.mjs",
150
+ "require": "./healthz-server/index.cjs",
151
+ "types": "./healthz-server/index.d.ts"
152
+ },
148
153
  "./http-status-codes": {
149
154
  "import": "./http-status-codes/index.mjs",
150
155
  "require": "./http-status-codes/index.cjs",