@logtape/redaction 2.3.2 → 2.3.3

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 +1 @@
1
- {"version":3,"file":"field.d.cts","names":[],"sources":["../src/field.ts"],"sourcesContent":[],"mappings":";;;;;;;AAeA;AAOA;AAMA;AAMY,KAnBA,YAAA,GAmBA,MAAyB,GAnBD,MAqB/B;AAML;AAYA;AAqBA;;;AAmBoB,KAxER,aAAA,GAAgB,YAwER,EAAA;;AAnBoD;AAyCxE;;AAM0B,KA9Fd,oBAAA,GA8Fc,QAAA,GAAA,CAAA,CAAA,KAAA,EAAA,OAAA,EAAA,GAAA,OAAA,CAAA;;;AANmD;AAgC7E;AAAyC,KAlH7B,yBAAA,GAkH6B,CAAA,KAAA,EAAA,OAAA,EAAA,GAhHpC,WAgHoC,CAAA,OAAA,CAAA;;;;AAIoB;AAgD7C,KA9JJ,iBAAA,GA8JiB,CAAA,KAAA,EAAA,OAAA,EAAA,GA9JuB,OA8JvB,CAAA,MAAA,CAAA;;;;;;;AAElB,cApJE,qBAoJF,EApJyB,aAoJzB;;;;;;AAC0B,UAhIpB,qBAAA,SAA8B,yBAgIV,CAAA;EAAe;AAsFpD;;;;;EACgC,SAAG,aAAA,EAhNT,aAgNS;EAAI;;;;AAEd;AAgLzB;;;;;EAEU,SAAA,MAAA,CAAA,EAxXU,oBAwXV;;;;;;;;;;;;;;;;;;;UAlWO,0BAAA,SAAmC;;;;;;0BAM1B;;;;mBAKP;;;;;;;;;;;;;;;;;;UAqBF,wBAAA;;;;yBAIQ,aAAa,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgDpC,aAAA,OACR,OAAO,OAAO,aAAa,OAAO,2BAC/B,wBAAwB,gBAChC,OAAO,OAAO,aAAa,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsFrB,kBAAA,OACR,OAAO,OAAO,aAAa,OAAO,0BAC/B,6BACR,OAAO;;;;;;;;;;;;;iBAgLY,uBAAA,UACX,2BACR,QAAQ"}
1
+ {"version":3,"file":"field.d.cts","names":[],"sources":["../src/field.ts"],"sourcesContent":[],"mappings":";;;;;;;AAeA;AAOA;AAMA;AAMY,KAnBA,YAAA,GAmBA,MAAyB,GAnBD,MAmBC;AAQrC;AAYA;AAqBA;;;AAmBoB,KAxER,aAAA,GAAgB,YAwER,EAAA;;AAnBoD;AAyCxE;;AAM0B,KA9Fd,oBAAA,GA8Fc,QAAA,GAAA,CAAA,CAAA,KAAA,EAAA,OAAA,EAAA,GAAA,OAAA,CAAA;;;AANmD;AAgC7E;AAAyC,KAlH7B,yBAAA,GAkH6B,CAAA,KAAA,EAAA,OAAA,EAAA,GAhHpC,WAgHoC,CAAA,OAAA,CAAA;;;;AAIoB;AAgD7C,KA9JJ,iBAAA,GA8JiB,CAAA,KAAA,EAAA,OAAA,EAAA,GA9JuB,OA8JvB,CAAA,MAAA,CAAA;;;;;;;AAElB,cApJE,qBAoJF,EApJyB,aAoJzB;;;;;;AAC0B,UAhIpB,qBAAA,SAA8B,yBAgIV,CAAA;EAAe;AAsFpD;;;;;EACgC,SAAG,aAAA,EAhNT,aAgNS;EAAI;;;;AAEd;AAgLzB;;;;;EAEU,SAAA,MAAA,CAAA,EAxXU,oBAwXV;;;;;;;;;;;;;;;;;;;UAlWO,0BAAA,SAAmC;;;;;;0BAM1B;;;;mBAKP;;;;;;;;;;;;;;;;;;UAqBF,wBAAA;;;;yBAIQ,aAAa,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgDpC,aAAA,OACR,OAAO,OAAO,aAAa,OAAO,2BAC/B,wBAAwB,gBAChC,OAAO,OAAO,aAAa,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsFrB,kBAAA,OACR,OAAO,OAAO,aAAa,OAAO,0BAC/B,6BACR,OAAO;;;;;;;;;;;;;iBAgLY,uBAAA,UACX,2BACR,QAAQ"}
package/dist/pattern.cjs CHANGED
@@ -13,13 +13,86 @@ const EMAIL_ADDRESS_PATTERN = {
13
13
  pattern: /[\p{L}0-9.!#$%&'*+/=?^_`{|}~-]+@[\p{L}0-9](?:[\p{L}0-9-]{0,61}[\p{L}0-9])?(?:\.[\p{L}0-9](?:[\p{L}0-9-]{0,61}[\p{L}0-9])?)+/gu,
14
14
  replacement: "REDACTED@EMAIL.ADDRESS"
15
15
  };
16
+ function hasValidLuhnChecksum(digits) {
17
+ let checksum = 0;
18
+ let shouldDouble = false;
19
+ for (let i = digits.length - 1; i >= 0; i--, shouldDouble = !shouldDouble) {
20
+ let digit = digits.charCodeAt(i) - 48;
21
+ if (shouldDouble) {
22
+ digit *= 2;
23
+ if (digit > 9) digit -= 9;
24
+ }
25
+ checksum += digit;
26
+ }
27
+ return checksum % 10 === 0;
28
+ }
29
+ function hasCommonCreditCardGrouping(groups) {
30
+ if (groups.length === 1) return groups[0].length >= 13 && groups[0].length <= 19;
31
+ if (groups.length === 3) return groups[0].length === 4 && (groups[1].length === 6 && groups[2].length >= 4 && groups[2].length <= 5 || (groups[1].length === 4 || groups[1].length === 5) && groups[2].length === 6);
32
+ if (groups.length === 4) return groups[0].length === 4 && groups[1].length === 4 && groups[2].length === 4 && groups[3].length >= 1 && groups[3].length <= 4;
33
+ if (groups.length === 5) return groups[0].length === 4 && groups[1].length === 4 && groups[2].length === 4 && groups[3].length === 4 && groups[4].length >= 1 && groups[4].length <= 3;
34
+ return false;
35
+ }
36
+ const creditCardNumberReplacement = "XXXX-XXXX-XXXX-XXXX";
37
+ function redactCreditCardNumber(match) {
38
+ const groups = [...match.matchAll(/\d+/g)];
39
+ const candidates = [];
40
+ const candidatesByStart = Array.from({ length: groups.length }, () => []);
41
+ for (let start = 0; start < groups.length; start++) {
42
+ let digits = "";
43
+ for (let end = start; end < groups.length; end++) {
44
+ digits += groups[end][0];
45
+ if (digits.length > 19) break;
46
+ if (digits.length >= 13 && hasCommonCreditCardGrouping(groups.slice(start, end + 1).map((group) => group[0])) && hasValidLuhnChecksum(digits)) {
47
+ const candidate$1 = {
48
+ start: groups[start].index,
49
+ end: groups[end].index + groups[end][0].length,
50
+ endGroup: end
51
+ };
52
+ candidates.push(candidate$1);
53
+ candidatesByStart[start].push(candidate$1);
54
+ }
55
+ }
56
+ }
57
+ if (candidates.length === 0) return match;
58
+ const completeCovers = [];
59
+ completeCovers[groups.length] = null;
60
+ for (let start = groups.length - 1; start >= 0; start--) for (const candidate$1 of candidatesByStart[start]) {
61
+ const tail = completeCovers[candidate$1.endGroup + 1];
62
+ if (tail !== void 0) {
63
+ completeCovers[start] = {
64
+ candidate: candidate$1,
65
+ next: tail
66
+ };
67
+ break;
68
+ }
69
+ }
70
+ const completeCover = completeCovers[0];
71
+ const intervals = completeCover === void 0 ? candidates : [];
72
+ for (let cover = completeCover; cover != null; cover = cover.next) intervals.push(cover.candidate);
73
+ const redacted = [];
74
+ let candidate = { ...intervals[0] };
75
+ let offset = 0;
76
+ for (let i = 1; i < intervals.length; i++) {
77
+ const next = intervals[i];
78
+ if (next.start < candidate.end) candidate.end = Math.max(candidate.end, next.end);
79
+ else {
80
+ redacted.push(match.slice(offset, candidate.start), creditCardNumberReplacement);
81
+ offset = candidate.end;
82
+ candidate = next;
83
+ }
84
+ }
85
+ redacted.push(match.slice(offset, candidate.start), creditCardNumberReplacement, match.slice(candidate.end));
86
+ return redacted.join("");
87
+ }
16
88
  /**
17
- * A redaction pattern for credit card numbers (including American Express).
89
+ * A redaction pattern for Luhn-valid credit card numbers with 13–19 digits,
90
+ * including numbers separated into common groups with spaces or hyphens.
18
91
  * @since 0.10.0
19
92
  */
20
93
  const CREDIT_CARD_NUMBER_PATTERN = {
21
- pattern: /(?:\d{4}-){2}(?:\d{4}-\d{4}|\d{6})/g,
22
- replacement: "XXXX-XXXX-XXXX-XXXX"
94
+ pattern: /(?<!\d)(?:\d{13,19}|\d{4}(?:(?: +|-)\d{1,6})+)(?!\d)/g,
95
+ replacement: redactCreditCardNumber
23
96
  };
24
97
  /**
25
98
  * A redaction pattern for U.S. Social Security numbers.
@@ -27,7 +27,8 @@ interface RedactionPattern {
27
27
  */
28
28
  declare const EMAIL_ADDRESS_PATTERN: RedactionPattern;
29
29
  /**
30
- * A redaction pattern for credit card numbers (including American Express).
30
+ * A redaction pattern for Luhn-valid credit card numbers with 13–19 digits,
31
+ * including numbers separated into common groups with spaces or hyphens.
31
32
  * @since 0.10.0
32
33
  */
33
34
  declare const CREDIT_CARD_NUMBER_PATTERN: RedactionPattern;
@@ -1 +1 @@
1
- {"version":3,"file":"pattern.d.cts","names":[],"sources":["../src/pattern.ts"],"sourcesContent":[],"mappings":";;;;;;;AAuBA;AAsBA;AAUA;AASa,UAzCI,gBAAA,CAyCY;EAUhB;AASb;AASA;AAMA;EAsEgB,SAAA,OAAA,EA5II,MA4IW;EAAA;;;;;EAIf,SAAA,WAAA,EAAA,MAAA,GAAA,CAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,SAAA,GAAA,EAAA,EAAA,GAAA,MAAA,CAAA;AAoChB;;;;;AAIG,cAvKU,qBAuKV,EAvKiC,gBAuKjC;AAAgB;;;;cA7JN,4BAA4B;;;;;cAS5B,gBAAgB;;;;;;cAUhB,gBAAgB;;;;;cAShB,aAAa;;;;;KASd,iBAAA,YAA6B;;;;;UAMxB,uBAAA,SAAgC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsEjC,eAAA,YACH,yBACD,6BACA,0BACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoCa,eAAA,YACH,4BACD,6BACA,0BACT"}
1
+ {"version":3,"file":"pattern.d.cts","names":[],"sources":["../src/pattern.ts"],"sourcesContent":[],"mappings":";;;;;;;AAuBA;AAsBA;AAiJA;AASa,UAhLI,gBAAA,CAgLY;EAUhB;AASb;AASA;AAMA;EAsEgB,SAAA,OAAA,EAnRI,MAmRW;EAAA;;;;;EAIf,SAAA,WAAA,EAAA,MAAA,GAAA,CAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,SAAA,GAAA,EAAA,EAAA,GAAA,MAAA,CAAA;AAoChB;;;;;AAIG,cA9SU,qBA8SV,EA9SiC,gBA8SjC;AAAgB;;;;;cA7JN,4BAA4B;;;;;cAS5B,gBAAgB;;;;;;cAUhB,gBAAgB;;;;;cAShB,aAAa;;;;;KASd,iBAAA,YAA6B;;;;;UAMxB,uBAAA,SAAgC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsEjC,eAAA,YACH,yBACD,6BACA,0BACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoCa,eAAA,YACH,4BACD,6BACA,0BACT"}
package/dist/pattern.d.ts CHANGED
@@ -27,7 +27,8 @@ interface RedactionPattern {
27
27
  */
28
28
  declare const EMAIL_ADDRESS_PATTERN: RedactionPattern;
29
29
  /**
30
- * A redaction pattern for credit card numbers (including American Express).
30
+ * A redaction pattern for Luhn-valid credit card numbers with 13–19 digits,
31
+ * including numbers separated into common groups with spaces or hyphens.
31
32
  * @since 0.10.0
32
33
  */
33
34
  declare const CREDIT_CARD_NUMBER_PATTERN: RedactionPattern;
@@ -1 +1 @@
1
- {"version":3,"file":"pattern.d.ts","names":[],"sources":["../src/pattern.ts"],"sourcesContent":[],"mappings":";;;;;;;AAuBA;AAsBA;AAUA;AASa,UAzCI,gBAAA,CAyCY;EAUhB;AASb;AASA;AAMA;EAsEgB,SAAA,OAAA,EA5II,MA4IW;EAAA;;;;;EAIf,SAAA,WAAA,EAAA,MAAA,GAAA,CAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,SAAA,GAAA,EAAA,EAAA,GAAA,MAAA,CAAA;AAoChB;;;;;AAIG,cAvKU,qBAuKV,EAvKiC,gBAuKjC;AAAgB;;;;cA7JN,4BAA4B;;;;;cAS5B,gBAAgB;;;;;;cAUhB,gBAAgB;;;;;cAShB,aAAa;;;;;KASd,iBAAA,YAA6B;;;;;UAMxB,uBAAA,SAAgC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsEjC,eAAA,YACH,yBACD,6BACA,0BACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoCa,eAAA,YACH,4BACD,6BACA,0BACT"}
1
+ {"version":3,"file":"pattern.d.ts","names":[],"sources":["../src/pattern.ts"],"sourcesContent":[],"mappings":";;;;;;;AAuBA;AAsBA;AAiJA;AASa,UAhLI,gBAAA,CAgLY;EAUhB;AASb;AASA;AAMA;EAsEgB,SAAA,OAAA,EAnRI,MAmRW;EAAA;;;;;EAIf,SAAA,WAAA,EAAA,MAAA,GAAA,CAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,SAAA,GAAA,EAAA,EAAA,GAAA,MAAA,CAAA;AAoChB;;;;;AAIG,cA9SU,qBA8SV,EA9SiC,gBA8SjC;AAAgB;;;;;cA7JN,4BAA4B;;;;;cAS5B,gBAAgB;;;;;;cAUhB,gBAAgB;;;;;cAShB,aAAa;;;;;KASd,iBAAA,YAA6B;;;;;UAMxB,uBAAA,SAAgC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsEjC,eAAA,YACH,yBACD,6BACA,0BACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoCa,eAAA,YACH,4BACD,6BACA,0BACT"}
package/dist/pattern.js CHANGED
@@ -12,13 +12,86 @@ const EMAIL_ADDRESS_PATTERN = {
12
12
  pattern: /[\p{L}0-9.!#$%&'*+/=?^_`{|}~-]+@[\p{L}0-9](?:[\p{L}0-9-]{0,61}[\p{L}0-9])?(?:\.[\p{L}0-9](?:[\p{L}0-9-]{0,61}[\p{L}0-9])?)+/gu,
13
13
  replacement: "REDACTED@EMAIL.ADDRESS"
14
14
  };
15
+ function hasValidLuhnChecksum(digits) {
16
+ let checksum = 0;
17
+ let shouldDouble = false;
18
+ for (let i = digits.length - 1; i >= 0; i--, shouldDouble = !shouldDouble) {
19
+ let digit = digits.charCodeAt(i) - 48;
20
+ if (shouldDouble) {
21
+ digit *= 2;
22
+ if (digit > 9) digit -= 9;
23
+ }
24
+ checksum += digit;
25
+ }
26
+ return checksum % 10 === 0;
27
+ }
28
+ function hasCommonCreditCardGrouping(groups) {
29
+ if (groups.length === 1) return groups[0].length >= 13 && groups[0].length <= 19;
30
+ if (groups.length === 3) return groups[0].length === 4 && (groups[1].length === 6 && groups[2].length >= 4 && groups[2].length <= 5 || (groups[1].length === 4 || groups[1].length === 5) && groups[2].length === 6);
31
+ if (groups.length === 4) return groups[0].length === 4 && groups[1].length === 4 && groups[2].length === 4 && groups[3].length >= 1 && groups[3].length <= 4;
32
+ if (groups.length === 5) return groups[0].length === 4 && groups[1].length === 4 && groups[2].length === 4 && groups[3].length === 4 && groups[4].length >= 1 && groups[4].length <= 3;
33
+ return false;
34
+ }
35
+ const creditCardNumberReplacement = "XXXX-XXXX-XXXX-XXXX";
36
+ function redactCreditCardNumber(match) {
37
+ const groups = [...match.matchAll(/\d+/g)];
38
+ const candidates = [];
39
+ const candidatesByStart = Array.from({ length: groups.length }, () => []);
40
+ for (let start = 0; start < groups.length; start++) {
41
+ let digits = "";
42
+ for (let end = start; end < groups.length; end++) {
43
+ digits += groups[end][0];
44
+ if (digits.length > 19) break;
45
+ if (digits.length >= 13 && hasCommonCreditCardGrouping(groups.slice(start, end + 1).map((group) => group[0])) && hasValidLuhnChecksum(digits)) {
46
+ const candidate$1 = {
47
+ start: groups[start].index,
48
+ end: groups[end].index + groups[end][0].length,
49
+ endGroup: end
50
+ };
51
+ candidates.push(candidate$1);
52
+ candidatesByStart[start].push(candidate$1);
53
+ }
54
+ }
55
+ }
56
+ if (candidates.length === 0) return match;
57
+ const completeCovers = [];
58
+ completeCovers[groups.length] = null;
59
+ for (let start = groups.length - 1; start >= 0; start--) for (const candidate$1 of candidatesByStart[start]) {
60
+ const tail = completeCovers[candidate$1.endGroup + 1];
61
+ if (tail !== void 0) {
62
+ completeCovers[start] = {
63
+ candidate: candidate$1,
64
+ next: tail
65
+ };
66
+ break;
67
+ }
68
+ }
69
+ const completeCover = completeCovers[0];
70
+ const intervals = completeCover === void 0 ? candidates : [];
71
+ for (let cover = completeCover; cover != null; cover = cover.next) intervals.push(cover.candidate);
72
+ const redacted = [];
73
+ let candidate = { ...intervals[0] };
74
+ let offset = 0;
75
+ for (let i = 1; i < intervals.length; i++) {
76
+ const next = intervals[i];
77
+ if (next.start < candidate.end) candidate.end = Math.max(candidate.end, next.end);
78
+ else {
79
+ redacted.push(match.slice(offset, candidate.start), creditCardNumberReplacement);
80
+ offset = candidate.end;
81
+ candidate = next;
82
+ }
83
+ }
84
+ redacted.push(match.slice(offset, candidate.start), creditCardNumberReplacement, match.slice(candidate.end));
85
+ return redacted.join("");
86
+ }
15
87
  /**
16
- * A redaction pattern for credit card numbers (including American Express).
88
+ * A redaction pattern for Luhn-valid credit card numbers with 13–19 digits,
89
+ * including numbers separated into common groups with spaces or hyphens.
17
90
  * @since 0.10.0
18
91
  */
19
92
  const CREDIT_CARD_NUMBER_PATTERN = {
20
- pattern: /(?:\d{4}-){2}(?:\d{4}-\d{4}|\d{6})/g,
21
- replacement: "XXXX-XXXX-XXXX-XXXX"
93
+ pattern: /(?<!\d)(?:\d{13,19}|\d{4}(?:(?: +|-)\d{1,6})+)(?!\d)/g,
94
+ replacement: redactCreditCardNumber
22
95
  };
23
96
  /**
24
97
  * A redaction pattern for U.S. Social Security numbers.
@@ -1 +1 @@
1
- {"version":3,"file":"pattern.js","names":["EMAIL_ADDRESS_PATTERN: RedactionPattern","CREDIT_CARD_NUMBER_PATTERN: RedactionPattern","US_SSN_PATTERN: RedactionPattern","KR_RRN_PATTERN: RedactionPattern","JWT_PATTERN: RedactionPattern","value: object","formatter: TextFormatter | ConsoleFormatter","patterns: RedactionPatterns","options: PatternRedactionOptions","str: string","object: unknown","context: RedactionTraversalContext","depth: number","copy: unknown[]","redacted: Record<string, unknown>","record: LogRecord","limit: RedactionLimit","limits: RedactionTraversalLimits","value: unknown","options: RedactionTraversalOptions"],"sources":["../src/pattern.ts"],"sourcesContent":["import {\n type ConsoleFormatter,\n getLogger,\n type LogRecord,\n type TextFormatter,\n} from \"@logtape/logtape\";\nimport {\n createRedactionTraversalContext,\n type RedactionLimit,\n type RedactionTraversalContext,\n type RedactionTraversalLimits,\n type RedactionTraversalOptions,\n redactionTruncatedValue,\n} from \"./traversal.ts\";\n\nconst metaLogger = getLogger([\"logtape\", \"meta\"]);\nlet reportingRedactionLimit = false;\n\n/**\n * A redaction pattern, which is a pair of regular expression and replacement\n * string or function.\n * @since 0.10.0\n */\nexport interface RedactionPattern {\n /**\n * The regular expression to match against. Note that it must have the\n * `g` (global) flag set, otherwise it will throw a `TypeError`.\n */\n readonly pattern: RegExp;\n\n /**\n * The replacement string or function. If the replacement is a function,\n * it will be called with the matched string and any capture groups (the same\n * signature as `String.prototype.replaceAll()`).\n */\n readonly replacement:\n | string\n // deno-lint-ignore no-explicit-any\n | ((match: string, ...rest: readonly any[]) => string);\n}\n\n/**\n * A redaction pattern for email addresses.\n * @since 0.10.0\n */\nexport const EMAIL_ADDRESS_PATTERN: RedactionPattern = {\n pattern:\n /[\\p{L}0-9.!#$%&'*+/=?^_`{|}~-]+@[\\p{L}0-9](?:[\\p{L}0-9-]{0,61}[\\p{L}0-9])?(?:\\.[\\p{L}0-9](?:[\\p{L}0-9-]{0,61}[\\p{L}0-9])?)+/gu,\n replacement: \"REDACTED@EMAIL.ADDRESS\",\n};\n\n/**\n * A redaction pattern for credit card numbers (including American Express).\n * @since 0.10.0\n */\nexport const CREDIT_CARD_NUMBER_PATTERN: RedactionPattern = {\n pattern: /(?:\\d{4}-){2}(?:\\d{4}-\\d{4}|\\d{6})/g,\n replacement: \"XXXX-XXXX-XXXX-XXXX\",\n};\n\n/**\n * A redaction pattern for U.S. Social Security numbers.\n * @since 0.10.0\n */\nexport const US_SSN_PATTERN: RedactionPattern = {\n pattern: /\\d{3}-\\d{2}-\\d{4}/g,\n replacement: \"XXX-XX-XXXX\",\n};\n\n/**\n * A redaction pattern for South Korean resident registration numbers\n * (住民登錄番號).\n * @since 0.10.0\n */\nexport const KR_RRN_PATTERN: RedactionPattern = {\n pattern: /\\d{6}-\\d{7}/g,\n replacement: \"XXXXXX-XXXXXXX\",\n};\n\n/**\n * A redaction pattern for JSON Web Tokens (JWT).\n * @since 0.10.0\n */\nexport const JWT_PATTERN: RedactionPattern = {\n pattern: /eyJ[a-zA-Z0-9_-]*\\.[a-zA-Z0-9_-]*\\.[a-zA-Z0-9_-]*/g,\n replacement: \"[JWT REDACTED]\",\n};\n\n/**\n * A list of {@link RedactionPattern}s.\n * @since 0.10.0\n */\nexport type RedactionPatterns = readonly RedactionPattern[];\n\n/**\n * Options for pattern-based redaction.\n * @since 2.2.0\n */\nexport interface PatternRedactionOptions extends RedactionTraversalOptions {\n /**\n * Maximum recursion depth for object and array traversal.\n * @default `20`\n */\n readonly maxDepth?: number;\n\n /**\n * Maximum number of properties or array elements to process per object.\n * @default `1000`\n */\n readonly maxProperties?: number;\n}\n\n/**\n * Checks if a value is a built-in object that should not be recursively\n * processed (e.g., Error, Date, RegExp, Map, Set, etc.).\n * @param value The value to check.\n * @returns `true` if the value is a built-in object, `false` otherwise.\n */\nfunction isBuiltInObject(value: object): boolean {\n return value instanceof Error ||\n value instanceof Date ||\n value instanceof RegExp ||\n value instanceof Map ||\n value instanceof Set ||\n value instanceof WeakMap ||\n value instanceof WeakSet ||\n value instanceof Promise ||\n value instanceof ArrayBuffer ||\n (typeof SharedArrayBuffer !== \"undefined\" &&\n value instanceof SharedArrayBuffer) ||\n ArrayBuffer.isView(value);\n}\n\n/**\n * Applies data redaction to a {@link TextFormatter}.\n *\n * Note that there are some built-in redaction patterns:\n *\n * - {@link CREDIT_CARD_NUMBER_PATTERN}\n * - {@link EMAIL_ADDRESS_PATTERN}\n * - {@link JWT_PATTERN}\n * - {@link KR_RRN_PATTERN}\n * - {@link US_SSN_PATTERN}\n *\n * @example\n * ```ts\n * import { getFileSink } from \"@logtape/file\";\n * import { getAnsiColorFormatter } from \"@logtape/logtape\";\n * import {\n * CREDIT_CARD_NUMBER_PATTERN,\n * EMAIL_ADDRESS_PATTERN,\n * JWT_PATTERN,\n * redactByPattern,\n * } from \"@logtape/redaction\";\n *\n * const formatter = redactByPattern(getAnsiConsoleFormatter(), [\n * CREDIT_CARD_NUMBER_PATTERN,\n * EMAIL_ADDRESS_PATTERN,\n * JWT_PATTERN,\n * ]);\n * const sink = getFileSink(\"my-app.log\", { formatter });\n * ```\n * @param formatter The text formatter to apply redaction to.\n * @param patterns The redaction patterns to apply.\n * @param options Options for bounding recursive traversal of formatter output.\n * @returns The redacted text formatter.\n * @since 0.10.0\n */\nexport function redactByPattern(\n formatter: TextFormatter,\n patterns: RedactionPatterns,\n options?: PatternRedactionOptions,\n): TextFormatter;\n\n/**\n * Applies data redaction to a {@link ConsoleFormatter}.\n *\n * Note that there are some built-in redaction patterns:\n *\n * - {@link CREDIT_CARD_NUMBER_PATTERN}\n * - {@link EMAIL_ADDRESS_PATTERN}\n * - {@link JWT_PATTERN}\n * - {@link KR_RRN_PATTERN}\n * - {@link US_SSN_PATTERN}\n *\n * @example\n * ```ts\n * import { defaultConsoleFormatter, getConsoleSink } from \"@logtape/logtape\";\n * import {\n * CREDIT_CARD_NUMBER_PATTERN,\n * EMAIL_ADDRESS_PATTERN,\n * JWT_PATTERN,\n * redactByPattern,\n * } from \"@logtape/redaction\";\n *\n * const formatter = redactByPattern(defaultConsoleFormatter, [\n * CREDIT_CARD_NUMBER_PATTERN,\n * EMAIL_ADDRESS_PATTERN,\n * JWT_PATTERN,\n * ]);\n * const sink = getConsoleSink({ formatter });\n * ```\n * @param formatter The console formatter to apply redaction to.\n * @param patterns The redaction patterns to apply.\n * @param options Options for bounding recursive traversal of formatter output.\n * @returns The redacted console formatter.\n * @since 0.10.0\n */\nexport function redactByPattern(\n formatter: ConsoleFormatter,\n patterns: RedactionPatterns,\n options?: PatternRedactionOptions,\n): ConsoleFormatter;\n\nexport function redactByPattern(\n formatter: TextFormatter | ConsoleFormatter,\n patterns: RedactionPatterns,\n options: PatternRedactionOptions = {},\n): (record: LogRecord) => string | readonly unknown[] {\n for (const { pattern } of patterns) {\n if (!pattern.global) {\n throw new TypeError(\n `Pattern ${pattern} does not have the global flag set.`,\n );\n }\n }\n\n function replaceString(str: string): string {\n for (const p of patterns) {\n // The following ternary operator may seem strange, but it's for\n // making TypeScript happy:\n str = typeof p.replacement === \"string\"\n ? str.replaceAll(p.pattern, p.replacement)\n : str.replaceAll(p.pattern, p.replacement);\n }\n return str;\n }\n\n function replaceObject(\n object: unknown,\n context: RedactionTraversalContext,\n depth: number,\n ): unknown {\n if (typeof object === \"object\" && object !== null) {\n if (context.visited.has(object)) {\n return context.visited.get(object)!; // Circular reference detected\n }\n }\n\n if (typeof object === \"string\") return replaceString(object);\n if (Array.isArray(object)) {\n const copy: unknown[] = [];\n const length = Math.min(object.length, context.limits.maxProperties);\n copy.length = length;\n context.visited.set(object, copy);\n if (object.length > context.limits.maxProperties) {\n reportLimitOnce(context, \"maxProperties\");\n }\n for (let i = 0; i < length; i++) {\n if (!(i in object)) continue;\n const item = object[i];\n if (\n shouldTraverseValue(item) && depth + 1 > context.limits.maxDepth\n ) {\n reportLimitOnce(context, \"maxDepth\");\n copy[i] = redactionTruncatedValue;\n } else {\n copy[i] = replaceObject(item, context, depth + 1);\n }\n }\n return copy;\n }\n if (typeof object === \"object\" && object !== null) {\n if (isBuiltInObject(object)) {\n return object;\n }\n const redacted: Record<string, unknown> = {};\n context.visited.set(object, redacted);\n const keys = Object.keys(object);\n if (keys.length > context.limits.maxProperties) {\n reportLimitOnce(context, \"maxProperties\");\n }\n for (const key of keys.slice(0, context.limits.maxProperties)) {\n const value = (object as Record<string, unknown>)[key];\n if (\n shouldTraverseValue(value) && depth + 1 > context.limits.maxDepth\n ) {\n reportLimitOnce(context, \"maxDepth\");\n redacted[key] = redactionTruncatedValue;\n } else {\n redacted[key] = replaceObject(value, context, depth + 1);\n }\n }\n return redacted;\n }\n return object;\n }\n\n return (record: LogRecord) => {\n const output = formatter(record);\n if (typeof output === \"string\") return replaceString(output);\n const context = createPatternRedactionContext(options);\n if (output.length > context.limits.maxProperties) {\n reportLimitOnce(context, \"maxProperties\");\n }\n return output\n .slice(0, context.limits.maxProperties)\n .map((obj) => replaceObject(obj, context, 0));\n };\n}\n\nfunction reportRedactionLimitExceeded(\n limit: RedactionLimit,\n limits: RedactionTraversalLimits,\n): void {\n if (reportingRedactionLimit || typeof metaLogger.warn !== \"function\") {\n return;\n }\n try {\n reportingRedactionLimit = true;\n metaLogger.warn(\n \"Redaction traversal exceeded {limit}; replacing or omitting \" +\n \"remaining data to keep logging bounded.\",\n { limit, ...limits },\n );\n } catch {\n // Meta logging failures must not make normal logging fail.\n } finally {\n reportingRedactionLimit = false;\n }\n}\n\nfunction shouldTraverseValue(value: unknown): boolean {\n return (typeof value === \"object\" && value !== null) &&\n !isBuiltInObject(value);\n}\n\nfunction createPatternRedactionContext(\n options: RedactionTraversalOptions,\n): RedactionTraversalContext {\n return createRedactionTraversalContext(\n options,\n reportRedactionLimitExceeded,\n );\n}\n\nfunction reportLimitOnce(\n context: RedactionTraversalContext,\n limit: RedactionLimit,\n): void {\n if (context.exceededLimits.has(limit)) return;\n context.reportLimitExceeded(limit);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pattern.js","names":["EMAIL_ADDRESS_PATTERN: RedactionPattern","digits: string","groups: readonly string[]","match: string","candidates: CreditCardCandidate[]","candidatesByStart: CreditCardCandidate[][]","candidate","completeCovers: (CreditCardCover | null | undefined)[]","redacted: string[]","CREDIT_CARD_NUMBER_PATTERN: RedactionPattern","US_SSN_PATTERN: RedactionPattern","KR_RRN_PATTERN: RedactionPattern","JWT_PATTERN: RedactionPattern","value: object","formatter: TextFormatter | ConsoleFormatter","patterns: RedactionPatterns","options: PatternRedactionOptions","str: string","object: unknown","context: RedactionTraversalContext","depth: number","copy: unknown[]","redacted: Record<string, unknown>","record: LogRecord","limit: RedactionLimit","limits: RedactionTraversalLimits","value: unknown","options: RedactionTraversalOptions"],"sources":["../src/pattern.ts"],"sourcesContent":["import {\n type ConsoleFormatter,\n getLogger,\n type LogRecord,\n type TextFormatter,\n} from \"@logtape/logtape\";\nimport {\n createRedactionTraversalContext,\n type RedactionLimit,\n type RedactionTraversalContext,\n type RedactionTraversalLimits,\n type RedactionTraversalOptions,\n redactionTruncatedValue,\n} from \"./traversal.ts\";\n\nconst metaLogger = getLogger([\"logtape\", \"meta\"]);\nlet reportingRedactionLimit = false;\n\n/**\n * A redaction pattern, which is a pair of regular expression and replacement\n * string or function.\n * @since 0.10.0\n */\nexport interface RedactionPattern {\n /**\n * The regular expression to match against. Note that it must have the\n * `g` (global) flag set, otherwise it will throw a `TypeError`.\n */\n readonly pattern: RegExp;\n\n /**\n * The replacement string or function. If the replacement is a function,\n * it will be called with the matched string and any capture groups (the same\n * signature as `String.prototype.replaceAll()`).\n */\n readonly replacement:\n | string\n // deno-lint-ignore no-explicit-any\n | ((match: string, ...rest: readonly any[]) => string);\n}\n\n/**\n * A redaction pattern for email addresses.\n * @since 0.10.0\n */\nexport const EMAIL_ADDRESS_PATTERN: RedactionPattern = {\n pattern:\n /[\\p{L}0-9.!#$%&'*+/=?^_`{|}~-]+@[\\p{L}0-9](?:[\\p{L}0-9-]{0,61}[\\p{L}0-9])?(?:\\.[\\p{L}0-9](?:[\\p{L}0-9-]{0,61}[\\p{L}0-9])?)+/gu,\n replacement: \"REDACTED@EMAIL.ADDRESS\",\n};\n\nfunction hasValidLuhnChecksum(digits: string): boolean {\n let checksum = 0;\n let shouldDouble = false;\n\n for (let i = digits.length - 1; i >= 0; i--, shouldDouble = !shouldDouble) {\n let digit = digits.charCodeAt(i) - 48;\n if (shouldDouble) {\n digit *= 2;\n if (digit > 9) digit -= 9;\n }\n checksum += digit;\n }\n\n return checksum % 10 === 0;\n}\n\nfunction hasCommonCreditCardGrouping(groups: readonly string[]): boolean {\n if (groups.length === 1) {\n return groups[0].length >= 13 && groups[0].length <= 19;\n }\n if (groups.length === 3) {\n return groups[0].length === 4 &&\n ((groups[1].length === 6 && groups[2].length >= 4 &&\n groups[2].length <= 5) ||\n ((groups[1].length === 4 || groups[1].length === 5) &&\n groups[2].length === 6));\n }\n if (groups.length === 4) {\n return groups[0].length === 4 && groups[1].length === 4 &&\n groups[2].length === 4 && groups[3].length >= 1 &&\n groups[3].length <= 4;\n }\n if (groups.length === 5) {\n return groups[0].length === 4 && groups[1].length === 4 &&\n groups[2].length === 4 && groups[3].length === 4 &&\n groups[4].length >= 1 && groups[4].length <= 3;\n }\n return false;\n}\n\nconst creditCardNumberReplacement = \"XXXX-XXXX-XXXX-XXXX\";\n\ntype CreditCardCandidate = {\n start: number;\n end: number;\n endGroup: number;\n};\n\ntype CreditCardCover = {\n readonly candidate: CreditCardCandidate;\n readonly next: CreditCardCover | null;\n};\n\nfunction redactCreditCardNumber(match: string): string {\n const groups = [...match.matchAll(/\\d+/g)];\n const candidates: CreditCardCandidate[] = [];\n const candidatesByStart: CreditCardCandidate[][] = Array.from(\n { length: groups.length },\n () => [],\n );\n\n for (let start = 0; start < groups.length; start++) {\n let digits = \"\";\n for (let end = start; end < groups.length; end++) {\n digits += groups[end][0];\n if (digits.length > 19) break;\n if (\n digits.length >= 13 &&\n hasCommonCreditCardGrouping(\n groups.slice(start, end + 1).map((group) => group[0]),\n ) &&\n hasValidLuhnChecksum(digits)\n ) {\n const candidate = {\n start: groups[start].index,\n end: groups[end].index + groups[end][0].length,\n endGroup: end,\n };\n candidates.push(candidate);\n candidatesByStart[start].push(candidate);\n }\n }\n }\n\n if (candidates.length === 0) return match;\n\n // Keep adjacent card numbers as separate redactions when their candidates\n // cover the complete group sequence without overlapping.\n const completeCovers: (CreditCardCover | null | undefined)[] = [];\n completeCovers[groups.length] = null;\n for (let start = groups.length - 1; start >= 0; start--) {\n for (const candidate of candidatesByStart[start]) {\n const tail = completeCovers[candidate.endGroup + 1];\n if (tail !== undefined) {\n completeCovers[start] = { candidate, next: tail };\n break;\n }\n }\n }\n\n const completeCover = completeCovers[0];\n const intervals = completeCover === undefined ? candidates : [];\n for (\n let cover = completeCover;\n cover != null;\n cover = cover.next\n ) {\n intervals.push(cover.candidate);\n }\n const redacted: string[] = [];\n let candidate = { ...intervals[0] };\n let offset = 0;\n for (let i = 1; i < intervals.length; i++) {\n const next = intervals[i];\n // Merge ambiguous overlaps so no portion of a possible PAN remains visible.\n if (next.start < candidate.end) {\n candidate.end = Math.max(candidate.end, next.end);\n } else {\n redacted.push(\n match.slice(offset, candidate.start),\n creditCardNumberReplacement,\n );\n offset = candidate.end;\n candidate = next;\n }\n }\n redacted.push(\n match.slice(offset, candidate.start),\n creditCardNumberReplacement,\n match.slice(candidate.end),\n );\n return redacted.join(\"\");\n}\n\n/**\n * A redaction pattern for Luhn-valid credit card numbers with 13–19 digits,\n * including numbers separated into common groups with spaces or hyphens.\n * @since 0.10.0\n */\nexport const CREDIT_CARD_NUMBER_PATTERN: RedactionPattern = {\n pattern: /(?<!\\d)(?:\\d{13,19}|\\d{4}(?:(?: +|-)\\d{1,6})+)(?!\\d)/g,\n replacement: redactCreditCardNumber,\n};\n\n/**\n * A redaction pattern for U.S. Social Security numbers.\n * @since 0.10.0\n */\nexport const US_SSN_PATTERN: RedactionPattern = {\n pattern: /\\d{3}-\\d{2}-\\d{4}/g,\n replacement: \"XXX-XX-XXXX\",\n};\n\n/**\n * A redaction pattern for South Korean resident registration numbers\n * (住民登錄番號).\n * @since 0.10.0\n */\nexport const KR_RRN_PATTERN: RedactionPattern = {\n pattern: /\\d{6}-\\d{7}/g,\n replacement: \"XXXXXX-XXXXXXX\",\n};\n\n/**\n * A redaction pattern for JSON Web Tokens (JWT).\n * @since 0.10.0\n */\nexport const JWT_PATTERN: RedactionPattern = {\n pattern: /eyJ[a-zA-Z0-9_-]*\\.[a-zA-Z0-9_-]*\\.[a-zA-Z0-9_-]*/g,\n replacement: \"[JWT REDACTED]\",\n};\n\n/**\n * A list of {@link RedactionPattern}s.\n * @since 0.10.0\n */\nexport type RedactionPatterns = readonly RedactionPattern[];\n\n/**\n * Options for pattern-based redaction.\n * @since 2.2.0\n */\nexport interface PatternRedactionOptions extends RedactionTraversalOptions {\n /**\n * Maximum recursion depth for object and array traversal.\n * @default `20`\n */\n readonly maxDepth?: number;\n\n /**\n * Maximum number of properties or array elements to process per object.\n * @default `1000`\n */\n readonly maxProperties?: number;\n}\n\n/**\n * Checks if a value is a built-in object that should not be recursively\n * processed (e.g., Error, Date, RegExp, Map, Set, etc.).\n * @param value The value to check.\n * @returns `true` if the value is a built-in object, `false` otherwise.\n */\nfunction isBuiltInObject(value: object): boolean {\n return value instanceof Error ||\n value instanceof Date ||\n value instanceof RegExp ||\n value instanceof Map ||\n value instanceof Set ||\n value instanceof WeakMap ||\n value instanceof WeakSet ||\n value instanceof Promise ||\n value instanceof ArrayBuffer ||\n (typeof SharedArrayBuffer !== \"undefined\" &&\n value instanceof SharedArrayBuffer) ||\n ArrayBuffer.isView(value);\n}\n\n/**\n * Applies data redaction to a {@link TextFormatter}.\n *\n * Note that there are some built-in redaction patterns:\n *\n * - {@link CREDIT_CARD_NUMBER_PATTERN}\n * - {@link EMAIL_ADDRESS_PATTERN}\n * - {@link JWT_PATTERN}\n * - {@link KR_RRN_PATTERN}\n * - {@link US_SSN_PATTERN}\n *\n * @example\n * ```ts\n * import { getFileSink } from \"@logtape/file\";\n * import { getAnsiColorFormatter } from \"@logtape/logtape\";\n * import {\n * CREDIT_CARD_NUMBER_PATTERN,\n * EMAIL_ADDRESS_PATTERN,\n * JWT_PATTERN,\n * redactByPattern,\n * } from \"@logtape/redaction\";\n *\n * const formatter = redactByPattern(getAnsiConsoleFormatter(), [\n * CREDIT_CARD_NUMBER_PATTERN,\n * EMAIL_ADDRESS_PATTERN,\n * JWT_PATTERN,\n * ]);\n * const sink = getFileSink(\"my-app.log\", { formatter });\n * ```\n * @param formatter The text formatter to apply redaction to.\n * @param patterns The redaction patterns to apply.\n * @param options Options for bounding recursive traversal of formatter output.\n * @returns The redacted text formatter.\n * @since 0.10.0\n */\nexport function redactByPattern(\n formatter: TextFormatter,\n patterns: RedactionPatterns,\n options?: PatternRedactionOptions,\n): TextFormatter;\n\n/**\n * Applies data redaction to a {@link ConsoleFormatter}.\n *\n * Note that there are some built-in redaction patterns:\n *\n * - {@link CREDIT_CARD_NUMBER_PATTERN}\n * - {@link EMAIL_ADDRESS_PATTERN}\n * - {@link JWT_PATTERN}\n * - {@link KR_RRN_PATTERN}\n * - {@link US_SSN_PATTERN}\n *\n * @example\n * ```ts\n * import { defaultConsoleFormatter, getConsoleSink } from \"@logtape/logtape\";\n * import {\n * CREDIT_CARD_NUMBER_PATTERN,\n * EMAIL_ADDRESS_PATTERN,\n * JWT_PATTERN,\n * redactByPattern,\n * } from \"@logtape/redaction\";\n *\n * const formatter = redactByPattern(defaultConsoleFormatter, [\n * CREDIT_CARD_NUMBER_PATTERN,\n * EMAIL_ADDRESS_PATTERN,\n * JWT_PATTERN,\n * ]);\n * const sink = getConsoleSink({ formatter });\n * ```\n * @param formatter The console formatter to apply redaction to.\n * @param patterns The redaction patterns to apply.\n * @param options Options for bounding recursive traversal of formatter output.\n * @returns The redacted console formatter.\n * @since 0.10.0\n */\nexport function redactByPattern(\n formatter: ConsoleFormatter,\n patterns: RedactionPatterns,\n options?: PatternRedactionOptions,\n): ConsoleFormatter;\n\nexport function redactByPattern(\n formatter: TextFormatter | ConsoleFormatter,\n patterns: RedactionPatterns,\n options: PatternRedactionOptions = {},\n): (record: LogRecord) => string | readonly unknown[] {\n for (const { pattern } of patterns) {\n if (!pattern.global) {\n throw new TypeError(\n `Pattern ${pattern} does not have the global flag set.`,\n );\n }\n }\n\n function replaceString(str: string): string {\n for (const p of patterns) {\n // The following ternary operator may seem strange, but it's for\n // making TypeScript happy:\n str = typeof p.replacement === \"string\"\n ? str.replaceAll(p.pattern, p.replacement)\n : str.replaceAll(p.pattern, p.replacement);\n }\n return str;\n }\n\n function replaceObject(\n object: unknown,\n context: RedactionTraversalContext,\n depth: number,\n ): unknown {\n if (typeof object === \"object\" && object !== null) {\n if (context.visited.has(object)) {\n return context.visited.get(object)!; // Circular reference detected\n }\n }\n\n if (typeof object === \"string\") return replaceString(object);\n if (Array.isArray(object)) {\n const copy: unknown[] = [];\n const length = Math.min(object.length, context.limits.maxProperties);\n copy.length = length;\n context.visited.set(object, copy);\n if (object.length > context.limits.maxProperties) {\n reportLimitOnce(context, \"maxProperties\");\n }\n for (let i = 0; i < length; i++) {\n if (!(i in object)) continue;\n const item = object[i];\n if (\n shouldTraverseValue(item) && depth + 1 > context.limits.maxDepth\n ) {\n reportLimitOnce(context, \"maxDepth\");\n copy[i] = redactionTruncatedValue;\n } else {\n copy[i] = replaceObject(item, context, depth + 1);\n }\n }\n return copy;\n }\n if (typeof object === \"object\" && object !== null) {\n if (isBuiltInObject(object)) {\n return object;\n }\n const redacted: Record<string, unknown> = {};\n context.visited.set(object, redacted);\n const keys = Object.keys(object);\n if (keys.length > context.limits.maxProperties) {\n reportLimitOnce(context, \"maxProperties\");\n }\n for (const key of keys.slice(0, context.limits.maxProperties)) {\n const value = (object as Record<string, unknown>)[key];\n if (\n shouldTraverseValue(value) && depth + 1 > context.limits.maxDepth\n ) {\n reportLimitOnce(context, \"maxDepth\");\n redacted[key] = redactionTruncatedValue;\n } else {\n redacted[key] = replaceObject(value, context, depth + 1);\n }\n }\n return redacted;\n }\n return object;\n }\n\n return (record: LogRecord) => {\n const output = formatter(record);\n if (typeof output === \"string\") return replaceString(output);\n const context = createPatternRedactionContext(options);\n if (output.length > context.limits.maxProperties) {\n reportLimitOnce(context, \"maxProperties\");\n }\n return output\n .slice(0, context.limits.maxProperties)\n .map((obj) => replaceObject(obj, context, 0));\n };\n}\n\nfunction reportRedactionLimitExceeded(\n limit: RedactionLimit,\n limits: RedactionTraversalLimits,\n): void {\n if (reportingRedactionLimit || typeof metaLogger.warn !== \"function\") {\n return;\n }\n try {\n reportingRedactionLimit = true;\n metaLogger.warn(\n \"Redaction traversal exceeded {limit}; replacing or omitting \" +\n \"remaining data to keep logging bounded.\",\n { limit, ...limits },\n );\n } catch {\n // Meta logging failures must not make normal logging fail.\n } finally {\n reportingRedactionLimit = false;\n }\n}\n\nfunction shouldTraverseValue(value: unknown): boolean {\n return (typeof value === \"object\" && value !== null) &&\n !isBuiltInObject(value);\n}\n\nfunction createPatternRedactionContext(\n options: RedactionTraversalOptions,\n): RedactionTraversalContext {\n return createRedactionTraversalContext(\n options,\n reportRedactionLimitExceeded,\n );\n}\n\nfunction reportLimitOnce(\n context: RedactionTraversalContext,\n limit: RedactionLimit,\n): void {\n if (context.exceededLimits.has(limit)) return;\n context.reportLimitExceeded(limit);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logtape/redaction",
3
- "version": "2.3.2",
3
+ "version": "2.3.3",
4
4
  "description": "Redact sensitive data from log messages",
5
5
  "keywords": [
6
6
  "logging",
@@ -49,7 +49,7 @@
49
49
  "dist/"
50
50
  ],
51
51
  "peerDependencies": {
52
- "@logtape/logtape": "^2.3.2"
52
+ "@logtape/logtape": "^2.3.3"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@alinea/suite": "^0.6.3",