@hackylabs/deep-redact 3.0.4 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +221 -106
  3. package/dist/adapters/console/index.cjs +74 -0
  4. package/dist/adapters/console/index.d.cts +22 -0
  5. package/dist/adapters/console/index.d.ts +22 -0
  6. package/dist/adapters/console/index.js +73 -0
  7. package/dist/index.cjs +2743 -0
  8. package/dist/index.d.cts +7 -0
  9. package/dist/index.d.ts +7 -0
  10. package/dist/index.js +2741 -55
  11. package/dist/node-console-sink-BnRUkAAr.cjs +19 -0
  12. package/dist/node-console-sink-DaQleNZ8.js +14 -0
  13. package/dist/public-Da0aARA9.d.cts +127 -0
  14. package/dist/public-Dw4ycNzO.d.ts +127 -0
  15. package/package.json +67 -102
  16. package/dist/index.mjs +0 -40
  17. package/dist/types/index.d.ts +0 -29
  18. package/dist/types/types.d.ts +0 -224
  19. package/dist/types/utils/TransformerRegistry.d.ts +0 -52
  20. package/dist/types/utils/index.d.ts +0 -131
  21. package/dist/types/utils/standardTransformers/bigint.d.ts +0 -2
  22. package/dist/types/utils/standardTransformers/date.d.ts +0 -2
  23. package/dist/types/utils/standardTransformers/error.d.ts +0 -2
  24. package/dist/types/utils/standardTransformers/index.d.ts +0 -9
  25. package/dist/types/utils/standardTransformers/map.d.ts +0 -2
  26. package/dist/types/utils/standardTransformers/regex.d.ts +0 -2
  27. package/dist/types/utils/standardTransformers/set.d.ts +0 -2
  28. package/dist/types/utils/standardTransformers/url.d.ts +0 -2
  29. package/dist/types.js +0 -2
  30. package/dist/types.mjs +0 -1
  31. package/dist/utils/TransformerRegistry.js +0 -100
  32. package/dist/utils/TransformerRegistry.mjs +0 -94
  33. package/dist/utils/index.js +0 -471
  34. package/dist/utils/index.mjs +0 -467
  35. package/dist/utils/standardTransformers/bigint.js +0 -10
  36. package/dist/utils/standardTransformers/bigint.mjs +0 -6
  37. package/dist/utils/standardTransformers/date.js +0 -9
  38. package/dist/utils/standardTransformers/date.mjs +0 -5
  39. package/dist/utils/standardTransformers/error.js +0 -16
  40. package/dist/utils/standardTransformers/error.mjs +0 -12
  41. package/dist/utils/standardTransformers/index.js +0 -38
  42. package/dist/utils/standardTransformers/index.mjs +0 -35
  43. package/dist/utils/standardTransformers/map.js +0 -9
  44. package/dist/utils/standardTransformers/map.mjs +0 -5
  45. package/dist/utils/standardTransformers/regex.js +0 -15
  46. package/dist/utils/standardTransformers/regex.mjs +0 -11
  47. package/dist/utils/standardTransformers/set.js +0 -9
  48. package/dist/utils/standardTransformers/set.mjs +0 -5
  49. package/dist/utils/standardTransformers/url.js +0 -9
  50. package/dist/utils/standardTransformers/url.mjs +0 -5
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Benjamin Green (https://bengreen.dev)
3
+ Copyright (c) 2026 Benjamin Green (https://bengreen.dev)
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -3,128 +3,243 @@
3
3
  [![npm version](https://badge.fury.io/js/@hackylabs%2Fdeep-redact.svg)](https://badge.fury.io/js/@hackylabs%2Fdeep-redact)
4
4
  [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/hackylabs/deep-redact/blob/main/LICENSE)
5
5
 
6
- Safer and more configurable than many other redaction solutions, Deep Redact is a zero-dependency tool that redacts
7
- sensitive information from strings and objects. It is designed to be used in a production environment where sensitive
8
- information needs to be redacted from logs, error messages, files, and other outputs. Supporting both strings and objects
9
- or a mix of both, Deep Redact can be used to redact sensitive information from more data structures than any other
10
- redaction library. Even partially redacting sensitive information from strings is supported, by way of custom regex
11
- patterns and replacers.
6
+ @hackylabs/deep-redact is a rule-driven redaction library for Node.js and Deno. It lets you express what to redact — a key name, a path, a regex, or a substring — and the runtime locates and replaces it everywhere in the payload without requiring exhaustive path enumeration. Policies are compiled once at initialisation and reused across every request, making it suitable for high-throughput production use.
12
7
 
13
- Circular references and other unsupported values are handled gracefully, and the library is designed to be as fast as
14
- possible while still being easy to use and configure.
15
-
16
- Supporting both CommonJS and ESM, with named and default exports, Deep Redact is designed to be versatile and easy to
17
- use in any modern JavaScript or TypeScript project in Node or the browser.
8
+ Libraries like `fast-redact` require an explicit path for every location where a sensitive field might appear (`a.b.password`, `a.c.password`, …). Deep Redact inverts this: a single `keys: ['password']` rule targets every `password` field at any depth. It also adds capabilities that path-enumeration libraries cannot offer, such as partial string redaction (targeting a sensitive pattern inside a larger value) and wildcard path selectors for repeated structures.
18
9
 
19
10
  [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/hackylabs)
20
11
 
12
+ ## Features
13
+
14
+ - **Depth-agnostic key rules** — redact any field by name at any nesting depth; no path enumeration required
15
+ - **Exact and structured path targeting** — pin redaction to a specific location when precision is needed
16
+ - **Regex property matching** — match field names by regular expression
17
+ - **Substring targeting** — redact a sensitive string that appears inside a larger value, not just whole-value replacement
18
+ - **Wildcard and exclusion path selectors** — handle repeated structures at virtually any depth (`['addresses.*', { ignore: 'country' }]`) without listing every index
19
+ - **Structured and serialised output** — return a live object graph or a guaranteed-JSON-safe string (`serialise: true`)
20
+ - **Built-in and custom transformers** — override how specific runtime types are represented in serialised output
21
+ - **Built-in security** — prevent prototype pollution and DoS by memory exhaustion
22
+ - **Console adapter** — optional `console.*` redaction via an explicit adapter
23
+
24
+ ## Contributor Baseline
25
+
26
+ - Node `24.14.1`
27
+ - `pnpm@10.33.0`
28
+ - `tsdown`
29
+ - `Vitest`
30
+ - `ESLint`
31
+
21
32
  ## Installation
22
33
 
23
- ```bash
34
+ ```sh
24
35
  npm install @hackylabs/deep-redact
36
+ pnpm add @hackylabs/deep-redact
37
+ yarn add @hackylabs/deep-redact
38
+ bun add @hackylabs/deep-redact
25
39
  ```
26
40
 
27
- ## Usage
41
+ **Deno** — add the package to your import map (`deno.json`):
28
42
 
29
- <h4 style="color: red">In order to maintain a consistent usage throughout your project, it is not advised to call this
30
- library outside of your global logging/error-reporting libraries.</h4>
43
+ ```json
44
+ {
45
+ "imports": {
46
+ "@hackylabs/deep-redact": "npm:@hackylabs/deep-redact@4.0.0"
47
+ }
48
+ }
49
+ ```
31
50
 
32
- ```typescript
33
- // ./src/example.ts
34
- import {DeepRedact} from '@hackylabs/deep-redact'; // If you're using CommonJS, import with require('@hackylabs/deep-redact') instead. Both CommonJS and ESM support named and default imports.
51
+ ## Quick Start
35
52
 
36
- const objRedaction = new DeepRedact({
37
- blacklistedKeys: ['sensitive', 'password', /name/i],
38
- })
53
+ ```ts
54
+ import { deepRedact } from '@hackylabs/deep-redact'
39
55
 
40
- const obj = {
41
- keepThis: 'This is fine',
42
- sensitive: 'This is not fine',
43
- user: {
44
- id: 1,
45
- password: '<h1><strong>Password</strong></h1>',
46
- firstName: 'John',
47
- }
48
- }
56
+ const redact = deepRedact({ keys: ['password', 'token'] })
49
57
 
50
- // Recursively redact sensitive information from an object
51
- objRedaction.redact(obj)
52
- // {
53
- // keepThis: 'This is fine',
54
- // sensitive: '[REDACTED]',
55
- // user: {
56
- // id: 1,
57
- // password: '[REDACTED]',
58
- // firstName: '[REDACTED]'
59
- // }
60
- // }
61
-
62
- const strRedaction = new DeepRedact({
63
- stringTests: [
64
- {
65
- pattern: /<(email|password)>([^<]+)<\/\1>/gi,
66
- replacer: (value: string, pattern: RegExp) => value.replace(pattern, '<$1>[REDACTED]</$1>'),
67
- },
68
- ],
69
- })
70
-
71
- // Partially redact sensitive information from a string
72
- strRedaction.redact('<email>someone@somewhere.com</email><keepThis>This is fine</keepThis><password>secret</password>')
73
- // '<email>[REDACTED]</email><keepThis>This is fine</keepThis><password>[REDACTED]</password>'
58
+ redact({ user: { password: 'secret', safe: 'keep this' }, token: 'abc123', id: 1 })
59
+ // { user: { password: '[REDACTED]', safe: 'keep this' }, token: '[REDACTED]', id: 1 }
74
60
  ```
75
61
 
76
- // Override the `transformers` property to handle unsupported values in your own way
77
- // WARNING: You are responsible for ensuring all used transformers safely and reliably transform unsupported values
78
-
79
- ```typescript
80
- const customRedaction = new DeepRedact({
81
- blacklistedKeys: ['sensitive', 'password', /name/i],
82
- transformers: [
83
- (value: unknown) => {
84
- if (typeof value !== 'bigint') return value
85
- return value.toString(10)
86
- },
87
- ...
88
- ]
89
- })
90
-
91
- customRedaction.redact({ a: BigInt(1) })
92
- ```
62
+ ## Benchmarks
63
+
64
+ Generated from committed artefacts in `test/artefacts/benchmarks/speed/`. Node.js 24 LTS, Apple M-series. Steady-state after 10,000 warmup iterations.
65
+
66
+ ### TLDR Headline
67
+
68
+ > **If raw throughput is your primary constraint and you can accept exhaustive path enumeration, use [`fast-redact`](https://www.npmjs.com/package/fast-redact) instead.** It is purpose-built for maximum JSON-string output speed and will outperform any rule-driven library in that narrow scenario. Use Deep Redact when you need depth-agnostic targeting, wildcard paths, substring redaction, or built-in security guarantees.
69
+
70
+ If you choose fast-redact instead, be aware of the trade-offs:
71
+
72
+ - **No depth-agnostic key rules** — every sensitive path must be enumerated explicitly
73
+ - **No double-wildcard (`**`) path selectors** — you cannot target `records.**.email` without listing every depth
74
+ - **No substring targeting** — whole-value replacement only; partial string redaction is not supported
75
+ - **No serialisation-safe transformer pipeline** — no built-in handling for `BigInt`, `Date`, `Map`, `Set`, `Error`, `RegExp`, or `URL` values
76
+ - **No prototype pollution protection** — no guard against `__proto__` and `constructor.prototype` injection
77
+ - **No configurable depth or node limits** — no protection against DoS via deeply nested or excessively large payloads
78
+
79
+ ### Structured output (`serialise: false`)
80
+
81
+ v4, v3, and fast-redact all return a plain JavaScript object.
82
+
83
+ ![Speed comparison — structured output](docs/benchmarks/charts/speed-comparison-non-serialised.svg)
84
+
85
+ v4 is **~17× faster** than v3 on path-based workloads and **~10× faster** on wildcard workloads.
86
+
87
+ ### Serialised output (`serialise: true`)
88
+
89
+ All four solutions return a JSON string.
90
+
91
+ ![Speed comparison — serialised output](docs/benchmarks/charts/speed-comparison-serialised.svg)
92
+
93
+ v4 remains faster than v3 in serialised mode. fast-redact and json-stringify-regex have a throughput advantage because their output path is oriented entirely toward string production.
94
+
95
+ Full speed and resource benchmark results: [`docs/benchmarks/speed-results.md`](docs/benchmarks/speed-results.md) and [`docs/benchmarks/resource-results.md`](docs/benchmarks/resource-results.md).
93
96
 
94
97
  ## Configuration
95
98
 
96
- ### Main Options
97
-
98
- | key | description | type | options | default | required |
99
- | --- | --- | --- | --- | --- | --- |
100
- | blacklistedKeys | Deeply compare names of these keys against the keys in your object. | array | Array<string│RegExp│BlacklistKeyConfig> | [] | N |
101
- | stringTests | Array of regular expressions to perform against string values, whether that value is a flat string or nested within an object. Can redact whole or partial string values. If a replacer function is provided in the config for the associated test, it will be used to redact the value if the value matches the test. | array | Array<RegExp│StringTestConfig> | [] | N |
102
- | fuzzyKeyMatch | Loosely compare key names by checking if the key name of your unredacted object is included anywhere within the name of your blacklisted key. For example, is "pass" (your key) included in "password" (from config). | boolean | | false | N |
103
- | caseSensitiveKeyMatch | Loosely compare key names by normalising the strings. This involves removing non-word characters and transforms the string to lowercase. This means you never have to worry having to list duplicate keys in different formats such as snake_case, camelCase, PascalCase or any other case. | boolean | | true | N |
104
- | remove | Determines whether or not to remove the key from the object when it is redacted. | boolean | | false | N |
105
- | retainStructure | Determines whether or not keep all nested values of a key that is going to be redacted. Circular references are always removed. | boolean | | false | N |
106
- | replacement | When a value is going to be redacted, what would you like to replace it with? | string function | | [REDACTED] | N |
107
- | replaceStringByLength | When a string value is going to be replaced, optionally replace it by repeating the `replacement` to match the length of the value. For example, if `replaceStringByLength` were set to `true` and `replacement` was set to "x", then redacting "secret" would return "xxxxxx". This is sometimes useful for debugging purposes, although it may be less secure as it could give hints to the original value. | boolean | | false | N |
108
- | types | JS types (values of `typeof` keyword). Only values with a typeof equal to `string`, `number`, `bigint`, `boolean`, `symbol`, `object`, or `function` will be redacted. Undefined values will never be redacted, although the type `undefined` is included in this list to keep TypeScript happy. | array | Array<'string'│'number'│'bigint'│'boolean'│'symbol'│'undefined'│'object'│'function'> | ['string'] | N |
109
- | serialise | Determines whether or not to serialise the object after redacting. Typical use cases for this are when you want to send it over the network or save to a file, both of which are common use cases for redacting sensitive information. | boolean | | true | N |
110
- | serialize | Alias of `serialise` for International-English users. | boolean | | Value of `serialise` | N |
111
- | transformers | A list of transformers to apply when transforming unsupported values. Each transformer should conditionally transform the value, or return the value unchanged to be passed to the next transformer. Transformers will be run in the order they are provided over the entire object. | array | Array<Transformer> | All standard transformers (bigint, date, error, map, regex, set, and url) | N |
112
-
113
- ### BlacklistKeyConfig
114
-
115
- | key | type | default | required |
116
- | --- | --- | --- | --- |
117
- | key | string│RegExp | | Y |
118
- | fuzzyKeyMatch | boolean | Main options `fuzzyKeyMatch` | N |
119
- | caseSensitiveKeyMatch | boolean | Main options `caseSensitiveKeyMatch` | N |
120
- | remove | boolean | Main options `remove` | N |
121
- | replacement | string│function | Main options `replacement` | N |
122
- | replaceStringByLength | boolean | Main options `replaceStringByLength` | N |
123
- | retainStructure | boolean | Main options `retainStructure` | N |
124
-
125
- ### StringTestConfig
126
-
127
- | key | description | type | required |
128
- | --- | --- | --- | --- |
129
- | pattern | A regular expression to perform against a string value, whether that value is a flat string or nested within an object. | RegExp | Y |
130
- | replacer | A function that will be called with the value of the string that matched the pattern and the pattern itself. This function should return the new (redacted) value to replace the original value. | function | Y |
99
+ `deepRedact(options)` accepts a single options object. All options are optional.
100
+
101
+ ### Top-level options
102
+
103
+ | Option | Type | Default | Required | Description |
104
+ |--------|------|---------|----------|-------------|
105
+ | `keys` | `KeySelector[]` | `[]` | No | Redact any field matching a listed key, at any depth |
106
+ | `paths` | `PathEntry[]` | `[]` | No | Redact fields at specific paths; supports exact strings, structured paths, wildcards, and exclusion selectors |
107
+ | `stringTests` | `StringTest[]` | `[]` | No | Redact matching patterns inside string values |
108
+ | `censor` | `string \| function` | `'[REDACTED]'` | No | Replacement value, or a function `(value, context) => replacement` |
109
+ | `serialise` | `boolean \| function` | `false` | No | `true` returns a JSON-safe string; a function receives the safe graph and may return any string |
110
+ | `remove` | `boolean` | `false` | No | Remove matched keys from the output instead of replacing their values |
111
+ | `retainStructure` | `boolean` | `false` | No | Keep descendant nodes traversable for lower-precedence rules when a parent is matched |
112
+ | `replaceStringByLength` | `boolean` | `false` | No | Repeat the `censor` string to match the character length of the redacted value |
113
+ | `types` | `ValueTypeName[]` | `['string']` | No | `typeof` categories eligible for redaction; a matched key whose value's type is not listed is left untouched |
114
+ | `fuzzyKeyMatch` | `boolean` | `false` | No | Match when the configured key is a substring of the field name (e.g. `'pass'` matches `'password'`) |
115
+ | `caseSensitiveKeyMatch` | `boolean` | `true` | No | When `false`, normalises key names (strips non-word characters, lowercases) before comparing |
116
+ | `maxDepth` | `number` | unlimited | No | Stop traversal beyond this nesting depth; guards against deeply nested payload DoS |
117
+ | `maxNodes` | `number` | unlimited | No | Stop traversal after this many nodes; guards against excessively large payload DoS |
118
+ | `transformers` | `TransformersOption` | built-in defaults | No | Override how runtime types are represented in serialised output |
119
+ | `diagnostics` | `DiagnosticsOptions` | | No | Receive a structured event when a value is degraded to `[UNSUPPORTED]` during serialisation |
120
+
121
+ `ValueTypeName` is one of: `'string'`, `'number'`, `'bigint'`, `'boolean'`, `'object'`, `'function'`, `'symbol'`, `'undefined'`.
122
+
123
+ ### `KeySelector`
124
+
125
+ Each entry in `keys` may be a plain `string`, a `RegExp`, or a `KeyRule` object for per-key overrides.
126
+
127
+ | Field | Type | Default | Required | Description |
128
+ |-------|------|---------|----------|-------------|
129
+ | `key` | `string \| RegExp` | — | Yes | The key to match |
130
+ | `censor` | `string \| function` | top-level `censor` | No | Override the censor for this key only |
131
+ | `remove` | `boolean` | top-level `remove` | No | Override remove for this key only |
132
+ | `retainStructure` | `boolean` | top-level `retainStructure` | No | Override retainStructure for this key only |
133
+ | `replaceStringByLength` | `boolean` | top-level `replaceStringByLength` | No | Override replaceStringByLength for this key only |
134
+ | `fuzzyKeyMatch` | `boolean` | top-level `fuzzyKeyMatch` | No | Override fuzzyKeyMatch for this key only |
135
+ | `caseSensitiveKeyMatch` | `boolean` | top-level `caseSensitiveKeyMatch` | No | Override caseSensitiveKeyMatch for this key only |
136
+
137
+ ### `PathEntry`
138
+
139
+ Each entry in `paths` may be a plain dot-notation string (e.g. `'user.profile.ssn'`), a structured path array, or a `PathRule` object for per-path overrides.
140
+
141
+ Structured path arrays may contain:
142
+
143
+ | Segment type | Example | Matches |
144
+ |---|---|---|
145
+ | `string` or `number` | `'password'` | Exact key or index |
146
+ | `RegExp` | `/^pass/i` | Keys matching the regex |
147
+ | `{ any: true }` | — | Any single key (`*`) |
148
+ | `{ anyDepth: true }` | — | Zero or more keys at any depth (`**`) |
149
+ | `{ ignore: string \| number \| RegExp }` | `{ ignore: 'country' }` | Excludes a key from a surrounding wildcard |
150
+
151
+ | Field | Type | Default | Required | Description |
152
+ |-------|------|---------|----------|-------------|
153
+ | `path` | `string \| PathSegments[]` | — | Yes | The path to match |
154
+ | `censor` | `string \| function` | top-level `censor` | No | Override the censor for this path only |
155
+ | `remove` | `boolean` | top-level `remove` | No | Override remove for this path only |
156
+ | `retainStructure` | `boolean` | top-level `retainStructure` | No | Override retainStructure for this path only |
157
+ | `replaceStringByLength` | `boolean` | top-level `replaceStringByLength` | No | Override replaceStringByLength for this path only |
158
+
159
+ ### `StringTest`
160
+
161
+ Each entry in `stringTests` may be a bare `RegExp` (whole-value replacement with `censor`) or a `SubstringRule` for partial replacement.
162
+
163
+ | Field | Type | Required | Description |
164
+ |-------|------|----------|-------------|
165
+ | `pattern` | `RegExp` | Yes | Regular expression tested against string values |
166
+ | `replacer` | `(value: string, pattern: RegExp) => string` | Yes | Returns the redacted string; called only when `pattern` matches |
167
+
168
+ ### `TransformersOption`
169
+
170
+ Transformers control how non-JSON-safe runtime types are represented when `serialise: true`. Each transformer is a function `(value: unknown) => unknown` that either returns a transformed value or returns the value unchanged to pass to the next transformer in the chain.
171
+
172
+ | Field | Type | Description |
173
+ |-------|------|-------------|
174
+ | `byType.bigint` | `Transformer[]` | Applied to `BigInt` values (does not consult `byType.object`) |
175
+ | `byType.object` | `Transformer[]` | Applied first for built-in constructor types and unknown object types |
176
+ | `byConstructor.Date` | `Transformer[]` | Applied to `Date` instances |
177
+ | `byConstructor.Error` | `Transformer[]` | Applied to `Error` instances |
178
+ | `byConstructor.Map` | `Transformer[]` | Applied to `Map` instances |
179
+ | `byConstructor.RegExp` | `Transformer[]` | Applied to `RegExp` instances |
180
+ | `byConstructor.Set` | `Transformer[]` | Applied to `Set` instances |
181
+ | `byConstructor.URL` | `Transformer[]` | Applied to `URL` instances |
182
+ | `byConstructor.custom` | `CustomConstructorTransformerRegistration[]` | Custom types matched by `instanceof` in declaration order |
183
+ | `fallback` | `Transformer[]` | Applied when no earlier transformer returns a changed value |
184
+
185
+ The built-in transformers produce deterministic marker objects (e.g. `{ _transformer: 'date', datetime: '<ISO string>' }`). See [`docs/architecture/serialise-output.md`](docs/architecture/serialise-output.md) for the full dispatch order and marker shapes.
186
+
187
+ ### `DiagnosticsOptions`
188
+
189
+ | Field | Type | Description |
190
+ |-------|------|-------------|
191
+ | `sink` | `(event: DiagnosticEvent) => void` | Called with a structured event when a value is degraded to `'[UNSUPPORTED]'` during serialisation |
192
+
193
+ ## Public API
194
+
195
+ ```ts
196
+ import { createRedactor, deepRedact } from '@hackylabs/deep-redact'
197
+
198
+ // deepRedact and createRedactor are the same factory under different names
199
+ const redact = deepRedact({ keys: ['secret'] })
200
+ const same = createRedactor({ keys: ['secret'] })
201
+ ```
202
+
203
+ ## Documentation
204
+
205
+ ### Worked examples
206
+
207
+ - [Key targeting](docs/examples/key-targeting.md)
208
+ - [Fuzzy and case-insensitive key matching](docs/examples/fuzzy-key-matching.md)
209
+ - [Path targeting](docs/examples/path-targeting.md)
210
+ - [Wildcard path segments and ignore selectors](docs/examples/path-segment-ignore.md)
211
+ - [Regex property matching](docs/examples/regex-property-matching.md)
212
+ - [Per-key rule overrides](docs/examples/per-key-rule-overrides-string.md)
213
+ - [Substring targeting](docs/examples/substring-targeting.md)
214
+ - [Replacement and removal](docs/examples/replacement-and-removal.md)
215
+ - [Structured vs serialised output](docs/examples/serialised-output.md)
216
+ - [Custom transformers](docs/examples/custom-transformer.md)
217
+ - [Value-type allowlist](docs/examples/value-type-allowlist-default.md)
218
+ - [Singleton / service-root setup](docs/examples/singleton-setup.md)
219
+ - [Console redaction adapter](docs/examples/console-redaction.md)
220
+
221
+ ### Migration guides
222
+
223
+ - [Migrating from Deep Redact v3](docs/migration/from-v3.md)
224
+ - [Migrating from fast-redact](docs/migration/from-fast-redact.md)
225
+
226
+ ### Architecture and design
227
+
228
+ - [Rule precedence and evaluation order](docs/architecture/precedence.md)
229
+ - [Serialised output: transformer dispatch and marker shapes](docs/architecture/serialise-output.md)
230
+ - [One-way redaction contract](docs/architecture/one-way-redaction.md)
231
+
232
+ ### Platform and security teams
233
+
234
+ - [Standardisation guide](docs/platform/standardisation-guide.md) — supported capabilities, targeting semantics, verification evidence, and adoption decision scope
235
+
236
+ ## Scripts
237
+
238
+ - `pnpm run build`
239
+ - `pnpm run lint`
240
+ - `pnpm run test`
241
+ - `pnpm run generate-exports`
242
+ - `pnpm run generate-readme`
243
+ - `pnpm run verify-generated-files`
244
+ - `pnpm run bench`
245
+ - `pnpm run bench:generate-charts`
@@ -0,0 +1,74 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_node_console_sink = require("../../node-console-sink-BnRUkAAr.cjs");
3
+ //#region src/core/diagnostics/console-diagnostic-event.ts
4
+ const consoleRecursionBlockedMessage = "Nested console adapter call was blocked to prevent a recursive redaction loop.";
5
+ const createConsoleRecursionBlockedDiagnosticEvent = (method) => {
6
+ return Object.freeze({
7
+ details: Object.freeze({ method }),
8
+ event: "console.recursion_blocked",
9
+ message: consoleRecursionBlockedMessage,
10
+ path: "",
11
+ valueType: "console"
12
+ });
13
+ };
14
+ const emitConsoleRecursionBlockedDiagnostic = (method, sink) => {
15
+ const resolvedSink = sink ?? require_node_console_sink.getNodeConsoleDiagnosticSink();
16
+ if (resolvedSink === void 0) return;
17
+ try {
18
+ resolvedSink(createConsoleRecursionBlockedDiagnosticEvent(method));
19
+ } catch {
20
+ return;
21
+ }
22
+ };
23
+ //#endregion
24
+ //#region src/adapters/console/recursion-guard.ts
25
+ const createConsoleRecursionGuard = (options = {}) => {
26
+ let active = false;
27
+ let emittedForActiveChain = false;
28
+ const sink = options.diagnostics?.sink;
29
+ return {
30
+ block(method) {
31
+ if (!emittedForActiveChain) {
32
+ emittedForActiveChain = true;
33
+ emitConsoleRecursionBlockedDiagnostic(method, sink);
34
+ }
35
+ },
36
+ isActive() {
37
+ return active;
38
+ },
39
+ run(operation) {
40
+ active = true;
41
+ emittedForActiveChain = false;
42
+ try {
43
+ return operation();
44
+ } finally {
45
+ active = false;
46
+ emittedForActiveChain = false;
47
+ }
48
+ }
49
+ };
50
+ };
51
+ //#endregion
52
+ //#region src/adapters/console/create-redacted-console.ts
53
+ const consoleMethodNames = Object.freeze([
54
+ "debug",
55
+ "error",
56
+ "info",
57
+ "log",
58
+ "trace",
59
+ "warn"
60
+ ]);
61
+ const createRedactedConsole = (redactor, target, options = {}) => {
62
+ const guard = createConsoleRecursionGuard(options);
63
+ const adapted = {};
64
+ for (const method of consoleMethodNames) adapted[method] = (...args) => {
65
+ if (guard.isActive()) return guard.block(method);
66
+ return guard.run(() => {
67
+ const redactedArgs = args.map((argument) => redactor(argument));
68
+ return target[method](...redactedArgs);
69
+ });
70
+ };
71
+ return adapted;
72
+ };
73
+ //#endregion
74
+ exports.createRedactedConsole = createRedactedConsole;
@@ -0,0 +1,22 @@
1
+ import { _ as DiagnosticSink, t as Redactor } from "../../public-Da0aARA9.cjs";
2
+
3
+ //#region src/adapters/console/create-redacted-console.d.ts
4
+ type ConsoleMethodName = 'debug' | 'error' | 'info' | 'log' | 'trace' | 'warn';
5
+ type ConsoleMethod = (...args: unknown[]) => unknown;
6
+ interface ConsoleLike {
7
+ readonly debug: ConsoleMethod;
8
+ readonly error: ConsoleMethod;
9
+ readonly info: ConsoleMethod;
10
+ readonly log: ConsoleMethod;
11
+ readonly trace: ConsoleMethod;
12
+ readonly warn: ConsoleMethod;
13
+ }
14
+ interface ConsoleRedactionOptions {
15
+ readonly diagnostics?: {
16
+ readonly sink?: DiagnosticSink;
17
+ };
18
+ }
19
+ type RedactedConsole = ConsoleLike;
20
+ declare const createRedactedConsole: (redactor: Redactor, target: ConsoleLike, options?: ConsoleRedactionOptions) => RedactedConsole;
21
+ //#endregion
22
+ export { type ConsoleLike, type ConsoleMethodName, type ConsoleRedactionOptions, type RedactedConsole, createRedactedConsole };
@@ -0,0 +1,22 @@
1
+ import { _ as DiagnosticSink, t as Redactor } from "../../public-Dw4ycNzO.js";
2
+
3
+ //#region src/adapters/console/create-redacted-console.d.ts
4
+ type ConsoleMethodName = 'debug' | 'error' | 'info' | 'log' | 'trace' | 'warn';
5
+ type ConsoleMethod = (...args: unknown[]) => unknown;
6
+ interface ConsoleLike {
7
+ readonly debug: ConsoleMethod;
8
+ readonly error: ConsoleMethod;
9
+ readonly info: ConsoleMethod;
10
+ readonly log: ConsoleMethod;
11
+ readonly trace: ConsoleMethod;
12
+ readonly warn: ConsoleMethod;
13
+ }
14
+ interface ConsoleRedactionOptions {
15
+ readonly diagnostics?: {
16
+ readonly sink?: DiagnosticSink;
17
+ };
18
+ }
19
+ type RedactedConsole = ConsoleLike;
20
+ declare const createRedactedConsole: (redactor: Redactor, target: ConsoleLike, options?: ConsoleRedactionOptions) => RedactedConsole;
21
+ //#endregion
22
+ export { type ConsoleLike, type ConsoleMethodName, type ConsoleRedactionOptions, type RedactedConsole, createRedactedConsole };
@@ -0,0 +1,73 @@
1
+ import { t as getNodeConsoleDiagnosticSink } from "../../node-console-sink-DaQleNZ8.js";
2
+ //#region src/core/diagnostics/console-diagnostic-event.ts
3
+ const consoleRecursionBlockedMessage = "Nested console adapter call was blocked to prevent a recursive redaction loop.";
4
+ const createConsoleRecursionBlockedDiagnosticEvent = (method) => {
5
+ return Object.freeze({
6
+ details: Object.freeze({ method }),
7
+ event: "console.recursion_blocked",
8
+ message: consoleRecursionBlockedMessage,
9
+ path: "",
10
+ valueType: "console"
11
+ });
12
+ };
13
+ const emitConsoleRecursionBlockedDiagnostic = (method, sink) => {
14
+ const resolvedSink = sink ?? getNodeConsoleDiagnosticSink();
15
+ if (resolvedSink === void 0) return;
16
+ try {
17
+ resolvedSink(createConsoleRecursionBlockedDiagnosticEvent(method));
18
+ } catch {
19
+ return;
20
+ }
21
+ };
22
+ //#endregion
23
+ //#region src/adapters/console/recursion-guard.ts
24
+ const createConsoleRecursionGuard = (options = {}) => {
25
+ let active = false;
26
+ let emittedForActiveChain = false;
27
+ const sink = options.diagnostics?.sink;
28
+ return {
29
+ block(method) {
30
+ if (!emittedForActiveChain) {
31
+ emittedForActiveChain = true;
32
+ emitConsoleRecursionBlockedDiagnostic(method, sink);
33
+ }
34
+ },
35
+ isActive() {
36
+ return active;
37
+ },
38
+ run(operation) {
39
+ active = true;
40
+ emittedForActiveChain = false;
41
+ try {
42
+ return operation();
43
+ } finally {
44
+ active = false;
45
+ emittedForActiveChain = false;
46
+ }
47
+ }
48
+ };
49
+ };
50
+ //#endregion
51
+ //#region src/adapters/console/create-redacted-console.ts
52
+ const consoleMethodNames = Object.freeze([
53
+ "debug",
54
+ "error",
55
+ "info",
56
+ "log",
57
+ "trace",
58
+ "warn"
59
+ ]);
60
+ const createRedactedConsole = (redactor, target, options = {}) => {
61
+ const guard = createConsoleRecursionGuard(options);
62
+ const adapted = {};
63
+ for (const method of consoleMethodNames) adapted[method] = (...args) => {
64
+ if (guard.isActive()) return guard.block(method);
65
+ return guard.run(() => {
66
+ const redactedArgs = args.map((argument) => redactor(argument));
67
+ return target[method](...redactedArgs);
68
+ });
69
+ };
70
+ return adapted;
71
+ };
72
+ //#endregion
73
+ export { createRedactedConsole };