mbkauthe 4.9.0 → 5.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.
@@ -1,4 +1,5 @@
1
- import path from "path";
1
+ import path from "path";
2
+ import crypto from "crypto";
2
3
  import { AsyncLocalStorage } from "async_hooks";
3
4
 
4
5
  const isDev = process.env.env === "dev" && process.env.dbLogs === "true";
@@ -11,6 +12,12 @@ const globalQueryState = {
11
12
  };
12
13
  let autoPoolId = 0;
13
14
 
15
+ const callsiteCaptureSetting = (process.env.dbLogsCallsite || "true").toLowerCase();
16
+ const parsedCallsiteSampleRate = Number(process.env.dbLogsCallsiteSample || "1");
17
+ const callsiteSampleRate = Number.isFinite(parsedCallsiteSampleRate)
18
+ ? Math.min(1, Math.max(0, parsedCallsiteSampleRate))
19
+ : 1;
20
+
14
21
  const safeValue = (value, depth = 0, seen = new WeakSet()) => {
15
22
  if (value == null) return value;
16
23
  if (typeof value === "string") {
@@ -59,40 +66,81 @@ const toWorkspacePath = (filePath) => {
59
66
  return rel.replace(/\\/g, "/");
60
67
  };
61
68
 
69
+ const isIgnorableStackFrame = (line) =>
70
+ !line.startsWith("at ") ||
71
+ line.includes("/lib/utils/dbQueryLogger.js") ||
72
+ line.includes("\\lib\\utils\\dbQueryLogger.js") ||
73
+ line.includes("node:internal") ||
74
+ line.includes("internal/process");
75
+
76
+ const parseStackFrame = (frame) => {
77
+ const withFunc = /^at\s+([^\s(]+)\s+\((.+):([0-9]+):([0-9]+)\)$/.exec(frame);
78
+ const noFunc = /^at\s+(.+):([0-9]+):([0-9]+)$/.exec(frame);
79
+
80
+ if (withFunc) {
81
+ return {
82
+ function: withFunc[1],
83
+ file: withFunc[2],
84
+ line: Number(withFunc[3]),
85
+ column: Number(withFunc[4]),
86
+ };
87
+ }
88
+
89
+ if (noFunc) {
90
+ return {
91
+ function: null,
92
+ file: noFunc[1],
93
+ line: Number(noFunc[2]),
94
+ column: Number(noFunc[3]),
95
+ };
96
+ }
97
+
98
+ return null;
99
+ };
100
+
101
+ const isNodeModulesFrame = (filePath) => /[\\/]node_modules[\\/]/i.test(filePath || "");
102
+
103
+ const shouldCaptureCallsite = () => {
104
+ if (callsiteCaptureSetting === "false" || callsiteCaptureSetting === "0") {
105
+ return false;
106
+ }
107
+
108
+ if (callsiteSampleRate <= 0) {
109
+ return false;
110
+ }
111
+
112
+ if (callsiteSampleRate >= 1) {
113
+ return true;
114
+ }
115
+
116
+ return Math.random() < callsiteSampleRate;
117
+ };
118
+
62
119
  const buildCallsite = () => {
120
+ if (!shouldCaptureCallsite()) {
121
+ return null;
122
+ }
123
+
63
124
  try {
64
125
  const stack = new Error().stack || "";
65
- const lines = stack.split("\n").map((l) => l.trim());
66
- const frame = lines.find(
67
- (line) =>
68
- line.startsWith("at ") &&
69
- !line.includes("/lib/utils/dbQueryLogger.js") &&
70
- !line.includes("node:internal") &&
71
- !line.includes("internal/process")
72
- );
73
-
74
- if (!frame) return null;
75
-
76
- const withFunc = /^at\s+([^\s(]+)\s+\((.+):([0-9]+):([0-9]+)\)$/.exec(frame);
77
- const noFunc = /^at\s+(.+):([0-9]+):([0-9]+)$/.exec(frame);
78
-
79
- if (withFunc) {
80
- return {
81
- function: withFunc[1],
82
- file: toWorkspacePath(withFunc[2]),
83
- line: Number(withFunc[3]),
84
- column: Number(withFunc[4]),
85
- };
86
- }
87
-
88
- if (noFunc) {
89
- return {
90
- function: null,
91
- file: toWorkspacePath(noFunc[1]),
92
- line: Number(noFunc[2]),
93
- column: Number(noFunc[3]),
94
- };
95
- }
126
+ const frames = stack
127
+ .split("\n")
128
+ .map((line) => line.trim())
129
+ .filter((line) => !isIgnorableStackFrame(line))
130
+ .map(parseStackFrame)
131
+ .filter(Boolean);
132
+
133
+ if (!frames.length) return null;
134
+
135
+ const preferredFrame = frames.find((frame) => !isNodeModulesFrame(frame.file));
136
+ if (!preferredFrame) return null;
137
+
138
+ return {
139
+ function: preferredFrame.function,
140
+ file: toWorkspacePath(preferredFrame.file),
141
+ line: preferredFrame.line,
142
+ column: preferredFrame.column,
143
+ };
96
144
  } catch {
97
145
  return null;
98
146
  }
@@ -100,6 +148,43 @@ const buildCallsite = () => {
100
148
  return null;
101
149
  };
102
150
 
151
+ const normalizeQueryText = (queryText) =>
152
+ String(queryText || "")
153
+ .replace(/\/\*[\s\S]*?\*\//g, " ")
154
+ .replace(/--.*$/gm, " ")
155
+ .replace(/\$[0-9]+\b/g, "?")
156
+ .replace(/\b[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/gi, "?")
157
+ .replace(/'(?:''|[^'])*'/g, "?")
158
+ .replace(/\b\d+(?:\.\d+)?\b/g, "?")
159
+ .replace(/\s+/g, " ")
160
+ .trim()
161
+ .toLowerCase();
162
+
163
+ const buildQueryFingerprint = (queryText) => {
164
+ const normalizedQuery = normalizeQueryText(queryText);
165
+ if (!normalizedQuery) {
166
+ return { fingerprint: null, normalizedQuery: "" };
167
+ }
168
+
169
+ return {
170
+ normalizedQuery,
171
+ fingerprint: crypto
172
+ .createHash("sha1")
173
+ .update(normalizedQuery)
174
+ .digest("hex")
175
+ .slice(0, 12),
176
+ };
177
+ };
178
+
179
+ const isSessionStoreQuery = (normalizedQuery, queryName) => {
180
+ const normalizedName = String(queryName || "").toLowerCase();
181
+ if (normalizedName.includes("session")) {
182
+ return true;
183
+ }
184
+
185
+ return /\b(?:from|update|into|delete from)\s+"session"\b/.test(normalizedQuery || "");
186
+ };
187
+
103
188
  const buildRequestContext = () => {
104
189
  const store = getRequestContext();
105
190
  const req = store?.req;
@@ -135,6 +220,59 @@ const buildReturnValue = (result) => {
135
220
  return returnValue;
136
221
  };
137
222
 
223
+ const buildTriggerContext = ({ request, callsite, normalizedQuery, queryName }) => {
224
+ const routeText = request ? `${request.method || ""} ${request.url || ""}`.trim() : "";
225
+ const sessionStore = isSessionStoreQuery(normalizedQuery, queryName);
226
+
227
+ if (request && sessionStore) {
228
+ return {
229
+ type: "request",
230
+ source: "session-store",
231
+ label: routeText ? `Session store during ${routeText}` : "Session store during request",
232
+ route: routeText || null,
233
+ };
234
+ }
235
+
236
+ if (request) {
237
+ return {
238
+ type: "request",
239
+ source: "route",
240
+ label: routeText || "Request route",
241
+ route: routeText || null,
242
+ };
243
+ }
244
+
245
+ if (callsite) {
246
+ const functionName = callsite.function || "(anonymous)";
247
+ const location = callsite.file
248
+ ? `${callsite.file}:${callsite.line}:${callsite.column}`
249
+ : "unknown location";
250
+
251
+ return {
252
+ type: "code",
253
+ source: "callsite",
254
+ label: `Code trigger: ${functionName} @ ${location}`,
255
+ route: null,
256
+ };
257
+ }
258
+
259
+ if (sessionStore) {
260
+ return {
261
+ type: "code",
262
+ source: "session-store",
263
+ label: "Session store outside request context",
264
+ route: null,
265
+ };
266
+ }
267
+
268
+ return {
269
+ type: "code",
270
+ source: "unknown",
271
+ label: "Code trigger (unresolved)",
272
+ route: null,
273
+ };
274
+ };
275
+
138
276
  const recordGlobalLog = (entry) => {
139
277
  globalQueryState.totalCount += 1;
140
278
  globalQueryState.log.push(entry);
@@ -171,12 +309,11 @@ export const getRequestContext = () => {
171
309
  };
172
310
 
173
311
  const resolveLoggerPool = (item) => {
174
- if (item && typeof item === 'object') {
312
+ if (item && typeof item === "object") {
175
313
  if (item.pool && item.pool.query) {
176
314
  return { pool: item.pool, name: item.name || item.pool.__mbkQueryLoggerName || item.pool.name };
177
315
  }
178
316
  if (item.query) {
179
- // Don't force 'default' here; let getPoolName assign a non-default auto name.
180
317
  return { pool: item, name: item.__mbkQueryLoggerName || item.name || item.options?.application_name || null };
181
318
  }
182
319
  }
@@ -188,16 +325,35 @@ const getPoolName = (pool, fallbackName) => {
188
325
  if (pool.__mbkQueryLoggerName) return pool.__mbkQueryLoggerName;
189
326
  if (pool.name) return pool.name;
190
327
  if (pool.options && pool.options.application_name) return pool.options.application_name;
191
- // never return "default"; always provide an actual pool name
192
328
  autoPoolId += 1;
193
329
  return `pool-${autoPoolId}`;
194
330
  };
195
331
 
332
+ const parseQueryArgs = (args) => {
333
+ let queryText = "";
334
+ let queryName = "";
335
+ let queryValues;
336
+
337
+ try {
338
+ if (typeof args[0] === "string") {
339
+ queryText = args[0];
340
+ queryValues = Array.isArray(args[1]) ? args[1] : undefined;
341
+ } else if (args[0] && typeof args[0] === "object") {
342
+ queryText = args[0].text || "";
343
+ queryName = args[0].name || "";
344
+ queryValues = Array.isArray(args[0].values) ? args[0].values : undefined;
345
+ }
346
+ } catch {
347
+ queryText = "";
348
+ }
349
+
350
+ return { queryText, queryName, queryValues };
351
+ };
352
+
196
353
  const attachSinglePool = (pool, poolName = null) => {
197
354
  if (!pool) return;
198
355
 
199
356
  if (pool.__mbkQueryLoggerInstalled) {
200
- // Allow late explicit naming to override a previous auto name.
201
357
  if (poolName && pool.__mbkQueryLoggerName !== poolName) {
202
358
  pool.__mbkQueryLoggerName = poolName;
203
359
  }
@@ -210,6 +366,7 @@ const attachSinglePool = (pool, poolName = null) => {
210
366
  let dbQueryCount = 0;
211
367
  const dbQueryLog = [];
212
368
  const originalQuery = pool.query.bind(pool);
369
+ const originalConnect = typeof pool.connect === "function" ? pool.connect.bind(pool) : null;
213
370
 
214
371
  const recordPoolLog = (entry) => {
215
372
  dbQueryCount += 1;
@@ -220,80 +377,328 @@ const attachSinglePool = (pool, poolName = null) => {
220
377
  recordGlobalLog(entry);
221
378
  };
222
379
 
223
- pool.query = (...args) => {
224
- let queryText = "";
225
- let queryName = "";
226
- let queryValues;
380
+ const recordLogEntry = ({
381
+ queryText,
382
+ queryName,
383
+ queryValues,
384
+ callsiteSnapshot,
385
+ success,
386
+ error,
387
+ result,
388
+ durationMs,
389
+ executionDurationMs,
390
+ poolWait,
391
+ }) => {
392
+ const request = buildRequestContext();
393
+ const returnValue = buildReturnValue(result);
394
+ const { fingerprint, normalizedQuery } = buildQueryFingerprint(queryText);
395
+ const trigger = buildTriggerContext({
396
+ request,
397
+ callsite: callsiteSnapshot,
398
+ normalizedQuery,
399
+ queryName,
400
+ });
401
+
402
+ recordPoolLog({
403
+ time: new Date().toISOString(),
404
+ query: queryText,
405
+ normalizedQuery,
406
+ fingerprint,
407
+ name: queryName || undefined,
408
+ values: queryValues,
409
+ durationMs,
410
+ executionDurationMs,
411
+ success,
412
+ error: error ? { message: error.message, code: error.code } : undefined,
413
+ returnValue,
414
+ request,
415
+ trigger,
416
+ pool: {
417
+ name: pool.__mbkQueryLoggerName,
418
+ total: pool.totalCount,
419
+ idle: pool.idleCount,
420
+ waiting: pool.waitingCount,
421
+ },
422
+ poolWait,
423
+ callsite: callsiteSnapshot,
424
+ });
425
+ };
227
426
 
228
- try {
229
- if (typeof args[0] === "string") {
230
- queryText = args[0];
231
- queryValues = Array.isArray(args[1]) ? args[1] : undefined;
232
- } else if (args[0] && typeof args[0] === "object") {
233
- queryText = args[0].text || "";
234
- queryName = args[0].name || "";
235
- queryValues = Array.isArray(args[0].values) ? args[0].values : undefined;
236
- }
237
- } catch {
238
- queryText = "";
427
+ const instrumentClient = (client, acquisition = null) => {
428
+ if (!client || typeof client.query !== "function") {
429
+ return client;
239
430
  }
240
431
 
241
- if (!queryText) {
242
- return originalQuery(...args);
432
+ client.__mbkQueryLoggerAcquisition = acquisition
433
+ ? { ...acquisition, attributedToQuery: false }
434
+ : null;
435
+
436
+ if (!client.__mbkQueryLoggerOriginalQuery) {
437
+ client.__mbkQueryLoggerOriginalQuery = client.query.bind(client);
243
438
  }
244
439
 
245
- const startTime = process.hrtime.bigint();
246
- const callsiteSnapshot = buildCallsite();
440
+ if (!client.__mbkQueryLoggerReleaseWrapped && typeof client.release === "function") {
441
+ const originalRelease = client.release.bind(client);
442
+ client.release = (...args) => {
443
+ client.__mbkQueryLoggerAcquisition = null;
444
+ return originalRelease(...args);
445
+ };
446
+ client.__mbkQueryLoggerReleaseWrapped = true;
447
+ }
448
+
449
+ if (client.__mbkQueryLoggerQueryWrapped) {
450
+ return client;
451
+ }
452
+
453
+ client.query = (...args) => {
454
+ const { queryText, queryName, queryValues } = parseQueryArgs(args);
455
+ if (!queryText) {
456
+ return client.__mbkQueryLoggerOriginalQuery(...args);
457
+ }
458
+
459
+ const callsiteSnapshot = buildCallsite();
460
+ const executionStartTime = process.hrtime.bigint();
461
+ const acquisitionMeta = client.__mbkQueryLoggerAcquisition;
462
+ const waitMs = acquisitionMeta && acquisitionMeta.attributedToQuery
463
+ ? 0
464
+ : acquisitionMeta?.waitMs || 0;
465
+ const waitingBefore = acquisitionMeta?.waitingBefore || 0;
466
+ const waitSource = acquisitionMeta?.source || "pool.connect";
467
+
468
+ if (acquisitionMeta && !acquisitionMeta.attributedToQuery) {
469
+ acquisitionMeta.attributedToQuery = true;
470
+ }
471
+
472
+ const finalize = (success, error, result) => {
473
+ const executionDurationMs = Number(process.hrtime.bigint() - executionStartTime) / 1_000_000;
474
+
475
+ recordLogEntry({
476
+ queryText,
477
+ queryName,
478
+ queryValues,
479
+ callsiteSnapshot,
480
+ success,
481
+ error,
482
+ result,
483
+ durationMs: executionDurationMs + waitMs,
484
+ executionDurationMs,
485
+ poolWait: {
486
+ source: waitSource,
487
+ waitMs,
488
+ waitingBefore,
489
+ waitingAfter: pool.waitingCount,
490
+ hadPoolPressure: waitingBefore > 0 || waitMs > 0,
491
+ captured: true,
492
+ },
493
+ });
494
+ };
495
+
496
+ try {
497
+ const result = client.__mbkQueryLoggerOriginalQuery(...args);
498
+ if (result && typeof result.then === "function") {
499
+ return result
500
+ .then((res) => {
501
+ finalize(true, null, res);
502
+ return res;
503
+ })
504
+ .catch((err) => {
505
+ finalize(false, err);
506
+ throw err;
507
+ });
508
+ }
509
+
510
+ finalize(true, null, result);
511
+ return result;
512
+ } catch (err) {
513
+ finalize(false, err);
514
+ throw err;
515
+ }
516
+ };
517
+
518
+ client.__mbkQueryLoggerQueryWrapped = true;
519
+ return client;
520
+ };
521
+
522
+ if (originalConnect) {
523
+ pool.connect = (...args) => {
524
+ const connectStartTime = process.hrtime.bigint();
525
+ const waitingBefore = pool.waitingCount;
526
+
527
+ if (typeof args[0] === "function") {
528
+ const callback = args[0];
529
+ return originalConnect((err, client, done) => {
530
+ if (err || !client) {
531
+ callback(err, client, done);
532
+ return;
533
+ }
534
+
535
+ callback(
536
+ null,
537
+ instrumentClient(client, {
538
+ source: "pool.connect",
539
+ waitMs: Number(process.hrtime.bigint() - connectStartTime) / 1_000_000,
540
+ waitingBefore,
541
+ }),
542
+ done
543
+ );
544
+ });
545
+ }
546
+
547
+ return originalConnect(...args).then((client) =>
548
+ instrumentClient(client, {
549
+ source: "pool.connect",
550
+ waitMs: Number(process.hrtime.bigint() - connectStartTime) / 1_000_000,
551
+ waitingBefore,
552
+ })
553
+ );
554
+ };
555
+ }
247
556
 
248
- const recordLog = (success, error, result) => {
557
+ const runDirectLoggedPoolQuery = (args, { queryText, queryName, queryValues }) => {
558
+ const callsiteSnapshot = buildCallsite();
559
+ const startTime = process.hrtime.bigint();
560
+ const waitingBefore = pool.waitingCount;
561
+ const finalize = (success, error, result) => {
249
562
  const durationMs = Number(process.hrtime.bigint() - startTime) / 1_000_000;
250
- const request = buildRequestContext();
251
- const returnValue = buildReturnValue(result);
252
-
253
- const entry = {
254
- time: new Date().toISOString(),
255
- query: queryText,
256
- name: queryName || undefined,
257
- values: queryValues,
258
- durationMs,
563
+
564
+ recordLogEntry({
565
+ queryText,
566
+ queryName,
567
+ queryValues,
568
+ callsiteSnapshot,
259
569
  success,
260
- error: error ? { message: error.message, code: error.code } : undefined,
261
- returnValue,
262
- request,
263
- pool: {
264
- name: pool.__mbkQueryLoggerName,
265
- total: pool.totalCount,
266
- idle: pool.idleCount,
267
- waiting: pool.waitingCount,
570
+ error,
571
+ result,
572
+ durationMs,
573
+ executionDurationMs: durationMs,
574
+ poolWait: {
575
+ source: "pool.query",
576
+ waitMs: 0,
577
+ waitingBefore,
578
+ waitingAfter: pool.waitingCount,
579
+ hadPoolPressure: waitingBefore > 0,
580
+ captured: false,
268
581
  },
269
- callsite: callsiteSnapshot,
582
+ });
583
+ };
584
+
585
+ const callbackIndex = args.findIndex((arg) => typeof arg === "function");
586
+ if (callbackIndex >= 0) {
587
+ const wrappedArgs = [...args];
588
+ const originalCallback = wrappedArgs[callbackIndex];
589
+ wrappedArgs[callbackIndex] = (err, result) => {
590
+ finalize(!err, err, result);
591
+ return originalCallback(err, result);
270
592
  };
271
593
 
272
- recordPoolLog(entry);
273
- };
594
+ try {
595
+ return originalQuery(...wrappedArgs);
596
+ } catch (err) {
597
+ finalize(false, err, null);
598
+ throw err;
599
+ }
600
+ }
274
601
 
275
602
  try {
276
603
  const result = originalQuery(...args);
277
604
  if (result && typeof result.then === "function") {
278
605
  return result
279
606
  .then((res) => {
280
- recordLog(true, null, res);
607
+ finalize(true, null, res);
281
608
  return res;
282
609
  })
283
610
  .catch((err) => {
284
- recordLog(false, err);
611
+ finalize(false, err, null);
285
612
  throw err;
286
613
  });
287
614
  }
288
615
 
289
- recordLog(true, null, result);
616
+ finalize(true, null, result);
290
617
  return result;
291
618
  } catch (err) {
292
- recordLog(false, err);
619
+ finalize(false, err, null);
293
620
  throw err;
294
621
  }
295
622
  };
296
623
 
624
+ pool.query = (...args) => {
625
+ const { queryText, queryName, queryValues } = parseQueryArgs(args);
626
+ const usesCallback = args.some((arg) => typeof arg === "function");
627
+
628
+ if (!queryText) {
629
+ return originalQuery(...args);
630
+ }
631
+
632
+ if (usesCallback || !originalConnect) {
633
+ return runDirectLoggedPoolQuery(args, { queryText, queryName, queryValues });
634
+ }
635
+
636
+ const callsiteSnapshot = buildCallsite();
637
+ const connectStartTime = process.hrtime.bigint();
638
+ const waitingBefore = pool.waitingCount;
639
+
640
+ return originalConnect()
641
+ .then(async (client) => {
642
+ const waitMs = Number(process.hrtime.bigint() - connectStartTime) / 1_000_000;
643
+ const rawQuery = client.query.bind(client);
644
+ const release = typeof client.release === "function" ? client.release.bind(client) : null;
645
+ const executionStartTime = process.hrtime.bigint();
646
+
647
+ try {
648
+ const result = await rawQuery(...args);
649
+ const executionDurationMs = Number(process.hrtime.bigint() - executionStartTime) / 1_000_000;
650
+
651
+ recordLogEntry({
652
+ queryText,
653
+ queryName,
654
+ queryValues,
655
+ callsiteSnapshot,
656
+ success: true,
657
+ error: null,
658
+ result,
659
+ durationMs: executionDurationMs + waitMs,
660
+ executionDurationMs,
661
+ poolWait: {
662
+ source: "pool.query",
663
+ waitMs,
664
+ waitingBefore,
665
+ waitingAfter: pool.waitingCount,
666
+ hadPoolPressure: waitingBefore > 0 || waitMs > 0,
667
+ captured: true,
668
+ },
669
+ });
670
+
671
+ return result;
672
+ } catch (err) {
673
+ const executionDurationMs = Number(process.hrtime.bigint() - executionStartTime) / 1_000_000;
674
+
675
+ recordLogEntry({
676
+ queryText,
677
+ queryName,
678
+ queryValues,
679
+ callsiteSnapshot,
680
+ success: false,
681
+ error: err,
682
+ result: null,
683
+ durationMs: executionDurationMs + waitMs,
684
+ executionDurationMs,
685
+ poolWait: {
686
+ source: "pool.query",
687
+ waitMs,
688
+ waitingBefore,
689
+ waitingAfter: pool.waitingCount,
690
+ hadPoolPressure: waitingBefore > 0 || waitMs > 0,
691
+ captured: true,
692
+ },
693
+ });
694
+
695
+ throw err;
696
+ } finally {
697
+ release?.();
698
+ }
699
+ });
700
+ };
701
+
297
702
  pool.getQueryCount = () => dbQueryCount;
298
703
  pool.resetQueryCount = () => {
299
704
  dbQueryCount = 0;
@@ -272,4 +272,4 @@ export default {
272
272
  getErrorByCode,
273
273
  createErrorResponse,
274
274
  logError
275
- };
275
+ };
@@ -0,0 +1,12 @@
1
+ import dotenv from "dotenv";
2
+ import createDebug from "debug";
3
+
4
+ dotenv.config();
5
+ createDebug.enable(process.env.DEBUG || "");
6
+
7
+ export const createLogger = (namespace = "") => {
8
+ const suffix = namespace ? `:${namespace}` : "";
9
+ return createDebug(`mbkauthe${suffix}`);
10
+ };
11
+
12
+ export const logDebug = createLogger();
@@ -32,4 +32,4 @@ export function timingSafeTokenMatch(providedToken, expectedToken) {
32
32
 
33
33
  const matches = timingSafeEqual(providedHash, expectedHash);
34
34
  return matches && expected.length > 0;
35
- }
35
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mbkauthe",
3
- "version": "4.9.0",
3
+ "version": "5.0.0",
4
4
  "description": "MBKTech's reusable authentication system for Node.js applications.",
5
5
  "main": "index.js",
6
6
  "type": "module",