@hackylabs/deep-redact 3.0.5 → 4.0.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.
Files changed (49) hide show
  1. package/README.md +229 -106
  2. package/dist/adapters/console/index.cjs +74 -0
  3. package/dist/adapters/console/index.d.cts +22 -0
  4. package/dist/adapters/console/index.d.ts +22 -0
  5. package/dist/adapters/console/index.js +73 -0
  6. package/dist/index.cjs +2782 -0
  7. package/dist/index.d.cts +7 -0
  8. package/dist/index.d.ts +7 -0
  9. package/dist/index.js +2780 -55
  10. package/dist/node-console-sink-BnRUkAAr.cjs +19 -0
  11. package/dist/node-console-sink-DaQleNZ8.js +14 -0
  12. package/dist/public-Da0aARA9.d.cts +127 -0
  13. package/dist/public-Dw4ycNzO.d.ts +127 -0
  14. package/package.json +68 -106
  15. package/dist/index.mjs +0 -40
  16. package/dist/types/index.d.ts +0 -29
  17. package/dist/types/types.d.ts +0 -224
  18. package/dist/types/utils/TransformerRegistry.d.ts +0 -52
  19. package/dist/types/utils/index.d.ts +0 -131
  20. package/dist/types/utils/standardTransformers/bigint.d.ts +0 -2
  21. package/dist/types/utils/standardTransformers/date.d.ts +0 -2
  22. package/dist/types/utils/standardTransformers/error.d.ts +0 -2
  23. package/dist/types/utils/standardTransformers/index.d.ts +0 -9
  24. package/dist/types/utils/standardTransformers/map.d.ts +0 -2
  25. package/dist/types/utils/standardTransformers/regex.d.ts +0 -2
  26. package/dist/types/utils/standardTransformers/set.d.ts +0 -2
  27. package/dist/types/utils/standardTransformers/url.d.ts +0 -2
  28. package/dist/types.js +0 -2
  29. package/dist/types.mjs +0 -1
  30. package/dist/utils/TransformerRegistry.js +0 -100
  31. package/dist/utils/TransformerRegistry.mjs +0 -94
  32. package/dist/utils/index.js +0 -471
  33. package/dist/utils/index.mjs +0 -467
  34. package/dist/utils/standardTransformers/bigint.js +0 -10
  35. package/dist/utils/standardTransformers/bigint.mjs +0 -6
  36. package/dist/utils/standardTransformers/date.js +0 -9
  37. package/dist/utils/standardTransformers/date.mjs +0 -5
  38. package/dist/utils/standardTransformers/error.js +0 -16
  39. package/dist/utils/standardTransformers/error.mjs +0 -12
  40. package/dist/utils/standardTransformers/index.js +0 -38
  41. package/dist/utils/standardTransformers/index.mjs +0 -35
  42. package/dist/utils/standardTransformers/map.js +0 -9
  43. package/dist/utils/standardTransformers/map.mjs +0 -5
  44. package/dist/utils/standardTransformers/regex.js +0 -15
  45. package/dist/utils/standardTransformers/regex.mjs +0 -11
  46. package/dist/utils/standardTransformers/set.js +0 -9
  47. package/dist/utils/standardTransformers/set.mjs +0 -5
  48. package/dist/utils/standardTransformers/url.js +0 -9
  49. package/dist/utils/standardTransformers/url.mjs +0 -5
package/dist/index.cjs ADDED
@@ -0,0 +1,2782 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_node_console_sink = require("./node-console-sink-BnRUkAAr.cjs");
3
+ //#region src/core/compiler/compile-diagnostics.ts
4
+ const compileDiagnostics = (diagnostics) => {
5
+ return Object.freeze({
6
+ eventName: "redaction.failure",
7
+ sink: diagnostics?.sink
8
+ });
9
+ };
10
+ //#endregion
11
+ //#region src/core/compiler/compile-ignored-value-types.ts
12
+ const compileIgnoredValueTypes = (configured) => {
13
+ return Object.freeze({
14
+ bigint: configured?.bigint === true,
15
+ Date: configured?.Date === true,
16
+ Error: configured?.Error === true,
17
+ Map: configured?.Map === true,
18
+ RegExp: configured?.RegExp === true,
19
+ Set: configured?.Set === true,
20
+ URL: configured?.URL === true
21
+ });
22
+ };
23
+ //#endregion
24
+ //#region src/core/compiler/compile-value-types.ts
25
+ const compileValueTypes = (configured) => {
26
+ const allows = (name) => {
27
+ return configured === void 0 ? name === "string" : configured.includes(name);
28
+ };
29
+ return Object.freeze({
30
+ string: allows("string"),
31
+ number: allows("number"),
32
+ bigint: allows("bigint"),
33
+ boolean: allows("boolean"),
34
+ object: allows("object"),
35
+ function: allows("function"),
36
+ symbol: allows("symbol"),
37
+ undefined: allows("undefined")
38
+ });
39
+ };
40
+ //#endregion
41
+ //#region src/transformers/built-ins.ts
42
+ const bigintTransformer = (value) => {
43
+ if (typeof value !== "bigint") return value;
44
+ return {
45
+ _transformer: "bigint",
46
+ value: {
47
+ radix: 10,
48
+ number: value.toString(10)
49
+ }
50
+ };
51
+ };
52
+ const dateTransformer = (value) => {
53
+ if (!(value instanceof Date)) return value;
54
+ return {
55
+ _transformer: "date",
56
+ datetime: value.toISOString()
57
+ };
58
+ };
59
+ const errorTransformer = (value) => {
60
+ if (!(value instanceof Error)) return value;
61
+ return {
62
+ _transformer: "error",
63
+ value: {
64
+ type: value.constructor.name,
65
+ message: value.message,
66
+ stack: value.stack
67
+ }
68
+ };
69
+ };
70
+ const mapTransformer = (value) => {
71
+ if (!(value instanceof Map)) return value;
72
+ return {
73
+ _transformer: "map",
74
+ value: Object.fromEntries(value.entries())
75
+ };
76
+ };
77
+ const regexTransformer = (value) => {
78
+ if (!(value instanceof RegExp)) return value;
79
+ return {
80
+ _transformer: "regex",
81
+ value: {
82
+ source: value.source,
83
+ flags: value.flags
84
+ }
85
+ };
86
+ };
87
+ const setTransformer = (value) => {
88
+ if (!(value instanceof Set)) return value;
89
+ return {
90
+ _transformer: "set",
91
+ value: [...value.values()]
92
+ };
93
+ };
94
+ const urlTransformer = (value) => {
95
+ if (!(value instanceof URL)) return value;
96
+ return {
97
+ _transformer: "url",
98
+ value: value.href
99
+ };
100
+ };
101
+ //#endregion
102
+ //#region src/core/compiler/compile-transformers.ts
103
+ const emptyTransformers = Object.freeze([]);
104
+ const mergeTransformers = (configured, builtIns = emptyTransformers) => {
105
+ return Object.freeze([...configured ?? emptyTransformers, ...builtIns]);
106
+ };
107
+ const compileByType = (configured) => {
108
+ return Object.freeze({
109
+ bigint: mergeTransformers(configured?.bigint, Object.freeze([bigintTransformer])),
110
+ object: mergeTransformers(configured?.object)
111
+ });
112
+ };
113
+ const compileByConstructor = (configured) => {
114
+ const customRegistrations = configured?.custom ?? [];
115
+ return Object.freeze({
116
+ Date: mergeTransformers(configured?.Date, Object.freeze([dateTransformer])),
117
+ Error: mergeTransformers(configured?.Error, Object.freeze([errorTransformer])),
118
+ Map: mergeTransformers(configured?.Map, Object.freeze([mapTransformer])),
119
+ RegExp: mergeTransformers(configured?.RegExp, Object.freeze([regexTransformer])),
120
+ Set: mergeTransformers(configured?.Set, Object.freeze([setTransformer])),
121
+ URL: mergeTransformers(configured?.URL, Object.freeze([urlTransformer])),
122
+ custom: Object.freeze(customRegistrations.map((registration) => {
123
+ return Object.freeze({
124
+ constructor: registration.constructor,
125
+ transformers: mergeTransformers(registration.transformers)
126
+ });
127
+ }))
128
+ });
129
+ };
130
+ const compileTransformers = (configured) => {
131
+ return Object.freeze({
132
+ byType: compileByType(configured?.byType),
133
+ byConstructor: compileByConstructor(configured?.byConstructor),
134
+ fallback: mergeTransformers(configured?.fallback)
135
+ });
136
+ };
137
+ //#endregion
138
+ //#region src/core/validation/regex-safety.ts
139
+ const cloneRegExp = (pattern) => {
140
+ return new RegExp(pattern.source, pattern.flags);
141
+ };
142
+ const maxRegexSourceLength = 256;
143
+ const nestedQuantifierRegexPattern = /\((?:\?:|\?=|\?!|\?<=|\?<!|\?<[^>]+>)?(?:\\.|[^()[\]\\]|\[[^\]]*])*(?:[+*]|\{\d+(?:,\d*)?\})(?:\\.|[^()[\]\\]|\[[^\]]*])*\)(?:[+*]|\{\d+(?:,\d*)?\})/;
144
+ const quantifiedGroupRegexPattern = /\((?:\?:|\?=|\?!|\?<=|\?<!|\?<[^>]+>)?((?:\\.|[^()[\]\\]|\[[^\]]*])*)\)(?:[+*]|\{\d+(?:,\d*)?\})/g;
145
+ const isRegExp = (value) => {
146
+ return value instanceof RegExp;
147
+ };
148
+ const splitRegexAlternatives = (source) => {
149
+ const alternatives = [""];
150
+ let inCharacterClass = false;
151
+ let groupDepth = 0;
152
+ let escaped = false;
153
+ for (const character of source) {
154
+ if (escaped) {
155
+ alternatives[alternatives.length - 1] += character;
156
+ escaped = false;
157
+ continue;
158
+ }
159
+ if (character === "\\") {
160
+ alternatives[alternatives.length - 1] += character;
161
+ escaped = true;
162
+ continue;
163
+ }
164
+ if (character === "[") {
165
+ inCharacterClass = true;
166
+ alternatives[alternatives.length - 1] += character;
167
+ continue;
168
+ }
169
+ if (character === "]" && inCharacterClass) {
170
+ inCharacterClass = false;
171
+ alternatives[alternatives.length - 1] += character;
172
+ continue;
173
+ }
174
+ if (!inCharacterClass) {
175
+ if (character === "(") {
176
+ groupDepth++;
177
+ alternatives[alternatives.length - 1] += character;
178
+ continue;
179
+ }
180
+ if (character === ")") {
181
+ groupDepth--;
182
+ alternatives[alternatives.length - 1] += character;
183
+ continue;
184
+ }
185
+ if (character === "|" && groupDepth === 0) {
186
+ alternatives.push("");
187
+ continue;
188
+ }
189
+ }
190
+ alternatives[alternatives.length - 1] += character;
191
+ }
192
+ return alternatives;
193
+ };
194
+ const hasPrefixOverlappingAlternatives = (alternatives) => {
195
+ const nonEmptyAlternatives = alternatives.filter((alternative) => alternative.length > 0);
196
+ return nonEmptyAlternatives.some((alternative, index) => {
197
+ return nonEmptyAlternatives.some((otherAlternative, otherIndex) => {
198
+ return index !== otherIndex && otherAlternative.startsWith(alternative);
199
+ });
200
+ });
201
+ };
202
+ const hasUnsafeOverlappingAlternation = (source) => {
203
+ for (const match of source.matchAll(quantifiedGroupRegexPattern)) {
204
+ const alternatives = splitRegexAlternatives(match[1] ?? "");
205
+ if (alternatives.length > 1 && hasPrefixOverlappingAlternatives(alternatives)) return true;
206
+ }
207
+ return false;
208
+ };
209
+ const lowercaseInitial = (value) => {
210
+ return `${value.charAt(0).toLowerCase()}${value.slice(1)}`;
211
+ };
212
+ const getUnsupportedRegexMessage = (selector, label, options = {}) => {
213
+ if (selector.sticky) return options.allowGlobal === true ? `${label} must not use sticky flag.` : `${label} must not use global or sticky flags.`;
214
+ if (selector.global && options.allowGlobal !== true) return `${label} must not use global or sticky flags.`;
215
+ if ([...selector.source].length > maxRegexSourceLength) return `${label} source must be at most ${maxRegexSourceLength} characters.`;
216
+ if (nestedQuantifierRegexPattern.test(selector.source)) return `Unsafe ${lowercaseInitial(label)} uses a nested quantified pattern.`;
217
+ if (hasUnsafeOverlappingAlternation(selector.source)) return `Unsafe ${lowercaseInitial(label)} uses an overlapping alternation pattern.`;
218
+ };
219
+ //#endregion
220
+ //#region src/core/matching/path-parser.ts
221
+ const barePropertyPattern = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
222
+ const indexSegmentPattern = /^(0|[1-9]\d*)$/;
223
+ const regexLikeSegmentPattern = /^\/.+\/[A-Za-z]*$/;
224
+ var PathSyntaxError = class extends SyntaxError {
225
+ selector;
226
+ constructor(selector, message) {
227
+ super(message);
228
+ this.name = "PathSyntaxError";
229
+ this.selector = selector;
230
+ }
231
+ };
232
+ const createPropertyPathSegment = (value) => {
233
+ return Object.freeze({
234
+ kind: "property",
235
+ value
236
+ });
237
+ };
238
+ const createIndexPathSegment = (value) => {
239
+ return Object.freeze({
240
+ kind: "index",
241
+ value
242
+ });
243
+ };
244
+ const createWildcardPathSegment = () => {
245
+ return Object.freeze({ kind: "wildcard" });
246
+ };
247
+ const createRecursiveWildcardPathSegment = () => {
248
+ return Object.freeze({ kind: "recursive-wildcard" });
249
+ };
250
+ const createIgnorePropertyPathSegment = (value) => {
251
+ return Object.freeze({
252
+ kind: "ignore-property",
253
+ value
254
+ });
255
+ };
256
+ const createIgnoreIndexPathSegment = (value) => {
257
+ return Object.freeze({
258
+ kind: "ignore-index",
259
+ value
260
+ });
261
+ };
262
+ const cloneRegexMatcher = (matcher) => {
263
+ return new RegExp(matcher.source, matcher.flags);
264
+ };
265
+ const createRegexPathSegment = (matcher) => {
266
+ return Object.freeze({
267
+ kind: "regex",
268
+ matcher: cloneRegexMatcher(matcher)
269
+ });
270
+ };
271
+ const createIgnoreRegexPathSegment = (matcher) => {
272
+ return Object.freeze({
273
+ kind: "ignore-regex",
274
+ matcher: cloneRegexMatcher(matcher)
275
+ });
276
+ };
277
+ const renderRawSelector = (selector) => {
278
+ return typeof selector === "string" ? selector : JSON.stringify(selector);
279
+ };
280
+ const createUnsupportedWildcardError = (selector, segment) => {
281
+ const rawSelector = renderRawSelector(selector);
282
+ if (segment === "**") return new PathSyntaxError(rawSelector, "Unsupported recursive wildcard segment \"**\".");
283
+ if (segment === "*") return new PathSyntaxError(rawSelector, "Unsupported wildcard segment \"*\".");
284
+ return new PathSyntaxError(rawSelector, `Unsupported wildcard syntax in segment "${segment}".`);
285
+ };
286
+ const createExactSegment = (selector, value) => {
287
+ if (typeof value === "number") return createIndexPathSegment(value);
288
+ if (value.length === 0) throw new PathSyntaxError(renderRawSelector(selector), "Path selectors must not contain empty segments.");
289
+ if (value.includes("*")) throw createUnsupportedWildcardError(selector, value);
290
+ if (regexLikeSegmentPattern.test(value)) throw new PathSyntaxError(renderRawSelector(selector), `Unsupported regex-like segment "${value}".`);
291
+ if (value.startsWith("!")) throw new PathSyntaxError(renderRawSelector(selector), `Unsupported exclusion syntax in segment "${value}". Ignore selectors are structured-selector only.`);
292
+ if (indexSegmentPattern.test(value)) return createIndexPathSegment(Number(value));
293
+ if (barePropertyPattern.test(value)) return createPropertyPathSegment(value);
294
+ throw new PathSyntaxError(renderRawSelector(selector), `Unsupported exact path segment "${value}". Use bracket-quoted property syntax for literal special-character keys.`);
295
+ };
296
+ const createLiteralStructuredPropertySegment = (selector, value) => {
297
+ if (value.length === 0) throw new PathSyntaxError(renderRawSelector(selector), "Path selectors must not contain empty segments.");
298
+ if (regexLikeSegmentPattern.test(value)) throw new PathSyntaxError(renderRawSelector(selector), `Unsupported regex-like segment "${value}".`);
299
+ return createPropertyPathSegment(value);
300
+ };
301
+ const createLiteralStructuredIndexSegment = (selector, value) => {
302
+ if (!Number.isInteger(value) || value < 0) throw new PathSyntaxError(renderRawSelector(selector), "Structured numeric segments must be non-negative integers.");
303
+ return createIndexPathSegment(value);
304
+ };
305
+ const createLiteralStructuredIgnorePropertySegment = (selector, value) => {
306
+ if (value.length === 0) throw new PathSyntaxError(renderRawSelector(selector), "Path selectors must not contain empty segments.");
307
+ if (regexLikeSegmentPattern.test(value)) throw new PathSyntaxError(renderRawSelector(selector), `Unsupported regex-like segment "${value}".`);
308
+ return createIgnorePropertyPathSegment(value);
309
+ };
310
+ const createLiteralStructuredIgnoreIndexSegment = (selector, value) => {
311
+ if (!Number.isInteger(value) || value < 0) throw new PathSyntaxError(renderRawSelector(selector), "Structured ignore indexes must be non-negative integers.");
312
+ return createIgnoreIndexPathSegment(value);
313
+ };
314
+ const parseQuotedProperty = (selector, rawSelector, startIndex) => {
315
+ const quote = selector[startIndex];
316
+ let index = startIndex + 1;
317
+ let value = "";
318
+ while (index < selector.length) {
319
+ const character = selector[index];
320
+ if (character === "\\") {
321
+ index += 1;
322
+ if (index >= selector.length) throw new PathSyntaxError(rawSelector, "Quoted property selector has an unfinished escape sequence.");
323
+ value += selector[index];
324
+ index += 1;
325
+ continue;
326
+ }
327
+ if (character === quote) {
328
+ if (value.length === 0) throw new PathSyntaxError(rawSelector, "Path selectors must not contain empty segments.");
329
+ return {
330
+ nextIndex: index + 1,
331
+ segment: createPropertyPathSegment(value)
332
+ };
333
+ }
334
+ value += character;
335
+ index += 1;
336
+ }
337
+ throw new PathSyntaxError(rawSelector, "Quoted property selector is not closed.");
338
+ };
339
+ const parseBracketSegment = (selector, rawSelector, startIndex) => {
340
+ const index = startIndex + 1;
341
+ if (index >= selector.length) throw new PathSyntaxError(rawSelector, "Bracket selector is not closed.");
342
+ if (selector[index] === "\"" || selector[index] === "'") {
343
+ const quotedProperty = parseQuotedProperty(selector, rawSelector, index);
344
+ if (selector[quotedProperty.nextIndex] !== "]") throw new PathSyntaxError(rawSelector, "Quoted property selector must be closed with ].");
345
+ return {
346
+ nextIndex: quotedProperty.nextIndex + 1,
347
+ segment: quotedProperty.segment
348
+ };
349
+ }
350
+ const closingIndex = selector.indexOf("]", index);
351
+ if (closingIndex === -1) throw new PathSyntaxError(rawSelector, "Bracket selector is not closed.");
352
+ const value = selector.slice(index, closingIndex);
353
+ if (value.length === 0) throw new PathSyntaxError(rawSelector, "Path selectors must not contain empty segments.");
354
+ if (value.includes("*")) throw createUnsupportedWildcardError(rawSelector, value);
355
+ if (regexLikeSegmentPattern.test(value)) throw new PathSyntaxError(rawSelector, `Unsupported regex-like segment "${value}".`);
356
+ if (!indexSegmentPattern.test(value)) throw new PathSyntaxError(rawSelector, `Unsupported bracket segment "${value}". Use numeric indexes or quoted property selectors only.`);
357
+ return {
358
+ nextIndex: closingIndex + 1,
359
+ segment: createIndexPathSegment(Number(value))
360
+ };
361
+ };
362
+ const parseBareSegment = (selector, rawSelector, startIndex) => {
363
+ let index = startIndex;
364
+ while (index < selector.length && selector[index] !== "." && selector[index] !== "[") {
365
+ if (selector[index] === "]") throw new PathSyntaxError(rawSelector, "Unexpected ] in path selector.");
366
+ index += 1;
367
+ }
368
+ const value = selector.slice(startIndex, index);
369
+ if (value === "*") return {
370
+ nextIndex: index,
371
+ segment: createWildcardPathSegment()
372
+ };
373
+ if (value === "**") return {
374
+ nextIndex: index,
375
+ segment: createRecursiveWildcardPathSegment()
376
+ };
377
+ return {
378
+ nextIndex: index,
379
+ segment: createExactSegment(rawSelector, value)
380
+ };
381
+ };
382
+ const parseStringPathSelector = (selector) => {
383
+ if (selector.length === 0) throw new PathSyntaxError(selector, "Path selectors must not be empty.");
384
+ const segments = [];
385
+ let index = 0;
386
+ let recursiveWildcardCount = 0;
387
+ while (index < selector.length) {
388
+ if (selector[index] === ".") throw new PathSyntaxError(selector, "Path selectors must not contain empty segments.");
389
+ const parsedSegment = selector[index] === "[" ? parseBracketSegment(selector, selector, index) : parseBareSegment(selector, selector, index);
390
+ segments.push(parsedSegment.segment);
391
+ if (parsedSegment.segment.kind === "recursive-wildcard") {
392
+ recursiveWildcardCount += 1;
393
+ if (recursiveWildcardCount > 1) throw new PathSyntaxError(selector, "Path selectors may contain at most one recursive wildcard segment \"**\".");
394
+ }
395
+ index = parsedSegment.nextIndex;
396
+ while (index < selector.length && selector[index] === "[") {
397
+ const bracketSegment = parseBracketSegment(selector, selector, index);
398
+ segments.push(bracketSegment.segment);
399
+ index = bracketSegment.nextIndex;
400
+ }
401
+ if (index >= selector.length) break;
402
+ if (selector[index] !== ".") throw new PathSyntaxError(selector, `Unexpected character "${selector[index]}" in path selector.`);
403
+ index += 1;
404
+ if (index >= selector.length || selector[index] === "." || selector[index] === "[") throw new PathSyntaxError(selector, "Path selectors must not contain empty segments.");
405
+ }
406
+ return Object.freeze({
407
+ raw: selector,
408
+ segments: Object.freeze(segments)
409
+ });
410
+ };
411
+ const isIgnorePathSegment = (value) => {
412
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
413
+ const keys = Object.keys(value);
414
+ return keys.length === 1 && keys[0] === "ignore";
415
+ };
416
+ const parseStructuredPathSelector = (selector) => {
417
+ if (selector.length === 0) throw new PathSyntaxError(renderRawSelector(selector), "Path selectors must not be empty.");
418
+ const segments = selector.map((segment) => {
419
+ if (isRegExp(segment)) return createRegexPathSegment(segment);
420
+ if (typeof segment === "string" || typeof segment === "number") return typeof segment === "string" ? createLiteralStructuredPropertySegment(selector, segment) : createLiteralStructuredIndexSegment(selector, segment);
421
+ if (!isIgnorePathSegment(segment)) throw new PathSyntaxError(renderRawSelector(selector), `Unsupported structured selector segment ${JSON.stringify(segment)}.`);
422
+ if (typeof segment.ignore === "string") return createLiteralStructuredIgnorePropertySegment(selector, segment.ignore);
423
+ if (typeof segment.ignore === "number") return createLiteralStructuredIgnoreIndexSegment(selector, segment.ignore);
424
+ if (isRegExp(segment.ignore)) return createIgnoreRegexPathSegment(segment.ignore);
425
+ throw new PathSyntaxError(renderRawSelector(selector), `Unsupported structured selector segment ${JSON.stringify(segment)}.`);
426
+ });
427
+ return Object.freeze({
428
+ raw: selector,
429
+ segments: Object.freeze(segments)
430
+ });
431
+ };
432
+ const isExactPathSegment = (segment) => {
433
+ return segment.kind === "property" || segment.kind === "index";
434
+ };
435
+ const isDynamicPathSegment = (segment) => {
436
+ return !isExactPathSegment(segment);
437
+ };
438
+ const isSingleWildcardSegment = (segment) => {
439
+ return segment.kind === "wildcard";
440
+ };
441
+ const containsOnlySingleWildcardDynamics = (segments) => {
442
+ return segments.every((segment) => isExactPathSegment(segment) || isSingleWildcardSegment(segment));
443
+ };
444
+ const exactSegmentsEqual = (left, right) => {
445
+ if (left.kind === "property" && right.kind === "property") return left.value === right.value;
446
+ if (left.kind === "index" && right.kind === "index") return left.value === right.value;
447
+ return false;
448
+ };
449
+ const occupiesWildcardDepthAsIntermediate = (candidate, wildcardRule) => {
450
+ const wildcardDepth = wildcardRule.findIndex((segment) => isSingleWildcardSegment(segment));
451
+ if (wildcardDepth === -1) return false;
452
+ if (candidate.length < wildcardDepth + 2) return false;
453
+ for (let depth = 0; depth <= wildcardDepth; depth += 1) if (!isExactPathSegment(candidate[depth])) return false;
454
+ for (let depth = 0; depth < wildcardDepth; depth += 1) if (!exactSegmentsEqual(candidate[depth], wildcardRule[depth])) return false;
455
+ return true;
456
+ };
457
+ const hasUnsafeWildcardOverlap = (rules) => {
458
+ return rules.some((wildcardRule) => rules.some((candidate) => occupiesWildcardDepthAsIntermediate(candidate, wildcardRule)));
459
+ };
460
+ const parsePathSelector = (selector) => {
461
+ return typeof selector === "string" ? parseStringPathSelector(selector) : parseStructuredPathSelector(selector);
462
+ };
463
+ //#endregion
464
+ //#region src/core/matching/key-normaliser.ts
465
+ const canonicaliseKey = (value) => {
466
+ return value.toLowerCase().trim().replaceAll(/[_-]/g, "");
467
+ };
468
+ //#endregion
469
+ //#region src/core/matching/path-normaliser.ts
470
+ const canonicalBarePropertyPattern = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
471
+ const renderPropertySegment = (value, isRoot) => {
472
+ if (canonicalBarePropertyPattern.test(value)) return `${isRoot ? "" : "."}${value}`;
473
+ return `[${JSON.stringify(value)}]`;
474
+ };
475
+ const renderExactPathSegment = (segment, isRoot) => {
476
+ if (segment.kind === "index") return `${isRoot ? "" : "."}${segment.value}`;
477
+ return renderPropertySegment(segment.value, isRoot);
478
+ };
479
+ const renderCanonicalPath = (segments) => {
480
+ return segments.map((segment, index) => renderExactPathSegment(segment, index === 0)).join("");
481
+ };
482
+ const appendCanonicalPathSegment = (parentPath, segment) => {
483
+ return `${parentPath ?? ""}${renderExactPathSegment(segment, parentPath === void 0)}`;
484
+ };
485
+ const renderDynamicPathSegment = (segment, isRoot) => {
486
+ if (segment.kind === "wildcard") return `${isRoot ? "" : "."}*`;
487
+ if (segment.kind === "recursive-wildcard") return `${isRoot ? "" : "."}**`;
488
+ if (segment.kind === "ignore-index") return `${isRoot ? "" : "."}{ignore:${segment.value}}`;
489
+ if (segment.kind === "ignore-property") return `${isRoot ? "" : "."}{ignore:${JSON.stringify(segment.value)}}`;
490
+ if (segment.kind === "regex") return `${isRoot ? "" : "."}{regex:${JSON.stringify({
491
+ source: segment.matcher.source,
492
+ flags: segment.matcher.flags
493
+ })}}`;
494
+ if (segment.kind === "ignore-regex") return `${isRoot ? "" : "."}{ignore-regex:${JSON.stringify({
495
+ source: segment.matcher.source,
496
+ flags: segment.matcher.flags
497
+ })}}`;
498
+ return renderExactPathSegment(segment, isRoot);
499
+ };
500
+ const renderSelectorSignature = (segments) => {
501
+ return segments.map((segment, index) => renderDynamicPathSegment(segment, index === 0)).join("");
502
+ };
503
+ const normaliseParsedPath = (parsedPath) => {
504
+ if (!parsedPath.segments.every((segment) => isExactPathSegment(segment))) throw new TypeError("Dynamic selectors cannot be canonicalised as exact paths.");
505
+ return Object.freeze({
506
+ canonicalPath: renderCanonicalPath(parsedPath.segments),
507
+ segments: parsedPath.segments
508
+ });
509
+ };
510
+ const createTraversalBudget = () => {
511
+ return {
512
+ depth: 0,
513
+ nodesVisited: 0
514
+ };
515
+ };
516
+ const isDepthExceeded = (budget, maxDepth) => {
517
+ return budget.depth > maxDepth;
518
+ };
519
+ const isNodeBudgetExceeded = (budget, maxNodes) => {
520
+ return budget.nodesVisited > maxNodes;
521
+ };
522
+ var BudgetExceededError = class extends Error {
523
+ code = "BUDGET_EXCEEDED";
524
+ constructor(message) {
525
+ super(message);
526
+ this.name = "BudgetExceededError";
527
+ }
528
+ };
529
+ const createBudgetExceededError = (kind, limit) => {
530
+ return new BudgetExceededError(kind === "depth" ? `Traversal depth limit (${limit}) exceeded.` : `Traversal node budget (${limit}) exceeded.`);
531
+ };
532
+ const isBudgetExceededError = (error) => {
533
+ return error instanceof BudgetExceededError;
534
+ };
535
+ //#endregion
536
+ //#region src/core/compiler/compile-redactor-plan.ts
537
+ const createLookupTable = () => {
538
+ return Object.create(null);
539
+ };
540
+ const toPublicPathSegment = (segment) => {
541
+ if (segment.kind === "property") return segment.value;
542
+ if (segment.kind === "index") return segment.value;
543
+ if (segment.kind === "wildcard") return Object.freeze({ any: true });
544
+ if (segment.kind === "recursive-wildcard") return Object.freeze({ anyDepth: true });
545
+ if (segment.kind === "ignore-property") return Object.freeze({ ignore: segment.value });
546
+ if (segment.kind === "ignore-index") return Object.freeze({ ignore: segment.value });
547
+ if (segment.kind === "regex") return new RegExp(segment.matcher.source, segment.matcher.flags);
548
+ return Object.freeze({ ignore: new RegExp(segment.matcher.source, segment.matcher.flags) });
549
+ };
550
+ const compileRulePath = (segments) => {
551
+ return Object.freeze(segments.map((segment) => toPublicPathSegment(segment)));
552
+ };
553
+ const createDefaultPolicy = (options) => {
554
+ return Object.freeze({
555
+ censor: options.censor,
556
+ remove: options.remove ?? false,
557
+ replaceStringByLength: options.replaceStringByLength ?? false,
558
+ retainStructure: options.retainStructure ?? false
559
+ });
560
+ };
561
+ const createKeyMatchDefaults = (options) => {
562
+ return Object.freeze({
563
+ caseSensitiveKeyMatch: options.caseSensitiveKeyMatch ?? true,
564
+ fuzzyKeyMatch: options.fuzzyKeyMatch ?? false
565
+ });
566
+ };
567
+ const mergePolicy = (defaults, overrides) => {
568
+ return Object.freeze({
569
+ censor: overrides.censor ?? defaults.censor,
570
+ remove: overrides.remove ?? defaults.remove,
571
+ replaceStringByLength: overrides.replaceStringByLength ?? defaults.replaceStringByLength,
572
+ retainStructure: overrides.retainStructure ?? defaults.retainStructure
573
+ });
574
+ };
575
+ const isPathRule = (pathEntry) => {
576
+ return typeof pathEntry === "object" && pathEntry !== null && !Array.isArray(pathEntry) && "path" in pathEntry;
577
+ };
578
+ const toPathRule = (pathEntry) => {
579
+ return isPathRule(pathEntry) ? pathEntry : { path: pathEntry };
580
+ };
581
+ const compilePathRule = (pathEntry, defaults) => {
582
+ const parsedPath = parsePathSelector(toPathRule(pathEntry).path);
583
+ const policy = mergePolicy(defaults, isPathRule(pathEntry) ? pathEntry : {});
584
+ const rulePath = compileRulePath(parsedPath.segments);
585
+ if (parsedPath.segments.some((segment) => isDynamicPathSegment(segment))) return Object.freeze({
586
+ signature: renderSelectorSignature(parsedPath.segments),
587
+ policy,
588
+ rulePath,
589
+ segments: parsedPath.segments
590
+ });
591
+ const normalisedPath = normaliseParsedPath(parsedPath);
592
+ return Object.freeze({
593
+ canonicalPath: normalisedPath.canonicalPath,
594
+ policy,
595
+ rulePath,
596
+ segments: normalisedPath.segments
597
+ });
598
+ };
599
+ const compilePathRules = (pathEntries, defaults) => {
600
+ const exactPathRules = createLookupTable();
601
+ const dynamicPathRules = [];
602
+ for (const pathEntry of pathEntries) {
603
+ const compiledRule = compilePathRule(pathEntry, defaults);
604
+ if ("canonicalPath" in compiledRule) {
605
+ exactPathRules[compiledRule.canonicalPath] = compiledRule;
606
+ continue;
607
+ }
608
+ dynamicPathRules.push(compiledRule);
609
+ }
610
+ return Object.freeze({
611
+ dynamicPathRules: Object.freeze(dynamicPathRules),
612
+ exactPathRules: Object.freeze(exactPathRules)
613
+ });
614
+ };
615
+ const isKeyRule = (keySelector) => {
616
+ return typeof keySelector === "object" && keySelector !== null && !(keySelector instanceof RegExp) && "key" in keySelector;
617
+ };
618
+ const keyRuleHasPolicyOverrides = (rule) => {
619
+ return rule.censor !== void 0 || rule.remove !== void 0 || rule.retainStructure !== void 0 || rule.replaceStringByLength !== void 0;
620
+ };
621
+ const compileKeyRulePolicy = (rule, defaults) => {
622
+ return keyRuleHasPolicyOverrides(rule) ? mergePolicy(defaults, {
623
+ censor: rule.censor,
624
+ remove: rule.remove,
625
+ retainStructure: rule.retainStructure,
626
+ replaceStringByLength: rule.replaceStringByLength
627
+ }) : void 0;
628
+ };
629
+ const toLiteralKeyRule = (configuredKey, overrides, defaults, keyDefaults) => {
630
+ const fuzzyKeyMatch = overrides?.fuzzyKeyMatch ?? keyDefaults.fuzzyKeyMatch;
631
+ const caseSensitiveKeyMatch = overrides?.caseSensitiveKeyMatch ?? keyDefaults.caseSensitiveKeyMatch;
632
+ let matchMode = "exact";
633
+ if (fuzzyKeyMatch) matchMode = caseSensitiveKeyMatch ? "contains" : "canonical-contains";
634
+ else if (!caseSensitiveKeyMatch) matchMode = "canonical-exact";
635
+ return Object.freeze({
636
+ canonicalKey: canonicaliseKey(configuredKey),
637
+ configuredKey,
638
+ matchMode,
639
+ policy: overrides === void 0 ? void 0 : compileKeyRulePolicy(overrides, defaults),
640
+ rulePath: Object.freeze([configuredKey])
641
+ });
642
+ };
643
+ const compileExactKeyRules = (keys, defaults, keyDefaults) => {
644
+ const literalMatchers = [];
645
+ for (const key of keys) if (typeof key === "string") literalMatchers.push(toLiteralKeyRule(key, void 0, defaults, keyDefaults));
646
+ else if (isKeyRule(key) && typeof key.key === "string") literalMatchers.push(toLiteralKeyRule(key.key, key, defaults, keyDefaults));
647
+ return Object.freeze({
648
+ literalMatchers: Object.freeze(literalMatchers),
649
+ policy: defaults,
650
+ requiresCanonicalKey: literalMatchers.some((rule) => rule.matchMode.startsWith("canonical"))
651
+ });
652
+ };
653
+ const compileRegexKeyRules = (keys, defaults) => {
654
+ const matchers = [];
655
+ for (const key of keys) if (key instanceof RegExp) matchers.push(Object.freeze({ matcher: new RegExp(key.source, key.flags) }));
656
+ else if (isKeyRule(key) && key.key instanceof RegExp) matchers.push(Object.freeze({
657
+ matcher: new RegExp(key.key.source, key.key.flags),
658
+ policy: compileKeyRulePolicy(key, defaults)
659
+ }));
660
+ return Object.freeze({
661
+ matchers: Object.freeze(matchers),
662
+ policy: defaults
663
+ });
664
+ };
665
+ const isSubstringRule = (stringTest) => {
666
+ return !(stringTest instanceof RegExp);
667
+ };
668
+ const compileSubstringRules = (stringTests, defaults) => {
669
+ return Object.freeze(stringTests.map((stringTest) => {
670
+ if (isSubstringRule(stringTest)) return Object.freeze({
671
+ kind: "structured-replacer",
672
+ pattern: cloneRegExp(stringTest.pattern),
673
+ replacer: stringTest.replacer
674
+ });
675
+ return Object.freeze({
676
+ kind: "whole-value",
677
+ pattern: cloneRegExp(stringTest),
678
+ policy: defaults
679
+ });
680
+ }));
681
+ };
682
+ const compileRedactorPlan = (options = {}) => {
683
+ const defaults = createDefaultPolicy(options);
684
+ const keyDefaults = createKeyMatchDefaults(options);
685
+ const compiledPathRules = compilePathRules(options.paths ?? [], defaults);
686
+ const exactKeyRules = compileExactKeyRules(options.keys ?? [], defaults, keyDefaults);
687
+ const regexKeyRules = compileRegexKeyRules(options.keys ?? [], defaults);
688
+ const substringRules = compileSubstringRules(options.stringTests ?? [], defaults);
689
+ const everyDynamicRuleIsSingleWildcard = compiledPathRules.dynamicPathRules.every((rule) => containsOnlySingleWildcardDynamics(rule.segments));
690
+ const hasAnyPathRule = Object.keys(compiledPathRules.exactPathRules).length > 0 || compiledPathRules.dynamicPathRules.length > 0;
691
+ const hasUnsafeOverlap = compiledPathRules.dynamicPathRules.length > 0 && hasUnsafeWildcardOverlap([...Object.values(compiledPathRules.exactPathRules).map((rule) => rule.segments), ...compiledPathRules.dynamicPathRules.map((rule) => rule.segments)]);
692
+ const pathDrivenOnly = everyDynamicRuleIsSingleWildcard && hasAnyPathRule && !hasUnsafeOverlap && exactKeyRules.literalMatchers.length === 0 && regexKeyRules.matchers.length === 0 && substringRules.length === 0 && !options.fuzzyKeyMatch && options.caseSensitiveKeyMatch !== false;
693
+ return Object.freeze({
694
+ diagnostics: compileDiagnostics(options.diagnostics),
695
+ defaults,
696
+ dynamicPathRules: compiledPathRules.dynamicPathRules,
697
+ exactKeyRules,
698
+ exactPathRules: compiledPathRules.exactPathRules,
699
+ ignoredValueTypes: compileIgnoredValueTypes(options.ignoredValueTypes),
700
+ pathDrivenOnly,
701
+ maxDepth: options.maxDepth ?? 500,
702
+ maxNodes: options.maxNodes ?? 5e4,
703
+ regexKeyRules,
704
+ serialise: options.serialise,
705
+ substringRules,
706
+ transformers: compileTransformers(options.transformers),
707
+ valueTypes: compileValueTypes(options.types)
708
+ });
709
+ };
710
+ //#endregion
711
+ //#region src/core/replacement/apply-redaction.ts
712
+ const defaultCensor = "[REDACTED]";
713
+ const isRedactableType = (value, valueTypes) => {
714
+ return valueTypes[typeof value];
715
+ };
716
+ const removedValue = Symbol("deep-redact.removed");
717
+ const isRemovedValue = (value) => {
718
+ return value === removedValue;
719
+ };
720
+ const buildSameLengthReplacement = (token, targetLength) => {
721
+ if (targetLength === 0) return "";
722
+ const tokenLength = token.length;
723
+ if (tokenLength === 0) return "";
724
+ const quotient = Math.floor(targetLength / tokenLength);
725
+ const remainder = targetLength % tokenLength;
726
+ return token.repeat(quotient) + token.slice(0, remainder);
727
+ };
728
+ const applyRedaction = (value, policy, context) => {
729
+ if (policy.remove) return removedValue;
730
+ if (typeof policy.censor === "function") return policy.censor.call(void 0, value, context);
731
+ const literalCensor = policy.censor ?? defaultCensor;
732
+ if (policy.replaceStringByLength && typeof value === "string") return buildSameLengthReplacement(literalCensor, value.length);
733
+ return literalCensor;
734
+ };
735
+ //#endregion
736
+ //#region src/transformers/resolve-transformer.ts
737
+ const supportedConstructorMatchers = Object.freeze([
738
+ {
739
+ name: "Date",
740
+ matches: (value) => value instanceof Date
741
+ },
742
+ {
743
+ name: "Error",
744
+ matches: (value) => value instanceof Error
745
+ },
746
+ {
747
+ name: "Map",
748
+ matches: (value) => value instanceof Map
749
+ },
750
+ {
751
+ name: "RegExp",
752
+ matches: (value) => value instanceof RegExp
753
+ },
754
+ {
755
+ name: "Set",
756
+ matches: (value) => value instanceof Set
757
+ },
758
+ {
759
+ name: "URL",
760
+ matches: (value) => value instanceof URL
761
+ }
762
+ ]);
763
+ const resolveSupportedConstructorName = (value) => {
764
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return;
765
+ for (const matcher of supportedConstructorMatchers) if (matcher.matches(value)) return matcher.name;
766
+ };
767
+ const resolveSupportedTransformableValueKind = (value) => {
768
+ if (typeof value === "bigint") return "bigint";
769
+ return resolveSupportedConstructorName(value);
770
+ };
771
+ const isSupportedTransformableValue = (value) => {
772
+ return resolveSupportedTransformableValueKind(value) !== void 0;
773
+ };
774
+ const applyFirstChangingTransformer = (value, transformers) => {
775
+ for (const transformer of transformers) {
776
+ const transformed = transformer(value);
777
+ if (transformed !== value) return transformed;
778
+ }
779
+ };
780
+ const resolveCustomConstructorTransformers = (value, plan) => {
781
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return;
782
+ for (const registration of plan.byConstructor.custom) if (value instanceof registration.constructor) return registration.transformers;
783
+ };
784
+ const resolveTransformedValue = (value, plan) => {
785
+ const supportedValueKind = resolveSupportedTransformableValueKind(value);
786
+ if (supportedValueKind === "bigint") return applyFirstChangingTransformer(value, [...plan.byType.bigint, ...plan.fallback]);
787
+ if (supportedValueKind === void 0) {
788
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return;
789
+ const byTypeResult = applyFirstChangingTransformer(value, plan.byType.object);
790
+ if (byTypeResult !== void 0) return byTypeResult;
791
+ return applyFirstChangingTransformer(value, [...resolveCustomConstructorTransformers(value, plan) ?? [], ...plan.fallback]);
792
+ }
793
+ const byTypeResult = applyFirstChangingTransformer(value, plan.byType.object);
794
+ if (byTypeResult !== void 0) return byTypeResult;
795
+ const byConstructorResult = applyFirstChangingTransformer(value, plan.byConstructor[supportedValueKind]);
796
+ if (byConstructorResult !== void 0) return byConstructorResult;
797
+ return applyFirstChangingTransformer(value, [...resolveCustomConstructorTransformers(value, plan) ?? [], ...plan.fallback]);
798
+ };
799
+ //#endregion
800
+ //#region src/core/diagnostics/sanitise-diagnostics.ts
801
+ const resolveObjectValueType = (value) => {
802
+ const constructorName = value.constructor?.name;
803
+ if (typeof constructorName === "string" && constructorName.length > 0 && constructorName !== "Object") return constructorName;
804
+ return "object";
805
+ };
806
+ const resolveDiagnosticValueType = (value, override) => {
807
+ if (override !== void 0) return override;
808
+ const supportedValueKind = resolveSupportedTransformableValueKind(value);
809
+ if (supportedValueKind !== void 0) return supportedValueKind;
810
+ if (Array.isArray(value)) return "array";
811
+ if (value === null) return "null";
812
+ if (typeof value === "object") return resolveObjectValueType(value);
813
+ return typeof value;
814
+ };
815
+ const resolveThrownType = (error) => {
816
+ if (error === null) return "null";
817
+ if (typeof error === "object") {
818
+ const constructorName = error.constructor?.name;
819
+ return typeof constructorName === "string" && constructorName.length > 0 ? constructorName : "object";
820
+ }
821
+ return typeof error;
822
+ };
823
+ const sanitiseDiagnosticDetails = (stage, error) => {
824
+ if (error instanceof Error) return Object.freeze({
825
+ errorName: error.name || "Error",
826
+ stage
827
+ });
828
+ return Object.freeze({
829
+ stage,
830
+ thrownType: resolveThrownType(error)
831
+ });
832
+ };
833
+ //#endregion
834
+ //#region src/core/diagnostics/diagnostic-event.ts
835
+ const unsupportedMessage = "Nested value could not be redacted safely and was replaced with [UNSUPPORTED].";
836
+ const createFallbackDiagnosticDetails = (stage) => {
837
+ return Object.freeze({
838
+ stage,
839
+ thrownType: "unknown"
840
+ });
841
+ };
842
+ const resolveFallbackValueType = (value, override) => {
843
+ if (override !== void 0) return override;
844
+ if (Array.isArray(value)) return "array";
845
+ if (value === null) return "null";
846
+ return typeof value === "object" ? "object" : typeof value;
847
+ };
848
+ const createFailureDiagnosticSnapshot = (input) => {
849
+ let details;
850
+ let valueType;
851
+ try {
852
+ details = sanitiseDiagnosticDetails(input.stage, input.error);
853
+ } catch {
854
+ details = createFallbackDiagnosticDetails(input.stage);
855
+ }
856
+ try {
857
+ valueType = resolveDiagnosticValueType(input.value, input.valueType);
858
+ } catch {
859
+ valueType = resolveFallbackValueType(input.value, input.valueType);
860
+ }
861
+ return Object.freeze({
862
+ details,
863
+ valueType
864
+ });
865
+ };
866
+ const createDiagnosticEvent = (plan, path, snapshot) => {
867
+ return Object.freeze({
868
+ details: snapshot.details,
869
+ event: plan.eventName,
870
+ message: unsupportedMessage,
871
+ path,
872
+ valueType: snapshot.valueType
873
+ });
874
+ };
875
+ //#endregion
876
+ //#region src/core/diagnostics/diagnostics-sink.ts
877
+ const emitDiagnosticEvent = (plan, event) => {
878
+ const sink = plan.sink ?? require_node_console_sink.getNodeConsoleDiagnosticSink();
879
+ if (sink === void 0) return;
880
+ try {
881
+ sink(event);
882
+ } catch {
883
+ return;
884
+ }
885
+ };
886
+ //#endregion
887
+ //#region src/core/runtime/redact-value.ts
888
+ const unsupportedValue$1 = "[UNSUPPORTED]";
889
+ const createCircularMarker = (path, value) => ({
890
+ _transformer: "circular",
891
+ path,
892
+ value
893
+ });
894
+ const isPlainObject$1 = (value) => {
895
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
896
+ const prototype = Object.getPrototypeOf(value);
897
+ return prototype === Object.prototype || prototype === null;
898
+ };
899
+ const isTraversableContainer = (value) => {
900
+ return Array.isArray(value) || isPlainObject$1(value);
901
+ };
902
+ const canRetainStructure = (value) => {
903
+ return isTraversableContainer(value) || isSupportedTransformableValue(value);
904
+ };
905
+ const hasLookupValue = (table, key) => {
906
+ return Object.hasOwn(table, key);
907
+ };
908
+ const setObjectEntry = (target, key, value) => {
909
+ if (key === "__proto__") Object.defineProperty(target, key, {
910
+ configurable: true,
911
+ enumerable: true,
912
+ value,
913
+ writable: true
914
+ });
915
+ else target[key] = value;
916
+ };
917
+ const findMatchingRegexKey = (matchers, key) => {
918
+ return matchers.find((rule) => {
919
+ rule.matcher.lastIndex = 0;
920
+ return rule.matcher.test(key);
921
+ });
922
+ };
923
+ const findMatchingLiteralKey = (literalMatchers, requiresCanonicalKey, key) => {
924
+ let canonicalKey;
925
+ for (const rule of literalMatchers) {
926
+ if (rule.matchMode === "exact" && key === rule.configuredKey) return rule;
927
+ if (rule.matchMode === "contains" && key.includes(rule.configuredKey)) return rule;
928
+ if (requiresCanonicalKey && (rule.matchMode === "canonical-exact" || rule.matchMode === "canonical-contains")) {
929
+ canonicalKey ??= canonicaliseKey(key);
930
+ if (rule.matchMode === "canonical-exact" && canonicalKey === rule.canonicalKey) return rule;
931
+ if (rule.matchMode === "canonical-contains" && canonicalKey.includes(rule.canonicalKey)) return rule;
932
+ }
933
+ }
934
+ };
935
+ const renderPathSegmentText = (pathSegment) => {
936
+ return pathSegment.kind === "index" ? String(pathSegment.value) : pathSegment.value;
937
+ };
938
+ const createTraversalState = (cycleRegistry) => {
939
+ return {
940
+ budget: createTraversalBudget(),
941
+ completedIdentities: /* @__PURE__ */ new WeakMap(),
942
+ completedSnapshots: /* @__PURE__ */ new WeakMap(),
943
+ cycleRegistry
944
+ };
945
+ };
946
+ const createTraversalBranchState = () => {
947
+ return { activePaths: /* @__PURE__ */ new WeakMap() };
948
+ };
949
+ const emitFailureDiagnostic = (plan, context, options) => {
950
+ const diagnostic = createFailureDiagnosticSnapshot({
951
+ error: options.error,
952
+ stage: options.stage,
953
+ value: options.value,
954
+ valueType: options.valueType
955
+ });
956
+ emitDiagnosticEvent(plan.diagnostics, createDiagnosticEvent(plan.diagnostics, context.canonicalPath ?? "", diagnostic));
957
+ return diagnostic;
958
+ };
959
+ const throwBudgetExceeded = (plan, context, kind) => {
960
+ const limit = kind === "depth" ? plan.maxDepth : plan.maxNodes;
961
+ emitDiagnosticEvent(plan.diagnostics, Object.freeze({
962
+ details: Object.freeze({
963
+ stage: "traversal",
964
+ kind,
965
+ limit
966
+ }),
967
+ event: "budget.exceeded",
968
+ message: kind === "depth" ? `Traversal depth limit (${limit}) exceeded.` : `Traversal node budget (${limit}) exceeded.`,
969
+ path: context.canonicalPath ?? "",
970
+ valueType: "unknown"
971
+ }));
972
+ throw createBudgetExceededError(kind, limit);
973
+ };
974
+ const createUnsupportedTraversalResult = () => {
975
+ return {
976
+ cacheValue: unsupportedValue$1,
977
+ changed: true,
978
+ pathStable: false,
979
+ value: unsupportedValue$1
980
+ };
981
+ };
982
+ const createFailureTraversalResult = (plan, context, options) => {
983
+ emitFailureDiagnostic(plan, context, options);
984
+ return createUnsupportedTraversalResult();
985
+ };
986
+ const renderRulePathSegment = (segment) => {
987
+ if (typeof segment === "string") return `property:${segment}`;
988
+ if (typeof segment === "number") return `index:${segment}`;
989
+ if (segment instanceof RegExp) return `regex:${segment.source}/${segment.flags}`;
990
+ if ("any" in segment && segment.any === true) return "wildcard:*";
991
+ if ("anyDepth" in segment && segment.anyDepth === true) return "wildcard:**";
992
+ if (!("ignore" in segment)) return "unknown";
993
+ const ignored = segment.ignore;
994
+ if (ignored instanceof RegExp) return `ignore-regex:${ignored.source}/${ignored.flags}`;
995
+ return typeof ignored === "number" ? `ignore-index:${ignored}` : `ignore-property:${ignored}`;
996
+ };
997
+ const buildRuleContextKey = (activePolicy) => {
998
+ if (activePolicy === void 0) return "none";
999
+ return `${activePolicy.source}:${activePolicy.rulePath.map((segment) => renderRulePathSegment(segment)).join("|")}`;
1000
+ };
1001
+ const usesPathSensitivePolicy = (activePolicy) => {
1002
+ return activePolicy?.source === "exact-path" || activePolicy?.source === "dynamic-path" || typeof activePolicy?.policy.censor === "function";
1003
+ };
1004
+ const resolveCompletedTraversal = (records, canonicalPath, ruleContextKey, value) => {
1005
+ const reusableRecord = records.find((record) => {
1006
+ return record.ruleContextKey === ruleContextKey && (record.pathStable || record.canonicalPath === canonicalPath);
1007
+ });
1008
+ if (reusableRecord !== void 0) return {
1009
+ cacheValue: reusableRecord.value,
1010
+ changed: reusableRecord.value !== value,
1011
+ pathStable: reusableRecord.pathStable,
1012
+ value: reusableRecord.value
1013
+ };
1014
+ };
1015
+ const storeCompletedTraversal = (state, value, record) => {
1016
+ const existingRecords = state.completedIdentities.get(value);
1017
+ if (existingRecords === void 0) {
1018
+ state.completedIdentities.set(value, [record]);
1019
+ return;
1020
+ }
1021
+ existingRecords.push(record);
1022
+ };
1023
+ const storeCompletedSnapshot = (state, value, snapshot) => {
1024
+ state.completedSnapshots.set(value, snapshot);
1025
+ };
1026
+ const withActiveIdentity = (branchState, value, canonicalPath, run) => {
1027
+ branchState.activePaths.set(value, canonicalPath);
1028
+ try {
1029
+ return run();
1030
+ } finally {
1031
+ branchState.activePaths.delete(value);
1032
+ }
1033
+ };
1034
+ const resolveDirectKeyMatch = (plan, key) => {
1035
+ const matchingLiteralRule = findMatchingLiteralKey(plan.exactKeyRules.literalMatchers, plan.exactKeyRules.requiresCanonicalKey, key);
1036
+ if (matchingLiteralRule !== void 0) return {
1037
+ source: "exact-key",
1038
+ rulePath: matchingLiteralRule.rulePath,
1039
+ policy: matchingLiteralRule.policy
1040
+ };
1041
+ const matchingRegexRule = findMatchingRegexKey(plan.regexKeyRules.matchers, key);
1042
+ if (matchingRegexRule !== void 0) return {
1043
+ source: "regex-key",
1044
+ rulePath: Object.freeze([matchingRegexRule.matcher]),
1045
+ policy: matchingRegexRule.policy
1046
+ };
1047
+ };
1048
+ const resolveExactPathRule = (plan, canonicalPath) => {
1049
+ if (canonicalPath === void 0) return;
1050
+ return hasLookupValue(plan.exactPathRules, canonicalPath) ? plan.exactPathRules[canonicalPath] : void 0;
1051
+ };
1052
+ const matchesSingleSegment = (selectorSegment, pathSegment) => {
1053
+ if (selectorSegment.kind === "wildcard") return true;
1054
+ if (selectorSegment.kind === "recursive-wildcard") return false;
1055
+ if (selectorSegment.kind === "ignore-index") return pathSegment.kind === "index" && pathSegment.value !== selectorSegment.value;
1056
+ if (selectorSegment.kind === "ignore-property") return pathSegment.kind === "property" && pathSegment.value !== selectorSegment.value;
1057
+ if (selectorSegment.kind === "regex") return selectorSegment.matcher.test(renderPathSegmentText(pathSegment));
1058
+ if (selectorSegment.kind === "ignore-regex") return !selectorSegment.matcher.test(renderPathSegmentText(pathSegment));
1059
+ if (selectorSegment.kind === "index") return pathSegment.kind === "index" && pathSegment.value === selectorSegment.value;
1060
+ return pathSegment.kind === "property" && pathSegment.value === selectorSegment.value;
1061
+ };
1062
+ const matchesDynamicRule = (selectorSegments, pathSegments, selectorIndex = 0, pathIndex = 0) => {
1063
+ if (selectorIndex >= selectorSegments.length) return pathIndex === pathSegments.length;
1064
+ const selectorSegment = selectorSegments[selectorIndex];
1065
+ if (selectorSegment.kind === "recursive-wildcard") {
1066
+ for (let nextPathIndex = pathIndex; nextPathIndex <= pathSegments.length; nextPathIndex += 1) if (matchesDynamicRule(selectorSegments, pathSegments, selectorIndex + 1, nextPathIndex)) return true;
1067
+ return false;
1068
+ }
1069
+ if (pathIndex >= pathSegments.length) return false;
1070
+ return matchesSingleSegment(selectorSegment, pathSegments[pathIndex]) && matchesDynamicRule(selectorSegments, pathSegments, selectorIndex + 1, pathIndex + 1);
1071
+ };
1072
+ const resolveDynamicPathRule = (plan, pathSegments) => {
1073
+ return plan.dynamicPathRules.find((rule) => matchesDynamicRule(rule.segments, pathSegments));
1074
+ };
1075
+ const selectActivePolicy = (plan, exactPathRule, dynamicPathRule, directKeyMatch, inheritedPolicy) => {
1076
+ if (exactPathRule !== void 0) return {
1077
+ policy: exactPathRule.policy,
1078
+ source: "exact-path",
1079
+ rulePath: exactPathRule.rulePath
1080
+ };
1081
+ if (dynamicPathRule !== void 0) return {
1082
+ policy: dynamicPathRule.policy,
1083
+ source: "dynamic-path",
1084
+ rulePath: dynamicPathRule.rulePath
1085
+ };
1086
+ if (inheritedPolicy?.source === "exact-path" || inheritedPolicy?.source === "dynamic-path" || inheritedPolicy?.source === "exact-key" || inheritedPolicy?.source === "regex-key") return inheritedPolicy;
1087
+ if (directKeyMatch?.source === "exact-key") return {
1088
+ policy: directKeyMatch.policy ?? plan.exactKeyRules.policy,
1089
+ source: "exact-key",
1090
+ rulePath: directKeyMatch.rulePath
1091
+ };
1092
+ if (directKeyMatch?.source === "regex-key") return {
1093
+ policy: directKeyMatch.policy ?? plan.regexKeyRules.policy,
1094
+ source: "regex-key",
1095
+ rulePath: directKeyMatch.rulePath
1096
+ };
1097
+ };
1098
+ const buildFunctionCensorContext = (pathSegments, rulePath, rootInput) => {
1099
+ const matchedPath = Object.freeze(pathSegments.map((seg) => seg.value));
1100
+ const rulePathCopy = Object.freeze([...rulePath]);
1101
+ const terminalKey = matchedPath.length > 0 ? matchedPath.at(-1) : void 0;
1102
+ return terminalKey === void 0 ? {
1103
+ matchedPath,
1104
+ rulePath: rulePathCopy,
1105
+ rootInput
1106
+ } : {
1107
+ matchedPath,
1108
+ rulePath: rulePathCopy,
1109
+ rootInput,
1110
+ terminalKey
1111
+ };
1112
+ };
1113
+ const applyConfiguredRedaction = (value, policy, rulePath, pathStable, plan, context) => {
1114
+ try {
1115
+ const redactedValue = applyRedaction(value, policy, buildFunctionCensorContext(context.pathSegments, rulePath, context.rootInput));
1116
+ return {
1117
+ cacheValue: redactedValue,
1118
+ changed: true,
1119
+ pathStable,
1120
+ value: redactedValue
1121
+ };
1122
+ } catch (error) {
1123
+ return createFailureTraversalResult(plan, context, {
1124
+ error,
1125
+ stage: "censor",
1126
+ value
1127
+ });
1128
+ }
1129
+ };
1130
+ const transformNestedNode = (value, plan, context, state, branchState) => {
1131
+ try {
1132
+ return transformNode(value, plan, context, state, branchState);
1133
+ } catch (error) {
1134
+ if (isBudgetExceededError(error)) throw error;
1135
+ return createFailureTraversalResult(plan, context, {
1136
+ error,
1137
+ stage: "traversal",
1138
+ value
1139
+ });
1140
+ }
1141
+ };
1142
+ const buildSubstringRulePath = (pattern) => {
1143
+ return Object.freeze([cloneRegExp(pattern)]);
1144
+ };
1145
+ const patternMatchesString = (pattern, value) => {
1146
+ pattern.lastIndex = 0;
1147
+ const matched = pattern.test(value);
1148
+ pattern.lastIndex = 0;
1149
+ return matched;
1150
+ };
1151
+ const applySubstringRule = (value, rule, plan, context) => {
1152
+ if (!patternMatchesString(rule.pattern, value)) return;
1153
+ if (rule.kind === "structured-replacer") try {
1154
+ const replacement = rule.replacer(value, cloneRegExp(rule.pattern));
1155
+ return {
1156
+ cacheValue: replacement,
1157
+ changed: replacement !== value,
1158
+ pathStable: true,
1159
+ value: replacement
1160
+ };
1161
+ } catch (error) {
1162
+ return createFailureTraversalResult(plan, context, {
1163
+ error,
1164
+ stage: "substring-replacer",
1165
+ value
1166
+ });
1167
+ }
1168
+ return applyConfiguredRedaction(value, rule.policy, buildSubstringRulePath(rule.pattern), typeof rule.policy.censor !== "function", plan, context);
1169
+ };
1170
+ const applyRootPrimitiveSubstringMatch = (value, rule, plan, context) => {
1171
+ if (!patternMatchesString(rule.pattern, value)) return;
1172
+ const policy = rule.kind === "whole-value" ? rule.policy : plan.defaults;
1173
+ return applyConfiguredRedaction(value, policy, buildSubstringRulePath(rule.pattern), typeof policy.censor !== "function", plan, context);
1174
+ };
1175
+ const transformSubstringValue = (value, plan, context) => {
1176
+ if (typeof value !== "string") return;
1177
+ const isRootInput = context.pathSegments.length === 0;
1178
+ for (const rule of plan.substringRules) {
1179
+ const result = isRootInput ? applyRootPrimitiveSubstringMatch(value, rule, plan, context) : applySubstringRule(value, rule, plan, context);
1180
+ if (result !== void 0) return result;
1181
+ }
1182
+ };
1183
+ const transformTrackedIdentity = (identity, plan, context, activePolicy, state, branchState, traverse) => {
1184
+ const canonicalPath = context.canonicalPath ?? "";
1185
+ const originalPath = branchState.activePaths.get(identity);
1186
+ if (originalPath !== void 0) {
1187
+ state.cycleRegistry?.set(identity, originalPath);
1188
+ if (plan.serialise) {
1189
+ const marker = createCircularMarker(canonicalPath, originalPath);
1190
+ return {
1191
+ cacheValue: marker,
1192
+ changed: true,
1193
+ pathStable: false,
1194
+ value: marker
1195
+ };
1196
+ }
1197
+ return {
1198
+ cacheValue: identity,
1199
+ changed: false,
1200
+ pathStable: true,
1201
+ value: identity
1202
+ };
1203
+ }
1204
+ const completedRecords = state.completedIdentities.get(identity);
1205
+ const ruleContextKey = activePolicy === void 0 ? "none" : buildRuleContextKey(activePolicy);
1206
+ const completedResult = completedRecords === void 0 ? void 0 : resolveCompletedTraversal(completedRecords, canonicalPath, ruleContextKey, identity);
1207
+ if (completedResult !== void 0) return completedResult;
1208
+ if (completedRecords !== void 0) {
1209
+ const snapshot = state.completedSnapshots.get(identity);
1210
+ if (snapshot !== void 0) return replayCompletedTraversal(identity, snapshot, plan, activePolicy, context, ruleContextKey, state, branchState);
1211
+ }
1212
+ state.budget.depth += 1;
1213
+ try {
1214
+ if (isDepthExceeded(state.budget, plan.maxDepth)) throwBudgetExceeded(plan, context, "depth");
1215
+ return withActiveIdentity(branchState, identity, canonicalPath, () => {
1216
+ const result = traverse();
1217
+ storeCompletedTraversal(state, identity, {
1218
+ canonicalPath,
1219
+ pathStable: result.pathStable,
1220
+ ruleContextKey,
1221
+ value: result.changed ? result.cacheValue : result.value
1222
+ });
1223
+ return result;
1224
+ });
1225
+ } finally {
1226
+ state.budget.depth -= 1;
1227
+ }
1228
+ };
1229
+ const transformArray = (value, plan, inheritedPolicy, canonicalPath, pathSegments, rootInput, suppressDescendantRedaction, state, branchState) => {
1230
+ const cacheValue = new Array(value.length);
1231
+ const transformedValue = new Array(value.length);
1232
+ const snapshotItems = new Array(value.length);
1233
+ const removedIndexes = [];
1234
+ let changed = false;
1235
+ let pathStable = true;
1236
+ for (let index = 0; index < value.length; index += 1) {
1237
+ if (!(index in value)) continue;
1238
+ const pathSegment = createIndexPathSegment(index);
1239
+ const itemPath = appendCanonicalPathSegment(canonicalPath, pathSegment);
1240
+ pathSegments.push(pathSegment);
1241
+ try {
1242
+ const itemContext = {
1243
+ canonicalPath: itemPath,
1244
+ inheritedPolicy,
1245
+ pathSegments,
1246
+ rootInput,
1247
+ suppressDescendantRedaction
1248
+ };
1249
+ let item;
1250
+ try {
1251
+ item = value[index];
1252
+ } catch (error) {
1253
+ snapshotItems[index] = {
1254
+ diagnostic: emitFailureDiagnostic(plan, itemContext, {
1255
+ error,
1256
+ stage: "traversal-read",
1257
+ value,
1258
+ valueType: "getter"
1259
+ }),
1260
+ present: true
1261
+ };
1262
+ const failureResult = createUnsupportedTraversalResult();
1263
+ cacheValue[index] = failureResult.cacheValue;
1264
+ transformedValue[index] = failureResult.value;
1265
+ changed = true;
1266
+ pathStable &&= failureResult.pathStable;
1267
+ continue;
1268
+ }
1269
+ snapshotItems[index] = {
1270
+ present: true,
1271
+ value: item
1272
+ };
1273
+ const itemResult = transformNestedNode(item, plan, itemContext, state, branchState);
1274
+ pathStable &&= itemResult.pathStable;
1275
+ if (isRemovedValue(itemResult.value)) {
1276
+ changed = true;
1277
+ removedIndexes.push(index);
1278
+ continue;
1279
+ }
1280
+ cacheValue[index] = itemResult.cacheValue;
1281
+ transformedValue[index] = itemResult.value;
1282
+ if (!itemResult.changed) continue;
1283
+ changed = true;
1284
+ } finally {
1285
+ pathSegments.pop();
1286
+ }
1287
+ }
1288
+ storeCompletedSnapshot(state, value, {
1289
+ items: snapshotItems,
1290
+ kind: "array"
1291
+ });
1292
+ const serialiseSnapshotOnly = !changed && Boolean(plan.serialise);
1293
+ if (!changed && !serialiseSnapshotOnly) return {
1294
+ cacheValue,
1295
+ changed: false,
1296
+ pathStable,
1297
+ value
1298
+ };
1299
+ if (removedIndexes.length === 0) return {
1300
+ cacheValue,
1301
+ changed: changed || serialiseSnapshotOnly,
1302
+ pathStable: serialiseSnapshotOnly ? false : pathStable,
1303
+ value: transformedValue
1304
+ };
1305
+ const compactedValue = transformedValue.slice();
1306
+ const compactedCacheValue = cacheValue.slice();
1307
+ let removedCount = 0;
1308
+ for (const removedIndex of removedIndexes) {
1309
+ compactedValue.splice(removedIndex - removedCount, 1);
1310
+ compactedCacheValue.splice(removedIndex - removedCount, 1);
1311
+ removedCount += 1;
1312
+ }
1313
+ return {
1314
+ cacheValue: compactedCacheValue,
1315
+ changed,
1316
+ pathStable,
1317
+ value: compactedValue
1318
+ };
1319
+ };
1320
+ const transformObject = (value, plan, inheritedPolicy, canonicalPath, pathSegments, rootInput, suppressDescendantRedaction, state, branchState) => {
1321
+ const cacheValue = {};
1322
+ const snapshotEntries = [];
1323
+ let changed = false;
1324
+ let pathStable = true;
1325
+ const transformedValue = {};
1326
+ let propertyKeys;
1327
+ try {
1328
+ propertyKeys = Object.keys(value);
1329
+ } catch (error) {
1330
+ return createFailureTraversalResult(plan, {
1331
+ canonicalPath,
1332
+ inheritedPolicy,
1333
+ pathSegments,
1334
+ rootInput,
1335
+ suppressDescendantRedaction
1336
+ }, {
1337
+ error,
1338
+ stage: "traversal-read",
1339
+ value
1340
+ });
1341
+ }
1342
+ for (const key of propertyKeys) {
1343
+ const pathSegment = createPropertyPathSegment(key);
1344
+ const propertyPath = appendCanonicalPathSegment(canonicalPath, pathSegment);
1345
+ pathSegments.push(pathSegment);
1346
+ try {
1347
+ const propertyContext = {
1348
+ canonicalPath: propertyPath,
1349
+ directKeyMatch: plan.exactKeyRules.literalMatchers.length === 0 && plan.regexKeyRules.matchers.length === 0 ? void 0 : resolveDirectKeyMatch(plan, key),
1350
+ inheritedPolicy,
1351
+ pathSegments,
1352
+ rootInput,
1353
+ suppressDescendantRedaction
1354
+ };
1355
+ let propertyValue;
1356
+ try {
1357
+ propertyValue = value[key];
1358
+ } catch (error) {
1359
+ const diagnostic = emitFailureDiagnostic(plan, propertyContext, {
1360
+ error,
1361
+ stage: "traversal-read",
1362
+ value,
1363
+ valueType: "getter"
1364
+ });
1365
+ snapshotEntries.push({
1366
+ diagnostic,
1367
+ key
1368
+ });
1369
+ const failureResult = createUnsupportedTraversalResult();
1370
+ setObjectEntry(cacheValue, key, failureResult.cacheValue);
1371
+ setObjectEntry(transformedValue, key, failureResult.value);
1372
+ changed = true;
1373
+ pathStable &&= failureResult.pathStable;
1374
+ continue;
1375
+ }
1376
+ snapshotEntries.push({
1377
+ key,
1378
+ value: propertyValue
1379
+ });
1380
+ const propertyResult = transformNestedNode(propertyValue, plan, propertyContext, state, branchState);
1381
+ pathStable &&= propertyResult.pathStable;
1382
+ if (isRemovedValue(propertyResult.value)) {
1383
+ changed = true;
1384
+ continue;
1385
+ }
1386
+ setObjectEntry(cacheValue, key, propertyResult.cacheValue);
1387
+ setObjectEntry(transformedValue, key, propertyResult.value);
1388
+ if (!propertyResult.changed) continue;
1389
+ changed = true;
1390
+ } finally {
1391
+ pathSegments.pop();
1392
+ }
1393
+ }
1394
+ storeCompletedSnapshot(state, value, {
1395
+ entries: snapshotEntries,
1396
+ kind: "object"
1397
+ });
1398
+ const serialiseSnapshotOnly = !changed && Boolean(plan.serialise);
1399
+ return {
1400
+ cacheValue,
1401
+ changed: changed || serialiseSnapshotOnly,
1402
+ pathStable: serialiseSnapshotOnly ? false : pathStable,
1403
+ value: changed || serialiseSnapshotOnly ? transformedValue : value
1404
+ };
1405
+ };
1406
+ const transformCompletedArray = (snapshot, plan, inheritedPolicy, canonicalPath, pathSegments, rootInput, suppressDescendantRedaction, state, branchState) => {
1407
+ const cacheValue = new Array(snapshot.items.length);
1408
+ const transformedValue = new Array(snapshot.items.length);
1409
+ const removedIndexes = [];
1410
+ let pathStable = true;
1411
+ for (let index = 0; index < snapshot.items.length; index += 1) {
1412
+ const itemSnapshot = snapshot.items[index];
1413
+ if (itemSnapshot === void 0 || !itemSnapshot.present) continue;
1414
+ const pathSegment = createIndexPathSegment(index);
1415
+ const itemPath = appendCanonicalPathSegment(canonicalPath, pathSegment);
1416
+ if (itemSnapshot.diagnostic !== void 0) {
1417
+ emitDiagnosticEvent(plan.diagnostics, createDiagnosticEvent(plan.diagnostics, itemPath, itemSnapshot.diagnostic));
1418
+ const failureResult = createUnsupportedTraversalResult();
1419
+ cacheValue[index] = failureResult.cacheValue;
1420
+ transformedValue[index] = failureResult.value;
1421
+ pathStable &&= failureResult.pathStable;
1422
+ continue;
1423
+ }
1424
+ pathSegments.push(pathSegment);
1425
+ try {
1426
+ const itemResult = transformNestedNode(itemSnapshot.value, plan, {
1427
+ canonicalPath: itemPath,
1428
+ inheritedPolicy,
1429
+ pathSegments,
1430
+ rootInput,
1431
+ suppressDescendantRedaction
1432
+ }, state, branchState);
1433
+ pathStable &&= itemResult.pathStable;
1434
+ if (isRemovedValue(itemResult.value)) {
1435
+ removedIndexes.push(index);
1436
+ continue;
1437
+ }
1438
+ cacheValue[index] = itemResult.cacheValue;
1439
+ transformedValue[index] = itemResult.value;
1440
+ } finally {
1441
+ pathSegments.pop();
1442
+ }
1443
+ }
1444
+ if (removedIndexes.length === 0) return {
1445
+ cacheValue,
1446
+ changed: true,
1447
+ pathStable,
1448
+ value: transformedValue
1449
+ };
1450
+ const compactedCacheValue = cacheValue.slice();
1451
+ const compactedValue = transformedValue.slice();
1452
+ let removedCount = 0;
1453
+ for (const removedIndex of removedIndexes) {
1454
+ compactedCacheValue.splice(removedIndex - removedCount, 1);
1455
+ compactedValue.splice(removedIndex - removedCount, 1);
1456
+ removedCount += 1;
1457
+ }
1458
+ return {
1459
+ cacheValue: compactedCacheValue,
1460
+ changed: true,
1461
+ pathStable,
1462
+ value: compactedValue
1463
+ };
1464
+ };
1465
+ const transformCompletedObject = (snapshot, plan, inheritedPolicy, canonicalPath, pathSegments, rootInput, suppressDescendantRedaction, state, branchState) => {
1466
+ const cacheValue = {};
1467
+ const transformedValue = {};
1468
+ let pathStable = true;
1469
+ for (const entry of snapshot.entries) {
1470
+ const pathSegment = createPropertyPathSegment(entry.key);
1471
+ const propertyPath = appendCanonicalPathSegment(canonicalPath, pathSegment);
1472
+ if (entry.diagnostic !== void 0) {
1473
+ emitDiagnosticEvent(plan.diagnostics, createDiagnosticEvent(plan.diagnostics, propertyPath, entry.diagnostic));
1474
+ const failureResult = createUnsupportedTraversalResult();
1475
+ setObjectEntry(cacheValue, entry.key, failureResult.cacheValue);
1476
+ setObjectEntry(transformedValue, entry.key, failureResult.value);
1477
+ pathStable &&= failureResult.pathStable;
1478
+ continue;
1479
+ }
1480
+ pathSegments.push(pathSegment);
1481
+ try {
1482
+ const propertyResult = transformNestedNode(entry.value, plan, {
1483
+ canonicalPath: propertyPath,
1484
+ directKeyMatch: plan.exactKeyRules.literalMatchers.length === 0 && plan.regexKeyRules.matchers.length === 0 ? void 0 : resolveDirectKeyMatch(plan, entry.key),
1485
+ inheritedPolicy,
1486
+ pathSegments,
1487
+ rootInput,
1488
+ suppressDescendantRedaction
1489
+ }, state, branchState);
1490
+ pathStable &&= propertyResult.pathStable;
1491
+ if (isRemovedValue(propertyResult.value)) continue;
1492
+ setObjectEntry(cacheValue, entry.key, propertyResult.cacheValue);
1493
+ setObjectEntry(transformedValue, entry.key, propertyResult.value);
1494
+ } finally {
1495
+ pathSegments.pop();
1496
+ }
1497
+ }
1498
+ return {
1499
+ cacheValue,
1500
+ changed: true,
1501
+ pathStable,
1502
+ value: transformedValue
1503
+ };
1504
+ };
1505
+ const replayCompletedTraversal = (value, snapshot, plan, inheritedPolicy, context, ruleContextKey, state, branchState) => {
1506
+ const canonicalPath = context.canonicalPath ?? "";
1507
+ const result = withActiveIdentity(branchState, value, canonicalPath, () => {
1508
+ return snapshot.kind === "array" ? transformCompletedArray(snapshot, plan, inheritedPolicy, context.canonicalPath, context.pathSegments, context.rootInput, context.suppressDescendantRedaction, state, branchState) : transformCompletedObject(snapshot, plan, inheritedPolicy, context.canonicalPath, context.pathSegments, context.rootInput, context.suppressDescendantRedaction, state, branchState);
1509
+ });
1510
+ storeCompletedTraversal(state, value, {
1511
+ canonicalPath,
1512
+ pathStable: result.pathStable,
1513
+ ruleContextKey,
1514
+ value: result.cacheValue
1515
+ });
1516
+ return result;
1517
+ };
1518
+ const transformNode = (value, plan, context, state, branchState) => {
1519
+ state.budget.nodesVisited += 1;
1520
+ if (isNodeBudgetExceeded(state.budget, plan.maxNodes)) throwBudgetExceeded(plan, context, "nodes");
1521
+ let activePolicy = context.suppressDescendantRedaction ? void 0 : selectActivePolicy(plan, resolveExactPathRule(plan, context.canonicalPath), plan.dynamicPathRules.length === 0 ? void 0 : resolveDynamicPathRule(plan, context.pathSegments), context.directKeyMatch, context.inheritedPolicy);
1522
+ if (activePolicy !== void 0 && (!activePolicy.policy.retainStructure || !canRetainStructure(value))) {
1523
+ if (isRedactableType(value, plan.valueTypes)) return applyConfiguredRedaction(value, activePolicy.policy, activePolicy.rulePath, !usesPathSensitivePolicy(activePolicy), plan, context);
1524
+ if (!isTraversableContainer(value)) return {
1525
+ cacheValue: value,
1526
+ changed: false,
1527
+ pathStable: true,
1528
+ value
1529
+ };
1530
+ activePolicy = context.inheritedPolicy;
1531
+ }
1532
+ if (!isTraversableContainer(value)) {
1533
+ const substringResult = context.suppressDescendantRedaction || plan.substringRules.length === 0 || !isRedactableType(value, plan.valueTypes) ? void 0 : transformSubstringValue(value, plan, context);
1534
+ if (substringResult !== void 0) return substringResult;
1535
+ return {
1536
+ cacheValue: value,
1537
+ changed: false,
1538
+ pathStable: true,
1539
+ value
1540
+ };
1541
+ }
1542
+ const inheritedPolicy = activePolicy;
1543
+ return transformTrackedIdentity(value, plan, context, activePolicy, state, branchState, () => {
1544
+ return Array.isArray(value) ? transformArray(value, plan, inheritedPolicy, context.canonicalPath, context.pathSegments, context.rootInput, context.suppressDescendantRedaction, state, branchState) : transformObject(value, plan, inheritedPolicy, context.canonicalPath, context.pathSegments, context.rootInput, context.suppressDescendantRedaction, state, branchState);
1545
+ });
1546
+ };
1547
+ const redactValue = (value, plan, cycleRegistry) => {
1548
+ const state = createTraversalState(cycleRegistry);
1549
+ const branchState = createTraversalBranchState();
1550
+ const result = transformNode(value, plan, {
1551
+ canonicalPath: void 0,
1552
+ inheritedPolicy: void 0,
1553
+ pathSegments: [],
1554
+ rootInput: value
1555
+ }, state, branchState);
1556
+ return isRemovedValue(result.value) ? void 0 : result.value;
1557
+ };
1558
+ //#endregion
1559
+ //#region src/core/runtime/navigate-exact-paths.ts
1560
+ const unsupportedValue = "[UNSUPPORTED]";
1561
+ const noContext = Object.freeze({
1562
+ matchedPath: Object.freeze([]),
1563
+ rootInput: void 0,
1564
+ rulePath: Object.freeze([])
1565
+ });
1566
+ const shallowCopyContainer = (container) => {
1567
+ if (Array.isArray(container)) {
1568
+ const copy = [];
1569
+ copy.length = container.length;
1570
+ for (let index = 0; index < container.length; index += 1) if (index in container) copy[index] = container[index];
1571
+ return copy;
1572
+ }
1573
+ return { ...container };
1574
+ };
1575
+ const appendMatchedKey = (matchedPath, key) => {
1576
+ return matchedPath === void 0 ? [key] : [...matchedPath, key];
1577
+ };
1578
+ const renderConcreteCanonicalPath = (matchedPath) => {
1579
+ let path;
1580
+ for (const key of matchedPath) path = appendCanonicalPathSegment(path, typeof key === "number" ? {
1581
+ kind: "index",
1582
+ value: key
1583
+ } : {
1584
+ kind: "property",
1585
+ value: key
1586
+ });
1587
+ return path ?? "";
1588
+ };
1589
+ const renderAncestorCopyMatchedPath = (matchedPath) => {
1590
+ if (matchedPath === void 0) return;
1591
+ return JSON.stringify(matchedPath.map((segment) => [typeof segment, segment]));
1592
+ };
1593
+ const getAncestorCopy = (ancestorCopies, source, level, matchedPath) => {
1594
+ const records = ancestorCopies.get(source);
1595
+ if (records === void 0) return;
1596
+ const matchedPathKey = renderAncestorCopyMatchedPath(matchedPath);
1597
+ return records.find((candidate) => {
1598
+ return candidate.level === level && candidate.matchedPathKey === matchedPathKey;
1599
+ })?.copy;
1600
+ };
1601
+ const storeAncestorCopy = (ancestorCopies, source, level, matchedPath, copy) => {
1602
+ const matchedPathKey = renderAncestorCopyMatchedPath(matchedPath);
1603
+ const records = ancestorCopies.get(source);
1604
+ if (records === void 0) {
1605
+ ancestorCopies.set(source, [{
1606
+ copy,
1607
+ level,
1608
+ matchedPathKey
1609
+ }]);
1610
+ return;
1611
+ }
1612
+ if (records.some((candidate) => candidate.level === level && candidate.matchedPathKey === matchedPathKey)) return;
1613
+ records.push({
1614
+ copy,
1615
+ level,
1616
+ matchedPathKey
1617
+ });
1618
+ };
1619
+ const insertRule = (root, segments, rule) => {
1620
+ let level = root;
1621
+ for (let index = 0; index < segments.length; index += 1) {
1622
+ const segment = segments[index];
1623
+ let node;
1624
+ switch (segment.kind) {
1625
+ case "index": {
1626
+ const map = level.indexChildren ??= /* @__PURE__ */ new Map();
1627
+ const existing = map.get(segment.value);
1628
+ if (existing === void 0) {
1629
+ node = {};
1630
+ map.set(segment.value, node);
1631
+ } else node = existing;
1632
+ break;
1633
+ }
1634
+ case "wildcard":
1635
+ node = level.wildcardChild ??= {};
1636
+ break;
1637
+ case "property": {
1638
+ const map = level.propertyChildren ??= /* @__PURE__ */ new Map();
1639
+ const existing = map.get(segment.value);
1640
+ if (existing === void 0) {
1641
+ node = {};
1642
+ map.set(segment.value, node);
1643
+ } else node = existing;
1644
+ break;
1645
+ }
1646
+ default: throw new TypeError(`Unsupported path segment kind "${segment.kind}" in rule-driven trie.`);
1647
+ }
1648
+ if (index === segments.length - 1) node.rule = rule;
1649
+ level = node;
1650
+ }
1651
+ };
1652
+ const markWildcardSubtrees = (node) => {
1653
+ let hasWildcard = node.wildcardChild !== void 0 || node.rule?.kind === "wildcard";
1654
+ if (node.wildcardChild !== void 0 && markWildcardSubtrees(node.wildcardChild)) hasWildcard = true;
1655
+ if (node.indexChildren !== void 0) {
1656
+ for (const child of node.indexChildren.values()) if (markWildcardSubtrees(child)) hasWildcard = true;
1657
+ }
1658
+ if (node.propertyChildren !== void 0) {
1659
+ for (const child of node.propertyChildren.values()) if (markWildcardSubtrees(child)) hasWildcard = true;
1660
+ }
1661
+ node.subtreeHasWildcard = hasWildcard;
1662
+ return hasWildcard;
1663
+ };
1664
+ const buildPrefixTree = (plan) => {
1665
+ const root = {};
1666
+ for (const rule of Object.values(plan.exactPathRules)) insertRule(root, rule.segments, {
1667
+ canonicalPath: rule.canonicalPath,
1668
+ kind: "exact",
1669
+ policy: rule.policy,
1670
+ rulePath: rule.rulePath
1671
+ });
1672
+ for (const rule of plan.dynamicPathRules) insertRule(root, rule.segments, {
1673
+ kind: "wildcard",
1674
+ policy: rule.policy,
1675
+ rulePath: rule.rulePath
1676
+ });
1677
+ markWildcardSubtrees(root);
1678
+ return root;
1679
+ };
1680
+ const emitCensorFailure = (plan, canonicalPath, value, error) => {
1681
+ emitDiagnosticEvent(plan.diagnostics, createDiagnosticEvent(plan.diagnostics, canonicalPath, createFailureDiagnosticSnapshot({
1682
+ error,
1683
+ stage: "censor",
1684
+ value
1685
+ })));
1686
+ };
1687
+ const applyTerminalRule = (value, rule, plan, rootInput) => {
1688
+ if (!isRedactableType(value, plan.valueTypes)) return value;
1689
+ try {
1690
+ if (typeof rule.policy.censor === "function") return applyRedaction(value, rule.policy, {
1691
+ matchedPath: rule.rulePath,
1692
+ rootInput,
1693
+ rulePath: rule.rulePath,
1694
+ terminalKey: rule.rulePath.at(-1)
1695
+ });
1696
+ return applyRedaction(value, rule.policy, noContext);
1697
+ } catch (error) {
1698
+ emitCensorFailure(plan, rule.canonicalPath, value, error);
1699
+ return unsupportedValue;
1700
+ }
1701
+ };
1702
+ const applyWildcardTerminalRule = (value, rule, plan, rootInput, matchedPath) => {
1703
+ if (!isRedactableType(value, plan.valueTypes)) return value;
1704
+ try {
1705
+ if (typeof rule.policy.censor === "function") return applyRedaction(value, rule.policy, {
1706
+ matchedPath: Object.freeze([...matchedPath]),
1707
+ rootInput,
1708
+ rulePath: rule.rulePath,
1709
+ terminalKey: matchedPath.at(-1)
1710
+ });
1711
+ return applyRedaction(value, rule.policy, noContext);
1712
+ } catch (error) {
1713
+ emitCensorFailure(plan, renderConcreteCanonicalPath(matchedPath), value, error);
1714
+ return unsupportedValue;
1715
+ }
1716
+ };
1717
+ const collectDescendedKeys = (chain) => {
1718
+ if (chain === void 0) return [];
1719
+ const keys = [];
1720
+ for (let node = chain; node !== void 0; node = node.parent) keys.push(node.key);
1721
+ keys.reverse();
1722
+ return keys;
1723
+ };
1724
+ const materialiseRetainMatchedPath = (inherited, leafKey) => {
1725
+ return [
1726
+ ...inherited.basePath,
1727
+ ...collectDescendedKeys(inherited.descended),
1728
+ leafKey
1729
+ ];
1730
+ };
1731
+ const materialiseRetainCanonical = (inherited, leafSegment) => {
1732
+ let path = inherited.baseCanonical;
1733
+ for (const key of collectDescendedKeys(inherited.descended)) path = appendCanonicalPathSegment(path, buildSegment(typeof key === "number" ? "index" : "property", key));
1734
+ return appendCanonicalPathSegment(path, leafSegment);
1735
+ };
1736
+ const applyInheritedLeaf = (value, inherited, segment, key, plan, rootInput) => {
1737
+ if (!isRedactableType(value, plan.valueTypes)) return value;
1738
+ try {
1739
+ if (typeof inherited.policy.censor === "function") return applyRedaction(value, inherited.policy, {
1740
+ matchedPath: materialiseRetainMatchedPath(inherited, key),
1741
+ rootInput,
1742
+ rulePath: inherited.rulePath,
1743
+ terminalKey: key
1744
+ });
1745
+ return applyRedaction(value, inherited.policy, noContext);
1746
+ } catch (error) {
1747
+ emitCensorFailure(plan, materialiseRetainCanonical(inherited, segment), value, error);
1748
+ return unsupportedValue;
1749
+ }
1750
+ };
1751
+ const enterRetain = (rule) => {
1752
+ return {
1753
+ basePath: rule.rulePath,
1754
+ baseCanonical: rule.canonicalPath,
1755
+ descended: void 0,
1756
+ policy: rule.policy,
1757
+ rulePath: rule.rulePath
1758
+ };
1759
+ };
1760
+ const enterRetainWildcard = (rule, matchedPath) => {
1761
+ return {
1762
+ basePath: matchedPath,
1763
+ baseCanonical: renderConcreteCanonicalPath(matchedPath),
1764
+ descended: void 0,
1765
+ policy: rule.policy,
1766
+ rulePath: rule.rulePath
1767
+ };
1768
+ };
1769
+ const descendRetain = (inherited, key) => {
1770
+ return {
1771
+ basePath: inherited.basePath,
1772
+ baseCanonical: inherited.baseCanonical,
1773
+ descended: {
1774
+ key,
1775
+ parent: inherited.descended
1776
+ },
1777
+ policy: inherited.policy,
1778
+ rulePath: inherited.rulePath
1779
+ };
1780
+ };
1781
+ const buildSegment = (kind, key) => {
1782
+ return kind === "index" ? {
1783
+ kind: "index",
1784
+ value: key
1785
+ } : {
1786
+ kind: "property",
1787
+ value: key
1788
+ };
1789
+ };
1790
+ const enterExactHop = (budget, plan) => {
1791
+ budget.depth += 1;
1792
+ budget.nodesVisited += 1;
1793
+ if (isDepthExceeded(budget, plan.maxDepth)) throw createBudgetExceededError("depth", plan.maxDepth);
1794
+ if (isNodeBudgetExceeded(budget, plan.maxNodes)) throw createBudgetExceededError("nodes", plan.maxNodes);
1795
+ };
1796
+ const leaveExactHop = (budget) => {
1797
+ budget.depth -= 1;
1798
+ };
1799
+ const resolveChildInherited = (node, inherited, key) => {
1800
+ if (node?.rule?.kind === "exact") return enterRetain(node.rule);
1801
+ if (inherited === void 0) return;
1802
+ return descendRetain(inherited, key);
1803
+ };
1804
+ const emptyLevel = {};
1805
+ const delegate = Symbol("deep-redact.rule-driven.delegate");
1806
+ const requiresDelegation = (value) => {
1807
+ return typeof value === "bigint" || value !== null && typeof value === "object";
1808
+ };
1809
+ const redactRetained = (container, level, inherited, plan, rootInput, budget) => {
1810
+ budget.depth += 1;
1811
+ if (isDepthExceeded(budget, plan.maxDepth)) throw createBudgetExceededError("depth", plan.maxDepth);
1812
+ try {
1813
+ if (level.wildcardChild !== void 0) return delegate;
1814
+ let copy;
1815
+ if (Array.isArray(container)) {
1816
+ const items = container;
1817
+ let removedIndices;
1818
+ for (let index = 0; index < items.length; index += 1) {
1819
+ if (!(index in items)) continue;
1820
+ budget.nodesVisited += 1;
1821
+ if (isNodeBudgetExceeded(budget, plan.maxNodes)) throw createBudgetExceededError("nodes", plan.maxNodes);
1822
+ const value = items[index];
1823
+ const node = level.indexChildren?.get(index);
1824
+ if (node?.rule?.kind === "wildcard") return delegate;
1825
+ if (node?.rule !== void 0 && !node.rule.policy.retainStructure) {
1826
+ if (isDescendable(value) && !isRedactableType(value, plan.valueTypes)) return delegate;
1827
+ const redacted = applyTerminalRule(value, node.rule, plan, rootInput);
1828
+ copy ??= shallowCopyContainer(container);
1829
+ if (isRemovedValue(redacted)) (removedIndices ??= []).push(index);
1830
+ else copy[index] = redacted;
1831
+ continue;
1832
+ }
1833
+ if (Array.isArray(value) || isPlainObject$1(value)) {
1834
+ const childInherited = resolveChildInherited(node, inherited, index);
1835
+ const child = redactRetained(value, node ?? emptyLevel, childInherited, plan, rootInput, budget);
1836
+ if (child === delegate) return delegate;
1837
+ if (child !== value) (copy ??= shallowCopyContainer(container))[index] = child;
1838
+ continue;
1839
+ }
1840
+ if (node?.rule !== void 0 || inherited !== void 0) {
1841
+ if (requiresDelegation(value)) return delegate;
1842
+ if (!isRedactableType(value, plan.valueTypes)) continue;
1843
+ const redacted = node?.rule === void 0 ? applyInheritedLeaf(value, inherited, buildSegment("index", index), index, plan, rootInput) : applyTerminalRule(value, node.rule, plan, rootInput);
1844
+ copy ??= shallowCopyContainer(container);
1845
+ if (isRemovedValue(redacted)) (removedIndices ??= []).push(index);
1846
+ else copy[index] = redacted;
1847
+ }
1848
+ }
1849
+ if (copy === void 0) return container;
1850
+ if (removedIndices !== void 0) {
1851
+ const compacted = copy;
1852
+ let removedCount = 0;
1853
+ for (const removedIndex of removedIndices) {
1854
+ compacted.splice(removedIndex - removedCount, 1);
1855
+ removedCount += 1;
1856
+ }
1857
+ }
1858
+ return copy;
1859
+ }
1860
+ for (const key of Object.keys(container)) {
1861
+ const node = level.propertyChildren?.get(key);
1862
+ budget.nodesVisited += 1;
1863
+ if (isNodeBudgetExceeded(budget, plan.maxNodes)) throw createBudgetExceededError("nodes", plan.maxNodes);
1864
+ const value = container[key];
1865
+ if (node?.rule?.kind === "wildcard") return delegate;
1866
+ if (node?.rule !== void 0 && !node.rule.policy.retainStructure) {
1867
+ if (isDescendable(value) && !isRedactableType(value, plan.valueTypes)) return delegate;
1868
+ const redacted = applyTerminalRule(value, node.rule, plan, rootInput);
1869
+ copy ??= shallowCopyContainer(container);
1870
+ if (isRemovedValue(redacted)) delete copy[key];
1871
+ else setObjectEntry(copy, key, redacted);
1872
+ continue;
1873
+ }
1874
+ if (Array.isArray(value) || isPlainObject$1(value)) {
1875
+ const childInherited = resolveChildInherited(node, inherited, key);
1876
+ const child = redactRetained(value, node ?? emptyLevel, childInherited, plan, rootInput, budget);
1877
+ if (child === delegate) return delegate;
1878
+ if (child !== value) setObjectEntry(copy ??= shallowCopyContainer(container), key, child);
1879
+ continue;
1880
+ }
1881
+ if (node?.rule !== void 0 || inherited !== void 0) {
1882
+ if (requiresDelegation(value)) return delegate;
1883
+ if (!isRedactableType(value, plan.valueTypes)) continue;
1884
+ const redacted = node?.rule === void 0 ? applyInheritedLeaf(value, inherited, buildSegment("property", key), key, plan, rootInput) : applyTerminalRule(value, node.rule, plan, rootInput);
1885
+ copy ??= shallowCopyContainer(container);
1886
+ if (isRemovedValue(redacted)) delete copy[key];
1887
+ else setObjectEntry(copy, key, redacted);
1888
+ }
1889
+ }
1890
+ return copy ?? container;
1891
+ } finally {
1892
+ budget.depth -= 1;
1893
+ }
1894
+ };
1895
+ const isDescendable = (value) => {
1896
+ return Array.isArray(value) || isPlainObject$1(value);
1897
+ };
1898
+ const resolveRetainTerminal = (value, childNode, plan, rootInput, ancestorCopies, budget) => {
1899
+ if (isDescendable(value)) {
1900
+ const existing = getAncestorCopy(ancestorCopies, value, childNode, void 0);
1901
+ if (existing !== void 0) return existing;
1902
+ const descended = redactRetained(value, childNode, enterRetain(childNode.rule), plan, rootInput, budget);
1903
+ if (descended === delegate) return delegate;
1904
+ if (descended !== value) storeAncestorCopy(ancestorCopies, value, childNode, void 0, descended);
1905
+ return descended;
1906
+ }
1907
+ if (requiresDelegation(value)) return delegate;
1908
+ return applyTerminalRule(value, childNode.rule, plan, rootInput);
1909
+ };
1910
+ const resolveRetainTerminalWildcard = (value, childNode, plan, rootInput, ancestorCopies, budget, matchedPath) => {
1911
+ const rule = childNode.rule;
1912
+ if (isDescendable(value)) {
1913
+ const existing = getAncestorCopy(ancestorCopies, value, childNode, matchedPath);
1914
+ if (existing !== void 0) return existing;
1915
+ const descended = redactRetained(value, childNode, enterRetainWildcard(rule, matchedPath), plan, rootInput, budget);
1916
+ if (descended === delegate) return delegate;
1917
+ if (descended !== value) storeAncestorCopy(ancestorCopies, value, childNode, matchedPath, descended);
1918
+ return descended;
1919
+ }
1920
+ if (requiresDelegation(value)) return delegate;
1921
+ return applyWildcardTerminalRule(value, rule, plan, rootInput, matchedPath);
1922
+ };
1923
+ const navigateNode = (container, level, plan, rootInput, ancestorCopies, compactedArrayCopies, budget, matchedPath) => {
1924
+ let copy = getAncestorCopy(ancestorCopies, container, level, matchedPath);
1925
+ let removedIndices;
1926
+ if (level.indexChildren !== void 0) for (const [index, childNode] of level.indexChildren) {
1927
+ if (!(index in container)) continue;
1928
+ enterExactHop(budget, plan);
1929
+ try {
1930
+ const value = container[index];
1931
+ if (childNode.rule?.kind === "wildcard") {
1932
+ const concreteMatchedPath = appendMatchedKey(matchedPath, index);
1933
+ if (childNode.rule.policy.retainStructure) {
1934
+ const retained = resolveRetainTerminalWildcard(value, childNode, plan, rootInput, ancestorCopies, budget, concreteMatchedPath);
1935
+ if (retained === delegate) return delegate;
1936
+ if (retained !== value) {
1937
+ if (copy === void 0) {
1938
+ copy = shallowCopyContainer(container);
1939
+ storeAncestorCopy(ancestorCopies, container, level, matchedPath, copy);
1940
+ }
1941
+ if (isRemovedValue(retained)) (removedIndices ??= []).push(index);
1942
+ else copy[index] = retained;
1943
+ }
1944
+ } else {
1945
+ if (isDescendable(value) && !isRedactableType(value, plan.valueTypes)) return delegate;
1946
+ const redacted = applyWildcardTerminalRule(value, childNode.rule, plan, rootInput, concreteMatchedPath);
1947
+ if (copy === void 0) {
1948
+ copy = shallowCopyContainer(container);
1949
+ storeAncestorCopy(ancestorCopies, container, level, matchedPath, copy);
1950
+ }
1951
+ if (isRemovedValue(redacted)) (removedIndices ??= []).push(index);
1952
+ else copy[index] = redacted;
1953
+ }
1954
+ continue;
1955
+ }
1956
+ if (childNode.rule !== void 0 && !childNode.rule.policy.retainStructure) {
1957
+ if (isDescendable(value) && !isRedactableType(value, plan.valueTypes)) return delegate;
1958
+ const redacted = applyTerminalRule(value, childNode.rule, plan, rootInput);
1959
+ if (copy === void 0) {
1960
+ copy = shallowCopyContainer(container);
1961
+ storeAncestorCopy(ancestorCopies, container, level, matchedPath, copy);
1962
+ }
1963
+ if (isRemovedValue(redacted)) (removedIndices ??= []).push(index);
1964
+ else copy[index] = redacted;
1965
+ continue;
1966
+ }
1967
+ if (childNode.rule !== void 0) {
1968
+ const retained = resolveRetainTerminal(value, childNode, plan, rootInput, ancestorCopies, budget);
1969
+ if (retained === delegate) return delegate;
1970
+ if (retained !== value) {
1971
+ if (copy === void 0) {
1972
+ copy = shallowCopyContainer(container);
1973
+ storeAncestorCopy(ancestorCopies, container, level, matchedPath, copy);
1974
+ }
1975
+ if (isRemovedValue(retained)) (removedIndices ??= []).push(index);
1976
+ else copy[index] = retained;
1977
+ }
1978
+ continue;
1979
+ }
1980
+ if (isDescendable(value)) {
1981
+ const child = navigateNode(value, childNode, plan, rootInput, ancestorCopies, compactedArrayCopies, budget, matchedPath !== void 0 || childNode.subtreeHasWildcard === true ? appendMatchedKey(matchedPath, index) : void 0);
1982
+ if (child === delegate) return delegate;
1983
+ if (child !== value) {
1984
+ if (copy === void 0) {
1985
+ copy = shallowCopyContainer(container);
1986
+ storeAncestorCopy(ancestorCopies, container, level, matchedPath, copy);
1987
+ }
1988
+ copy[index] = child;
1989
+ }
1990
+ continue;
1991
+ }
1992
+ if (value !== null && typeof value === "object") return delegate;
1993
+ } finally {
1994
+ leaveExactHop(budget);
1995
+ }
1996
+ }
1997
+ if (level.propertyChildren !== void 0) for (const [key, childNode] of level.propertyChildren) {
1998
+ if (!(key in container)) continue;
1999
+ enterExactHop(budget, plan);
2000
+ try {
2001
+ const value = container[key];
2002
+ if (childNode.rule?.kind === "wildcard") {
2003
+ const concreteMatchedPath = appendMatchedKey(matchedPath, key);
2004
+ if (childNode.rule.policy.retainStructure) {
2005
+ const retained = resolveRetainTerminalWildcard(value, childNode, plan, rootInput, ancestorCopies, budget, concreteMatchedPath);
2006
+ if (retained === delegate) return delegate;
2007
+ if (retained !== value) {
2008
+ if (copy === void 0) {
2009
+ copy = shallowCopyContainer(container);
2010
+ storeAncestorCopy(ancestorCopies, container, level, matchedPath, copy);
2011
+ }
2012
+ if (isRemovedValue(retained)) delete copy[key];
2013
+ else setObjectEntry(copy, key, retained);
2014
+ }
2015
+ } else {
2016
+ if (isDescendable(value) && !isRedactableType(value, plan.valueTypes)) return delegate;
2017
+ const redacted = applyWildcardTerminalRule(value, childNode.rule, plan, rootInput, concreteMatchedPath);
2018
+ if (copy === void 0) {
2019
+ copy = shallowCopyContainer(container);
2020
+ storeAncestorCopy(ancestorCopies, container, level, matchedPath, copy);
2021
+ }
2022
+ if (isRemovedValue(redacted)) delete copy[key];
2023
+ else setObjectEntry(copy, key, redacted);
2024
+ }
2025
+ continue;
2026
+ }
2027
+ if (childNode.rule !== void 0 && !childNode.rule.policy.retainStructure) {
2028
+ if (isDescendable(value) && !isRedactableType(value, plan.valueTypes)) return delegate;
2029
+ const redacted = applyTerminalRule(value, childNode.rule, plan, rootInput);
2030
+ if (copy === void 0) {
2031
+ copy = shallowCopyContainer(container);
2032
+ storeAncestorCopy(ancestorCopies, container, level, matchedPath, copy);
2033
+ }
2034
+ if (isRemovedValue(redacted)) delete copy[key];
2035
+ else setObjectEntry(copy, key, redacted);
2036
+ continue;
2037
+ }
2038
+ if (childNode.rule !== void 0) {
2039
+ const retained = resolveRetainTerminal(value, childNode, plan, rootInput, ancestorCopies, budget);
2040
+ if (retained === delegate) return delegate;
2041
+ if (retained !== value) {
2042
+ if (copy === void 0) {
2043
+ copy = shallowCopyContainer(container);
2044
+ storeAncestorCopy(ancestorCopies, container, level, matchedPath, copy);
2045
+ }
2046
+ if (isRemovedValue(retained)) delete copy[key];
2047
+ else setObjectEntry(copy, key, retained);
2048
+ }
2049
+ continue;
2050
+ }
2051
+ if (isDescendable(value)) {
2052
+ const child = navigateNode(value, childNode, plan, rootInput, ancestorCopies, compactedArrayCopies, budget, matchedPath !== void 0 || childNode.subtreeHasWildcard === true ? appendMatchedKey(matchedPath, key) : void 0);
2053
+ if (child === delegate) return delegate;
2054
+ if (child !== value) {
2055
+ if (copy === void 0) {
2056
+ copy = shallowCopyContainer(container);
2057
+ storeAncestorCopy(ancestorCopies, container, level, matchedPath, copy);
2058
+ }
2059
+ setObjectEntry(copy, key, child);
2060
+ }
2061
+ continue;
2062
+ }
2063
+ if (value !== null && typeof value === "object") return delegate;
2064
+ } finally {
2065
+ leaveExactHop(budget);
2066
+ }
2067
+ }
2068
+ if (level.wildcardChild !== void 0) {
2069
+ const wildcardChild = level.wildcardChild;
2070
+ if (Array.isArray(container)) {
2071
+ const items = container;
2072
+ for (let index = 0; index < items.length; index += 1) {
2073
+ if (!(index in items)) continue;
2074
+ if (level.indexChildren?.has(index)) continue;
2075
+ budget.nodesVisited += 1;
2076
+ if (isNodeBudgetExceeded(budget, plan.maxNodes)) throw createBudgetExceededError("nodes", plan.maxNodes);
2077
+ const value = items[index];
2078
+ const result = applyWildcardEdge(value, wildcardChild, appendMatchedKey(matchedPath, index), plan, rootInput, ancestorCopies, compactedArrayCopies, budget);
2079
+ if (result === delegate) return delegate;
2080
+ if (result !== value) {
2081
+ if (copy === void 0) {
2082
+ copy = shallowCopyContainer(container);
2083
+ storeAncestorCopy(ancestorCopies, container, level, matchedPath, copy);
2084
+ }
2085
+ if (isRemovedValue(result)) (removedIndices ??= []).push(index);
2086
+ else copy[index] = result;
2087
+ }
2088
+ }
2089
+ } else for (const key of Object.keys(container)) {
2090
+ if (level.propertyChildren?.has(key)) continue;
2091
+ budget.nodesVisited += 1;
2092
+ if (isNodeBudgetExceeded(budget, plan.maxNodes)) throw createBudgetExceededError("nodes", plan.maxNodes);
2093
+ const value = container[key];
2094
+ const result = applyWildcardEdge(value, wildcardChild, appendMatchedKey(matchedPath, key), plan, rootInput, ancestorCopies, compactedArrayCopies, budget);
2095
+ if (result === delegate) return delegate;
2096
+ if (result !== value) {
2097
+ if (copy === void 0) {
2098
+ copy = shallowCopyContainer(container);
2099
+ storeAncestorCopy(ancestorCopies, container, level, matchedPath, copy);
2100
+ }
2101
+ if (isRemovedValue(result)) delete copy[key];
2102
+ else setObjectEntry(copy, key, result);
2103
+ }
2104
+ }
2105
+ }
2106
+ if (copy === void 0) return container;
2107
+ if (removedIndices !== void 0 && Array.isArray(copy) && !compactedArrayCopies.has(copy)) {
2108
+ const compacted = copy;
2109
+ let removedCount = 0;
2110
+ for (const removedIndex of removedIndices.sort((a, b) => a - b)) {
2111
+ compacted.splice(removedIndex - removedCount, 1);
2112
+ removedCount += 1;
2113
+ }
2114
+ compactedArrayCopies.add(copy);
2115
+ }
2116
+ return copy;
2117
+ };
2118
+ const applyWildcardEdge = (value, wildcardChild, matchedPath, plan, rootInput, ancestorCopies, compactedArrayCopies, budget) => {
2119
+ if (wildcardChild.rule !== void 0) {
2120
+ if (!wildcardChild.rule.policy.retainStructure) {
2121
+ if (isDescendable(value) && !isRedactableType(value, plan.valueTypes)) return delegate;
2122
+ return applyWildcardTerminalRule(value, wildcardChild.rule, plan, rootInput, matchedPath);
2123
+ }
2124
+ return resolveRetainTerminalWildcard(value, wildcardChild, plan, rootInput, ancestorCopies, budget, matchedPath);
2125
+ }
2126
+ if (isDescendable(value)) return navigateNode(value, wildcardChild, plan, rootInput, ancestorCopies, compactedArrayCopies, budget, matchedPath);
2127
+ if (value !== null && typeof value === "object") return delegate;
2128
+ return value;
2129
+ };
2130
+ const buildPathDrivenExecutor = (plan, fallback) => {
2131
+ const root = buildPrefixTree(plan);
2132
+ return function pathDriven(input) {
2133
+ if (input === null || typeof input !== "object") return input;
2134
+ if (!(Array.isArray(input) || isPlainObject$1(input))) return fallback(input);
2135
+ const budget = createTraversalBudget();
2136
+ const compactedArrayCopies = /* @__PURE__ */ new Set();
2137
+ let result;
2138
+ try {
2139
+ result = navigateNode(input, root, plan, input, /* @__PURE__ */ new Map(), compactedArrayCopies, budget, void 0);
2140
+ } catch (error) {
2141
+ if (isBudgetExceededError(error)) throw error;
2142
+ return fallback(input);
2143
+ }
2144
+ return result === delegate ? fallback(input) : result;
2145
+ };
2146
+ };
2147
+ //#endregion
2148
+ //#region src/core/validation/validation-report.ts
2149
+ const formatValidationIssues = (issues) => {
2150
+ return issues.map((issue, index) => `${index + 1}. ${issue.path}: ${issue.message}`).join("\n");
2151
+ };
2152
+ var DeepRedactValidationError = class extends TypeError {
2153
+ issues;
2154
+ constructor(issues) {
2155
+ super(formatValidationIssues(issues));
2156
+ this.name = "DeepRedactValidationError";
2157
+ this.issues = Object.freeze([...issues]);
2158
+ }
2159
+ };
2160
+ const createValidationReport = (issues) => {
2161
+ return Object.freeze({
2162
+ valid: issues.length === 0,
2163
+ issues: Object.freeze([...issues])
2164
+ });
2165
+ };
2166
+ const assertValidConfig = (report) => {
2167
+ if (!report.valid) throw new DeepRedactValidationError(report.issues);
2168
+ };
2169
+ //#endregion
2170
+ //#region src/core/validation/validate-paths.ts
2171
+ const pushIssue$1 = (issues, path, message) => {
2172
+ issues.push({
2173
+ path,
2174
+ message
2175
+ });
2176
+ };
2177
+ const validateRegexPathSegments = (segments, path, issues) => {
2178
+ let valid = true;
2179
+ for (const segment of segments) {
2180
+ if (segment.kind !== "regex" && segment.kind !== "ignore-regex") continue;
2181
+ const unsupportedRegexMessage = getUnsupportedRegexMessage(segment.matcher, "Regex path segment");
2182
+ if (unsupportedRegexMessage !== void 0) {
2183
+ pushIssue$1(issues, path, unsupportedRegexMessage);
2184
+ valid = false;
2185
+ }
2186
+ }
2187
+ return valid;
2188
+ };
2189
+ const validatePathSelectors = (selectorCandidates, issues) => {
2190
+ const seenCanonicalPaths = /* @__PURE__ */ new Map();
2191
+ const seenDynamicSelectors = /* @__PURE__ */ new Map();
2192
+ for (const selectorCandidate of selectorCandidates) try {
2193
+ const parsedPath = parsePathSelector(selectorCandidate.selector);
2194
+ if (!validateRegexPathSegments(parsedPath.segments, selectorCandidate.configPath, issues)) continue;
2195
+ if (parsedPath.segments.some((segment) => isDynamicPathSegment(segment))) {
2196
+ const signature = renderSelectorSignature(parsedPath.segments);
2197
+ const previousDefinitionPath = seenDynamicSelectors.get(signature);
2198
+ if (previousDefinitionPath !== void 0) {
2199
+ pushIssue$1(issues, selectorCandidate.configPath, `Duplicate dynamic selector "${signature}" already defined at ${previousDefinitionPath}.`);
2200
+ continue;
2201
+ }
2202
+ seenDynamicSelectors.set(signature, selectorCandidate.configPath);
2203
+ continue;
2204
+ }
2205
+ const normalisedPath = normaliseParsedPath(parsedPath);
2206
+ const previousDefinitionPath = seenCanonicalPaths.get(normalisedPath.canonicalPath);
2207
+ if (previousDefinitionPath !== void 0) {
2208
+ pushIssue$1(issues, selectorCandidate.configPath, `Duplicate canonical selector "${normalisedPath.canonicalPath}" already defined at ${previousDefinitionPath}.`);
2209
+ continue;
2210
+ }
2211
+ seenCanonicalPaths.set(normalisedPath.canonicalPath, selectorCandidate.configPath);
2212
+ } catch (error) {
2213
+ pushIssue$1(issues, selectorCandidate.configPath, error instanceof Error ? error.message : "Invalid path selector.");
2214
+ }
2215
+ };
2216
+ //#endregion
2217
+ //#region src/core/validation/validate-config.ts
2218
+ const rootOptionNames = new Set([
2219
+ "caseSensitiveKeyMatch",
2220
+ "censor",
2221
+ "diagnostics",
2222
+ "fuzzyKeyMatch",
2223
+ "keys",
2224
+ "maxDepth",
2225
+ "maxNodes",
2226
+ "paths",
2227
+ "remove",
2228
+ "replaceStringByLength",
2229
+ "retainStructure",
2230
+ "ignoredValueTypes",
2231
+ "serialise",
2232
+ "stringTests",
2233
+ "transformers",
2234
+ "types"
2235
+ ]);
2236
+ const valueTypeNames = new Set([
2237
+ "string",
2238
+ "number",
2239
+ "bigint",
2240
+ "boolean",
2241
+ "object",
2242
+ "function",
2243
+ "symbol",
2244
+ "undefined"
2245
+ ]);
2246
+ const pathRuleOptionNames = new Set([
2247
+ "path",
2248
+ "censor",
2249
+ "remove",
2250
+ "replaceStringByLength",
2251
+ "retainStructure"
2252
+ ]);
2253
+ const substringRuleOptionNames = new Set(["pattern", "replacer"]);
2254
+ const keyRuleOptionNames = new Set([
2255
+ "caseSensitiveKeyMatch",
2256
+ "censor",
2257
+ "fuzzyKeyMatch",
2258
+ "key",
2259
+ "remove",
2260
+ "replaceStringByLength",
2261
+ "retainStructure"
2262
+ ]);
2263
+ const transformerOptionNames = new Set([
2264
+ "byType",
2265
+ "byConstructor",
2266
+ "fallback"
2267
+ ]);
2268
+ const transformerByTypeOptionNames = new Set(["bigint", "object"]);
2269
+ const transformerByConstructorOptionNames = new Set([
2270
+ "Date",
2271
+ "Error",
2272
+ "Map",
2273
+ "RegExp",
2274
+ "Set",
2275
+ "URL",
2276
+ "custom"
2277
+ ]);
2278
+ const ignoredValueTypeOptionNames = new Set([
2279
+ "bigint",
2280
+ "Date",
2281
+ "Error",
2282
+ "Map",
2283
+ "RegExp",
2284
+ "Set",
2285
+ "URL"
2286
+ ]);
2287
+ const diagnosticsOptionNames = new Set(["sink"]);
2288
+ const customConstructorRegistrationOptionNames = new Set(["constructor", "transformers"]);
2289
+ const disallowedCustomConstructors = new Set([
2290
+ Object,
2291
+ Array,
2292
+ Date,
2293
+ Error,
2294
+ Map,
2295
+ RegExp,
2296
+ Set,
2297
+ URL
2298
+ ]);
2299
+ const isPlainObject = (value) => {
2300
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
2301
+ const prototype = Object.getPrototypeOf(value);
2302
+ return prototype === Object.prototype || prototype === null;
2303
+ };
2304
+ const isPathSelector = (value) => {
2305
+ return typeof value === "string" || Array.isArray(value);
2306
+ };
2307
+ const pushIssue = (issues, path, message) => {
2308
+ issues.push({
2309
+ path,
2310
+ message
2311
+ });
2312
+ };
2313
+ const validateAllowedOptions = (value, allowedOptions, path, issues) => {
2314
+ for (const optionName of Object.keys(value)) if (!allowedOptions.has(optionName)) pushIssue(issues, path, `Unsupported option "${optionName}".`);
2315
+ };
2316
+ const validateBooleanOption = (value, path, optionName, issues) => {
2317
+ if (value !== void 0 && typeof value !== "boolean") pushIssue(issues, `${path}.${optionName}`, `${optionName} must be a boolean.`);
2318
+ };
2319
+ const validatePositiveIntegerOption = (value, path, optionName, issues) => {
2320
+ if (value === void 0) return;
2321
+ if (!Number.isInteger(value) || value < 1) pushIssue(issues, `${path}.${optionName}`, `${optionName} must be a positive integer.`);
2322
+ };
2323
+ const validateCensorOption = (value, path, issues) => {
2324
+ if (value !== void 0 && typeof value !== "string" && typeof value !== "function") pushIssue(issues, `${path}.censor`, "censor must be a string or function.");
2325
+ };
2326
+ const validateSerialiseOption = (value, path, issues) => {
2327
+ if (value !== void 0 && typeof value !== "boolean" && typeof value !== "function") {
2328
+ pushIssue(issues, `${path}.serialise`, "serialise must be a boolean or function.");
2329
+ return false;
2330
+ }
2331
+ return true;
2332
+ };
2333
+ const validateConflictingOptions = (value, path, issues) => {
2334
+ if (value.remove === true && value.censor !== void 0) pushIssue(issues, path, "remove cannot be combined with censor.");
2335
+ if (value.remove === true && value.retainStructure === true) pushIssue(issues, path, "remove cannot be combined with retainStructure.");
2336
+ if (value.remove === true && value.replaceStringByLength === true) pushIssue(issues, path, "remove cannot be combined with replaceStringByLength.");
2337
+ if (value.replaceStringByLength === true && value.censor === "") pushIssue(issues, path, "replaceStringByLength cannot be combined with an empty string censor.");
2338
+ };
2339
+ const regexLikeKeySelectorPattern = /^\/.+\/[A-Za-z]*$/;
2340
+ const getUnsupportedKeySelectorMessage = (selector) => {
2341
+ if (selector.startsWith("!")) return `Unsupported exclusion key selector "${selector}". Ignore selectors must use structured selector objects and are not supported in this configuration format.`;
2342
+ if (selector.includes("**")) return `Unsupported recursive wildcard key selector "${selector}".`;
2343
+ if (selector.includes("*")) return `Unsupported wildcard key selector "${selector}".`;
2344
+ if (regexLikeKeySelectorPattern.test(selector)) return `Unsupported regex-like key selector "${selector}".`;
2345
+ };
2346
+ const getUnsupportedKeyRegexMessage = (selector) => {
2347
+ return getUnsupportedRegexMessage(selector, "Regex key selector");
2348
+ };
2349
+ const validateLiteralKeySelector = (entry, path, issues) => {
2350
+ if (entry.length === 0) {
2351
+ pushIssue(issues, path, "key selectors must not be empty.");
2352
+ return;
2353
+ }
2354
+ const unsupportedSelectorMessage = getUnsupportedKeySelectorMessage(entry);
2355
+ if (unsupportedSelectorMessage !== void 0) pushIssue(issues, path, unsupportedSelectorMessage);
2356
+ };
2357
+ const validateKeyRule = (value, path, defaults, issues) => {
2358
+ validateAllowedOptions(value, keyRuleOptionNames, path, issues);
2359
+ if (isRegExp(value.key)) {
2360
+ const unsupportedRegexMessage = getUnsupportedKeyRegexMessage(value.key);
2361
+ if (unsupportedRegexMessage !== void 0) pushIssue(issues, `${path}.key`, unsupportedRegexMessage);
2362
+ } else if (typeof value.key !== "string") pushIssue(issues, `${path}.key`, "key must be a string or RegExp.");
2363
+ else if (value.key.length === 0) pushIssue(issues, `${path}.key`, "key must not be empty.");
2364
+ else {
2365
+ const unsupportedSelectorMessage = getUnsupportedKeySelectorMessage(value.key);
2366
+ if (unsupportedSelectorMessage !== void 0) pushIssue(issues, `${path}.key`, unsupportedSelectorMessage);
2367
+ }
2368
+ validateBooleanOption(value.fuzzyKeyMatch, path, "fuzzyKeyMatch", issues);
2369
+ validateBooleanOption(value.caseSensitiveKeyMatch, path, "caseSensitiveKeyMatch", issues);
2370
+ validateCensorOption(value.censor, path, issues);
2371
+ validateBooleanOption(value.remove, path, "remove", issues);
2372
+ validateBooleanOption(value.retainStructure, path, "retainStructure", issues);
2373
+ validateBooleanOption(value.replaceStringByLength, path, "replaceStringByLength", issues);
2374
+ const effectiveReplaceStringByLength = typeof value.replaceStringByLength === "boolean" ? value.replaceStringByLength : defaults.replaceStringByLength;
2375
+ validateConflictingOptions({
2376
+ censor: value.censor ?? defaults.censor,
2377
+ remove: value.remove ?? defaults.remove,
2378
+ retainStructure: value.retainStructure ?? defaults.retainStructure,
2379
+ replaceStringByLength: effectiveReplaceStringByLength
2380
+ }, path, issues);
2381
+ };
2382
+ const validateKeys = (value, path, defaults, issues) => {
2383
+ if (value === void 0) return;
2384
+ if (!Array.isArray(value)) {
2385
+ pushIssue(issues, path, "keys must be an array.");
2386
+ return;
2387
+ }
2388
+ for (const [index, entry] of value.entries()) {
2389
+ const entryPath = `${path}[${index}]`;
2390
+ if (isRegExp(entry)) {
2391
+ const unsupportedRegexMessage = getUnsupportedKeyRegexMessage(entry);
2392
+ if (unsupportedRegexMessage !== void 0) pushIssue(issues, entryPath, unsupportedRegexMessage);
2393
+ continue;
2394
+ }
2395
+ if (typeof entry === "string") {
2396
+ validateLiteralKeySelector(entry, entryPath, issues);
2397
+ continue;
2398
+ }
2399
+ if (isPlainObject(entry)) {
2400
+ validateKeyRule(entry, entryPath, defaults, issues);
2401
+ continue;
2402
+ }
2403
+ pushIssue(issues, entryPath, "key selectors must be strings or RegExp instances or key-rule objects.");
2404
+ }
2405
+ };
2406
+ const zeroLengthProbeValues = Object.freeze([
2407
+ "",
2408
+ "a",
2409
+ "safe",
2410
+ "secret",
2411
+ "token=secret",
2412
+ "api-key=secret",
2413
+ "prefix-secret-suffix"
2414
+ ]);
2415
+ const patternCanMatchZeroLength = (pattern) => {
2416
+ const matcher = new RegExp(pattern.source, pattern.flags);
2417
+ return zeroLengthProbeValues.some((probe) => {
2418
+ matcher.lastIndex = 0;
2419
+ const match = matcher.exec(probe);
2420
+ matcher.lastIndex = 0;
2421
+ return match?.[0] === "";
2422
+ });
2423
+ };
2424
+ const validateSubstringPattern = (pattern, path, issues) => {
2425
+ const unsupportedRegexMessage = getUnsupportedRegexMessage(pattern, "Substring rule pattern", { allowGlobal: true });
2426
+ if (unsupportedRegexMessage !== void 0) pushIssue(issues, path, unsupportedRegexMessage);
2427
+ if (patternCanMatchZeroLength(pattern)) pushIssue(issues, path, "Substring rule pattern must not match zero-length strings.");
2428
+ };
2429
+ const validateStringTests = (value, path, issues) => {
2430
+ if (value === void 0) return;
2431
+ if (!Array.isArray(value)) {
2432
+ pushIssue(issues, path, "stringTests must be an array.");
2433
+ return;
2434
+ }
2435
+ for (const [index, entry] of value.entries()) {
2436
+ const entryPath = `${path}[${index}]`;
2437
+ if (isRegExp(entry)) {
2438
+ validateSubstringPattern(entry, entryPath, issues);
2439
+ continue;
2440
+ }
2441
+ if (!isPlainObject(entry)) {
2442
+ pushIssue(issues, entryPath, "string test entries must be RegExp instances or substring rule objects.");
2443
+ continue;
2444
+ }
2445
+ validateAllowedOptions(entry, substringRuleOptionNames, entryPath, issues);
2446
+ if (isRegExp(entry.pattern)) validateSubstringPattern(entry.pattern, `${entryPath}.pattern`, issues);
2447
+ else pushIssue(issues, `${entryPath}.pattern`, "pattern must be a RegExp instance.");
2448
+ if (typeof entry.replacer !== "function") pushIssue(issues, `${entryPath}.replacer`, "replacer must be a function.");
2449
+ }
2450
+ };
2451
+ const validateTransformerEntries = (value, path, issues) => {
2452
+ if (value === void 0) return;
2453
+ if (!Array.isArray(value)) {
2454
+ pushIssue(issues, path, `${path.split(".").at(-1) ?? "transformers"} must be an array.`);
2455
+ return;
2456
+ }
2457
+ for (const [index, entry] of value.entries()) if (typeof entry !== "function") pushIssue(issues, `${path}[${index}]`, "Transformer entries must be functions.");
2458
+ };
2459
+ const isConstructable = (value) => {
2460
+ if (typeof value !== "function") return false;
2461
+ try {
2462
+ Reflect.construct(Object, [], value);
2463
+ return true;
2464
+ } catch {
2465
+ return false;
2466
+ }
2467
+ };
2468
+ const isArraySubclassConstructor = (value) => {
2469
+ const prototype = value.prototype;
2470
+ return typeof prototype === "object" && prototype !== null && Object.prototype.isPrototypeOf.call(Array.prototype, prototype);
2471
+ };
2472
+ const validateCustomConstructorRegistrations = (value, path, issues) => {
2473
+ if (value === void 0) return;
2474
+ if (!Array.isArray(value)) {
2475
+ pushIssue(issues, path, "custom must be an array.");
2476
+ return;
2477
+ }
2478
+ const seenConstructors = /* @__PURE__ */ new Set();
2479
+ for (const [index, registration] of value.entries()) {
2480
+ const registrationPath = `${path}[${index}]`;
2481
+ if (!isPlainObject(registration)) {
2482
+ pushIssue(issues, registrationPath, "Custom constructor registrations must be objects.");
2483
+ continue;
2484
+ }
2485
+ validateAllowedOptions(registration, customConstructorRegistrationOptionNames, registrationPath, issues);
2486
+ const constructor = Object.hasOwn(registration, "constructor") ? registration.constructor : void 0;
2487
+ if (!isConstructable(constructor)) pushIssue(issues, `${registrationPath}.constructor`, "Custom constructor must be constructable.");
2488
+ else if (disallowedCustomConstructors.has(constructor)) pushIssue(issues, `${registrationPath}.constructor`, "Custom constructor must not be Object, Array, or a built-in constructor.");
2489
+ else if (constructor === Function || isArraySubclassConstructor(constructor)) pushIssue(issues, `${registrationPath}.constructor`, "Custom constructor must not be Object, Array, or a built-in constructor; Function and Array subclasses are also unsupported.");
2490
+ else if (seenConstructors.has(constructor)) pushIssue(issues, `${registrationPath}.constructor`, "Custom constructor registrations must not repeat the same constructor.");
2491
+ else seenConstructors.add(constructor);
2492
+ if (Array.isArray(registration.transformers)) validateTransformerEntries(registration.transformers, `${registrationPath}.transformers`, issues);
2493
+ else pushIssue(issues, `${registrationPath}.transformers`, "transformers must be an array.");
2494
+ }
2495
+ };
2496
+ const validateTransformerBuckets = (value, path, allowedOptions, issues) => {
2497
+ if (value === void 0) return;
2498
+ if (!isPlainObject(value)) {
2499
+ pushIssue(issues, path, `${path.split(".").at(-1) ?? "bucket"} must be an object.`);
2500
+ return;
2501
+ }
2502
+ validateAllowedOptions(value, allowedOptions, path, issues);
2503
+ if (allowedOptions.has("custom")) validateCustomConstructorRegistrations(value.custom, `${path}.custom`, issues);
2504
+ for (const [bucketName, entries] of Object.entries(value)) {
2505
+ if (bucketName === "custom") continue;
2506
+ if (!allowedOptions.has(bucketName)) continue;
2507
+ validateTransformerEntries(entries, `${path}.${bucketName}`, issues);
2508
+ }
2509
+ };
2510
+ const validateTransformers = (value, path, issues) => {
2511
+ if (value === void 0) return;
2512
+ if (!isPlainObject(value)) {
2513
+ pushIssue(issues, path, "transformers must be an object.");
2514
+ return;
2515
+ }
2516
+ validateAllowedOptions(value, transformerOptionNames, path, issues);
2517
+ validateTransformerBuckets(value.byType, `${path}.byType`, transformerByTypeOptionNames, issues);
2518
+ validateTransformerBuckets(value.byConstructor, `${path}.byConstructor`, transformerByConstructorOptionNames, issues);
2519
+ validateTransformerEntries(value.fallback, `${path}.fallback`, issues);
2520
+ };
2521
+ const validateIgnoredValueTypes = (value, path, issues) => {
2522
+ if (value === void 0) return;
2523
+ if (!isPlainObject(value)) {
2524
+ pushIssue(issues, path, "ignoredValueTypes must be an object.");
2525
+ return;
2526
+ }
2527
+ validateAllowedOptions(value, ignoredValueTypeOptionNames, path, issues);
2528
+ for (const optionName of Object.keys(value)) {
2529
+ if (!ignoredValueTypeOptionNames.has(optionName)) continue;
2530
+ validateBooleanOption(value[optionName], path, optionName, issues);
2531
+ }
2532
+ };
2533
+ const validateValueTypes = (value, path, issues) => {
2534
+ if (value === void 0) return;
2535
+ if (!Array.isArray(value)) {
2536
+ pushIssue(issues, path, "types must be an array of value-type names.");
2537
+ return;
2538
+ }
2539
+ for (const [index, entry] of value.entries()) if (typeof entry !== "string" || !valueTypeNames.has(entry)) pushIssue(issues, `${path}[${index}]`, `Unsupported value type name "${String(entry)}". Supported names: ${[...valueTypeNames].join(", ")}.`);
2540
+ };
2541
+ const validateDiagnostics = (value, path, issues) => {
2542
+ if (value === void 0) return;
2543
+ if (!isPlainObject(value)) {
2544
+ pushIssue(issues, path, "diagnostics must be an object.");
2545
+ return;
2546
+ }
2547
+ validateAllowedOptions(value, diagnosticsOptionNames, path, issues);
2548
+ if (value.sink !== void 0 && typeof value.sink !== "function") pushIssue(issues, `${path}.sink`, "sink must be a function.");
2549
+ };
2550
+ const validatePathRule = (value, path, defaults, issues, selectorCandidates) => {
2551
+ if (!isPlainObject(value)) {
2552
+ pushIssue(issues, path, `${path.split(".").at(-1) ?? "entry"} must be a string selector or path-rule object.`);
2553
+ return;
2554
+ }
2555
+ validateAllowedOptions(value, pathRuleOptionNames, path, issues);
2556
+ if (isPathSelector(value.path)) selectorCandidates.push({
2557
+ configPath: `${path}.path`,
2558
+ selector: value.path
2559
+ });
2560
+ else pushIssue(issues, `${path}.path`, "path must be a string or structured selector array.");
2561
+ validateCensorOption(value.censor, path, issues);
2562
+ validateBooleanOption(value.remove, path, "remove", issues);
2563
+ validateBooleanOption(value.retainStructure, path, "retainStructure", issues);
2564
+ validateBooleanOption(value.replaceStringByLength, path, "replaceStringByLength", issues);
2565
+ const effectiveReplaceStringByLength = typeof value.replaceStringByLength === "boolean" ? value.replaceStringByLength : defaults.replaceStringByLength;
2566
+ validateConflictingOptions({
2567
+ censor: value.censor ?? defaults.censor,
2568
+ remove: value.remove ?? defaults.remove,
2569
+ retainStructure: value.retainStructure ?? defaults.retainStructure,
2570
+ replaceStringByLength: effectiveReplaceStringByLength
2571
+ }, path, issues);
2572
+ };
2573
+ const validatePaths = (value, path, defaults, issues, selectorCandidates) => {
2574
+ if (value === void 0) return;
2575
+ if (!Array.isArray(value)) {
2576
+ pushIssue(issues, path, "paths must be an array.");
2577
+ return;
2578
+ }
2579
+ for (const [index, entry] of value.entries()) {
2580
+ const entryPath = `${path}[${index}]`;
2581
+ if (isPathSelector(entry)) {
2582
+ selectorCandidates.push({
2583
+ configPath: entryPath,
2584
+ selector: entry
2585
+ });
2586
+ continue;
2587
+ }
2588
+ if (!isPlainObject(entry)) {
2589
+ pushIssue(issues, entryPath, `${entryPath.split(".").at(-1) ?? "entry"} must be a string selector or path-rule object.`);
2590
+ continue;
2591
+ }
2592
+ validatePathRule(entry, entryPath, defaults, issues, selectorCandidates);
2593
+ }
2594
+ };
2595
+ const validateConfig = (options) => {
2596
+ const issues = [];
2597
+ const selectorCandidates = [];
2598
+ if (options === void 0) return createValidationReport(issues);
2599
+ if (!isPlainObject(options)) {
2600
+ pushIssue(issues, "options", "options must be an object.");
2601
+ return createValidationReport(issues);
2602
+ }
2603
+ const rootDefaults = {
2604
+ censor: options.censor,
2605
+ remove: options.remove === true,
2606
+ retainStructure: options.retainStructure === true,
2607
+ replaceStringByLength: options.replaceStringByLength === true
2608
+ };
2609
+ validateAllowedOptions(options, rootOptionNames, "options", issues);
2610
+ validateBooleanOption(options.caseSensitiveKeyMatch, "options", "caseSensitiveKeyMatch", issues);
2611
+ validateCensorOption(options.censor, "options", issues);
2612
+ validateDiagnostics(options.diagnostics, "options.diagnostics", issues);
2613
+ validateBooleanOption(options.fuzzyKeyMatch, "options", "fuzzyKeyMatch", issues);
2614
+ validateIgnoredValueTypes(options.ignoredValueTypes, "options.ignoredValueTypes", issues);
2615
+ validateValueTypes(options.types, "options.types", issues);
2616
+ validateKeys(options.keys, "options.keys", rootDefaults, issues);
2617
+ validateStringTests(options.stringTests, "options.stringTests", issues);
2618
+ validateTransformers(options.transformers, "options.transformers", issues);
2619
+ validatePositiveIntegerOption(options.maxDepth, "options", "maxDepth", issues);
2620
+ validatePositiveIntegerOption(options.maxNodes, "options", "maxNodes", issues);
2621
+ validateBooleanOption(options.remove, "options", "remove", issues);
2622
+ validateBooleanOption(options.retainStructure, "options", "retainStructure", issues);
2623
+ validateBooleanOption(options.replaceStringByLength, "options", "replaceStringByLength", issues);
2624
+ validateSerialiseOption(options.serialise, "options", issues);
2625
+ validateConflictingOptions(rootDefaults, "options", issues);
2626
+ validatePaths(options.paths, "options.paths", rootDefaults, issues, selectorCandidates);
2627
+ validatePathSelectors(selectorCandidates, issues);
2628
+ return createValidationReport(issues);
2629
+ };
2630
+ //#endregion
2631
+ //#region src/core/replacement/serialise-output.ts
2632
+ const bareIdentifierPattern = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
2633
+ const MAX_SERIALISE_DEPTH = 3e3;
2634
+ const NO_INDEX = -1;
2635
+ const buildObjectChildPath = (parentPath, key) => {
2636
+ if (!bareIdentifierPattern.test(key)) return `${parentPath ?? ""}["${key.replaceAll("\\", "\\\\").replaceAll("\"", String.raw`\"`)}"]`;
2637
+ return parentPath === void 0 ? key : `${parentPath}.${key}`;
2638
+ };
2639
+ const buildArrayChildPath = (parentPath, index) => {
2640
+ return parentPath === void 0 ? String(index) : `${parentPath}.${index}`;
2641
+ };
2642
+ const isStrictDescendantPath = (ancestor, path) => {
2643
+ if (path === ancestor) return false;
2644
+ if (ancestor === "") return true;
2645
+ return path.startsWith(`${ancestor}.`) || path.startsWith(`${ancestor}[`);
2646
+ };
2647
+ const materialiseFramePath = (frame) => {
2648
+ const chain = [];
2649
+ for (let node = frame; node !== void 0; node = node.parent) chain.push(node);
2650
+ let path;
2651
+ for (let i = chain.length - 1; i >= 0; i -= 1) {
2652
+ const node = chain[i];
2653
+ if (node.index >= 0) path = buildArrayChildPath(path, node.index);
2654
+ else if (node.key !== void 0) path = buildObjectChildPath(path, node.key);
2655
+ }
2656
+ return path;
2657
+ };
2658
+ const materialiseStepPath = (parentFrame, key, index) => {
2659
+ const parentPath = materialiseFramePath(parentFrame);
2660
+ if (index >= 0) return buildArrayChildPath(parentPath, index);
2661
+ if (key !== void 0) return buildObjectChildPath(parentPath, key);
2662
+ return parentPath;
2663
+ };
2664
+ const materialiseAncestorPath = (frame, identity) => {
2665
+ for (let node = frame; node !== void 0; node = node.parent) if (node.identity === identity) return materialiseFramePath(node);
2666
+ };
2667
+ const buildSafeGraph = (value, transformers, seen, parentFrame, stepKey, stepIndex, cycleRegistry, depth) => {
2668
+ if (depth > MAX_SERIALISE_DEPTH) return "[UNSUPPORTED]";
2669
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === void 0) return value;
2670
+ if (typeof value === "function" || typeof value === "symbol") return "[UNSUPPORTED]";
2671
+ const supportedKind = resolveSupportedTransformableValueKind(value);
2672
+ if (supportedKind !== void 0) {
2673
+ const runtimeIdentity = supportedKind === "bigint" ? void 0 : value;
2674
+ if (runtimeIdentity !== void 0 && seen.has(runtimeIdentity)) return {
2675
+ _transformer: "circular",
2676
+ path: materialiseStepPath(parentFrame, stepKey, stepIndex) ?? "",
2677
+ value: materialiseAncestorPath(parentFrame, runtimeIdentity) ?? ""
2678
+ };
2679
+ const frame = {
2680
+ parent: parentFrame,
2681
+ key: stepKey,
2682
+ index: stepIndex,
2683
+ identity: runtimeIdentity
2684
+ };
2685
+ if (runtimeIdentity !== void 0) seen.add(runtimeIdentity);
2686
+ try {
2687
+ const transformed = resolveTransformedValue(value, transformers);
2688
+ if (transformed === void 0) return "[UNSUPPORTED]";
2689
+ return buildTransformedGraph(transformed, transformers, seen, frame, cycleRegistry, depth + 1);
2690
+ } catch {
2691
+ return "[UNSUPPORTED]";
2692
+ } finally {
2693
+ if (runtimeIdentity !== void 0) seen.delete(runtimeIdentity);
2694
+ }
2695
+ }
2696
+ const identity = value;
2697
+ if (seen.has(identity)) return {
2698
+ _transformer: "circular",
2699
+ path: materialiseStepPath(parentFrame, stepKey, stepIndex) ?? "",
2700
+ value: materialiseAncestorPath(parentFrame, identity) ?? ""
2701
+ };
2702
+ if (cycleRegistry?.has(identity)) {
2703
+ const registryPath = cycleRegistry.get(identity);
2704
+ const currentPath = materialiseStepPath(parentFrame, stepKey, stepIndex) ?? "";
2705
+ if (isStrictDescendantPath(registryPath, currentPath)) return {
2706
+ _transformer: "circular",
2707
+ path: currentPath,
2708
+ value: registryPath
2709
+ };
2710
+ }
2711
+ const frame = {
2712
+ parent: parentFrame,
2713
+ key: stepKey,
2714
+ index: stepIndex,
2715
+ identity
2716
+ };
2717
+ seen.add(identity);
2718
+ try {
2719
+ if (Array.isArray(value)) {
2720
+ const result = [];
2721
+ result.length = value.length;
2722
+ for (let index = 0; index < value.length; index += 1) {
2723
+ if (!(index in value)) continue;
2724
+ result[index] = buildSafeGraph(value[index], transformers, seen, frame, void 0, index, cycleRegistry, depth + 1);
2725
+ }
2726
+ return result;
2727
+ }
2728
+ if (isPlainObject$1(value)) {
2729
+ const result = {};
2730
+ for (const key of Object.keys(value)) result[key] = buildSafeGraph(value[key], transformers, seen, frame, key, NO_INDEX, cycleRegistry, depth + 1);
2731
+ return result;
2732
+ }
2733
+ try {
2734
+ const transformed = resolveTransformedValue(value, transformers);
2735
+ if (transformed !== void 0) return buildTransformedGraph(transformed, transformers, seen, frame, cycleRegistry, depth + 1);
2736
+ } catch {
2737
+ return "[UNSUPPORTED]";
2738
+ }
2739
+ return "[UNSUPPORTED]";
2740
+ } finally {
2741
+ seen.delete(identity);
2742
+ }
2743
+ };
2744
+ const buildTransformedGraph = (transformed, transformers, seen, containerFrame, cycleRegistry, depth) => {
2745
+ if (isPlainObject$1(transformed) && typeof transformed._transformer === "string" && "value" in transformed) {
2746
+ const wrapper = transformed;
2747
+ const result = {};
2748
+ for (const key of Object.keys(wrapper)) result[key] = key === "value" ? buildSafeGraph(wrapper.value, transformers, seen, containerFrame, void 0, NO_INDEX, cycleRegistry, depth + 1) : buildSafeGraph(wrapper[key], transformers, seen, containerFrame, key, NO_INDEX, cycleRegistry, depth + 1);
2749
+ return result;
2750
+ }
2751
+ return buildSafeGraph(transformed, transformers, seen, containerFrame, void 0, NO_INDEX, cycleRegistry, depth + 1);
2752
+ };
2753
+ const serialiseOutput = (value, transformers, serialise, cycleRegistry) => {
2754
+ if (!serialise) return value;
2755
+ const safeGraph = value === void 0 ? "[UNSUPPORTED]" : buildSafeGraph(value, transformers, /* @__PURE__ */ new WeakSet(), void 0, void 0, NO_INDEX, cycleRegistry, 0);
2756
+ if (serialise === true) return JSON.stringify(safeGraph);
2757
+ return serialise(safeGraph);
2758
+ };
2759
+ //#endregion
2760
+ //#region src/core/create-redactor.ts
2761
+ const createCallableRedactor = (plan) => {
2762
+ const generalTraversal = (value, cycleRegistry) => redactValue(value, plan, cycleRegistry);
2763
+ const executor = plan.pathDrivenOnly ? buildPathDrivenExecutor(plan, (v) => generalTraversal(v)) : (v) => generalTraversal(v);
2764
+ return function redact(value) {
2765
+ if (plan.serialise) {
2766
+ const cycleRegistry = /* @__PURE__ */ new WeakMap();
2767
+ return serialiseOutput(generalTraversal(value, cycleRegistry), plan.transformers, plan.serialise, cycleRegistry);
2768
+ }
2769
+ return executor(value);
2770
+ };
2771
+ };
2772
+ const createRedactor$1 = (options) => {
2773
+ assertValidConfig(validateConfig(options));
2774
+ return createCallableRedactor(compileRedactorPlan(options ?? {}));
2775
+ };
2776
+ //#endregion
2777
+ //#region src/index.ts
2778
+ const deepRedact = createRedactor$1;
2779
+ const createRedactor = deepRedact;
2780
+ //#endregion
2781
+ exports.createRedactor = createRedactor;
2782
+ exports.deepRedact = deepRedact;