@cqrs-ddd/safe-stringify 0.4.0 → 0.4.2
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 +2 -148
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,16 +3,7 @@
|
|
|
3
3
|
Two JSON serializers with opposite goals, and the key-segment helpers that build
|
|
4
4
|
identity keys. No runtime dependencies and no framework.
|
|
5
5
|
|
|
6
|
-
|
|
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.
|
|
6
|
+
**Documentation:** [guide](https://aristoteliss.github.io/nestjs-pipeline/packages/cqrs-ddd/safe-stringify/) · [API reference](https://aristoteliss.github.io/nestjs-pipeline/api/cqrs-ddd/safe-stringify/) · [all packages](https://aristoteliss.github.io/nestjs-pipeline/)
|
|
16
7
|
|
|
17
8
|
## Installation
|
|
18
9
|
|
|
@@ -24,144 +15,7 @@ pnpm add @cqrs-ddd/safe-stringify
|
|
|
24
15
|
|
|
25
16
|
Requires Node.js 22.12 or later.
|
|
26
17
|
|
|
27
|
-
Published as an ES module; a CommonJS application loads it with `require()`. Coming from
|
|
28
|
-
0.3.x, see [Upgrading from 0.3.x](https://github.com/aristoteliss/nestjs-pipeline#upgrading-from-03x).
|
|
29
|
-
|
|
30
|
-
## Strict serializer
|
|
31
|
-
|
|
32
|
-
```typescript
|
|
33
|
-
import { stableStringify, toStrictJsonValue } from '@cqrs-ddd/safe-stringify';
|
|
34
|
-
|
|
35
|
-
stableStringify({ z: 1, a: { d: 2, c: new Date(0) } });
|
|
36
|
-
// '{"a":{"c":"1970-01-01T00:00:00.000Z","d":2},"z":1}'
|
|
37
|
-
|
|
38
|
-
toStrictJsonValue({ b: 2, a: 1 }, true); // { a: 1, b: 2 }, a null-prototype object
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
- Strings, booleans, `null` and finite numbers are kept. Dates become ISO-8601 strings.
|
|
42
|
-
An object with `toJSON()` is replaced by what that method returns.
|
|
43
|
-
- Everything else throws a `TypeError`: `undefined`, functions, `bigint`, symbols and
|
|
44
|
-
symbol-keyed properties, non-finite numbers, invalid dates, cycles, sparse arrays,
|
|
45
|
-
`Map`, `Set`, `WeakMap`, `WeakSet`, `Error`, `RegExp`, `Promise`, `ArrayBuffer` and
|
|
46
|
-
typed arrays.
|
|
47
|
-
- `stableStringify` wraps that failure in a `TypeError` whose `cause` is the original.
|
|
48
|
-
- Structurally equal values produce byte-identical strings, whatever their property
|
|
49
|
-
insertion order. Keys sort by UTF-16 code unit, as `Array.prototype.sort` does.
|
|
50
|
-
|
|
51
|
-
## Safe serializer
|
|
52
|
-
|
|
53
|
-
```typescript
|
|
54
|
-
import {
|
|
55
|
-
DEFAULT_REDACT_KEYS,
|
|
56
|
-
redactValue,
|
|
57
|
-
safeStringify,
|
|
58
|
-
} from '@cqrs-ddd/safe-stringify';
|
|
59
|
-
|
|
60
|
-
const payload: Record<string, unknown> = { user: 'jane', password: 'secret', amount: 10n };
|
|
61
|
-
payload.self = payload;
|
|
62
|
-
|
|
63
|
-
safeStringify(payload);
|
|
64
|
-
// '{"user":"jane","password":"secret","amount":"[bigint]","self":"[Circular]"}'
|
|
65
|
-
|
|
66
|
-
safeStringify(payload, { redactKeys: DEFAULT_REDACT_KEYS });
|
|
67
|
-
// '{"user":"jane","password":"[REDACTED]","amount":"[bigint]","self":"[Circular]"}'
|
|
68
|
-
|
|
69
|
-
redactValue(payload); // a deep clone with DEFAULT_REDACT_KEYS masked
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
- `safeStringify(value, options?, indent?)` and `safeSanitize(value, options?)` never
|
|
73
|
-
throw. Cycles become `"[Circular]"`, errors are expanded to `name`, `message`, `stack`
|
|
74
|
-
and their own enumerable properties, and values JSON cannot hold are replaced by a
|
|
75
|
-
readable marker (`"[bigint]"`, `"[Function]"`, `"[Invalid Date]"`, `"[Binary Data]"`,
|
|
76
|
-
`"[Stream]"`). A `Set<string>` in place of `options` is read as `excludeKeys`.
|
|
77
|
-
`safeStringify(undefined)` returns the string `'undefined'`.
|
|
78
|
-
- **They redact nothing unless you pass `redactKeys`.** `redactValue(value, keys?)`
|
|
79
|
-
applies `DEFAULT_REDACT_KEYS` by default and keeps rich types (`mode: 'clone'`).
|
|
80
|
-
- `SanitizeOptions`:
|
|
81
|
-
|
|
82
|
-
| Option | Effect |
|
|
83
|
-
| --- | --- |
|
|
84
|
-
| `excludeKeys` | keys or dot-paths removed from the output; exact, case-sensitive match |
|
|
85
|
-
| `redactKeys` | keys or dot-paths masked; matched ignoring case, `_` and `-`, so `refreshToken` also masks `refresh_token` |
|
|
86
|
-
| `redactReplacement` | the mask; default `REDACTED` (`"[REDACTED]"`) |
|
|
87
|
-
| `mode` | `'json'` (default) turns rich types into JSON-friendly values; `'clone'` deep-clones them keeping their classes |
|
|
88
|
-
|
|
89
|
-
- Exclusion and redaction, with a key matched at any depth or a dot-path matched at one
|
|
90
|
-
place only:
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
safeStringify(
|
|
94
|
-
{ user: { profile: { email: 'x' }, token: 't' } },
|
|
95
|
-
{ excludeKeys: ['user.profile'], redactKeys: ['token'], redactReplacement: '***' },
|
|
96
|
-
);
|
|
97
|
-
// '{"user":{"token":"***"}}'
|
|
98
|
-
|
|
99
|
-
safeStringify({ when: new Date(0), tags: new Set(['a']), limits: new Map([['k', 1]]) });
|
|
100
|
-
// '{"when":"1970-01-01T00:00:00.000Z","tags":["a"],"limits":{"k":1}}'
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
- `DEFAULT_REDACT_KEYS` lists common secret names (passwords, tokens, API keys,
|
|
104
|
-
authorization headers, cookies, card data). No list recognizes every secret: add your
|
|
105
|
-
application's own names.
|
|
106
|
-
|
|
107
|
-
## Key segments
|
|
108
|
-
|
|
109
|
-
```typescript
|
|
110
|
-
import {
|
|
111
|
-
ABSENT_SEGMENT,
|
|
112
|
-
escapeKeySegment,
|
|
113
|
-
joinKeySegments,
|
|
114
|
-
} from '@cqrs-ddd/safe-stringify';
|
|
115
|
-
|
|
116
|
-
joinKeySegments(['cache', 'tenant:a', undefined, 'user']);
|
|
117
|
-
// 'cache:tenant\\:a:\\-:user'
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
- `joinKeySegments(segments)` escapes each segment and joins them with `:`.
|
|
121
|
-
`undefined` and `null` become `ABSENT_SEGMENT` (`\-`), so `['a', undefined]` and
|
|
122
|
-
`['a', '']` produce different keys.
|
|
123
|
-
- `escapeKeySegment(value)` escapes `\` first, then `:`. A segment that is literally
|
|
124
|
-
`\-` is written `\\-`, so it never collides with an absent segment.
|
|
125
|
-
|
|
126
|
-
Build a cache key from a tenant, a request name and a strict fingerprint of the payload:
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
|
-
import { joinKeySegments, stableStringify } from '@cqrs-ddd/safe-stringify';
|
|
130
|
-
import { createHash } from 'node:crypto';
|
|
131
|
-
|
|
132
|
-
function cacheKey(tenantId: string | undefined, name: string, payload: unknown): string {
|
|
133
|
-
const fingerprint = createHash('sha256').update(stableStringify(payload)).digest('hex');
|
|
134
|
-
return joinKeySegments(['cache', tenantId, name, fingerprint]);
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
The strict serializer's output and these key formats are part of the package contract:
|
|
139
|
-
changing them would orphan every stored cache entry, rate-limit bucket and idempotency
|
|
140
|
-
fingerprint. The package's golden-output specs pin them.
|
|
141
|
-
|
|
142
|
-
## Migrating from @nestjs-pipeline/core 0.1.x
|
|
143
|
-
|
|
144
|
-
`@nestjs-pipeline/core` 0.1.x did not export any serializer or key-segment helper from its
|
|
145
|
-
entry point: `safeStringify` and `safeSanitize` lived in its internal
|
|
146
|
-
`dist/helpers/safeStringify` module, and `stableStringify`, `toStrictJsonValue`,
|
|
147
|
-
`redactValue`, `DEFAULT_REDACT_KEYS`, `REDACTED` and the key-segment helpers did not exist.
|
|
148
|
-
Code that deep-imported the internal module moves to this package:
|
|
149
|
-
|
|
150
|
-
```typescript
|
|
151
|
-
// Before (0.1.x, an internal path)
|
|
152
|
-
import { safeSanitize, safeStringify } from '@nestjs-pipeline/core/dist/helpers/safeStringify';
|
|
153
|
-
|
|
154
|
-
safeStringify(value, new Set(['token', 'ctx.sessionUser']), 2);
|
|
155
|
-
|
|
156
|
-
// After (0.2.0)
|
|
157
|
-
import { safeSanitize, safeStringify } from '@cqrs-ddd/safe-stringify';
|
|
158
|
-
|
|
159
|
-
safeStringify(value, { excludeKeys: ['token', 'ctx.sessionUser'] }, 2);
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
- The `Set<string>` second argument is still accepted and means `excludeKeys`.
|
|
163
|
-
- `safeStringify(undefined)` now returns `'undefined'` instead of `undefined`.
|
|
164
|
-
- Use `stableStringify`, not `safeStringify`, for any key or fingerprint you build.
|
|
18
|
+
Published as an ES module; a CommonJS application loads it with `require()`. Coming from 0.3.x, see [Upgrading from 0.3.x](https://aristoteliss.github.io/nestjs-pipeline/upgrading/from-0-3/).
|
|
165
19
|
|
|
166
20
|
## License
|
|
167
21
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cqrs-ddd/safe-stringify",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Deterministic JSON for identity (cache keys, fingerprints) and safe, redacting JSON for logs, with no dependencies and no framework.",
|
|
5
5
|
"author": "Aristotelis",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"url": "git+https://github.com/aristoteliss/nestjs-pipeline.git",
|
|
14
14
|
"directory": "packages/safe-stringify"
|
|
15
15
|
},
|
|
16
|
-
"homepage": "https://github.
|
|
16
|
+
"homepage": "https://aristoteliss.github.io/nestjs-pipeline/packages/cqrs-ddd/safe-stringify/",
|
|
17
17
|
"bugs": {
|
|
18
18
|
"url": "https://github.com/aristoteliss/nestjs-pipeline/issues"
|
|
19
19
|
},
|