@cqrs-ddd/safe-stringify 0.2.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.
package/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # @cqrs-ddd/safe-stringify
2
+
3
+ Two JSON serializers with opposite goals, and the key-segment helpers that build
4
+ identity keys. No runtime dependencies and no framework.
5
+
6
+ | | Strict: `stableStringify`, `toStrictJsonValue` | Safe: `safeStringify`, `safeSanitize`, `redactValue` |
7
+ | --- | --- | --- |
8
+ | Use for | identity: cache keys, idempotency fingerprints, stored snapshots | display: logs, audit and dead-letter payloads |
9
+ | Output | deterministic JSON, object keys sorted at every level, stable across releases | readable JSON in insertion order; the format may evolve |
10
+ | Unsupported input | throws a `TypeError` | never throws |
11
+ | Secrets | never redacts, because a key must not change | excludes and redacts on request |
12
+
13
+ **Use the strict serializer for anything that identifies data. Never use `safeStringify`
14
+ for a key or a fingerprint:** its output is not sorted, it replaces values it cannot
15
+ represent, and its format may change.
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @cqrs-ddd/safe-stringify
21
+ # or
22
+ pnpm add @cqrs-ddd/safe-stringify
23
+ ```
24
+
25
+ Requires Node.js 22 or later.
26
+
27
+ ## Strict serializer
28
+
29
+ ```typescript
30
+ import { stableStringify, toStrictJsonValue } from '@cqrs-ddd/safe-stringify';
31
+
32
+ stableStringify({ z: 1, a: { d: 2, c: new Date(0) } });
33
+ // '{"a":{"c":"1970-01-01T00:00:00.000Z","d":2},"z":1}'
34
+
35
+ toStrictJsonValue({ b: 2, a: 1 }, true); // { a: 1, b: 2 }, a null-prototype object
36
+ ```
37
+
38
+ - Strings, booleans, `null` and finite numbers are kept. Dates become ISO-8601 strings.
39
+ An object with `toJSON()` is replaced by what that method returns.
40
+ - Everything else throws a `TypeError`: `undefined`, functions, `bigint`, symbols and
41
+ symbol-keyed properties, non-finite numbers, invalid dates, cycles, sparse arrays,
42
+ `Map`, `Set`, `WeakMap`, `WeakSet`, `Error`, `RegExp`, `Promise`, `ArrayBuffer` and
43
+ typed arrays.
44
+ - `stableStringify` wraps that failure in a `TypeError` whose `cause` is the original.
45
+ - Structurally equal values produce byte-identical strings, whatever their property
46
+ insertion order. Keys sort by UTF-16 code unit, as `Array.prototype.sort` does.
47
+
48
+ ## Safe serializer
49
+
50
+ ```typescript
51
+ import {
52
+ DEFAULT_REDACT_KEYS,
53
+ redactValue,
54
+ safeStringify,
55
+ } from '@cqrs-ddd/safe-stringify';
56
+
57
+ const payload: Record<string, unknown> = { user: 'jane', password: 'secret', amount: 10n };
58
+ payload.self = payload;
59
+
60
+ safeStringify(payload);
61
+ // '{"user":"jane","password":"secret","amount":"[bigint]","self":"[Circular]"}'
62
+
63
+ safeStringify(payload, { redactKeys: DEFAULT_REDACT_KEYS });
64
+ // '{"user":"jane","password":"[REDACTED]","amount":"[bigint]","self":"[Circular]"}'
65
+
66
+ redactValue(payload); // a deep clone with DEFAULT_REDACT_KEYS masked
67
+ ```
68
+
69
+ - `safeStringify(value, options?, indent?)` and `safeSanitize(value, options?)` never
70
+ throw. Cycles become `"[Circular]"`, errors are expanded to `name`, `message`, `stack`
71
+ and their own enumerable properties, and values JSON cannot hold are replaced by a
72
+ readable marker (`"[bigint]"`, `"[Function]"`, `"[Invalid Date]"`, `"[Binary Data]"`,
73
+ `"[Stream]"`). A `Set<string>` in place of `options` is read as `excludeKeys`.
74
+ `safeStringify(undefined)` returns the string `'undefined'`.
75
+ - **They redact nothing unless you pass `redactKeys`.** `redactValue(value, keys?)`
76
+ applies `DEFAULT_REDACT_KEYS` by default and keeps rich types (`mode: 'clone'`).
77
+ - `SanitizeOptions`:
78
+
79
+ | Option | Effect |
80
+ | --- | --- |
81
+ | `excludeKeys` | keys or dot-paths removed from the output; exact, case-sensitive match |
82
+ | `redactKeys` | keys or dot-paths masked; matched ignoring case, `_` and `-`, so `refreshToken` also masks `refresh_token` |
83
+ | `redactReplacement` | the mask; default `REDACTED` (`"[REDACTED]"`) |
84
+ | `mode` | `'json'` (default) turns rich types into JSON-friendly values; `'clone'` deep-clones them keeping their classes |
85
+
86
+ - Exclusion and redaction, with a key matched at any depth or a dot-path matched at one
87
+ place only:
88
+
89
+ ```typescript
90
+ safeStringify(
91
+ { user: { profile: { email: 'x' }, token: 't' } },
92
+ { excludeKeys: ['user.profile'], redactKeys: ['token'], redactReplacement: '***' },
93
+ );
94
+ // '{"user":{"token":"***"}}'
95
+
96
+ safeStringify({ when: new Date(0), tags: new Set(['a']), limits: new Map([['k', 1]]) });
97
+ // '{"when":"1970-01-01T00:00:00.000Z","tags":["a"],"limits":{"k":1}}'
98
+ ```
99
+
100
+ - `DEFAULT_REDACT_KEYS` lists common secret names (passwords, tokens, API keys,
101
+ authorization headers, cookies, card data). No list recognizes every secret: add your
102
+ application's own names.
103
+
104
+ ## Key segments
105
+
106
+ ```typescript
107
+ import {
108
+ ABSENT_SEGMENT,
109
+ escapeKeySegment,
110
+ joinKeySegments,
111
+ } from '@cqrs-ddd/safe-stringify';
112
+
113
+ joinKeySegments(['cache', 'tenant:a', undefined, 'user']);
114
+ // 'cache:tenant\\:a:\\-:user'
115
+ ```
116
+
117
+ - `joinKeySegments(segments)` escapes each segment and joins them with `:`.
118
+ `undefined` and `null` become `ABSENT_SEGMENT` (`\-`), so `['a', undefined]` and
119
+ `['a', '']` produce different keys.
120
+ - `escapeKeySegment(value)` escapes `\` first, then `:`. A segment that is literally
121
+ `\-` is written `\\-`, so it never collides with an absent segment.
122
+
123
+ Build a cache key from a tenant, a request name and a strict fingerprint of the payload:
124
+
125
+ ```typescript
126
+ import { joinKeySegments, stableStringify } from '@cqrs-ddd/safe-stringify';
127
+ import { createHash } from 'node:crypto';
128
+
129
+ function cacheKey(tenantId: string | undefined, name: string, payload: unknown): string {
130
+ const fingerprint = createHash('sha256').update(stableStringify(payload)).digest('hex');
131
+ return joinKeySegments(['cache', tenantId, name, fingerprint]);
132
+ }
133
+ ```
134
+
135
+ The strict serializer's output and these key formats are part of the package contract:
136
+ changing them would orphan every stored cache entry, rate-limit bucket and idempotency
137
+ fingerprint. The package's golden-output specs pin them.
138
+
139
+ ## Migrating from @nestjs-pipeline/core 0.1.x
140
+
141
+ `@nestjs-pipeline/core` 0.1.x did not export any serializer or key-segment helper from its
142
+ entry point: `safeStringify` and `safeSanitize` lived in its internal
143
+ `dist/helpers/safeStringify` module, and `stableStringify`, `toStrictJsonValue`,
144
+ `redactValue`, `DEFAULT_REDACT_KEYS`, `REDACTED` and the key-segment helpers did not exist.
145
+ Code that deep-imported the internal module moves to this package:
146
+
147
+ ```typescript
148
+ // Before (0.1.x, an internal path)
149
+ import { safeSanitize, safeStringify } from '@nestjs-pipeline/core/dist/helpers/safeStringify';
150
+
151
+ safeStringify(value, new Set(['token', 'ctx.sessionUser']), 2);
152
+
153
+ // After (0.2.0)
154
+ import { safeSanitize, safeStringify } from '@cqrs-ddd/safe-stringify';
155
+
156
+ safeStringify(value, { excludeKeys: ['token', 'ctx.sessionUser'] }, 2);
157
+ ```
158
+
159
+ - The `Set<string>` second argument is still accepted and means `excludeKeys`.
160
+ - `safeStringify(undefined)` now returns `'undefined'` instead of `undefined`.
161
+ - Use `stableStringify`, not `safeStringify`, for any key or fingerprint you build.
162
+
163
+ ## License
164
+
165
+ Dual-licensed under **AGPLv3** and a **Commercial License**. See the root
166
+ [`LICENSE`](https://github.com/aristoteliss/nestjs-pipeline/blob/master/LICENSE) and
167
+ [`COMMERCIAL_LICENSE.txt`](https://github.com/aristoteliss/nestjs-pipeline/blob/master/COMMERCIAL_LICENSE.txt)
168
+ for details.
@@ -0,0 +1,4 @@
1
+ export { ABSENT_SEGMENT, escapeKeySegment, joinKeySegments, } from './key-segment';
2
+ export * from './safeStringify';
3
+ export * from './stableStringify';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
16
+ };
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.joinKeySegments = exports.escapeKeySegment = exports.ABSENT_SEGMENT = void 0;
19
+ var key_segment_1 = require("./key-segment");
20
+ Object.defineProperty(exports, "ABSENT_SEGMENT", { enumerable: true, get: function () { return key_segment_1.ABSENT_SEGMENT; } });
21
+ Object.defineProperty(exports, "escapeKeySegment", { enumerable: true, get: function () { return key_segment_1.escapeKeySegment; } });
22
+ Object.defineProperty(exports, "joinKeySegments", { enumerable: true, get: function () { return key_segment_1.joinKeySegments; } });
23
+ __exportStar(require("./safeStringify"), exports);
24
+ __exportStar(require("./stableStringify"), exports);
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,sEAAsE;;;;;;;;;;;;;;;;;AAEtE,6CAIuB;AAHrB,6GAAA,cAAc,OAAA;AACd,+GAAA,gBAAgB,OAAA;AAChB,8GAAA,eAAe,OAAA;AAEjB,kDAAgC;AAChC,oDAAkC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Marker written in place of a segment whose value is absent.
3
+ *
4
+ * Deliberately `\-` rather than a control character, so a key can be stored in
5
+ * a PostgreSQL `text` column, which rejects `U+0000`. It is also unambiguous — escaping a real
6
+ * value only ever produces `\\` or `\:`, so no genuine segment can render as
7
+ * `\-`, and a literal `\-` inside a value escapes to `\\-`.
8
+ */
9
+ export declare const ABSENT_SEGMENT = "\\-";
10
+ /**
11
+ * Escapes the structural characters of a `:`-joined composite key.
12
+ *
13
+ * Joining raw values with `:` makes two different tuples collapse into one key
14
+ * whenever a value contains the separator:
15
+ *
16
+ * ```text
17
+ * ['a:b', 'c'] → "a:b:c"
18
+ * ['a', 'b:c'] → "a:b:c" ← same key, different callers
19
+ * ```
20
+ *
21
+ * Identifiers that reach a key are routinely email addresses, external subject
22
+ * claims and composite IDs. A collision means two callers share a rate-limit
23
+ * bucket or a cache entry.
24
+ *
25
+ * The backslash is escaped first so that an input already containing one cannot
26
+ * be used to forge a separator.
27
+ */
28
+ export declare function escapeKeySegment(value: string): string;
29
+ /**
30
+ * Joins segments into one key, escaping each so distinct tuples stay distinct.
31
+ *
32
+ * `undefined` and `null` become {@link ABSENT_SEGMENT} rather than being dropped,
33
+ * so a tuple with a missing value cannot collapse onto a shorter tuple that never
34
+ * had that position at all. Omitting an absent tenant, for example, would make a
35
+ * tenant-less key identical to the key of a single-tenant deployment.
36
+ */
37
+ export declare function joinKeySegments(segments: readonly (string | undefined | null)[]): string;
38
+ //# sourceMappingURL=key-segment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-segment.d.ts","sourceRoot":"","sources":["../src/key-segment.ts"],"names":[],"mappings":"AAKA;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,QAAQ,CAAC;AAEpC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC,EAAE,GAC/C,MAAM,CAQR"}
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.ABSENT_SEGMENT = void 0;
5
+ exports.escapeKeySegment = escapeKeySegment;
6
+ exports.joinKeySegments = joinKeySegments;
7
+ /** Character that separates the segments of a composite key. */
8
+ const SEGMENT_SEPARATOR = ':';
9
+ /**
10
+ * Marker written in place of a segment whose value is absent.
11
+ *
12
+ * Deliberately `\-` rather than a control character, so a key can be stored in
13
+ * a PostgreSQL `text` column, which rejects `U+0000`. It is also unambiguous — escaping a real
14
+ * value only ever produces `\\` or `\:`, so no genuine segment can render as
15
+ * `\-`, and a literal `\-` inside a value escapes to `\\-`.
16
+ */
17
+ exports.ABSENT_SEGMENT = '\\-';
18
+ /**
19
+ * Escapes the structural characters of a `:`-joined composite key.
20
+ *
21
+ * Joining raw values with `:` makes two different tuples collapse into one key
22
+ * whenever a value contains the separator:
23
+ *
24
+ * ```text
25
+ * ['a:b', 'c'] → "a:b:c"
26
+ * ['a', 'b:c'] → "a:b:c" ← same key, different callers
27
+ * ```
28
+ *
29
+ * Identifiers that reach a key are routinely email addresses, external subject
30
+ * claims and composite IDs. A collision means two callers share a rate-limit
31
+ * bucket or a cache entry.
32
+ *
33
+ * The backslash is escaped first so that an input already containing one cannot
34
+ * be used to forge a separator.
35
+ */
36
+ function escapeKeySegment(value) {
37
+ return value.replace(/\\/g, '\\\\').replace(/:/g, '\\:');
38
+ }
39
+ /**
40
+ * Joins segments into one key, escaping each so distinct tuples stay distinct.
41
+ *
42
+ * `undefined` and `null` become {@link ABSENT_SEGMENT} rather than being dropped,
43
+ * so a tuple with a missing value cannot collapse onto a shorter tuple that never
44
+ * had that position at all. Omitting an absent tenant, for example, would make a
45
+ * tenant-less key identical to the key of a single-tenant deployment.
46
+ */
47
+ function joinKeySegments(segments) {
48
+ return segments
49
+ .map((segment) => segment === undefined || segment === null
50
+ ? exports.ABSENT_SEGMENT
51
+ : escapeKeySegment(segment))
52
+ .join(SEGMENT_SEPARATOR);
53
+ }
54
+ //# sourceMappingURL=key-segment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-segment.js","sourceRoot":"","sources":["../src/key-segment.ts"],"names":[],"mappings":";AAAA,sEAAsE;;;AAiCtE,4CAEC;AAUD,0CAUC;AArDD,gEAAgE;AAChE,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAE9B;;;;;;;GAOG;AACU,QAAA,cAAc,GAAG,KAAK,CAAC;AAEpC;;;;;;;;;;;;;;;;;GAiBG;AACH,SAAgB,gBAAgB,CAAC,KAAa;IAC5C,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,eAAe,CAC7B,QAAgD;IAEhD,OAAO,QAAQ;SACZ,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CACf,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,IAAI;QACvC,CAAC,CAAC,sBAAc;QAChB,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAC9B;SACA,IAAI,CAAC,iBAAiB,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,47 @@
1
+ /** Placeholder substituted for the value of a redacted field. */
2
+ export declare const REDACTED = "[REDACTED]";
3
+ /**
4
+ * Commonly sensitive field names. Redaction matches a key ignoring case, `_`
5
+ * and `-`, so `refreshToken` also masks `refresh_token` and `REFRESH-TOKEN`;
6
+ * a name list cannot recognize every secret, so add application-specific
7
+ * names through `redactKeys`.
8
+ */
9
+ export declare const DEFAULT_REDACT_KEYS: readonly string[];
10
+ export interface SanitizeOptions {
11
+ /** Keys or dot-paths to completely exclude from the output (exact, case-sensitive match). */
12
+ excludeKeys?: Set<string> | readonly string[];
13
+ /** Keys or dot-paths to mask with {@link REDACTED}, matched ignoring case, `_` and `-`. */
14
+ redactKeys?: Set<string> | readonly string[];
15
+ /** Replacement string for redacted fields. Default: {@link REDACTED}. */
16
+ redactReplacement?: string;
17
+ /**
18
+ * Sanitization mode:
19
+ * - 'json': normalizes rich types (Date, Map, Set, Error) to JSON-friendly primitives/objects.
20
+ * - 'clone': deeply clones Date, RegExp, Map, Set, and binary values with their classes. An
21
+ * Error becomes a plain `Error` carrying its name, message, stack, and own enumerable
22
+ * properties; any other object becomes a plain object.
23
+ */
24
+ mode?: 'json' | 'clone';
25
+ }
26
+ /**
27
+ * Sanitizes a value for structured logging or general processing: handles
28
+ * circular references, non-serializable types, exclusion, and redaction.
29
+ */
30
+ export declare function safeSanitize(value: unknown, options?: SanitizeOptions | Set<string>): unknown;
31
+ /**
32
+ * Safely converts a value to a JSON string, handling circular references,
33
+ * non-serializable types, and optional key exclusion/redaction.
34
+ */
35
+ export declare function safeStringify(value: unknown, options?: SanitizeOptions | Set<string>, indent?: number): string;
36
+ /**
37
+ * Returns a copy of `value` with the given keys masked with {@link REDACTED}.
38
+ *
39
+ * Copies in the `'clone'` mode of {@link SanitizeOptions}: Date, RegExp, Map,
40
+ * Set, and binary values keep their classes; an Error becomes a plain `Error`
41
+ * with the same name, message, stack, and own enumerable properties; any other
42
+ * object becomes a plain object. A cyclic reference becomes `'[Circular]'`,
43
+ * streams and Multer files become placeholder strings, and functions are
44
+ * returned by reference.
45
+ */
46
+ export declare function redactValue(value: unknown, keys?: readonly string[]): unknown;
47
+ //# sourceMappingURL=safeStringify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safeStringify.d.ts","sourceRoot":"","sources":["../src/safeStringify.ts"],"names":[],"mappings":"AAEA,iEAAiE;AACjE,eAAO,MAAM,QAAQ,eAAe,CAAC;AAErC;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAuBhD,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B,6FAA6F;IAC7F,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,MAAM,EAAE,CAAC;IAC9C,2FAA2F;IAC3F,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,MAAM,EAAE,CAAC;IAC7C,yEAAyE;IACzE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACzB;AAwPD;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,eAAe,GAAG,GAAG,CAAC,MAAM,CAAC,GACtC,OAAO,CAGT;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,eAAe,GAAG,GAAG,CAAC,MAAM,CAAC,EACvC,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAGR;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,OAAO,EACd,IAAI,GAAE,SAAS,MAAM,EAAwB,GAC5C,OAAO,CAKT"}
@@ -0,0 +1,259 @@
1
+ "use strict";
2
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.DEFAULT_REDACT_KEYS = exports.REDACTED = void 0;
5
+ exports.safeSanitize = safeSanitize;
6
+ exports.safeStringify = safeStringify;
7
+ exports.redactValue = redactValue;
8
+ /** Placeholder substituted for the value of a redacted field. */
9
+ exports.REDACTED = '[REDACTED]';
10
+ /**
11
+ * Commonly sensitive field names. Redaction matches a key ignoring case, `_`
12
+ * and `-`, so `refreshToken` also masks `refresh_token` and `REFRESH-TOKEN`;
13
+ * a name list cannot recognize every secret, so add application-specific
14
+ * names through `redactKeys`.
15
+ */
16
+ exports.DEFAULT_REDACT_KEYS = [
17
+ 'password',
18
+ 'passwordHash',
19
+ 'pass',
20
+ 'pwd',
21
+ 'token',
22
+ 'accessToken',
23
+ 'refreshToken',
24
+ 'idToken',
25
+ 'sessionToken',
26
+ 'secret',
27
+ 'clientSecret',
28
+ 'privateKey',
29
+ 'apiKey',
30
+ 'xApiKey',
31
+ 'authorization',
32
+ 'proxyAuthorization',
33
+ 'cookie',
34
+ 'setCookie',
35
+ 'ssn',
36
+ 'creditCard',
37
+ 'cardNumber',
38
+ 'cvv',
39
+ ];
40
+ const SAFE_PRIMITIVES = new Set(['string', 'number', 'boolean']);
41
+ function buildMatchers(options) {
42
+ const flatExclude = new Set();
43
+ const pathExclude = new Set();
44
+ const flatRedact = new Set();
45
+ const pathRedact = new Set();
46
+ let redactReplacement = exports.REDACTED;
47
+ let mode = 'json';
48
+ if (options instanceof Set)
49
+ options = { excludeKeys: options };
50
+ if (options) {
51
+ if (options.mode)
52
+ mode = options.mode;
53
+ if (options.redactReplacement)
54
+ redactReplacement = options.redactReplacement;
55
+ if (options.excludeKeys) {
56
+ for (const key of options.excludeKeys) {
57
+ if (key.includes('.'))
58
+ pathExclude.add(key);
59
+ else
60
+ flatExclude.add(key);
61
+ }
62
+ }
63
+ if (options.redactKeys) {
64
+ for (const key of options.redactKeys) {
65
+ const normalized = normalizeRedactKey(key);
66
+ if (normalized.includes('.'))
67
+ pathRedact.add(normalized);
68
+ else
69
+ flatRedact.add(normalized);
70
+ }
71
+ }
72
+ }
73
+ return {
74
+ flatExclude,
75
+ pathExclude,
76
+ flatRedact,
77
+ pathRedact,
78
+ redactReplacement,
79
+ mode,
80
+ };
81
+ }
82
+ function normalizeRedactKey(key) {
83
+ return key.toLowerCase().replace(/[_-]/g, '');
84
+ }
85
+ function isRedacted(key, path, matchers) {
86
+ return (matchers.flatRedact.has(normalizeRedactKey(key)) ||
87
+ matchers.pathRedact.has(normalizeRedactKey(path)));
88
+ }
89
+ function isExcluded(key, path, matchers) {
90
+ return matchers.flatExclude.has(key) || matchers.pathExclude.has(path);
91
+ }
92
+ function sanitizeValue(val, path, matchers, ancestors) {
93
+ if (val === null)
94
+ return null;
95
+ if (val === undefined)
96
+ return undefined;
97
+ // Primitives
98
+ if (typeof val !== 'object' && typeof val !== 'function') {
99
+ if (matchers.mode === 'clone')
100
+ return val;
101
+ const type = typeof val;
102
+ return SAFE_PRIMITIVES.has(type) ? val : `[${type}]`;
103
+ }
104
+ if (typeof val === 'function') {
105
+ return matchers.mode === 'clone' ? val : '[Function]';
106
+ }
107
+ // Circular reference guard
108
+ if (ancestors.has(val))
109
+ return '[Circular]';
110
+ ancestors.add(val);
111
+ try {
112
+ // 1. Date
113
+ if (val instanceof Date) {
114
+ if (matchers.mode === 'clone')
115
+ return new Date(val);
116
+ return Number.isNaN(val.getTime()) ? '[Invalid Date]' : val.toISOString();
117
+ }
118
+ // 2. RegExp
119
+ if (val instanceof RegExp) {
120
+ if (matchers.mode === 'clone') {
121
+ const clone = new RegExp(val.source, val.flags);
122
+ clone.lastIndex = val.lastIndex;
123
+ return clone;
124
+ }
125
+ return val.toString();
126
+ }
127
+ // 3. Error
128
+ if (val instanceof Error) {
129
+ if (matchers.mode === 'clone') {
130
+ const clone = new Error(val.message);
131
+ clone.name = val.name;
132
+ clone.stack = val.stack;
133
+ copyProperties(val, clone, path, matchers, ancestors);
134
+ return clone;
135
+ }
136
+ return { name: val.name, message: val.message, stack: val.stack };
137
+ }
138
+ // 4. Binary data
139
+ if ((typeof Buffer !== 'undefined' && Buffer.isBuffer(val)) ||
140
+ val instanceof ArrayBuffer ||
141
+ ArrayBuffer.isView(val)) {
142
+ if (matchers.mode === 'clone')
143
+ return structuredClone(val);
144
+ return '[Binary Data]';
145
+ }
146
+ // 5. Streams
147
+ if (typeof val.pipe === 'function' &&
148
+ typeof val.on === 'function') {
149
+ return '[Stream]';
150
+ }
151
+ // 6. Multer File
152
+ if ('originalname' in val &&
153
+ 'buffer' in val &&
154
+ Buffer.isBuffer(val.buffer)) {
155
+ return `[File: ${val.originalname}]`;
156
+ }
157
+ // 7. Map
158
+ if (val instanceof Map) {
159
+ if (matchers.mode === 'clone') {
160
+ const clone = new Map();
161
+ for (const [k, v] of val) {
162
+ const keyStr = typeof k === 'string' ? k : '';
163
+ const currentPath = path && keyStr ? `${path}.${keyStr}` : keyStr;
164
+ if (keyStr && isExcluded(keyStr, currentPath, matchers))
165
+ continue;
166
+ const isRedact = keyStr
167
+ ? isRedacted(keyStr, currentPath, matchers)
168
+ : false;
169
+ clone.set(sanitizeValue(k, path, matchers, ancestors), isRedact
170
+ ? matchers.redactReplacement
171
+ : sanitizeValue(v, currentPath, matchers, ancestors));
172
+ }
173
+ return clone;
174
+ }
175
+ return sanitizeValue(Object.fromEntries(val), path, matchers, ancestors);
176
+ }
177
+ // 8. Set
178
+ if (val instanceof Set) {
179
+ if (matchers.mode === 'clone') {
180
+ return new Set(Array.from(val, (item) => sanitizeValue(item, path, matchers, ancestors)));
181
+ }
182
+ return sanitizeValue([...val], path, matchers, ancestors);
183
+ }
184
+ // 9. Array
185
+ if (Array.isArray(val)) {
186
+ return val.map((item) => sanitizeValue(item, path, matchers, ancestors));
187
+ }
188
+ // 10. Generic Object
189
+ const out = matchers.mode === 'json' ? Object.create(null) : {};
190
+ copyProperties(val, out, path, matchers, ancestors);
191
+ return out;
192
+ }
193
+ finally {
194
+ ancestors.delete(val);
195
+ }
196
+ }
197
+ function copyProperties(source, target, path, matchers, ancestors) {
198
+ for (const [k, v] of Object.entries(source)) {
199
+ const currentPath = path ? `${path}.${k}` : k;
200
+ if (isExcluded(k, currentPath, matchers)) {
201
+ continue;
202
+ }
203
+ const valOut = isRedacted(k, currentPath, matchers)
204
+ ? matchers.redactReplacement
205
+ : sanitizeValue(v, currentPath, matchers, ancestors);
206
+ Object.defineProperty(target, k, {
207
+ value: valOut,
208
+ writable: true,
209
+ enumerable: true,
210
+ configurable: true,
211
+ });
212
+ }
213
+ if (matchers.mode !== 'clone')
214
+ return;
215
+ for (const sym of Object.getOwnPropertySymbols(source)) {
216
+ const descriptor = Object.getOwnPropertyDescriptor(source, sym);
217
+ if (!descriptor?.enumerable)
218
+ continue;
219
+ Object.defineProperty(target, sym, {
220
+ value: sanitizeValue(Reflect.get(source, sym), path, matchers, ancestors),
221
+ writable: true,
222
+ enumerable: true,
223
+ configurable: true,
224
+ });
225
+ }
226
+ }
227
+ /**
228
+ * Sanitizes a value for structured logging or general processing: handles
229
+ * circular references, non-serializable types, exclusion, and redaction.
230
+ */
231
+ function safeSanitize(value, options) {
232
+ const matchers = buildMatchers(options);
233
+ return sanitizeValue(value, '', matchers, new WeakSet());
234
+ }
235
+ /**
236
+ * Safely converts a value to a JSON string, handling circular references,
237
+ * non-serializable types, and optional key exclusion/redaction.
238
+ */
239
+ function safeStringify(value, options, indent) {
240
+ const sanitized = safeSanitize(value, options);
241
+ return JSON.stringify(sanitized, undefined, indent) ?? 'undefined';
242
+ }
243
+ /**
244
+ * Returns a copy of `value` with the given keys masked with {@link REDACTED}.
245
+ *
246
+ * Copies in the `'clone'` mode of {@link SanitizeOptions}: Date, RegExp, Map,
247
+ * Set, and binary values keep their classes; an Error becomes a plain `Error`
248
+ * with the same name, message, stack, and own enumerable properties; any other
249
+ * object becomes a plain object. A cyclic reference becomes `'[Circular]'`,
250
+ * streams and Multer files become placeholder strings, and functions are
251
+ * returned by reference.
252
+ */
253
+ function redactValue(value, keys = exports.DEFAULT_REDACT_KEYS) {
254
+ return safeSanitize(value, {
255
+ redactKeys: keys,
256
+ mode: 'clone',
257
+ });
258
+ }
259
+ //# sourceMappingURL=safeStringify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safeStringify.js","sourceRoot":"","sources":["../src/safeStringify.ts"],"names":[],"mappings":"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"}