first commit
This commit is contained in:
123
docs/foundation/data/json/README.md
Normal file
123
docs/foundation/data/json/README.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# @suaveplan/json - Safe JSON parse/stringify with prototype-pollution protection, circular-ref detection, and OTel instrumentation
|
||||
|
||||
## Summary
|
||||
|
||||
Safe, enterprise-grade JSON utilities with prototype-pollution protection,
|
||||
circular-reference detection, and OpenTelemetry instrumentation.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [errors](./src/errors/json-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [json-error](./src/errors/json-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [safe-json](./src/safe-json/safe-json.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [safe-json](./src/safe-json/safe-json.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
<!-- end:auto-docs --> - [json-error](./src/errors/json-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [safe-json](./src/safe-json/safe-json.md) — <!-- begin:auto-docs --> _(no submodules)_- [safe-json](./src/safe-json/safe-json.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [json-error](./src/errors/json-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [safe-json](./src/safe-json/safe-json.md) — <!-- begin:auto-docs --> _(no submodules)_- [safe-json](./src/safe-json/safe-json.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [json-error](./src/errors/json-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [safe-json](./src/safe-json/safe-json.md) — <!-- begin:auto-docs --> _(no submodules)_- [safe-json](./src/safe-json/safe-json.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [json-error](./src/errors/json-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [safe-json](./src/safe-json/safe-json.md) — <!-- begin:auto-docs --> _(no submodules)_ - [safe-json](./src/safe-json/safe-json.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_ - [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_
|
||||
## Install
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/json
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
```ts
|
||||
import { safeParse, safeStringify, tryParse, isValidJSON, getStringifySize } from "@suaveplan/json";
|
||||
|
||||
// Safe parse — throws JsonError on invalid JSON or forbidden keys
|
||||
const obj = safeParse<{ a: number }>('{"a": 1}');
|
||||
|
||||
// Safe stringify — throws JsonError on circular references
|
||||
const json = safeStringify(obj);
|
||||
|
||||
// Non-throwing parse
|
||||
const val = tryParse("bad input"); // undefined
|
||||
|
||||
// Validity check (handles "null" correctly)
|
||||
isValidJSON("null"); // true
|
||||
isValidJSON("bad"); // false
|
||||
|
||||
// UTF-8 byte size of JSON representation
|
||||
getStringifySize({ hello: "world" }); // 15
|
||||
```
|
||||
|
||||
## Modules
|
||||
|
||||
| Module | Description |
|
||||
|--------|-------------|
|
||||
| `@suaveplan/json` | Main entry — all public exports |
|
||||
| `@suaveplan/json/safe-json` | `safeParse`, `safeStringify`, `tryParse`, `isValidJSON`, `getStringifySize` |
|
||||
| `@suaveplan/json/errors` | `JsonError`, `JSON_CODES`, `JsonCode` |
|
||||
|
||||
## Error handling
|
||||
|
||||
Every failure is a structured `JsonError` with a stable code:
|
||||
|
||||
| Code | Trigger |
|
||||
|------|---------|
|
||||
| `JSON.PARSE.FAILED` | Invalid JSON syntax or aborted `AbortSignal` |
|
||||
| `JSON.PARSE.PROTOTYPE_POLLUTION` | Forbidden key (`__proto__`, `constructor`, `prototype`) |
|
||||
| `JSON.STRINGIFY.CIRCULAR` | True circular reference detected |
|
||||
| `JSON.STRINGIFY.FAILED` | Aborted signal or non-serialisable value (e.g. `BigInt`) |
|
||||
|
||||
```ts
|
||||
import { safeParse, JsonError, JSON_CODES } from "@suaveplan/json";
|
||||
|
||||
try {
|
||||
safeParse('{"__proto__": {}}');
|
||||
} catch (err) {
|
||||
if (err instanceof JsonError && err.code === JSON_CODES["PARSE.PROTOTYPE_POLLUTION"]) {
|
||||
console.error("Prototype pollution blocked");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### `ParseOptions`
|
||||
|
||||
```ts
|
||||
interface ParseOptions {
|
||||
forbiddenKeys?: string[]; // default: ["__proto__", "constructor", "prototype"]
|
||||
maxDepth?: number; // default: 50
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
```
|
||||
|
||||
### `StringifyOptions`
|
||||
|
||||
```ts
|
||||
interface StringifyOptions {
|
||||
space?: number;
|
||||
signal?: AbortSignal;
|
||||
maxDepth?: number; // default: 100
|
||||
}
|
||||
```
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Measured on Intel Xeon E5-2696 v4 @ 2.20 GHz, Bun 1.3.13:
|
||||
|
||||
```text
|
||||
benchmark avg (min … max)
|
||||
-------------------------------------------
|
||||
safeParse small 9.84 µs/iter
|
||||
JSON.parse small 592.70 ns/iter
|
||||
safeStringify small 1.99 µs/iter
|
||||
JSON.stringify small 212.99 ns/iter
|
||||
safeParse medium 42.11 µs/iter
|
||||
safeStringify medium 11.92 µs/iter
|
||||
```
|
||||
|
||||
The overhead relative to the native functions comes from the OTel span
|
||||
creation, reviver/replacer function calls, and ancestor-stack tracking.
|
||||
In production with a no-op tracer the spans cost ~0 ns.
|
||||
|
||||
## Links
|
||||
|
||||
- [`src/safe-json/safe-json.md`](src/safe-json/safe-json.md) — implementation notes
|
||||
- [`src/errors/json-error.md`](src/errors/json-error.md) — error codes
|
||||
- [`src/types/types.md`](src/types/types.md) — option types
|
||||
|
||||
103
docs/foundation/data/json/src/errors/json-error.md
Normal file
103
docs/foundation/data/json/src/errors/json-error.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# json-error
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides the typed error class and stable code registry for `@suaveplan/json`.
|
||||
All error conditions produced by this package surface as `JsonError` instances
|
||||
with machine-readable codes so callers can branch on failure mode without
|
||||
parsing message strings.
|
||||
|
||||
## Features
|
||||
|
||||
- `JSON_CODES` registry — four stable codes under the `JSON.*` namespace
|
||||
- `JsonCode` union type for compile-time exhaustiveness checks
|
||||
- `JsonError` extends `SuaveplanError` — carries `code`, `cause`, and optional
|
||||
context payload; integrates with the OTel span-event adapter from
|
||||
`@suaveplan/error`
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { JsonError, JSON_CODES } from "@suaveplan/json/errors";
|
||||
|
||||
throw new JsonError(
|
||||
JSON_CODES["PARSE.FAILED"],
|
||||
"Unexpected token at position 0"
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { JsonError, JSON_CODES } from "@suaveplan/json/errors";
|
||||
|
||||
function parseSafe(text: string): unknown {
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (cause) {
|
||||
throw new JsonError(
|
||||
JSON_CODES["PARSE.FAILED"],
|
||||
`JSON parse failed: ${(cause as Error).message}`,
|
||||
{ cause }
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `JSON_CODES`
|
||||
|
||||
```ts
|
||||
const JSON_CODES: {
|
||||
"PARSE.FAILED": "JSON/PARSE.FAILED";
|
||||
"PARSE.PROTOTYPE_POLLUTION": "JSON/PARSE.PROTOTYPE_POLLUTION";
|
||||
"STRINGIFY.CIRCULAR": "JSON/STRINGIFY.CIRCULAR";
|
||||
"STRINGIFY.FAILED": "JSON/STRINGIFY.FAILED";
|
||||
};
|
||||
```
|
||||
|
||||
Stable, namespaced error codes produced by `defineCodes("JSON", [...])`.
|
||||
Each code is a string literal of the form `"JSON/<segment>"`.
|
||||
|
||||
### `JsonCode`
|
||||
|
||||
```ts
|
||||
type JsonCode = (typeof JSON_CODES)[keyof typeof JSON_CODES];
|
||||
```
|
||||
|
||||
Union of all four code literals. Use this type for exhaustive `switch`
|
||||
statements or discriminated-union handlers.
|
||||
|
||||
### `class JsonError`
|
||||
|
||||
```ts
|
||||
class JsonError extends SuaveplanError<JsonCode> {
|
||||
constructor(code: JsonCode, message: string, options?: SuaveplanErrorOptions);
|
||||
}
|
||||
```
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `code` | `JsonCode` | Machine-readable error code from `JSON_CODES`. |
|
||||
| `message` | `string` | Human-readable description of the error. |
|
||||
| `options` | `SuaveplanErrorOptions?` | Optional `cause`, `context`, and `meta` fields. |
|
||||
|
||||
**Throws** — never; constructor only constructs.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The constructor is flagged with `// biome-ignore lint/complexity/noUselessConstructor`
|
||||
because it widens the visibility of the `SuaveplanError` constructor from
|
||||
`protected` to `public`. This is required by the Biome linter which otherwise
|
||||
flags it as a no-op, but the widening is intentional and load-bearing.
|
||||
|
||||
Error codes are registered once at module load time via `defineCodes`. The
|
||||
`defineCodes` helper from `@suaveplan/error` is idempotent within a process
|
||||
(same namespace + same codes → same object reference), making it safe to import
|
||||
across multiple entry points without duplication.
|
||||
209
docs/foundation/data/json/src/safe-json/safe-json.md
Normal file
209
docs/foundation/data/json/src/safe-json/safe-json.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# safe-json
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides enterprise-grade `JSON.parse` and `JSON.stringify` wrappers that
|
||||
defend against prototype pollution, circular references, and AbortSignal
|
||||
cancellation. Every error surface is a structured `JsonError` with a stable,
|
||||
machine-readable code — no raw `SyntaxError` instances escape this module.
|
||||
All I/O-critical entry points emit OpenTelemetry spans for distributed tracing.
|
||||
|
||||
## Features
|
||||
|
||||
- `safeParse` — parses JSON with a reviver that blocks forbidden keys
|
||||
(`__proto__`, `constructor`, `prototype` by default); configurable via
|
||||
`ParseOptions.forbiddenKeys`
|
||||
- `safeStringify` — stringifies with ancestor-stack cycle detection; DAG
|
||||
sharing (same object at two different paths) is allowed; only true cycles
|
||||
throw `STRINGIFY.CIRCULAR`
|
||||
- `tryParse` — non-throwing wrapper returning `undefined` on any error
|
||||
- `isValidJSON` — boolean check that correctly handles the `"null"` literal
|
||||
- `getStringifySize` — UTF-8 byte size of the JSON representation
|
||||
- OTel spans on `safeParse` and `safeStringify`
|
||||
- AbortSignal support on both `safeParse` and `safeStringify`
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { safeParse, safeStringify, tryParse, isValidJSON, getStringifySize } from "@suaveplan/json";
|
||||
|
||||
// Parse
|
||||
const obj = safeParse<{ a: number }>('{"a":1}');
|
||||
console.log(obj.a); // 1
|
||||
|
||||
// Stringify
|
||||
const json = safeStringify({ a: 1 });
|
||||
console.log(json); // '{"a":1}'
|
||||
|
||||
// Non-throwing parse
|
||||
const result = tryParse("not-json"); // undefined
|
||||
|
||||
// Validity check
|
||||
isValidJSON("null"); // true
|
||||
isValidJSON("bad"); // false
|
||||
|
||||
// Byte size
|
||||
getStringifySize({ hello: "world" }); // 15
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
safeParse,
|
||||
safeStringify,
|
||||
JsonError,
|
||||
JSON_CODES,
|
||||
type ParseOptions,
|
||||
type StringifyOptions,
|
||||
} from "@suaveplan/json";
|
||||
|
||||
// Block additional keys beyond the defaults
|
||||
const parseOpts: ParseOptions = {
|
||||
forbiddenKeys: ["__proto__", "constructor", "prototype", "eval"],
|
||||
};
|
||||
|
||||
try {
|
||||
const data = safeParse<unknown>('{"eval":"alert(1)"}', parseOpts);
|
||||
} catch (err) {
|
||||
if (err instanceof JsonError && err.code === JSON_CODES["PARSE.PROTOTYPE_POLLUTION"]) {
|
||||
console.error("Prototype pollution attempt blocked");
|
||||
}
|
||||
}
|
||||
|
||||
// AbortSignal cancellation
|
||||
const controller = new AbortController();
|
||||
setTimeout(() => controller.abort(), 10);
|
||||
|
||||
const stringifyOpts: StringifyOptions = {
|
||||
space: 2,
|
||||
signal: controller.signal,
|
||||
};
|
||||
|
||||
const largePayload = Array.from({ length: 10000 }, (_, i) => ({ id: i }));
|
||||
try {
|
||||
safeStringify(largePayload, stringifyOpts);
|
||||
} catch (err) {
|
||||
if (err instanceof JsonError && err.code === JSON_CODES["STRINGIFY.FAILED"]) {
|
||||
console.error("Stringify cancelled");
|
||||
}
|
||||
}
|
||||
|
||||
// Circular reference detection
|
||||
const node: Record<string, unknown> = { name: "root" };
|
||||
node.self = node;
|
||||
|
||||
try {
|
||||
safeStringify(node);
|
||||
} catch (err) {
|
||||
if (err instanceof JsonError && err.code === JSON_CODES["STRINGIFY.CIRCULAR"]) {
|
||||
console.error("Circular reference blocked");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `safeParse<T>(text, options?): T`
|
||||
|
||||
Parses a JSON string with prototype-pollution protection.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `text` | `string` | Raw JSON text to parse. |
|
||||
| `options` | `ParseOptions?` | Optional parse configuration. |
|
||||
|
||||
**Returns** — The parsed value typed as `T`.
|
||||
|
||||
**Throws**
|
||||
|
||||
- `JsonError(PARSE.FAILED)` — invalid JSON syntax or aborted signal.
|
||||
- `JsonError(PARSE.PROTOTYPE_POLLUTION)` — a forbidden key was encountered.
|
||||
|
||||
---
|
||||
|
||||
### `safeStringify(value, options?): string`
|
||||
|
||||
Stringifies a value with circular-reference detection.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `value` | `unknown` | Value to serialise. |
|
||||
| `options` | `StringifyOptions?` | Optional stringify configuration. |
|
||||
|
||||
**Returns** — The JSON string.
|
||||
|
||||
**Throws**
|
||||
|
||||
- `JsonError(STRINGIFY.CIRCULAR)` — a true cycle was detected.
|
||||
- `JsonError(STRINGIFY.FAILED)` — aborted signal or non-serialisable value (e.g. `BigInt`).
|
||||
|
||||
---
|
||||
|
||||
### `tryParse<T>(text, options?): T | undefined`
|
||||
|
||||
Non-throwing parse; returns `undefined` on any error.
|
||||
|
||||
**Parameters** — same as `safeParse`.
|
||||
|
||||
**Returns** — The parsed value or `undefined`.
|
||||
|
||||
---
|
||||
|
||||
### `isValidJSON(text): boolean`
|
||||
|
||||
Returns `true` if the string is parseable by `safeParse` (respects the default
|
||||
forbidden-key list). Returns `true` for `"null"` (unlike a naïve
|
||||
`tryParse(text) !== undefined` check).
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `text` | `string` | The string to test. |
|
||||
|
||||
**Returns** — `boolean`.
|
||||
|
||||
---
|
||||
|
||||
### `getStringifySize(value): number`
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `value` | `unknown` | Any JSON-serialisable value. |
|
||||
|
||||
**Returns** — UTF-8 byte count of `JSON.stringify(value)`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
**Prototype pollution guard** — `JSON.parse` supports a reviver function that
|
||||
is called for every key-value pair during object construction. The reviver
|
||||
inspects each key against a `Set<string>` built from `forbiddenKeys` (default:
|
||||
`__proto__`, `constructor`, `prototype`). The root `""` key is exempt because
|
||||
the reviver is called with the empty string for the top-level result.
|
||||
|
||||
**Cycle detection** — The replacer receives `this` bound to the parent object at
|
||||
each step, which allows reconstructing the ancestor stack. When entering a
|
||||
child object we find the parent's index in the stack and truncate everything
|
||||
after it, then check whether the new value is already in the stack before
|
||||
pushing it. This correctly allows DAG sharing (the same object referenced
|
||||
at two sibling paths) while rejecting true back-edges.
|
||||
|
||||
**`isValidJSON` and the `"null"` edge case** — A naïve implementation using
|
||||
`tryParse(text) !== undefined` would return `false` for the `"null"` literal
|
||||
because `JSON.parse("null")` returns `null`. This module instead wraps
|
||||
`safeParse` in a `try/catch` and returns `true` on success.
|
||||
|
||||
**OTel instrumentation** — `safeParse` and `safeStringify` each create an
|
||||
active span via `trace.getTracer("@suaveplan/json").startActiveSpan(...)`.
|
||||
When no SDK is configured the tracer is a no-op, so there is zero overhead
|
||||
in un-instrumented environments.
|
||||
78
docs/foundation/data/json/src/types/types.md
Normal file
78
docs/foundation/data/json/src/types/types.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# types
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Defines the public option types consumed by `@suaveplan/json` operations.
|
||||
Centralising these interfaces in a dedicated `types/` module keeps the
|
||||
implementation files focused on logic and lets external callers import only the
|
||||
types they need without pulling in any runtime code.
|
||||
|
||||
## Features
|
||||
|
||||
- `ParseOptions` — controls forbidden-key list, max nesting depth, and abort
|
||||
signal for `safeParse` / `tryParse`
|
||||
- `StringifyOptions` — controls pretty-print spacing, abort signal, and
|
||||
circular-ref depth limit for `safeStringify`
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import type { ParseOptions } from "@suaveplan/json";
|
||||
|
||||
const opts: ParseOptions = {
|
||||
forbiddenKeys: ["__proto__", "constructor"],
|
||||
maxDepth: 20,
|
||||
};
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import type { ParseOptions, StringifyOptions } from "@suaveplan/json";
|
||||
|
||||
const controller = new AbortController();
|
||||
|
||||
const parseOpts: ParseOptions = {
|
||||
forbiddenKeys: ["__proto__", "constructor", "prototype", "eval"],
|
||||
maxDepth: 10,
|
||||
signal: controller.signal,
|
||||
};
|
||||
|
||||
const stringifyOpts: StringifyOptions = {
|
||||
space: 2,
|
||||
signal: controller.signal,
|
||||
maxDepth: 50,
|
||||
};
|
||||
|
||||
// Abort after 500 ms
|
||||
setTimeout(() => controller.abort(), 500);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `ParseOptions`
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `forbiddenKeys` | `string[]?` | `["__proto__", "constructor", "prototype"]` | Keys that trigger a `PARSE.PROTOTYPE_POLLUTION` error when encountered anywhere in the object tree. |
|
||||
| `maxDepth` | `number?` | `50` | Maximum JSON nesting depth; deeper structures throw `PARSE.FAILED`. |
|
||||
| `signal` | `AbortSignal?` | — | Optional cancellation token; aborted signals cause `safeParse` to throw `PARSE.FAILED`. |
|
||||
|
||||
### `StringifyOptions`
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `space` | `number?` | — | Forwarded verbatim to `JSON.stringify`; produces indented output when set. |
|
||||
| `signal` | `AbortSignal?` | — | Optional cancellation token; aborted signals cause `safeStringify` to throw `STRINGIFY.FAILED`. |
|
||||
| `maxDepth` | `number?` | `100` | Maximum depth before `safeStringify` throws `STRINGIFY.CIRCULAR`. |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Both interfaces are pure TypeScript — no runtime object is emitted. The types
|
||||
file is excluded from coverage measurement because there is nothing to execute.
|
||||
Default values are applied inside the respective implementation functions
|
||||
(`safeParse`, `safeStringify`) using object destructuring with defaults, keeping
|
||||
the type definitions free of runtime assumptions.
|
||||
Reference in New Issue
Block a user