ohbee-safe-json 1.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.
- package/README.md +257 -0
- package/dist/index.cjs +329 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +71 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.js +323 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# ohbee-safe-json
|
|
2
|
+
|
|
3
|
+
`JSON.stringify()` is fine for data.
|
|
4
|
+
|
|
5
|
+
It is not fine for production logs.
|
|
6
|
+
|
|
7
|
+
`ohbee-safe-json` safely serializes unknown JavaScript values by redacting secrets, handling circular references, limiting output size, and preserving useful error information.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { safeClone } from "ohbee-safe-json";
|
|
11
|
+
|
|
12
|
+
logger.error("Checkout failed", {
|
|
13
|
+
error: safeClone(error),
|
|
14
|
+
payload: safeClone(req.body),
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Why
|
|
21
|
+
|
|
22
|
+
| Problem with `JSON.stringify` | This library |
|
|
23
|
+
|---|---|
|
|
24
|
+
| Leaks `password`, `token`, `authorization` | Auto-redacts 16 sensitive keys by default |
|
|
25
|
+
| Throws on circular references | Detects and replaces with `[Circular]` |
|
|
26
|
+
| `new Error("x")` serializes as `{}` | Captures `name`, `message`, `cause`, custom fields |
|
|
27
|
+
| 50 MB payloads in logs | Bounds depth, array length, string length |
|
|
28
|
+
| Non-deterministic key order | Optional stable sort for diffs and snapshots |
|
|
29
|
+
| Everyone redacts differently | Shared presets: `log`, `debug`, `http`, `audit` |
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
npm install ohbee-safe-json
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Quick start
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { safeClone, safeStringify, presets, createSafeJson } from "ohbee-safe-json";
|
|
45
|
+
|
|
46
|
+
// clone to a safe plain object (use when your logger stringifies internally)
|
|
47
|
+
logger.info({ payload: safeClone(req.body) });
|
|
48
|
+
|
|
49
|
+
// or stringify directly
|
|
50
|
+
const line = safeStringify(req.body);
|
|
51
|
+
|
|
52
|
+
// use a preset
|
|
53
|
+
logger.error({ error: safeClone(err, presets.debug) });
|
|
54
|
+
|
|
55
|
+
// define policy once, reuse everywhere
|
|
56
|
+
const safeJson = createSafeJson({
|
|
57
|
+
redactKeys: ["email", "phone"],
|
|
58
|
+
maxDepth: 5,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
safeJson.forLog(payload);
|
|
62
|
+
safeJson.forDebug(error);
|
|
63
|
+
safeJson.forHttp(req.headers);
|
|
64
|
+
safeJson.forAudit(record);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Default redacted keys
|
|
70
|
+
|
|
71
|
+
These are masked by default (case-insensitive):
|
|
72
|
+
|
|
73
|
+
`password` · `pass` · `pwd` · `secret` · `token` · `accessToken` · `refreshToken` · `authorization` · `cookie` · `set-cookie` · `apiKey` · `privateKey` · `otp` · `pin` · `cardNumber` · `cvv`
|
|
74
|
+
|
|
75
|
+
Add more without removing defaults:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
safeClone(payload, { redactKeys: ["email", "phone"] });
|
|
79
|
+
// defaults are still active
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Redact by pattern:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
safeClone(payload, { redactByPattern: [/secret/i, /internal/i] });
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Options
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
type SafeJsonOptions = {
|
|
94
|
+
// Redaction
|
|
95
|
+
redactKeys?: string[]; // merged with defaults (case-insensitive)
|
|
96
|
+
redactByPattern?: RegExp[]; // redact keys matching a pattern
|
|
97
|
+
replacement?: string; // default: "[REDACTED]"
|
|
98
|
+
|
|
99
|
+
// Bounds
|
|
100
|
+
maxDepth?: number; // default: 6
|
|
101
|
+
maxArrayLength?: number; // default: 100
|
|
102
|
+
maxObjectKeys?: number; // default: 100
|
|
103
|
+
maxStringLength?: number; // default: 4000
|
|
104
|
+
|
|
105
|
+
// Error
|
|
106
|
+
includeErrorStack?: boolean; // default: false
|
|
107
|
+
|
|
108
|
+
// Output
|
|
109
|
+
stable?: boolean; // sort keys alphabetically, default: false
|
|
110
|
+
pretty?: boolean | number; // indent spaces, default: false
|
|
111
|
+
|
|
112
|
+
// Special types
|
|
113
|
+
handleBigInt?: "string" | "number" | "redact"; // default: "string"
|
|
114
|
+
handleFunction?: "omit" | "name" | "placeholder"; // default: "omit"
|
|
115
|
+
handleSymbol?: "omit" | "description" | "placeholder"; // default: "omit"
|
|
116
|
+
onCircular?: "placeholder" | "path"; // default: "placeholder"
|
|
117
|
+
};
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Presets
|
|
123
|
+
|
|
124
|
+
Composable plain objects:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { presets } from "ohbee-safe-json";
|
|
128
|
+
|
|
129
|
+
safeClone(value, presets.log);
|
|
130
|
+
safeClone(value, presets.debug);
|
|
131
|
+
safeClone(value, presets.http);
|
|
132
|
+
safeClone(value, presets.audit);
|
|
133
|
+
|
|
134
|
+
// composable
|
|
135
|
+
safeClone(value, { ...presets.log, maxDepth: 10 });
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
| Preset | maxDepth | maxArrayLength | maxStringLength | stack | stable |
|
|
139
|
+
|---|---|---|---|---|---|
|
|
140
|
+
| `log` | 5 | 50 | 2000 | no | no |
|
|
141
|
+
| `debug` | 8 | 200 | 8000 | yes | no |
|
|
142
|
+
| `http` | 5 | 50 | 2000 | no | no |
|
|
143
|
+
| `audit` | 5 | 100 | 4000 | no | yes |
|
|
144
|
+
|
|
145
|
+
`http` also redacts `headers`, `cookie`, `set-cookie`, `authorization`.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## API
|
|
150
|
+
|
|
151
|
+
### `safeClone(value, options?)`
|
|
152
|
+
|
|
153
|
+
Returns a safe plain JS value. Use when your logger accepts objects.
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
logger.info({ payload: safeClone(req.body) });
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### `safeStringify(value, options?)`
|
|
160
|
+
|
|
161
|
+
Returns a JSON string. Use when you need a string directly.
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
const line = safeStringify(req.body, { pretty: 2 });
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### `redact(value, options?)`
|
|
168
|
+
|
|
169
|
+
Focused wrapper for key masking.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
const clean = redact(config, { keys: ["connectionString", "jwtSecret"] });
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### `createSafeJson(baseOptions?)`
|
|
176
|
+
|
|
177
|
+
Factory — define policy once, reuse everywhere.
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
const safeJson = createSafeJson({
|
|
181
|
+
redactKeys: ["email"],
|
|
182
|
+
maxDepth: 5,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
safeJson.stringify(value);
|
|
186
|
+
safeJson.clone(value);
|
|
187
|
+
safeJson.forLog(value);
|
|
188
|
+
safeJson.forDebug(value);
|
|
189
|
+
safeJson.forHttp(value);
|
|
190
|
+
safeJson.forAudit(value);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Type handling
|
|
196
|
+
|
|
197
|
+
| Type | Default output |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `Date` | `"2024-01-01T00:00:00.000Z"` (ISO string) |
|
|
200
|
+
| `Error` | `{ name, message, cause?, code?, ...custom }` |
|
|
201
|
+
| `Map` | plain object |
|
|
202
|
+
| `Set` | array |
|
|
203
|
+
| `BigInt` | string (e.g. `"9007199254740993"`) |
|
|
204
|
+
| `Function` | omitted |
|
|
205
|
+
| `Symbol` | omitted |
|
|
206
|
+
| `undefined` | omitted (same as native JSON) |
|
|
207
|
+
| Circular ref | `"[Circular]"` |
|
|
208
|
+
| Depth exceeded | `"[MaxDepth]"` |
|
|
209
|
+
| Array truncated | `["...items...", "[Truncated N more items]"]` |
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Examples
|
|
214
|
+
|
|
215
|
+
### Express / NestJS request logging
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
app.use((req, res, next) => {
|
|
219
|
+
logger.info("Incoming request", {
|
|
220
|
+
method: req.method,
|
|
221
|
+
url: req.url,
|
|
222
|
+
headers: safeClone(req.headers, presets.http),
|
|
223
|
+
body: safeClone(req.body, presets.log),
|
|
224
|
+
});
|
|
225
|
+
next();
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Error logging
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
try {
|
|
233
|
+
await paymentService.charge(input);
|
|
234
|
+
} catch (err) {
|
|
235
|
+
logger.error("Payment failed", {
|
|
236
|
+
error: safeClone(err, presets.debug),
|
|
237
|
+
input: safeClone(input, presets.log),
|
|
238
|
+
});
|
|
239
|
+
throw err;
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Config debug
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
logger.info("Config loaded", {
|
|
247
|
+
config: safeClone(config, {
|
|
248
|
+
redactKeys: ["connectionString", "jwtSecret", "apiKey"],
|
|
249
|
+
}),
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## License
|
|
256
|
+
|
|
257
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/utils/is-plain-object.ts
|
|
4
|
+
function isPlainObject(value) {
|
|
5
|
+
if (typeof value !== "object" || value === null) return false;
|
|
6
|
+
const proto = Object.getPrototypeOf(value);
|
|
7
|
+
return proto === Object.prototype || proto === null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// src/utils/stable-sort.ts
|
|
11
|
+
function sortedKeys(obj) {
|
|
12
|
+
return Object.keys(obj).sort();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/safe-clone.ts
|
|
16
|
+
var DEFAULT_REDACT_KEYS = [
|
|
17
|
+
"password",
|
|
18
|
+
"pass",
|
|
19
|
+
"pwd",
|
|
20
|
+
"secret",
|
|
21
|
+
"token",
|
|
22
|
+
"accesstoken",
|
|
23
|
+
"refreshtoken",
|
|
24
|
+
"authorization",
|
|
25
|
+
"cookie",
|
|
26
|
+
"set-cookie",
|
|
27
|
+
"apikey",
|
|
28
|
+
"privatekey",
|
|
29
|
+
"otp",
|
|
30
|
+
"pin",
|
|
31
|
+
"cardnumber",
|
|
32
|
+
"cvv"
|
|
33
|
+
];
|
|
34
|
+
var DEFAULTS = {
|
|
35
|
+
redactKeys: DEFAULT_REDACT_KEYS,
|
|
36
|
+
redactByPattern: [],
|
|
37
|
+
replacement: "[REDACTED]",
|
|
38
|
+
maxDepth: 6,
|
|
39
|
+
maxArrayLength: 100,
|
|
40
|
+
maxObjectKeys: 100,
|
|
41
|
+
maxStringLength: 4e3,
|
|
42
|
+
includeErrorStack: false,
|
|
43
|
+
stable: false,
|
|
44
|
+
pretty: false,
|
|
45
|
+
handleBigInt: "string",
|
|
46
|
+
handleFunction: "omit",
|
|
47
|
+
handleSymbol: "omit",
|
|
48
|
+
onCircular: "placeholder"
|
|
49
|
+
};
|
|
50
|
+
function resolveOptions(options) {
|
|
51
|
+
if (!options) return DEFAULTS;
|
|
52
|
+
const userKeys = options.redactKeys?.map((k) => k.toLowerCase()) ?? [];
|
|
53
|
+
const mergedKeys = [...DEFAULT_REDACT_KEYS, ...userKeys];
|
|
54
|
+
return {
|
|
55
|
+
redactKeys: mergedKeys,
|
|
56
|
+
redactByPattern: options.redactByPattern ?? DEFAULTS.redactByPattern,
|
|
57
|
+
replacement: options.replacement ?? DEFAULTS.replacement,
|
|
58
|
+
maxDepth: options.maxDepth ?? DEFAULTS.maxDepth,
|
|
59
|
+
maxArrayLength: options.maxArrayLength ?? DEFAULTS.maxArrayLength,
|
|
60
|
+
maxObjectKeys: options.maxObjectKeys ?? DEFAULTS.maxObjectKeys,
|
|
61
|
+
maxStringLength: options.maxStringLength ?? DEFAULTS.maxStringLength,
|
|
62
|
+
includeErrorStack: options.includeErrorStack ?? DEFAULTS.includeErrorStack,
|
|
63
|
+
stable: options.stable ?? DEFAULTS.stable,
|
|
64
|
+
pretty: options.pretty ?? DEFAULTS.pretty,
|
|
65
|
+
handleBigInt: options.handleBigInt ?? DEFAULTS.handleBigInt,
|
|
66
|
+
handleFunction: options.handleFunction ?? DEFAULTS.handleFunction,
|
|
67
|
+
handleSymbol: options.handleSymbol ?? DEFAULTS.handleSymbol,
|
|
68
|
+
onCircular: options.onCircular ?? DEFAULTS.onCircular
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function isRedactedKey(key, opts) {
|
|
72
|
+
const lower = key.toLowerCase();
|
|
73
|
+
if (opts.redactKeys.includes(lower)) return true;
|
|
74
|
+
for (const pattern of opts.redactByPattern) {
|
|
75
|
+
if (pattern.test(key)) return true;
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
function serializeError(err, opts, seen, depth) {
|
|
80
|
+
const result = {
|
|
81
|
+
name: err.name,
|
|
82
|
+
message: err.message
|
|
83
|
+
};
|
|
84
|
+
if (opts.includeErrorStack && err.stack) {
|
|
85
|
+
result["stack"] = err.stack.length > opts.maxStringLength ? err.stack.slice(0, opts.maxStringLength) + "...[Truncated]" : err.stack;
|
|
86
|
+
}
|
|
87
|
+
if (err.cause !== void 0) {
|
|
88
|
+
result["cause"] = walk(err.cause, opts, seen, depth + 1);
|
|
89
|
+
}
|
|
90
|
+
for (const key of Object.keys(err)) {
|
|
91
|
+
if (key in result) continue;
|
|
92
|
+
if (isRedactedKey(key, opts)) {
|
|
93
|
+
result[key] = opts.replacement;
|
|
94
|
+
} else {
|
|
95
|
+
result[key] = walk(
|
|
96
|
+
err[key],
|
|
97
|
+
opts,
|
|
98
|
+
seen,
|
|
99
|
+
depth + 1
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
function walk(value, opts, seen, depth) {
|
|
106
|
+
if (value === null) return null;
|
|
107
|
+
if (value === void 0) return void 0;
|
|
108
|
+
if (typeof value === "boolean" || typeof value === "number") return value;
|
|
109
|
+
if (typeof value === "string") {
|
|
110
|
+
if (value.length > opts.maxStringLength) {
|
|
111
|
+
return value.slice(0, opts.maxStringLength) + "...[Truncated]";
|
|
112
|
+
}
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
if (typeof value === "bigint") {
|
|
116
|
+
if (opts.handleBigInt === "redact") return opts.replacement;
|
|
117
|
+
if (opts.handleBigInt === "number") return Number(value);
|
|
118
|
+
return value.toString();
|
|
119
|
+
}
|
|
120
|
+
if (typeof value === "function") {
|
|
121
|
+
if (opts.handleFunction === "omit") return void 0;
|
|
122
|
+
if (opts.handleFunction === "name")
|
|
123
|
+
return value.name ? `[Function: ${value.name}]` : "[Function]";
|
|
124
|
+
return "[Function]";
|
|
125
|
+
}
|
|
126
|
+
if (typeof value === "symbol") {
|
|
127
|
+
if (opts.handleSymbol === "omit") return void 0;
|
|
128
|
+
if (opts.handleSymbol === "description")
|
|
129
|
+
return value.description ?? "[Symbol]";
|
|
130
|
+
return "[Symbol]";
|
|
131
|
+
}
|
|
132
|
+
if (depth > opts.maxDepth) return "[MaxDepth]";
|
|
133
|
+
if (value instanceof Date) return value.toISOString();
|
|
134
|
+
if (value instanceof Error) {
|
|
135
|
+
if (seen.has(value)) {
|
|
136
|
+
return opts.onCircular === "placeholder" ? "[Circular]" : "[Circular]";
|
|
137
|
+
}
|
|
138
|
+
seen.add(value);
|
|
139
|
+
const result = serializeError(value, opts, seen, depth);
|
|
140
|
+
seen.delete(value);
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
if (value instanceof Map) {
|
|
144
|
+
if (seen.has(value)) return "[Circular]";
|
|
145
|
+
seen.add(value);
|
|
146
|
+
const obj = {};
|
|
147
|
+
let keyCount = 0;
|
|
148
|
+
for (const [k, v] of value) {
|
|
149
|
+
if (keyCount >= opts.maxObjectKeys) break;
|
|
150
|
+
const keyStr = String(k);
|
|
151
|
+
obj[keyStr] = isRedactedKey(keyStr, opts) ? opts.replacement : walk(v, opts, seen, depth + 1);
|
|
152
|
+
keyCount++;
|
|
153
|
+
}
|
|
154
|
+
seen.delete(value);
|
|
155
|
+
return obj;
|
|
156
|
+
}
|
|
157
|
+
if (value instanceof Set) {
|
|
158
|
+
if (seen.has(value)) return "[Circular]";
|
|
159
|
+
seen.add(value);
|
|
160
|
+
const arr = [...value].slice(0, opts.maxArrayLength);
|
|
161
|
+
const result = arr.map((v) => walk(v, opts, seen, depth + 1));
|
|
162
|
+
if (value.size > opts.maxArrayLength) {
|
|
163
|
+
result.push(`[Truncated ${value.size - opts.maxArrayLength} more items]`);
|
|
164
|
+
}
|
|
165
|
+
seen.delete(value);
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
if (Array.isArray(value)) {
|
|
169
|
+
if (seen.has(value)) return "[Circular]";
|
|
170
|
+
seen.add(value);
|
|
171
|
+
const slice = value.slice(0, opts.maxArrayLength);
|
|
172
|
+
const result = slice.map((v) => walk(v, opts, seen, depth + 1));
|
|
173
|
+
if (value.length > opts.maxArrayLength) {
|
|
174
|
+
result.push(`[Truncated ${value.length - opts.maxArrayLength} more items]`);
|
|
175
|
+
}
|
|
176
|
+
seen.delete(value);
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
if (isPlainObject(value)) {
|
|
180
|
+
if (seen.has(value)) return "[Circular]";
|
|
181
|
+
seen.add(value);
|
|
182
|
+
const keys = opts.stable ? sortedKeys(value) : Object.keys(value);
|
|
183
|
+
const result = {};
|
|
184
|
+
let keyCount = 0;
|
|
185
|
+
for (const key of keys) {
|
|
186
|
+
if (keyCount >= opts.maxObjectKeys) break;
|
|
187
|
+
if (isRedactedKey(key, opts)) {
|
|
188
|
+
result[key] = opts.replacement;
|
|
189
|
+
} else {
|
|
190
|
+
const walked = walk(value[key], opts, seen, depth + 1);
|
|
191
|
+
if (walked !== void 0) {
|
|
192
|
+
result[key] = walked;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
keyCount++;
|
|
196
|
+
}
|
|
197
|
+
seen.delete(value);
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
if (typeof value === "object") {
|
|
201
|
+
if (seen.has(value)) return "[Circular]";
|
|
202
|
+
seen.add(value);
|
|
203
|
+
const keys = opts.stable ? Object.keys(value).sort() : Object.keys(value);
|
|
204
|
+
const result = {};
|
|
205
|
+
let keyCount = 0;
|
|
206
|
+
for (const key of keys) {
|
|
207
|
+
if (keyCount >= opts.maxObjectKeys) break;
|
|
208
|
+
if (isRedactedKey(key, opts)) {
|
|
209
|
+
result[key] = opts.replacement;
|
|
210
|
+
} else {
|
|
211
|
+
const walked = walk(
|
|
212
|
+
value[key],
|
|
213
|
+
opts,
|
|
214
|
+
seen,
|
|
215
|
+
depth + 1
|
|
216
|
+
);
|
|
217
|
+
if (walked !== void 0) {
|
|
218
|
+
result[key] = walked;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
keyCount++;
|
|
222
|
+
}
|
|
223
|
+
seen.delete(value);
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
return value;
|
|
227
|
+
}
|
|
228
|
+
function safeClone(value, options) {
|
|
229
|
+
const opts = resolveOptions(options);
|
|
230
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
231
|
+
return walk(value, opts, seen, 0);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/safe-stringify.ts
|
|
235
|
+
function safeStringify(value, options) {
|
|
236
|
+
const opts = resolveOptions(options);
|
|
237
|
+
const cloned = safeClone(value, options);
|
|
238
|
+
const indent = opts.pretty === true ? 2 : opts.pretty === false ? void 0 : opts.pretty;
|
|
239
|
+
return JSON.stringify(cloned, null, indent) ?? "undefined";
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/redact.ts
|
|
243
|
+
function redact(value, options) {
|
|
244
|
+
const opts = {};
|
|
245
|
+
if (options?.keys !== void 0) opts.redactKeys = options.keys;
|
|
246
|
+
if (options?.pattern !== void 0) opts.redactByPattern = options.pattern;
|
|
247
|
+
if (options?.replacement !== void 0) opts.replacement = options.replacement;
|
|
248
|
+
return safeClone(value, opts);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/presets.ts
|
|
252
|
+
var presets = {
|
|
253
|
+
log: {
|
|
254
|
+
maxDepth: 5,
|
|
255
|
+
maxArrayLength: 50,
|
|
256
|
+
maxStringLength: 2e3,
|
|
257
|
+
includeErrorStack: false,
|
|
258
|
+
stable: false
|
|
259
|
+
},
|
|
260
|
+
debug: {
|
|
261
|
+
maxDepth: 8,
|
|
262
|
+
maxArrayLength: 200,
|
|
263
|
+
maxStringLength: 8e3,
|
|
264
|
+
includeErrorStack: true,
|
|
265
|
+
stable: false
|
|
266
|
+
},
|
|
267
|
+
http: {
|
|
268
|
+
maxDepth: 5,
|
|
269
|
+
maxArrayLength: 50,
|
|
270
|
+
maxStringLength: 2e3,
|
|
271
|
+
includeErrorStack: false,
|
|
272
|
+
stable: false,
|
|
273
|
+
redactKeys: ["headers", "cookie", "set-cookie", "authorization"]
|
|
274
|
+
},
|
|
275
|
+
audit: {
|
|
276
|
+
maxDepth: 5,
|
|
277
|
+
maxArrayLength: 100,
|
|
278
|
+
maxStringLength: 4e3,
|
|
279
|
+
includeErrorStack: false,
|
|
280
|
+
stable: true
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
// src/create-safe-json.ts
|
|
285
|
+
function createSafeJson(base = {}) {
|
|
286
|
+
function merge(overrides) {
|
|
287
|
+
if (!overrides) return base;
|
|
288
|
+
const mergedKeys = [
|
|
289
|
+
...base.redactKeys ?? [],
|
|
290
|
+
...overrides.redactKeys ?? []
|
|
291
|
+
];
|
|
292
|
+
const mergedPatterns = [
|
|
293
|
+
...base.redactByPattern ?? [],
|
|
294
|
+
...overrides.redactByPattern ?? []
|
|
295
|
+
];
|
|
296
|
+
const merged = { ...base, ...overrides };
|
|
297
|
+
if (mergedKeys.length > 0) merged.redactKeys = mergedKeys;
|
|
298
|
+
if (mergedPatterns.length > 0) merged.redactByPattern = mergedPatterns;
|
|
299
|
+
return merged;
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
stringify(value, overrides) {
|
|
303
|
+
return safeStringify(value, merge(overrides));
|
|
304
|
+
},
|
|
305
|
+
clone(value, overrides) {
|
|
306
|
+
return safeClone(value, merge(overrides));
|
|
307
|
+
},
|
|
308
|
+
forLog(value) {
|
|
309
|
+
return safeClone(value, merge(presets.log));
|
|
310
|
+
},
|
|
311
|
+
forDebug(value) {
|
|
312
|
+
return safeClone(value, merge(presets.debug));
|
|
313
|
+
},
|
|
314
|
+
forHttp(value) {
|
|
315
|
+
return safeClone(value, merge(presets.http));
|
|
316
|
+
},
|
|
317
|
+
forAudit(value) {
|
|
318
|
+
return safeClone(value, merge(presets.audit));
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
exports.createSafeJson = createSafeJson;
|
|
324
|
+
exports.presets = presets;
|
|
325
|
+
exports.redact = redact;
|
|
326
|
+
exports.safeClone = safeClone;
|
|
327
|
+
exports.safeStringify = safeStringify;
|
|
328
|
+
//# sourceMappingURL=index.cjs.map
|
|
329
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/is-plain-object.ts","../src/utils/stable-sort.ts","../src/safe-clone.ts","../src/safe-stringify.ts","../src/redact.ts","../src/presets.ts","../src/create-safe-json.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["export function isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null) return false;\n const proto = Object.getPrototypeOf(value) as unknown;\n return proto === Object.prototype || proto === null;\n}\n","export function sortedKeys(obj: Record<string, unknown>): string[] {\n return Object.keys(obj).sort();\n}\n","import type { ResolvedOptions, SafeJsonOptions } from \"./types.js\";\nimport { isPlainObject } from \"./utils/is-plain-object.js\";\nimport { sortedKeys } from \"./utils/stable-sort.js\";\n\nconst DEFAULT_REDACT_KEYS = [\n \"password\",\n \"pass\",\n \"pwd\",\n \"secret\",\n \"token\",\n \"accesstoken\",\n \"refreshtoken\",\n \"authorization\",\n \"cookie\",\n \"set-cookie\",\n \"apikey\",\n \"privatekey\",\n \"otp\",\n \"pin\",\n \"cardnumber\",\n \"cvv\",\n];\n\nconst DEFAULTS: ResolvedOptions = {\n redactKeys: DEFAULT_REDACT_KEYS,\n redactByPattern: [],\n replacement: \"[REDACTED]\",\n maxDepth: 6,\n maxArrayLength: 100,\n maxObjectKeys: 100,\n maxStringLength: 4000,\n includeErrorStack: false,\n stable: false,\n pretty: false,\n handleBigInt: \"string\",\n handleFunction: \"omit\",\n handleSymbol: \"omit\",\n onCircular: \"placeholder\",\n};\n\nexport function resolveOptions(options?: SafeJsonOptions): ResolvedOptions {\n if (!options) return DEFAULTS;\n\n const userKeys = options.redactKeys?.map((k) => k.toLowerCase()) ?? [];\n const mergedKeys = [...DEFAULT_REDACT_KEYS, ...userKeys];\n\n return {\n redactKeys: mergedKeys,\n redactByPattern: options.redactByPattern ?? DEFAULTS.redactByPattern,\n replacement: options.replacement ?? DEFAULTS.replacement,\n maxDepth: options.maxDepth ?? DEFAULTS.maxDepth,\n maxArrayLength: options.maxArrayLength ?? DEFAULTS.maxArrayLength,\n maxObjectKeys: options.maxObjectKeys ?? DEFAULTS.maxObjectKeys,\n maxStringLength: options.maxStringLength ?? DEFAULTS.maxStringLength,\n includeErrorStack: options.includeErrorStack ?? DEFAULTS.includeErrorStack,\n stable: options.stable ?? DEFAULTS.stable,\n pretty: options.pretty ?? DEFAULTS.pretty,\n handleBigInt: options.handleBigInt ?? DEFAULTS.handleBigInt,\n handleFunction: options.handleFunction ?? DEFAULTS.handleFunction,\n handleSymbol: options.handleSymbol ?? DEFAULTS.handleSymbol,\n onCircular: options.onCircular ?? DEFAULTS.onCircular,\n };\n}\n\nfunction isRedactedKey(key: string, opts: ResolvedOptions): boolean {\n const lower = key.toLowerCase();\n if (opts.redactKeys.includes(lower)) return true;\n for (const pattern of opts.redactByPattern) {\n if (pattern.test(key)) return true;\n }\n return false;\n}\n\nfunction serializeError(\n err: Error,\n opts: ResolvedOptions,\n seen: WeakSet<object>,\n depth: number\n): Record<string, unknown> {\n const result: Record<string, unknown> = {\n name: err.name,\n message: err.message,\n };\n\n if (opts.includeErrorStack && err.stack) {\n result[\"stack\"] =\n err.stack.length > opts.maxStringLength\n ? err.stack.slice(0, opts.maxStringLength) + \"...[Truncated]\"\n : err.stack;\n }\n\n if (err.cause !== undefined) {\n result[\"cause\"] = walk(err.cause, opts, seen, depth + 1);\n }\n\n for (const key of Object.keys(err)) {\n if (key in result) continue;\n if (isRedactedKey(key, opts)) {\n result[key] = opts.replacement;\n } else {\n result[key] = walk(\n (err as unknown as Record<string, unknown>)[key],\n opts,\n seen,\n depth + 1\n );\n }\n }\n\n return result;\n}\n\nfunction walk(\n value: unknown,\n opts: ResolvedOptions,\n seen: WeakSet<object>,\n depth: number\n): unknown {\n // primitives\n if (value === null) return null;\n if (value === undefined) return undefined;\n\n if (typeof value === \"boolean\" || typeof value === \"number\") return value;\n\n if (typeof value === \"string\") {\n if (value.length > opts.maxStringLength) {\n return value.slice(0, opts.maxStringLength) + \"...[Truncated]\";\n }\n return value;\n }\n\n if (typeof value === \"bigint\") {\n if (opts.handleBigInt === \"redact\") return opts.replacement;\n if (opts.handleBigInt === \"number\") return Number(value);\n return value.toString();\n }\n\n if (typeof value === \"function\") {\n if (opts.handleFunction === \"omit\") return undefined;\n if (opts.handleFunction === \"name\")\n return value.name ? `[Function: ${value.name}]` : \"[Function]\";\n return \"[Function]\";\n }\n\n if (typeof value === \"symbol\") {\n if (opts.handleSymbol === \"omit\") return undefined;\n if (opts.handleSymbol === \"description\")\n return value.description ?? \"[Symbol]\";\n return \"[Symbol]\";\n }\n\n // objects\n if (depth > opts.maxDepth) return \"[MaxDepth]\";\n\n if (value instanceof Date) return value.toISOString();\n\n if (value instanceof Error) {\n if (seen.has(value)) {\n return opts.onCircular === \"placeholder\" ? \"[Circular]\" : \"[Circular]\";\n }\n seen.add(value);\n const result = serializeError(value, opts, seen, depth);\n seen.delete(value);\n return result;\n }\n\n if (value instanceof Map) {\n if (seen.has(value)) return \"[Circular]\";\n seen.add(value);\n const obj: Record<string, unknown> = {};\n let keyCount = 0;\n for (const [k, v] of value) {\n if (keyCount >= opts.maxObjectKeys) break;\n const keyStr = String(k);\n obj[keyStr] = isRedactedKey(keyStr, opts)\n ? opts.replacement\n : walk(v, opts, seen, depth + 1);\n keyCount++;\n }\n seen.delete(value);\n return obj;\n }\n\n if (value instanceof Set) {\n if (seen.has(value)) return \"[Circular]\";\n seen.add(value);\n const arr = [...value].slice(0, opts.maxArrayLength);\n const result = arr.map((v) => walk(v, opts, seen, depth + 1));\n if (value.size > opts.maxArrayLength) {\n result.push(`[Truncated ${value.size - opts.maxArrayLength} more items]`);\n }\n seen.delete(value);\n return result;\n }\n\n if (Array.isArray(value)) {\n if (seen.has(value)) return \"[Circular]\";\n seen.add(value);\n const slice = value.slice(0, opts.maxArrayLength);\n const result = slice.map((v) => walk(v, opts, seen, depth + 1));\n if (value.length > opts.maxArrayLength) {\n result.push(`[Truncated ${value.length - opts.maxArrayLength} more items]`);\n }\n seen.delete(value);\n return result;\n }\n\n if (isPlainObject(value)) {\n if (seen.has(value)) return \"[Circular]\";\n seen.add(value);\n\n const keys = opts.stable ? sortedKeys(value) : Object.keys(value);\n const result: Record<string, unknown> = {};\n let keyCount = 0;\n\n for (const key of keys) {\n if (keyCount >= opts.maxObjectKeys) break;\n if (isRedactedKey(key, opts)) {\n result[key] = opts.replacement;\n } else {\n const walked = walk(value[key], opts, seen, depth + 1);\n if (walked !== undefined) {\n result[key] = walked;\n }\n }\n keyCount++;\n }\n\n seen.delete(value);\n return result;\n }\n\n // non-plain objects (class instances) — treat as plain, extract own enumerable keys\n if (typeof value === \"object\") {\n if (seen.has(value as object)) return \"[Circular]\";\n seen.add(value as object);\n\n const keys = opts.stable\n ? Object.keys(value as object).sort()\n : Object.keys(value as object);\n const result: Record<string, unknown> = {};\n let keyCount = 0;\n\n for (const key of keys) {\n if (keyCount >= opts.maxObjectKeys) break;\n if (isRedactedKey(key, opts)) {\n result[key] = opts.replacement;\n } else {\n const walked = walk(\n (value as Record<string, unknown>)[key],\n opts,\n seen,\n depth + 1\n );\n if (walked !== undefined) {\n result[key] = walked;\n }\n }\n keyCount++;\n }\n\n seen.delete(value as object);\n return result;\n }\n\n return value;\n}\n\nexport function safeClone(value: unknown, options?: SafeJsonOptions): unknown {\n const opts = resolveOptions(options);\n const seen = new WeakSet<object>();\n return walk(value, opts, seen, 0);\n}\n","import type { SafeJsonOptions } from \"./types.js\";\nimport { safeClone, resolveOptions } from \"./safe-clone.js\";\n\nexport function safeStringify(value: unknown, options?: SafeJsonOptions): string {\n const opts = resolveOptions(options);\n const cloned = safeClone(value, options);\n const indent = opts.pretty === true ? 2 : opts.pretty === false ? undefined : opts.pretty;\n return JSON.stringify(cloned, null, indent) ?? \"undefined\";\n}\n","import type { SafeJsonOptions } from \"./types.js\";\nimport { safeClone } from \"./safe-clone.js\";\n\nexport type RedactOptions = {\n keys?: string[];\n pattern?: RegExp[];\n replacement?: string;\n};\n\nexport function redact(value: unknown, options?: RedactOptions): unknown {\n const opts: SafeJsonOptions = {};\n if (options?.keys !== undefined) opts.redactKeys = options.keys;\n if (options?.pattern !== undefined) opts.redactByPattern = options.pattern;\n if (options?.replacement !== undefined) opts.replacement = options.replacement;\n return safeClone(value, opts);\n}\n","import type { SafeJsonOptions } from \"./types.js\";\n\nexport const presets = {\n log: {\n maxDepth: 5,\n maxArrayLength: 50,\n maxStringLength: 2000,\n includeErrorStack: false,\n stable: false,\n } satisfies SafeJsonOptions,\n\n debug: {\n maxDepth: 8,\n maxArrayLength: 200,\n maxStringLength: 8000,\n includeErrorStack: true,\n stable: false,\n } satisfies SafeJsonOptions,\n\n http: {\n maxDepth: 5,\n maxArrayLength: 50,\n maxStringLength: 2000,\n includeErrorStack: false,\n stable: false,\n redactKeys: [\"headers\", \"cookie\", \"set-cookie\", \"authorization\"],\n } satisfies SafeJsonOptions,\n\n audit: {\n maxDepth: 5,\n maxArrayLength: 100,\n maxStringLength: 4000,\n includeErrorStack: false,\n stable: true,\n } satisfies SafeJsonOptions,\n} as const;\n","import type { SafeJsonOptions, SafeJsonInstance } from \"./types.js\";\nimport { safeClone } from \"./safe-clone.js\";\nimport { safeStringify } from \"./safe-stringify.js\";\nimport { presets } from \"./presets.js\";\n\nexport function createSafeJson(base: SafeJsonOptions = {}): SafeJsonInstance {\n function merge(overrides?: SafeJsonOptions): SafeJsonOptions {\n if (!overrides) return base;\n const mergedKeys = [\n ...(base.redactKeys ?? []),\n ...(overrides.redactKeys ?? []),\n ];\n const mergedPatterns = [\n ...(base.redactByPattern ?? []),\n ...(overrides.redactByPattern ?? []),\n ];\n const merged: SafeJsonOptions = { ...base, ...overrides };\n if (mergedKeys.length > 0) merged.redactKeys = mergedKeys;\n if (mergedPatterns.length > 0) merged.redactByPattern = mergedPatterns;\n return merged;\n }\n\n return {\n stringify(value, overrides) {\n return safeStringify(value, merge(overrides));\n },\n clone(value, overrides) {\n return safeClone(value, merge(overrides));\n },\n forLog(value) {\n return safeClone(value, merge(presets.log));\n },\n forDebug(value) {\n return safeClone(value, merge(presets.debug));\n },\n forHttp(value) {\n return safeClone(value, merge(presets.http));\n },\n forAudit(value) {\n return safeClone(value, merge(presets.audit));\n },\n };\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
type SafeJsonOptions = {
|
|
2
|
+
redactKeys?: string[];
|
|
3
|
+
redactByPattern?: RegExp[];
|
|
4
|
+
replacement?: string;
|
|
5
|
+
maxDepth?: number;
|
|
6
|
+
maxArrayLength?: number;
|
|
7
|
+
maxObjectKeys?: number;
|
|
8
|
+
maxStringLength?: number;
|
|
9
|
+
includeErrorStack?: boolean;
|
|
10
|
+
stable?: boolean;
|
|
11
|
+
pretty?: boolean | number;
|
|
12
|
+
handleBigInt?: "string" | "number" | "redact";
|
|
13
|
+
handleFunction?: "omit" | "name" | "placeholder";
|
|
14
|
+
handleSymbol?: "omit" | "description" | "placeholder";
|
|
15
|
+
onCircular?: "placeholder" | "path";
|
|
16
|
+
};
|
|
17
|
+
type SafeJsonInstance = {
|
|
18
|
+
stringify(value: unknown, overrides?: SafeJsonOptions): string;
|
|
19
|
+
clone(value: unknown, overrides?: SafeJsonOptions): unknown;
|
|
20
|
+
forLog(value: unknown): unknown;
|
|
21
|
+
forDebug(value: unknown): unknown;
|
|
22
|
+
forHttp(value: unknown): unknown;
|
|
23
|
+
forAudit(value: unknown): unknown;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
declare function safeClone(value: unknown, options?: SafeJsonOptions): unknown;
|
|
27
|
+
|
|
28
|
+
declare function safeStringify(value: unknown, options?: SafeJsonOptions): string;
|
|
29
|
+
|
|
30
|
+
type RedactOptions = {
|
|
31
|
+
keys?: string[];
|
|
32
|
+
pattern?: RegExp[];
|
|
33
|
+
replacement?: string;
|
|
34
|
+
};
|
|
35
|
+
declare function redact(value: unknown, options?: RedactOptions): unknown;
|
|
36
|
+
|
|
37
|
+
declare function createSafeJson(base?: SafeJsonOptions): SafeJsonInstance;
|
|
38
|
+
|
|
39
|
+
declare const presets: {
|
|
40
|
+
readonly log: {
|
|
41
|
+
maxDepth: number;
|
|
42
|
+
maxArrayLength: number;
|
|
43
|
+
maxStringLength: number;
|
|
44
|
+
includeErrorStack: false;
|
|
45
|
+
stable: false;
|
|
46
|
+
};
|
|
47
|
+
readonly debug: {
|
|
48
|
+
maxDepth: number;
|
|
49
|
+
maxArrayLength: number;
|
|
50
|
+
maxStringLength: number;
|
|
51
|
+
includeErrorStack: true;
|
|
52
|
+
stable: false;
|
|
53
|
+
};
|
|
54
|
+
readonly http: {
|
|
55
|
+
maxDepth: number;
|
|
56
|
+
maxArrayLength: number;
|
|
57
|
+
maxStringLength: number;
|
|
58
|
+
includeErrorStack: false;
|
|
59
|
+
stable: false;
|
|
60
|
+
redactKeys: string[];
|
|
61
|
+
};
|
|
62
|
+
readonly audit: {
|
|
63
|
+
maxDepth: number;
|
|
64
|
+
maxArrayLength: number;
|
|
65
|
+
maxStringLength: number;
|
|
66
|
+
includeErrorStack: false;
|
|
67
|
+
stable: true;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export { type SafeJsonInstance, type SafeJsonOptions, createSafeJson, presets, redact, safeClone, safeStringify };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
type SafeJsonOptions = {
|
|
2
|
+
redactKeys?: string[];
|
|
3
|
+
redactByPattern?: RegExp[];
|
|
4
|
+
replacement?: string;
|
|
5
|
+
maxDepth?: number;
|
|
6
|
+
maxArrayLength?: number;
|
|
7
|
+
maxObjectKeys?: number;
|
|
8
|
+
maxStringLength?: number;
|
|
9
|
+
includeErrorStack?: boolean;
|
|
10
|
+
stable?: boolean;
|
|
11
|
+
pretty?: boolean | number;
|
|
12
|
+
handleBigInt?: "string" | "number" | "redact";
|
|
13
|
+
handleFunction?: "omit" | "name" | "placeholder";
|
|
14
|
+
handleSymbol?: "omit" | "description" | "placeholder";
|
|
15
|
+
onCircular?: "placeholder" | "path";
|
|
16
|
+
};
|
|
17
|
+
type SafeJsonInstance = {
|
|
18
|
+
stringify(value: unknown, overrides?: SafeJsonOptions): string;
|
|
19
|
+
clone(value: unknown, overrides?: SafeJsonOptions): unknown;
|
|
20
|
+
forLog(value: unknown): unknown;
|
|
21
|
+
forDebug(value: unknown): unknown;
|
|
22
|
+
forHttp(value: unknown): unknown;
|
|
23
|
+
forAudit(value: unknown): unknown;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
declare function safeClone(value: unknown, options?: SafeJsonOptions): unknown;
|
|
27
|
+
|
|
28
|
+
declare function safeStringify(value: unknown, options?: SafeJsonOptions): string;
|
|
29
|
+
|
|
30
|
+
type RedactOptions = {
|
|
31
|
+
keys?: string[];
|
|
32
|
+
pattern?: RegExp[];
|
|
33
|
+
replacement?: string;
|
|
34
|
+
};
|
|
35
|
+
declare function redact(value: unknown, options?: RedactOptions): unknown;
|
|
36
|
+
|
|
37
|
+
declare function createSafeJson(base?: SafeJsonOptions): SafeJsonInstance;
|
|
38
|
+
|
|
39
|
+
declare const presets: {
|
|
40
|
+
readonly log: {
|
|
41
|
+
maxDepth: number;
|
|
42
|
+
maxArrayLength: number;
|
|
43
|
+
maxStringLength: number;
|
|
44
|
+
includeErrorStack: false;
|
|
45
|
+
stable: false;
|
|
46
|
+
};
|
|
47
|
+
readonly debug: {
|
|
48
|
+
maxDepth: number;
|
|
49
|
+
maxArrayLength: number;
|
|
50
|
+
maxStringLength: number;
|
|
51
|
+
includeErrorStack: true;
|
|
52
|
+
stable: false;
|
|
53
|
+
};
|
|
54
|
+
readonly http: {
|
|
55
|
+
maxDepth: number;
|
|
56
|
+
maxArrayLength: number;
|
|
57
|
+
maxStringLength: number;
|
|
58
|
+
includeErrorStack: false;
|
|
59
|
+
stable: false;
|
|
60
|
+
redactKeys: string[];
|
|
61
|
+
};
|
|
62
|
+
readonly audit: {
|
|
63
|
+
maxDepth: number;
|
|
64
|
+
maxArrayLength: number;
|
|
65
|
+
maxStringLength: number;
|
|
66
|
+
includeErrorStack: false;
|
|
67
|
+
stable: true;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export { type SafeJsonInstance, type SafeJsonOptions, createSafeJson, presets, redact, safeClone, safeStringify };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
// src/utils/is-plain-object.ts
|
|
2
|
+
function isPlainObject(value) {
|
|
3
|
+
if (typeof value !== "object" || value === null) return false;
|
|
4
|
+
const proto = Object.getPrototypeOf(value);
|
|
5
|
+
return proto === Object.prototype || proto === null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// src/utils/stable-sort.ts
|
|
9
|
+
function sortedKeys(obj) {
|
|
10
|
+
return Object.keys(obj).sort();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// src/safe-clone.ts
|
|
14
|
+
var DEFAULT_REDACT_KEYS = [
|
|
15
|
+
"password",
|
|
16
|
+
"pass",
|
|
17
|
+
"pwd",
|
|
18
|
+
"secret",
|
|
19
|
+
"token",
|
|
20
|
+
"accesstoken",
|
|
21
|
+
"refreshtoken",
|
|
22
|
+
"authorization",
|
|
23
|
+
"cookie",
|
|
24
|
+
"set-cookie",
|
|
25
|
+
"apikey",
|
|
26
|
+
"privatekey",
|
|
27
|
+
"otp",
|
|
28
|
+
"pin",
|
|
29
|
+
"cardnumber",
|
|
30
|
+
"cvv"
|
|
31
|
+
];
|
|
32
|
+
var DEFAULTS = {
|
|
33
|
+
redactKeys: DEFAULT_REDACT_KEYS,
|
|
34
|
+
redactByPattern: [],
|
|
35
|
+
replacement: "[REDACTED]",
|
|
36
|
+
maxDepth: 6,
|
|
37
|
+
maxArrayLength: 100,
|
|
38
|
+
maxObjectKeys: 100,
|
|
39
|
+
maxStringLength: 4e3,
|
|
40
|
+
includeErrorStack: false,
|
|
41
|
+
stable: false,
|
|
42
|
+
pretty: false,
|
|
43
|
+
handleBigInt: "string",
|
|
44
|
+
handleFunction: "omit",
|
|
45
|
+
handleSymbol: "omit",
|
|
46
|
+
onCircular: "placeholder"
|
|
47
|
+
};
|
|
48
|
+
function resolveOptions(options) {
|
|
49
|
+
if (!options) return DEFAULTS;
|
|
50
|
+
const userKeys = options.redactKeys?.map((k) => k.toLowerCase()) ?? [];
|
|
51
|
+
const mergedKeys = [...DEFAULT_REDACT_KEYS, ...userKeys];
|
|
52
|
+
return {
|
|
53
|
+
redactKeys: mergedKeys,
|
|
54
|
+
redactByPattern: options.redactByPattern ?? DEFAULTS.redactByPattern,
|
|
55
|
+
replacement: options.replacement ?? DEFAULTS.replacement,
|
|
56
|
+
maxDepth: options.maxDepth ?? DEFAULTS.maxDepth,
|
|
57
|
+
maxArrayLength: options.maxArrayLength ?? DEFAULTS.maxArrayLength,
|
|
58
|
+
maxObjectKeys: options.maxObjectKeys ?? DEFAULTS.maxObjectKeys,
|
|
59
|
+
maxStringLength: options.maxStringLength ?? DEFAULTS.maxStringLength,
|
|
60
|
+
includeErrorStack: options.includeErrorStack ?? DEFAULTS.includeErrorStack,
|
|
61
|
+
stable: options.stable ?? DEFAULTS.stable,
|
|
62
|
+
pretty: options.pretty ?? DEFAULTS.pretty,
|
|
63
|
+
handleBigInt: options.handleBigInt ?? DEFAULTS.handleBigInt,
|
|
64
|
+
handleFunction: options.handleFunction ?? DEFAULTS.handleFunction,
|
|
65
|
+
handleSymbol: options.handleSymbol ?? DEFAULTS.handleSymbol,
|
|
66
|
+
onCircular: options.onCircular ?? DEFAULTS.onCircular
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function isRedactedKey(key, opts) {
|
|
70
|
+
const lower = key.toLowerCase();
|
|
71
|
+
if (opts.redactKeys.includes(lower)) return true;
|
|
72
|
+
for (const pattern of opts.redactByPattern) {
|
|
73
|
+
if (pattern.test(key)) return true;
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
function serializeError(err, opts, seen, depth) {
|
|
78
|
+
const result = {
|
|
79
|
+
name: err.name,
|
|
80
|
+
message: err.message
|
|
81
|
+
};
|
|
82
|
+
if (opts.includeErrorStack && err.stack) {
|
|
83
|
+
result["stack"] = err.stack.length > opts.maxStringLength ? err.stack.slice(0, opts.maxStringLength) + "...[Truncated]" : err.stack;
|
|
84
|
+
}
|
|
85
|
+
if (err.cause !== void 0) {
|
|
86
|
+
result["cause"] = walk(err.cause, opts, seen, depth + 1);
|
|
87
|
+
}
|
|
88
|
+
for (const key of Object.keys(err)) {
|
|
89
|
+
if (key in result) continue;
|
|
90
|
+
if (isRedactedKey(key, opts)) {
|
|
91
|
+
result[key] = opts.replacement;
|
|
92
|
+
} else {
|
|
93
|
+
result[key] = walk(
|
|
94
|
+
err[key],
|
|
95
|
+
opts,
|
|
96
|
+
seen,
|
|
97
|
+
depth + 1
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
function walk(value, opts, seen, depth) {
|
|
104
|
+
if (value === null) return null;
|
|
105
|
+
if (value === void 0) return void 0;
|
|
106
|
+
if (typeof value === "boolean" || typeof value === "number") return value;
|
|
107
|
+
if (typeof value === "string") {
|
|
108
|
+
if (value.length > opts.maxStringLength) {
|
|
109
|
+
return value.slice(0, opts.maxStringLength) + "...[Truncated]";
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
if (typeof value === "bigint") {
|
|
114
|
+
if (opts.handleBigInt === "redact") return opts.replacement;
|
|
115
|
+
if (opts.handleBigInt === "number") return Number(value);
|
|
116
|
+
return value.toString();
|
|
117
|
+
}
|
|
118
|
+
if (typeof value === "function") {
|
|
119
|
+
if (opts.handleFunction === "omit") return void 0;
|
|
120
|
+
if (opts.handleFunction === "name")
|
|
121
|
+
return value.name ? `[Function: ${value.name}]` : "[Function]";
|
|
122
|
+
return "[Function]";
|
|
123
|
+
}
|
|
124
|
+
if (typeof value === "symbol") {
|
|
125
|
+
if (opts.handleSymbol === "omit") return void 0;
|
|
126
|
+
if (opts.handleSymbol === "description")
|
|
127
|
+
return value.description ?? "[Symbol]";
|
|
128
|
+
return "[Symbol]";
|
|
129
|
+
}
|
|
130
|
+
if (depth > opts.maxDepth) return "[MaxDepth]";
|
|
131
|
+
if (value instanceof Date) return value.toISOString();
|
|
132
|
+
if (value instanceof Error) {
|
|
133
|
+
if (seen.has(value)) {
|
|
134
|
+
return opts.onCircular === "placeholder" ? "[Circular]" : "[Circular]";
|
|
135
|
+
}
|
|
136
|
+
seen.add(value);
|
|
137
|
+
const result = serializeError(value, opts, seen, depth);
|
|
138
|
+
seen.delete(value);
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
if (value instanceof Map) {
|
|
142
|
+
if (seen.has(value)) return "[Circular]";
|
|
143
|
+
seen.add(value);
|
|
144
|
+
const obj = {};
|
|
145
|
+
let keyCount = 0;
|
|
146
|
+
for (const [k, v] of value) {
|
|
147
|
+
if (keyCount >= opts.maxObjectKeys) break;
|
|
148
|
+
const keyStr = String(k);
|
|
149
|
+
obj[keyStr] = isRedactedKey(keyStr, opts) ? opts.replacement : walk(v, opts, seen, depth + 1);
|
|
150
|
+
keyCount++;
|
|
151
|
+
}
|
|
152
|
+
seen.delete(value);
|
|
153
|
+
return obj;
|
|
154
|
+
}
|
|
155
|
+
if (value instanceof Set) {
|
|
156
|
+
if (seen.has(value)) return "[Circular]";
|
|
157
|
+
seen.add(value);
|
|
158
|
+
const arr = [...value].slice(0, opts.maxArrayLength);
|
|
159
|
+
const result = arr.map((v) => walk(v, opts, seen, depth + 1));
|
|
160
|
+
if (value.size > opts.maxArrayLength) {
|
|
161
|
+
result.push(`[Truncated ${value.size - opts.maxArrayLength} more items]`);
|
|
162
|
+
}
|
|
163
|
+
seen.delete(value);
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
if (Array.isArray(value)) {
|
|
167
|
+
if (seen.has(value)) return "[Circular]";
|
|
168
|
+
seen.add(value);
|
|
169
|
+
const slice = value.slice(0, opts.maxArrayLength);
|
|
170
|
+
const result = slice.map((v) => walk(v, opts, seen, depth + 1));
|
|
171
|
+
if (value.length > opts.maxArrayLength) {
|
|
172
|
+
result.push(`[Truncated ${value.length - opts.maxArrayLength} more items]`);
|
|
173
|
+
}
|
|
174
|
+
seen.delete(value);
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
if (isPlainObject(value)) {
|
|
178
|
+
if (seen.has(value)) return "[Circular]";
|
|
179
|
+
seen.add(value);
|
|
180
|
+
const keys = opts.stable ? sortedKeys(value) : Object.keys(value);
|
|
181
|
+
const result = {};
|
|
182
|
+
let keyCount = 0;
|
|
183
|
+
for (const key of keys) {
|
|
184
|
+
if (keyCount >= opts.maxObjectKeys) break;
|
|
185
|
+
if (isRedactedKey(key, opts)) {
|
|
186
|
+
result[key] = opts.replacement;
|
|
187
|
+
} else {
|
|
188
|
+
const walked = walk(value[key], opts, seen, depth + 1);
|
|
189
|
+
if (walked !== void 0) {
|
|
190
|
+
result[key] = walked;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
keyCount++;
|
|
194
|
+
}
|
|
195
|
+
seen.delete(value);
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
if (typeof value === "object") {
|
|
199
|
+
if (seen.has(value)) return "[Circular]";
|
|
200
|
+
seen.add(value);
|
|
201
|
+
const keys = opts.stable ? Object.keys(value).sort() : Object.keys(value);
|
|
202
|
+
const result = {};
|
|
203
|
+
let keyCount = 0;
|
|
204
|
+
for (const key of keys) {
|
|
205
|
+
if (keyCount >= opts.maxObjectKeys) break;
|
|
206
|
+
if (isRedactedKey(key, opts)) {
|
|
207
|
+
result[key] = opts.replacement;
|
|
208
|
+
} else {
|
|
209
|
+
const walked = walk(
|
|
210
|
+
value[key],
|
|
211
|
+
opts,
|
|
212
|
+
seen,
|
|
213
|
+
depth + 1
|
|
214
|
+
);
|
|
215
|
+
if (walked !== void 0) {
|
|
216
|
+
result[key] = walked;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
keyCount++;
|
|
220
|
+
}
|
|
221
|
+
seen.delete(value);
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
return value;
|
|
225
|
+
}
|
|
226
|
+
function safeClone(value, options) {
|
|
227
|
+
const opts = resolveOptions(options);
|
|
228
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
229
|
+
return walk(value, opts, seen, 0);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/safe-stringify.ts
|
|
233
|
+
function safeStringify(value, options) {
|
|
234
|
+
const opts = resolveOptions(options);
|
|
235
|
+
const cloned = safeClone(value, options);
|
|
236
|
+
const indent = opts.pretty === true ? 2 : opts.pretty === false ? void 0 : opts.pretty;
|
|
237
|
+
return JSON.stringify(cloned, null, indent) ?? "undefined";
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/redact.ts
|
|
241
|
+
function redact(value, options) {
|
|
242
|
+
const opts = {};
|
|
243
|
+
if (options?.keys !== void 0) opts.redactKeys = options.keys;
|
|
244
|
+
if (options?.pattern !== void 0) opts.redactByPattern = options.pattern;
|
|
245
|
+
if (options?.replacement !== void 0) opts.replacement = options.replacement;
|
|
246
|
+
return safeClone(value, opts);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/presets.ts
|
|
250
|
+
var presets = {
|
|
251
|
+
log: {
|
|
252
|
+
maxDepth: 5,
|
|
253
|
+
maxArrayLength: 50,
|
|
254
|
+
maxStringLength: 2e3,
|
|
255
|
+
includeErrorStack: false,
|
|
256
|
+
stable: false
|
|
257
|
+
},
|
|
258
|
+
debug: {
|
|
259
|
+
maxDepth: 8,
|
|
260
|
+
maxArrayLength: 200,
|
|
261
|
+
maxStringLength: 8e3,
|
|
262
|
+
includeErrorStack: true,
|
|
263
|
+
stable: false
|
|
264
|
+
},
|
|
265
|
+
http: {
|
|
266
|
+
maxDepth: 5,
|
|
267
|
+
maxArrayLength: 50,
|
|
268
|
+
maxStringLength: 2e3,
|
|
269
|
+
includeErrorStack: false,
|
|
270
|
+
stable: false,
|
|
271
|
+
redactKeys: ["headers", "cookie", "set-cookie", "authorization"]
|
|
272
|
+
},
|
|
273
|
+
audit: {
|
|
274
|
+
maxDepth: 5,
|
|
275
|
+
maxArrayLength: 100,
|
|
276
|
+
maxStringLength: 4e3,
|
|
277
|
+
includeErrorStack: false,
|
|
278
|
+
stable: true
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// src/create-safe-json.ts
|
|
283
|
+
function createSafeJson(base = {}) {
|
|
284
|
+
function merge(overrides) {
|
|
285
|
+
if (!overrides) return base;
|
|
286
|
+
const mergedKeys = [
|
|
287
|
+
...base.redactKeys ?? [],
|
|
288
|
+
...overrides.redactKeys ?? []
|
|
289
|
+
];
|
|
290
|
+
const mergedPatterns = [
|
|
291
|
+
...base.redactByPattern ?? [],
|
|
292
|
+
...overrides.redactByPattern ?? []
|
|
293
|
+
];
|
|
294
|
+
const merged = { ...base, ...overrides };
|
|
295
|
+
if (mergedKeys.length > 0) merged.redactKeys = mergedKeys;
|
|
296
|
+
if (mergedPatterns.length > 0) merged.redactByPattern = mergedPatterns;
|
|
297
|
+
return merged;
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
stringify(value, overrides) {
|
|
301
|
+
return safeStringify(value, merge(overrides));
|
|
302
|
+
},
|
|
303
|
+
clone(value, overrides) {
|
|
304
|
+
return safeClone(value, merge(overrides));
|
|
305
|
+
},
|
|
306
|
+
forLog(value) {
|
|
307
|
+
return safeClone(value, merge(presets.log));
|
|
308
|
+
},
|
|
309
|
+
forDebug(value) {
|
|
310
|
+
return safeClone(value, merge(presets.debug));
|
|
311
|
+
},
|
|
312
|
+
forHttp(value) {
|
|
313
|
+
return safeClone(value, merge(presets.http));
|
|
314
|
+
},
|
|
315
|
+
forAudit(value) {
|
|
316
|
+
return safeClone(value, merge(presets.audit));
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export { createSafeJson, presets, redact, safeClone, safeStringify };
|
|
322
|
+
//# sourceMappingURL=index.js.map
|
|
323
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/is-plain-object.ts","../src/utils/stable-sort.ts","../src/safe-clone.ts","../src/safe-stringify.ts","../src/redact.ts","../src/presets.ts","../src/create-safe-json.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export function isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null) return false;\n const proto = Object.getPrototypeOf(value) as unknown;\n return proto === Object.prototype || proto === null;\n}\n","export function sortedKeys(obj: Record<string, unknown>): string[] {\n return Object.keys(obj).sort();\n}\n","import type { ResolvedOptions, SafeJsonOptions } from \"./types.js\";\nimport { isPlainObject } from \"./utils/is-plain-object.js\";\nimport { sortedKeys } from \"./utils/stable-sort.js\";\n\nconst DEFAULT_REDACT_KEYS = [\n \"password\",\n \"pass\",\n \"pwd\",\n \"secret\",\n \"token\",\n \"accesstoken\",\n \"refreshtoken\",\n \"authorization\",\n \"cookie\",\n \"set-cookie\",\n \"apikey\",\n \"privatekey\",\n \"otp\",\n \"pin\",\n \"cardnumber\",\n \"cvv\",\n];\n\nconst DEFAULTS: ResolvedOptions = {\n redactKeys: DEFAULT_REDACT_KEYS,\n redactByPattern: [],\n replacement: \"[REDACTED]\",\n maxDepth: 6,\n maxArrayLength: 100,\n maxObjectKeys: 100,\n maxStringLength: 4000,\n includeErrorStack: false,\n stable: false,\n pretty: false,\n handleBigInt: \"string\",\n handleFunction: \"omit\",\n handleSymbol: \"omit\",\n onCircular: \"placeholder\",\n};\n\nexport function resolveOptions(options?: SafeJsonOptions): ResolvedOptions {\n if (!options) return DEFAULTS;\n\n const userKeys = options.redactKeys?.map((k) => k.toLowerCase()) ?? [];\n const mergedKeys = [...DEFAULT_REDACT_KEYS, ...userKeys];\n\n return {\n redactKeys: mergedKeys,\n redactByPattern: options.redactByPattern ?? DEFAULTS.redactByPattern,\n replacement: options.replacement ?? DEFAULTS.replacement,\n maxDepth: options.maxDepth ?? DEFAULTS.maxDepth,\n maxArrayLength: options.maxArrayLength ?? DEFAULTS.maxArrayLength,\n maxObjectKeys: options.maxObjectKeys ?? DEFAULTS.maxObjectKeys,\n maxStringLength: options.maxStringLength ?? DEFAULTS.maxStringLength,\n includeErrorStack: options.includeErrorStack ?? DEFAULTS.includeErrorStack,\n stable: options.stable ?? DEFAULTS.stable,\n pretty: options.pretty ?? DEFAULTS.pretty,\n handleBigInt: options.handleBigInt ?? DEFAULTS.handleBigInt,\n handleFunction: options.handleFunction ?? DEFAULTS.handleFunction,\n handleSymbol: options.handleSymbol ?? DEFAULTS.handleSymbol,\n onCircular: options.onCircular ?? DEFAULTS.onCircular,\n };\n}\n\nfunction isRedactedKey(key: string, opts: ResolvedOptions): boolean {\n const lower = key.toLowerCase();\n if (opts.redactKeys.includes(lower)) return true;\n for (const pattern of opts.redactByPattern) {\n if (pattern.test(key)) return true;\n }\n return false;\n}\n\nfunction serializeError(\n err: Error,\n opts: ResolvedOptions,\n seen: WeakSet<object>,\n depth: number\n): Record<string, unknown> {\n const result: Record<string, unknown> = {\n name: err.name,\n message: err.message,\n };\n\n if (opts.includeErrorStack && err.stack) {\n result[\"stack\"] =\n err.stack.length > opts.maxStringLength\n ? err.stack.slice(0, opts.maxStringLength) + \"...[Truncated]\"\n : err.stack;\n }\n\n if (err.cause !== undefined) {\n result[\"cause\"] = walk(err.cause, opts, seen, depth + 1);\n }\n\n for (const key of Object.keys(err)) {\n if (key in result) continue;\n if (isRedactedKey(key, opts)) {\n result[key] = opts.replacement;\n } else {\n result[key] = walk(\n (err as unknown as Record<string, unknown>)[key],\n opts,\n seen,\n depth + 1\n );\n }\n }\n\n return result;\n}\n\nfunction walk(\n value: unknown,\n opts: ResolvedOptions,\n seen: WeakSet<object>,\n depth: number\n): unknown {\n // primitives\n if (value === null) return null;\n if (value === undefined) return undefined;\n\n if (typeof value === \"boolean\" || typeof value === \"number\") return value;\n\n if (typeof value === \"string\") {\n if (value.length > opts.maxStringLength) {\n return value.slice(0, opts.maxStringLength) + \"...[Truncated]\";\n }\n return value;\n }\n\n if (typeof value === \"bigint\") {\n if (opts.handleBigInt === \"redact\") return opts.replacement;\n if (opts.handleBigInt === \"number\") return Number(value);\n return value.toString();\n }\n\n if (typeof value === \"function\") {\n if (opts.handleFunction === \"omit\") return undefined;\n if (opts.handleFunction === \"name\")\n return value.name ? `[Function: ${value.name}]` : \"[Function]\";\n return \"[Function]\";\n }\n\n if (typeof value === \"symbol\") {\n if (opts.handleSymbol === \"omit\") return undefined;\n if (opts.handleSymbol === \"description\")\n return value.description ?? \"[Symbol]\";\n return \"[Symbol]\";\n }\n\n // objects\n if (depth > opts.maxDepth) return \"[MaxDepth]\";\n\n if (value instanceof Date) return value.toISOString();\n\n if (value instanceof Error) {\n if (seen.has(value)) {\n return opts.onCircular === \"placeholder\" ? \"[Circular]\" : \"[Circular]\";\n }\n seen.add(value);\n const result = serializeError(value, opts, seen, depth);\n seen.delete(value);\n return result;\n }\n\n if (value instanceof Map) {\n if (seen.has(value)) return \"[Circular]\";\n seen.add(value);\n const obj: Record<string, unknown> = {};\n let keyCount = 0;\n for (const [k, v] of value) {\n if (keyCount >= opts.maxObjectKeys) break;\n const keyStr = String(k);\n obj[keyStr] = isRedactedKey(keyStr, opts)\n ? opts.replacement\n : walk(v, opts, seen, depth + 1);\n keyCount++;\n }\n seen.delete(value);\n return obj;\n }\n\n if (value instanceof Set) {\n if (seen.has(value)) return \"[Circular]\";\n seen.add(value);\n const arr = [...value].slice(0, opts.maxArrayLength);\n const result = arr.map((v) => walk(v, opts, seen, depth + 1));\n if (value.size > opts.maxArrayLength) {\n result.push(`[Truncated ${value.size - opts.maxArrayLength} more items]`);\n }\n seen.delete(value);\n return result;\n }\n\n if (Array.isArray(value)) {\n if (seen.has(value)) return \"[Circular]\";\n seen.add(value);\n const slice = value.slice(0, opts.maxArrayLength);\n const result = slice.map((v) => walk(v, opts, seen, depth + 1));\n if (value.length > opts.maxArrayLength) {\n result.push(`[Truncated ${value.length - opts.maxArrayLength} more items]`);\n }\n seen.delete(value);\n return result;\n }\n\n if (isPlainObject(value)) {\n if (seen.has(value)) return \"[Circular]\";\n seen.add(value);\n\n const keys = opts.stable ? sortedKeys(value) : Object.keys(value);\n const result: Record<string, unknown> = {};\n let keyCount = 0;\n\n for (const key of keys) {\n if (keyCount >= opts.maxObjectKeys) break;\n if (isRedactedKey(key, opts)) {\n result[key] = opts.replacement;\n } else {\n const walked = walk(value[key], opts, seen, depth + 1);\n if (walked !== undefined) {\n result[key] = walked;\n }\n }\n keyCount++;\n }\n\n seen.delete(value);\n return result;\n }\n\n // non-plain objects (class instances) — treat as plain, extract own enumerable keys\n if (typeof value === \"object\") {\n if (seen.has(value as object)) return \"[Circular]\";\n seen.add(value as object);\n\n const keys = opts.stable\n ? Object.keys(value as object).sort()\n : Object.keys(value as object);\n const result: Record<string, unknown> = {};\n let keyCount = 0;\n\n for (const key of keys) {\n if (keyCount >= opts.maxObjectKeys) break;\n if (isRedactedKey(key, opts)) {\n result[key] = opts.replacement;\n } else {\n const walked = walk(\n (value as Record<string, unknown>)[key],\n opts,\n seen,\n depth + 1\n );\n if (walked !== undefined) {\n result[key] = walked;\n }\n }\n keyCount++;\n }\n\n seen.delete(value as object);\n return result;\n }\n\n return value;\n}\n\nexport function safeClone(value: unknown, options?: SafeJsonOptions): unknown {\n const opts = resolveOptions(options);\n const seen = new WeakSet<object>();\n return walk(value, opts, seen, 0);\n}\n","import type { SafeJsonOptions } from \"./types.js\";\nimport { safeClone, resolveOptions } from \"./safe-clone.js\";\n\nexport function safeStringify(value: unknown, options?: SafeJsonOptions): string {\n const opts = resolveOptions(options);\n const cloned = safeClone(value, options);\n const indent = opts.pretty === true ? 2 : opts.pretty === false ? undefined : opts.pretty;\n return JSON.stringify(cloned, null, indent) ?? \"undefined\";\n}\n","import type { SafeJsonOptions } from \"./types.js\";\nimport { safeClone } from \"./safe-clone.js\";\n\nexport type RedactOptions = {\n keys?: string[];\n pattern?: RegExp[];\n replacement?: string;\n};\n\nexport function redact(value: unknown, options?: RedactOptions): unknown {\n const opts: SafeJsonOptions = {};\n if (options?.keys !== undefined) opts.redactKeys = options.keys;\n if (options?.pattern !== undefined) opts.redactByPattern = options.pattern;\n if (options?.replacement !== undefined) opts.replacement = options.replacement;\n return safeClone(value, opts);\n}\n","import type { SafeJsonOptions } from \"./types.js\";\n\nexport const presets = {\n log: {\n maxDepth: 5,\n maxArrayLength: 50,\n maxStringLength: 2000,\n includeErrorStack: false,\n stable: false,\n } satisfies SafeJsonOptions,\n\n debug: {\n maxDepth: 8,\n maxArrayLength: 200,\n maxStringLength: 8000,\n includeErrorStack: true,\n stable: false,\n } satisfies SafeJsonOptions,\n\n http: {\n maxDepth: 5,\n maxArrayLength: 50,\n maxStringLength: 2000,\n includeErrorStack: false,\n stable: false,\n redactKeys: [\"headers\", \"cookie\", \"set-cookie\", \"authorization\"],\n } satisfies SafeJsonOptions,\n\n audit: {\n maxDepth: 5,\n maxArrayLength: 100,\n maxStringLength: 4000,\n includeErrorStack: false,\n stable: true,\n } satisfies SafeJsonOptions,\n} as const;\n","import type { SafeJsonOptions, SafeJsonInstance } from \"./types.js\";\nimport { safeClone } from \"./safe-clone.js\";\nimport { safeStringify } from \"./safe-stringify.js\";\nimport { presets } from \"./presets.js\";\n\nexport function createSafeJson(base: SafeJsonOptions = {}): SafeJsonInstance {\n function merge(overrides?: SafeJsonOptions): SafeJsonOptions {\n if (!overrides) return base;\n const mergedKeys = [\n ...(base.redactKeys ?? []),\n ...(overrides.redactKeys ?? []),\n ];\n const mergedPatterns = [\n ...(base.redactByPattern ?? []),\n ...(overrides.redactByPattern ?? []),\n ];\n const merged: SafeJsonOptions = { ...base, ...overrides };\n if (mergedKeys.length > 0) merged.redactKeys = mergedKeys;\n if (mergedPatterns.length > 0) merged.redactByPattern = mergedPatterns;\n return merged;\n }\n\n return {\n stringify(value, overrides) {\n return safeStringify(value, merge(overrides));\n },\n clone(value, overrides) {\n return safeClone(value, merge(overrides));\n },\n forLog(value) {\n return safeClone(value, merge(presets.log));\n },\n forDebug(value) {\n return safeClone(value, merge(presets.debug));\n },\n forHttp(value) {\n return safeClone(value, merge(presets.http));\n },\n forAudit(value) {\n return safeClone(value, merge(presets.audit));\n },\n };\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ohbee-safe-json",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "JSON.stringify for production logs: safe, lightweight, redacted, bounded, and predictable.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"json",
|
|
7
|
+
"stringify",
|
|
8
|
+
"logging",
|
|
9
|
+
"redact",
|
|
10
|
+
"safe",
|
|
11
|
+
"serialize",
|
|
12
|
+
"security"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Ohbee",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/index.cjs",
|
|
18
|
+
"module": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"import": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"require": {
|
|
27
|
+
"types": "./dist/index.d.cts",
|
|
28
|
+
"default": "./dist/index.cjs"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "./node_modules/.bin/tsup",
|
|
37
|
+
"dev": "./node_modules/.bin/tsup --watch",
|
|
38
|
+
"test": "./node_modules/.bin/vitest run",
|
|
39
|
+
"test:watch": "./node_modules/.bin/vitest",
|
|
40
|
+
"test:coverage": "./node_modules/.bin/vitest run --coverage",
|
|
41
|
+
"typecheck": "./node_modules/.bin/tsc --noEmit",
|
|
42
|
+
"lint": "./node_modules/.bin/tsc --noEmit",
|
|
43
|
+
"prepublishOnly": "npm run test && npm run build"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@vitest/coverage-v8": "^3.2.2",
|
|
47
|
+
"tsup": "^8.4.0",
|
|
48
|
+
"typescript": "^5.8.3",
|
|
49
|
+
"vitest": "^3.2.2"
|
|
50
|
+
}
|
|
51
|
+
}
|