@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/COMMERCIAL_LICENSE.txt +34 -0
- package/LICENSE +661 -0
- package/README.md +168 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/key-segment.d.ts +38 -0
- package/dist/key-segment.d.ts.map +1 -0
- package/dist/key-segment.js +54 -0
- package/dist/key-segment.js.map +1 -0
- package/dist/safeStringify.d.ts +47 -0
- package/dist/safeStringify.d.ts.map +1 -0
- package/dist/safeStringify.js +259 -0
- package/dist/safeStringify.js.map +1 -0
- package/dist/stableStringify.d.ts +64 -0
- package/dist/stableStringify.d.ts.map +1 -0
- package/dist/stableStringify.js +149 -0
- package/dist/stableStringify.js.map +1 -0
- package/package.json +49 -0
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.
|
package/dist/index.d.ts
ADDED
|
@@ -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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"}
|