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 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"]}
@@ -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 };
@@ -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
+ }