@hackylabs/deep-redact 2.2.1 → 3.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 (36) hide show
  1. package/README.md +23 -73
  2. package/dist/cjs/index.js +10 -138
  3. package/dist/cjs/utils/index.js +429 -0
  4. package/dist/cjs/utils/standardTransformers/bigint.js +10 -0
  5. package/dist/cjs/utils/standardTransformers/date.js +9 -0
  6. package/dist/cjs/utils/standardTransformers/error.js +16 -0
  7. package/dist/cjs/utils/standardTransformers/index.js +19 -0
  8. package/dist/cjs/utils/standardTransformers/map.js +9 -0
  9. package/dist/cjs/utils/standardTransformers/regex.js +15 -0
  10. package/dist/cjs/utils/standardTransformers/set.js +9 -0
  11. package/dist/cjs/utils/standardTransformers/url.js +9 -0
  12. package/dist/esm/index.mjs +9 -135
  13. package/dist/esm/utils/index.mjs +423 -0
  14. package/dist/esm/utils/standardTransformers/bigint.js +6 -0
  15. package/dist/esm/utils/standardTransformers/date.js +5 -0
  16. package/dist/esm/utils/standardTransformers/error.js +12 -0
  17. package/dist/esm/utils/standardTransformers/index.js +16 -0
  18. package/dist/esm/utils/standardTransformers/map.js +5 -0
  19. package/dist/esm/utils/standardTransformers/regex.js +11 -0
  20. package/dist/esm/utils/standardTransformers/set.js +5 -0
  21. package/dist/esm/utils/standardTransformers/url.js +5 -0
  22. package/dist/types/index.d.ts +3 -41
  23. package/dist/types/types.d.ts +48 -17
  24. package/dist/types/utils/index.d.ts +130 -0
  25. package/dist/types/utils/standardTransformers/bigint.d.ts +2 -0
  26. package/dist/types/utils/standardTransformers/date.d.ts +2 -0
  27. package/dist/types/utils/standardTransformers/error.d.ts +2 -0
  28. package/dist/types/utils/standardTransformers/index.d.ts +2 -0
  29. package/dist/types/utils/standardTransformers/map.d.ts +2 -0
  30. package/dist/types/utils/standardTransformers/regex.d.ts +2 -0
  31. package/dist/types/utils/standardTransformers/set.d.ts +2 -0
  32. package/dist/types/utils/standardTransformers/url.d.ts +2 -0
  33. package/package.json +66 -13
  34. package/dist/cjs/utils/redactorUtils.js +0 -252
  35. package/dist/esm/utils/redactorUtils.mjs +0 -253
  36. package/dist/types/utils/redactorUtils.d.ts +0 -91
@@ -1,15 +1,10 @@
1
- import RedactorUtils from './utils/redactorUtils';
1
+ import RedactorUtils from './utils';
2
2
  class DeepRedact {
3
3
  /**
4
4
  * The redactorUtils instance to handle the redaction.
5
5
  * @private
6
6
  */
7
7
  redactorUtils;
8
- /**
9
- * A WeakSet to store circular references during redaction. Reset to null after redaction is complete.
10
- * @private
11
- */
12
- circularReference = null;
13
8
  /**
14
9
  * The configuration for the redaction.
15
10
  * @private
@@ -25,141 +20,20 @@ class DeepRedact {
25
20
  */
26
21
  constructor(config) {
27
22
  const { serialise, serialize, ...rest } = config;
28
- this.redactorUtils = new RedactorUtils(rest);
29
- if (serialise !== undefined)
30
- this.config.serialise = serialise;
31
- if (serialize !== undefined)
32
- this.config.serialise = serialize;
23
+ const englishSerialise = serialise ?? serialize;
24
+ if (typeof englishSerialise === 'boolean')
25
+ this.config.serialise = englishSerialise;
26
+ this.redactorUtils = new RedactorUtils({ ...rest });
33
27
  }
34
- /**
35
- * A transformer for unsupported data types. If `serialise` is false, the value will be returned as is,
36
- * otherwise it will transform the value into a format that is supported by JSON.stringify.
37
- *
38
- * Error, RegExp, and Date instances are technically supported by JSON.stringify,
39
- * but they returned as empty objects, therefore they are also transformed here.
40
- * @protected
41
- * @param {unknown} value The value that is not supported by JSON.stringify.
42
- * @returns {unknown} The value in a format that is supported by JSON.stringify.
43
- */
44
- unsupportedTransformer = (value) => {
45
- if (typeof value === 'bigint') {
46
- return {
47
- __unsupported: {
48
- type: 'bigint',
49
- value: value.toString(10),
50
- radix: 10,
51
- },
52
- };
53
- }
54
- if (value instanceof Error) {
55
- return {
56
- __unsupported: {
57
- type: 'error',
58
- name: value.name,
59
- message: value.message,
60
- stack: value.stack,
61
- },
62
- };
63
- }
64
- if (value instanceof RegExp) {
65
- return {
66
- __unsupported: {
67
- type: 'regexp',
68
- source: value.source,
69
- flags: value.flags,
70
- },
71
- };
72
- }
73
- if (value instanceof Set) {
74
- return {
75
- __unsupported: {
76
- type: 'set',
77
- values: Array.from(value),
78
- },
79
- };
80
- }
81
- if (value instanceof Map) {
82
- return {
83
- __unsupported: {
84
- type: 'map',
85
- entries: Object.fromEntries(value.entries()),
86
- },
87
- };
88
- }
89
- if (value instanceof URL)
90
- return value.toString();
91
- if (value instanceof Date)
92
- return value.toISOString();
93
- return value;
94
- };
95
- /**
96
- * Calls `unsupportedTransformer` on the provided value and rewrites any circular references.
97
- *
98
- * Circular references will always be removed to avoid infinite recursion.
99
- * When a circular reference is found, the value will be replaced with `[[CIRCULAR_REFERENCE: path.to.original.value]]`.
100
- * @protected
101
- * @param {unknown} value The value to rewrite.
102
- * @param {string | undefined} path The path to the value in the object.
103
- * @returns {unknown} The rewritten value.
104
- */
105
- rewriteUnsupported = (value, path) => {
106
- const safeValue = this.unsupportedTransformer(value);
107
- if (!(safeValue instanceof Object))
108
- return safeValue;
109
- if (this.circularReference === null)
110
- this.circularReference = new WeakSet();
111
- if (Array.isArray(safeValue)) {
112
- return safeValue.map((val, index) => {
113
- const newPath = path ? `${path}.[${index}]` : `[${index}]`;
114
- if (this.circularReference?.has(val))
115
- return `[[CIRCULAR_REFERENCE: ${newPath}]]`;
116
- if (val instanceof Object) {
117
- this.circularReference?.add(val);
118
- return this.rewriteUnsupported(val, newPath);
119
- }
120
- return val;
121
- });
122
- }
123
- return Object.fromEntries(Object.entries(safeValue).map(([key, val]) => {
124
- const newPath = path ? `${path}.${key}` : key;
125
- if (this.circularReference?.has(val))
126
- return [key, `[[CIRCULAR_REFERENCE: ${newPath}]]`];
127
- if (val instanceof Object)
128
- this.circularReference?.add(val);
129
- return [key, this.rewriteUnsupported(val, path ? `${path}.${key}` : key)];
130
- }));
131
- };
132
- /**
133
- * Depending on the value of `serialise`, return the value as a JSON string or as the provided value.
134
- *
135
- * Also resets the `circularReference` property to null after redaction is complete.
136
- * This is to ensure that the WeakSet doesn't cause memory leaks.
137
- * @private
138
- * @param value
139
- * @returns {unknown} The value as a JSON string or as the provided value.
140
- * @throws {Error} If the value cannot be serialised.
141
- */
142
- maybeSerialise = (value) => {
143
- this.circularReference = null;
144
- if (!this.config.serialise)
145
- return value;
146
- if (typeof value === 'string')
147
- return value;
148
- try {
149
- return JSON.stringify(value);
150
- }
151
- catch (error) {
152
- throw new Error('Failed to serialise value. Did you override the `unsupportedTransformer` method and return a value that is not supported by JSON.stringify?');
153
- }
154
- };
155
28
  /**
156
29
  * Redact the provided value. The value will be stripped of any circular references and other unsupported data types, before being redacted according to the configuration and finally serialised if required.
157
30
  * @param {unknown} value The value to redact.
158
31
  * @returns {unknown} The redacted value.
159
- * @throws {Error} If the value cannot be serialised.
32
+ * @throws {Error} If the value cannot be serialised to JSON and serialise is true.
160
33
  */
161
34
  redact = (value) => {
162
- return this.maybeSerialise(this.redactorUtils.recurse(this.rewriteUnsupported(value)));
35
+ const redacted = this.redactorUtils.traverse(value);
36
+ return this.config.serialise ? JSON.stringify(redacted) : redacted;
163
37
  };
164
38
  }
165
- export { DeepRedact as default, DeepRedact };
39
+ export { DeepRedact, DeepRedact as default, };
@@ -0,0 +1,423 @@
1
+ import { standardTransformers } from './standardTransformers';
2
+ const defaultConfig = {
3
+ stringTests: [],
4
+ blacklistedKeys: [],
5
+ fuzzyKeyMatch: false,
6
+ caseSensitiveKeyMatch: true,
7
+ retainStructure: false,
8
+ remove: false,
9
+ replaceStringByLength: false,
10
+ replacement: '[REDACTED]',
11
+ types: ['string'],
12
+ transformers: standardTransformers,
13
+ };
14
+ class RedactorUtils {
15
+ /**
16
+ * The configuration for the redaction.
17
+ * @private
18
+ */
19
+ config = defaultConfig;
20
+ /**
21
+ * The computed regex pattern generated from sanitised blacklist keys of flat strings
22
+ * @private
23
+ */
24
+ computedRegex = null;
25
+ /**
26
+ * Regex to sanitise strings for the computed regex
27
+ * @private
28
+ */
29
+ sanitiseRegex = /[^a-zA-Z0-9_\-\$]/g;
30
+ /**
31
+ * The transformed blacklist keys of flat regex patterns and complex config objects
32
+ * @private
33
+ */
34
+ blacklistedKeysTransformed = [];
35
+ constructor(customConfig) {
36
+ this.config = {
37
+ ...defaultConfig,
38
+ ...customConfig,
39
+ };
40
+ this.blacklistedKeysTransformed = (customConfig.blacklistedKeys ?? []).filter(key => typeof key !== 'string').map((key) => this.createTransformedBlacklistedKey(key, customConfig));
41
+ const stringKeys = (customConfig.blacklistedKeys ?? []).filter(key => typeof key === 'string');
42
+ if (stringKeys.length > 0)
43
+ this.computedRegex = new RegExp(stringKeys.map(this.sanitiseStringForRegex).filter(Boolean).join('|'));
44
+ }
45
+ createTransformedBlacklistedKey = (key, customConfig) => {
46
+ if (key instanceof RegExp) {
47
+ return {
48
+ key,
49
+ fuzzyKeyMatch: customConfig.fuzzyKeyMatch ?? defaultConfig.fuzzyKeyMatch,
50
+ caseSensitiveKeyMatch: customConfig.caseSensitiveKeyMatch ?? defaultConfig.caseSensitiveKeyMatch,
51
+ retainStructure: customConfig.retainStructure ?? defaultConfig.retainStructure,
52
+ replacement: customConfig.replacement ?? defaultConfig.replacement,
53
+ replaceStringByLength: customConfig.replaceStringByLength ?? defaultConfig.replaceStringByLength,
54
+ remove: customConfig.remove ?? defaultConfig.remove,
55
+ };
56
+ }
57
+ return {
58
+ fuzzyKeyMatch: key.fuzzyKeyMatch ?? customConfig.fuzzyKeyMatch ?? defaultConfig.fuzzyKeyMatch,
59
+ caseSensitiveKeyMatch: key.caseSensitiveKeyMatch ?? customConfig.caseSensitiveKeyMatch ?? defaultConfig.caseSensitiveKeyMatch,
60
+ retainStructure: key.retainStructure ?? customConfig.retainStructure ?? defaultConfig.retainStructure,
61
+ replacement: key.replacement ?? customConfig.replacement ?? defaultConfig.replacement,
62
+ replaceStringByLength: key.replaceStringByLength ?? customConfig.replaceStringByLength ?? defaultConfig.replaceStringByLength,
63
+ remove: key.remove ?? customConfig.remove ?? defaultConfig.remove,
64
+ key: key.key,
65
+ };
66
+ };
67
+ /**
68
+ * Applies transformers to a value
69
+ * @param value - The value to transform
70
+ * @param key - The key to check
71
+ * @returns The transformed value
72
+ * @private
73
+ */
74
+ applyTransformers = (value, key, referenceMap) => {
75
+ if (typeof value === 'string')
76
+ return value;
77
+ let transformed = value;
78
+ for (const transformer of this.config.transformers) {
79
+ transformed = transformer(transformed, key, referenceMap);
80
+ if (transformed !== value)
81
+ return transformed;
82
+ }
83
+ return value;
84
+ };
85
+ /**
86
+ * Sanitises a string for the computed regex
87
+ * @param key - The string to sanitise
88
+ * @returns The sanitised string
89
+ * @private
90
+ */
91
+ sanitiseStringForRegex = (key) => key.replace(this.sanitiseRegex, '');
92
+ /**
93
+ * Checks if a key should be redacted
94
+ * @param key - The key to check
95
+ * @returns Whether the key should be redacted
96
+ * @private
97
+ */
98
+ shouldRedactKey = (key) => {
99
+ if (this.computedRegex?.test(this.sanitiseStringForRegex(key)))
100
+ return true;
101
+ return this.blacklistedKeysTransformed.some(config => {
102
+ const pattern = config.key;
103
+ if (pattern instanceof RegExp)
104
+ return pattern.test(key);
105
+ if (!config.fuzzyKeyMatch && !config.caseSensitiveKeyMatch)
106
+ return key.toLowerCase() === pattern.toLowerCase();
107
+ if (config.fuzzyKeyMatch && !config.caseSensitiveKeyMatch)
108
+ return key.toLowerCase().includes(pattern.toLowerCase());
109
+ if (config.fuzzyKeyMatch && config.caseSensitiveKeyMatch)
110
+ return key.includes(pattern);
111
+ if (!config.fuzzyKeyMatch && config.caseSensitiveKeyMatch)
112
+ return key === pattern;
113
+ });
114
+ };
115
+ /**
116
+ * Checks if a value should be redacted
117
+ * @param value - The value to check
118
+ * @param key - The key to check
119
+ * @returns Whether the value should be redacted
120
+ * @private
121
+ */
122
+ shouldRedactValue = (value, valueKey) => {
123
+ if (!this.config.types.includes(typeof value))
124
+ return false;
125
+ return this.shouldRedactKey(valueKey);
126
+ };
127
+ /**
128
+ * Redacts a value based on the key-specific config
129
+ * @param value - The value to redact
130
+ * @param key - The key to check
131
+ * @param redactingParent - Whether the parent is being redacted
132
+ * @returns The redacted value
133
+ * @private
134
+ */
135
+ redactValue = (value, redactingParent, keyConfig) => {
136
+ if (!this.config.types.includes(typeof value))
137
+ return { transformed: value, redactingParent };
138
+ const remove = keyConfig?.remove ?? this.config.remove;
139
+ const replacement = keyConfig?.replacement ?? this.config.replacement;
140
+ const replaceStringByLength = keyConfig?.replaceStringByLength ?? this.config.replaceStringByLength;
141
+ const retainStructure = keyConfig?.retainStructure ?? this.config.retainStructure;
142
+ if (retainStructure && typeof value === 'object' && value !== null)
143
+ return { transformed: value, redactingParent: true };
144
+ if (remove)
145
+ return { transformed: undefined, redactingParent };
146
+ if (typeof replacement === 'function')
147
+ return { transformed: replacement(value), redactingParent };
148
+ return {
149
+ redactingParent,
150
+ transformed: (typeof value === 'string' && replaceStringByLength)
151
+ ? replacement.toString().repeat(value.length)
152
+ : replacement,
153
+ };
154
+ };
155
+ /**
156
+ * Applies string transformations
157
+ * @param value - The value to transform
158
+ * @param key - The key to check
159
+ * @returns The transformed value
160
+ * @private
161
+ */
162
+ applyStringTransformations(value, amRedactingParent, keyConfig) {
163
+ if ((this.config.stringTests ?? []).length === 0)
164
+ return { transformed: value, redactingParent: amRedactingParent };
165
+ for (const test of this.config.stringTests) {
166
+ if (test instanceof RegExp) {
167
+ if (test.test(value)) {
168
+ const { transformed, redactingParent } = this.redactValue(value, amRedactingParent, keyConfig);
169
+ return { transformed: transformed, redactingParent };
170
+ }
171
+ }
172
+ else {
173
+ if (test.pattern.test(value)) {
174
+ const transformed = test.replacer(value, test.pattern);
175
+ return { transformed, redactingParent: amRedactingParent };
176
+ }
177
+ }
178
+ }
179
+ return { transformed: value, redactingParent: amRedactingParent };
180
+ }
181
+ /**
182
+ * Handles primitive values
183
+ * @param value - The value to handle
184
+ * @param key - The key to check
185
+ * @param redactingParent - Whether the parent is being redacted
186
+ * @param keyConfig - The key config
187
+ * @returns The transformed value
188
+ * @private
189
+ */
190
+ handlePrimitiveValue(value, valueKey, redactingParent, keyConfig) {
191
+ let transformed = value;
192
+ if (redactingParent) {
193
+ if (valueKey === '_transformer' || !this.config.types.includes(typeof value)) {
194
+ return { transformed: value, redactingParent };
195
+ }
196
+ const { transformed: transformedValue } = this.redactValue(value, redactingParent, keyConfig);
197
+ return { transformed: transformedValue, redactingParent };
198
+ }
199
+ if (keyConfig || this.shouldRedactValue(value, valueKey)) {
200
+ return this.redactValue(value, redactingParent, keyConfig);
201
+ }
202
+ if (typeof value === 'string') {
203
+ return this.applyStringTransformations(value, redactingParent, keyConfig);
204
+ }
205
+ return { transformed, redactingParent };
206
+ }
207
+ /**
208
+ * Handles object values
209
+ * @param value - The value to handle
210
+ * @param key - The key to check
211
+ * @param path - The path to the value
212
+ * @param redactingParent - Whether the parent is being redacted
213
+ * @param referenceMap - The reference map
214
+ * @returns The transformed value and stack
215
+ * @private
216
+ */
217
+ handleObjectValue(value, key, path, amRedactingParent, referenceMap, keyConfig) {
218
+ const fullPath = path.join('.');
219
+ const shouldRedact = amRedactingParent || Boolean(keyConfig) || this.shouldRedactValue(value, key);
220
+ referenceMap.set(value, fullPath);
221
+ if (shouldRedact && !(keyConfig?.retainStructure ?? this.config.retainStructure)) {
222
+ const { transformed, redactingParent } = this.redactValue(value, amRedactingParent, keyConfig);
223
+ return { transformed, redactingParent, stack: [] };
224
+ }
225
+ return this.handleRetainStructure(value, path, shouldRedact);
226
+ }
227
+ /**
228
+ * Handles object values
229
+ * @param value - The value to handle
230
+ * @param path - The path to the value
231
+ * @param redactingParent - Whether the parent is being redacted
232
+ * @returns The transformed value and stack
233
+ * @private
234
+ */
235
+ handleRetainStructure(value, path, redactingParent) {
236
+ const newValue = Array.isArray(value) ? [] : {};
237
+ const stack = [];
238
+ if (Array.isArray(value)) {
239
+ for (let i = value.length - 1; i >= 0; i--) {
240
+ stack.push({
241
+ parent: newValue,
242
+ key: i.toString(),
243
+ value: value[i],
244
+ path: [...path, i],
245
+ redactingParent,
246
+ keyConfig: this.findMatchingKeyConfig(i.toString()),
247
+ });
248
+ }
249
+ }
250
+ else {
251
+ for (const [propKey, propValue] of Object.entries(value).reverse()) {
252
+ stack.push({
253
+ parent: newValue,
254
+ key: propKey,
255
+ value: propValue,
256
+ path: [...path, propKey],
257
+ redactingParent,
258
+ keyConfig: this.findMatchingKeyConfig(propKey),
259
+ });
260
+ }
261
+ }
262
+ return { transformed: newValue, redactingParent, stack };
263
+ }
264
+ /**
265
+ * Finds the matching key config
266
+ * @param key - The key to find
267
+ * @returns The matching key config
268
+ * @private
269
+ */
270
+ findMatchingKeyConfig(key) {
271
+ if (this.computedRegex?.test(key)) {
272
+ return {
273
+ key,
274
+ fuzzyKeyMatch: this.config.fuzzyKeyMatch,
275
+ caseSensitiveKeyMatch: this.config.caseSensitiveKeyMatch,
276
+ replaceStringByLength: this.config.replaceStringByLength,
277
+ replacement: this.config.replacement,
278
+ retainStructure: this.config.retainStructure,
279
+ remove: this.config.remove,
280
+ };
281
+ }
282
+ return this.blacklistedKeysTransformed.find(config => {
283
+ const pattern = config.key;
284
+ if (pattern instanceof RegExp)
285
+ return pattern.test(key);
286
+ if (config.fuzzyKeyMatch) {
287
+ const compareKey = config.caseSensitiveKeyMatch ? key : key.toLowerCase();
288
+ const comparePattern = config.caseSensitiveKeyMatch ? pattern : pattern.toLowerCase();
289
+ return compareKey.includes(comparePattern);
290
+ }
291
+ return config.caseSensitiveKeyMatch ? key === pattern : key.toLowerCase() === pattern.toLowerCase();
292
+ });
293
+ }
294
+ /**
295
+ * Initialises the traversal
296
+ * @param raw - The raw value to traverse
297
+ * @returns The output and stack
298
+ * @private
299
+ */
300
+ initialiseTraversal(raw) {
301
+ const output = Array.isArray(raw) ? [] : {};
302
+ const stack = [];
303
+ if (typeof raw === 'object' && raw !== null) {
304
+ if (Array.isArray(raw)) {
305
+ for (let i = raw.length - 1; i >= 0; i--) {
306
+ stack.push({
307
+ parent: output,
308
+ key: i.toString(),
309
+ value: raw[i],
310
+ path: [i],
311
+ redactingParent: false,
312
+ keyConfig: this.findMatchingKeyConfig(i.toString()),
313
+ });
314
+ }
315
+ }
316
+ else {
317
+ for (const [propKey, propValue] of Object.entries(raw).reverse()) {
318
+ stack.push({
319
+ parent: output,
320
+ key: propKey,
321
+ value: propValue,
322
+ path: [propKey],
323
+ redactingParent: false,
324
+ keyConfig: this.findMatchingKeyConfig(propKey),
325
+ });
326
+ }
327
+ }
328
+ }
329
+ return { output, stack };
330
+ }
331
+ /**
332
+ * Pre-processes the input to replace circular references with transformer objects
333
+ * @param raw - The raw value to process
334
+ * @returns The processed value with circular references replaced
335
+ * @private
336
+ */
337
+ replaceCircularReferences(raw) {
338
+ if (typeof raw !== 'object' || raw === null)
339
+ return raw;
340
+ const visiting = new WeakSet();
341
+ const pathMap = new WeakMap();
342
+ const processValue = (value, path) => {
343
+ if (typeof value !== 'object' || value === null)
344
+ return value;
345
+ if (visiting.has(value)) {
346
+ const originalPath = pathMap.get(value) || '';
347
+ return {
348
+ _transformer: 'circular',
349
+ value: originalPath,
350
+ path: path
351
+ };
352
+ }
353
+ visiting.add(value);
354
+ pathMap.set(value, path);
355
+ let result;
356
+ if (Array.isArray(value)) {
357
+ let hasCircular = false;
358
+ const newArray = value.map((item, index) => {
359
+ const itemPath = path ? `${path}.${index}` : index.toString();
360
+ const processed = processValue(item, itemPath);
361
+ if (processed !== item)
362
+ hasCircular = true;
363
+ return processed;
364
+ });
365
+ result = hasCircular ? newArray : value;
366
+ }
367
+ else {
368
+ let hasCircular = false;
369
+ const newObj = {};
370
+ for (const [key, val] of Object.entries(value)) {
371
+ const valuePath = path ? `${path}.${key}` : key;
372
+ const processed = processValue(val, valuePath);
373
+ newObj[key] = processed;
374
+ if (processed !== val)
375
+ hasCircular = true;
376
+ }
377
+ result = hasCircular ? newObj : value;
378
+ }
379
+ visiting.delete(value);
380
+ return result;
381
+ };
382
+ return processValue(raw, '');
383
+ }
384
+ /**
385
+ * Traverses the raw value
386
+ * @param raw - The raw value to traverse
387
+ * @returns The transformed value
388
+ */
389
+ traverse = (raw) => {
390
+ if (typeof raw === 'string') {
391
+ const { transformed } = this.applyStringTransformations(raw, false);
392
+ return transformed;
393
+ }
394
+ if (typeof raw !== 'object' || raw === null)
395
+ return raw;
396
+ const referenceMap = new WeakMap();
397
+ const cleanedInput = this.replaceCircularReferences(raw);
398
+ const { output, stack } = this.initialiseTraversal(cleanedInput);
399
+ if (typeof cleanedInput === 'object' && cleanedInput !== null)
400
+ referenceMap.set(cleanedInput, '');
401
+ while (stack.length > 0) {
402
+ const { parent, key, value, path, redactingParent: amRedactingParent, keyConfig } = stack.pop();
403
+ let transformed = this.applyTransformers(value, key, referenceMap);
404
+ let redactingParent = amRedactingParent;
405
+ if (typeof transformed !== 'object' || transformed === null) {
406
+ const primitiveResult = this.handlePrimitiveValue(transformed, key, amRedactingParent, keyConfig);
407
+ redactingParent = primitiveResult.redactingParent;
408
+ transformed = primitiveResult.transformed;
409
+ if (typeof transformed === 'undefined')
410
+ continue;
411
+ }
412
+ else {
413
+ const objectResult = this.handleObjectValue(transformed, key, path, redactingParent, referenceMap, keyConfig);
414
+ transformed = objectResult.transformed;
415
+ stack.push(...objectResult.stack);
416
+ }
417
+ if (parent !== null && key !== null)
418
+ parent[key] = transformed;
419
+ }
420
+ return output;
421
+ };
422
+ }
423
+ export default RedactorUtils;
@@ -0,0 +1,6 @@
1
+ export const _bigint = (value) => {
2
+ if (typeof value !== 'bigint')
3
+ return value;
4
+ const radix = 10;
5
+ return { value: { radix, number: value.toString(radix) }, _transformer: 'bigint' };
6
+ };
@@ -0,0 +1,5 @@
1
+ export const _date = (value) => {
2
+ if (value instanceof Date)
3
+ return { datetime: value.toISOString(), _transformer: 'date' };
4
+ return value;
5
+ };
@@ -0,0 +1,12 @@
1
+ export const _error = (value) => {
2
+ if (!(value instanceof Error))
3
+ return value;
4
+ return {
5
+ _transformer: 'error',
6
+ value: {
7
+ type: value.constructor.name,
8
+ message: value.message,
9
+ stack: value.stack,
10
+ },
11
+ };
12
+ };
@@ -0,0 +1,16 @@
1
+ import { _bigint } from "./bigint";
2
+ import { _date } from "./date";
3
+ import { _error } from "./error";
4
+ import { _map } from "./map";
5
+ import { _regex } from "./regex";
6
+ import { _set } from "./set";
7
+ import { _url } from "./url";
8
+ export const standardTransformers = [
9
+ _bigint,
10
+ _url,
11
+ _date,
12
+ _error,
13
+ _map,
14
+ _set,
15
+ _regex,
16
+ ];
@@ -0,0 +1,5 @@
1
+ export const _map = (value) => {
2
+ if (value instanceof Map)
3
+ return { value: Object.fromEntries(value.entries()), _transformer: 'map' };
4
+ return value;
5
+ };
@@ -0,0 +1,11 @@
1
+ export const _regex = (value) => {
2
+ if (!(value instanceof RegExp))
3
+ return value;
4
+ return {
5
+ _transformer: 'regex',
6
+ value: {
7
+ source: value.source,
8
+ flags: value.flags,
9
+ },
10
+ };
11
+ };
@@ -0,0 +1,5 @@
1
+ export const _set = (value) => {
2
+ if (value instanceof Set)
3
+ return { value: Array.from(value), _transformer: 'set' };
4
+ return value;
5
+ };
@@ -0,0 +1,5 @@
1
+ export const _url = (value) => {
2
+ if (value instanceof URL)
3
+ return { value: value.toString(), _transformer: 'url' };
4
+ return value;
5
+ };