{"version":3,"file":"safe-stringify.cjs","names":[],"sources":["../../src/utils/safe-stringify.ts"],"sourcesContent":["/**\n * Safely JSON-stringifies a value, replacing circular references with \"[Circular]\".\n * Uses a stack-based approach so shared (non-circular) references are preserved.\n */\nexport function safeStringify(value: unknown, space?: string | number): string {\n  const stack: unknown[] = [];\n  const result: string | undefined = JSON.stringify(\n    value,\n    function (this: unknown, _key: string, val: unknown) {\n      if (typeof val === 'bigint') return val.toString();\n      if (val !== null && typeof val === 'object') {\n        while (stack.length > 0 && stack[stack.length - 1] !== this) {\n          stack.pop();\n        }\n        if (stack.includes(val)) return '[Circular]';\n        stack.push(val);\n      }\n      return val;\n    },\n    space,\n  );\n  // JSON.stringify returns undefined for unsupported top-level values (undefined, functions, symbols).\n  return result ?? 'null';\n}\n\n/**\n * Maximum number of nodes the `isBoundedSerializable` probe lets `JSON.stringify`\n * visit for a single value.\n *\n * `JSON.stringify` expands shared (non-circular) references once per path, so an\n * acyclic graph with layered sharing (`{ a: n, b: n }` nested `d` deep) holds\n * `d + 1` objects but expands to `2^d` visited nodes — enough to block the event\n * loop for minutes. The budget makes the probe bail in bounded time; a value\n * that exceeds it is treated as \"not directly serializable\".\n */\nconst SERIALIZATION_NODE_BUDGET = 1_000_000;\n\n/**\n * Serialize a value to JSON while visiting no more than\n * `SERIALIZATION_NODE_BUDGET` nodes. Returns the JSON string, or `undefined`\n * when the value cannot be represented as JSON: it threw (a cycle or BigInt),\n * exhausted the budget (a shared-reference graph `JSON.stringify` would expand\n * exponentially), or produced no output (top-level `undefined`, a function, a\n * symbol, or an object whose `toJSON()` returns `undefined`).\n *\n * The value is read exactly once, so a caller that needs both a serializability\n * check and the serialized result can use this rather than probing and then\n * re-serializing — closing a TOCTOU gap where a stateful getter/`toJSON()`\n * returns a different (e.g. much larger) value the second time.\n */\nexport function boundedStringify(value: unknown): string | undefined {\n  let budget = SERIALIZATION_NODE_BUDGET;\n  try {\n    return JSON.stringify(value, (_key, val) => {\n      if (--budget < 0) {\n        throw new RangeError('boundedStringify: value exceeds the serialization node budget');\n      }\n      return val;\n    });\n  } catch {\n    return undefined;\n  }\n}\n\n/**\n * Whether `value` can be serialized to JSON within `SERIALIZATION_NODE_BUDGET`\n * nodes. `false` for cycles, BigInt, over-budget shared-reference graphs, and\n * values that produce no JSON output (top-level undefined/function/symbol, or an\n * object whose `toJSON()` returns undefined).\n */\nexport function isBoundedSerializable(value: unknown): boolean {\n  return boundedStringify(value) !== undefined;\n}\n\n/**\n * Cycle- and shared-reference-safe stringify: every object is serialized at most\n * once, and any repeat — a true cycle OR a shared/diamond reference — becomes\n * \"[Circular]\". Unlike `safeStringify`, this cannot expand a shared-reference\n * graph exponentially, so it is a bounded fallback for values that overflow the\n * `isBoundedSerializable` probe.\n */\nfunction collapseStringify(value: unknown): string {\n  const seen = new WeakSet<object>();\n  const result: string | undefined = JSON.stringify(value, function (_key: string, val: unknown) {\n    if (typeof val === 'bigint') return val.toString();\n    if (val !== null && typeof val === 'object') {\n      if (seen.has(val)) return '[Circular]';\n      seen.add(val);\n    }\n    return val;\n  });\n  return result ?? 'null';\n}\n\n/**\n * Returns a JSON-serializable copy of a value.\n *\n * If the value already serializes within the node budget it is returned\n * unchanged (no cloning overhead). Otherwise — a cycle, a BigInt, or a\n * shared-reference graph too large for the probe — it is rebuilt through\n * `collapseStringify`, which dedupes repeated references to `[Circular]` and so\n * completes in bounded time instead of hanging on the exponential expansion.\n */\nexport function ensureSerializable(value: unknown): unknown {\n  if (value === null || typeof value !== 'object') return value;\n  if (isBoundedSerializable(value)) return value;\n  return JSON.parse(collapseStringify(value));\n}\n"],"mappings":";;;;;;AAIA,SAAgB,cAAc,OAAgB,OAAiC;CAC7E,MAAM,QAAmB,CAAC;CAiB1B,OAhBmC,KAAK,UACtC,OACA,SAAyB,MAAc,KAAc;EACnD,IAAI,OAAO,QAAQ,UAAU,OAAO,IAAI,SAAS;EACjD,IAAI,QAAQ,QAAQ,OAAO,QAAQ,UAAU;GAC3C,OAAO,MAAM,SAAS,KAAK,MAAM,MAAM,SAAS,OAAO,MACrD,MAAM,IAAI;GAEZ,IAAI,MAAM,SAAS,GAAG,GAAG,OAAO;GAChC,MAAM,KAAK,GAAG;EAChB;EACA,OAAO;CACT,GACA,KAGU,KAAK;AACnB;;;;;;;;;;;AAYA,MAAM,4BAA4B;;;;;;;;;;;;;;AAelC,SAAgB,iBAAiB,OAAoC;CACnE,IAAI,SAAS;CACb,IAAI;EACF,OAAO,KAAK,UAAU,QAAQ,MAAM,QAAQ;GAC1C,IAAI,EAAE,SAAS,GACb,MAAM,IAAI,WAAW,+DAA+D;GAEtF,OAAO;EACT,CAAC;CACH,QAAQ;EACN;CACF;AACF;;;;;;;AAQA,SAAgB,sBAAsB,OAAyB;CAC7D,OAAO,iBAAiB,KAAK,MAAM,KAAA;AACrC;;;;;;;;AASA,SAAS,kBAAkB,OAAwB;CACjD,MAAM,uBAAO,IAAI,QAAgB;CASjC,OARmC,KAAK,UAAU,OAAO,SAAU,MAAc,KAAc;EAC7F,IAAI,OAAO,QAAQ,UAAU,OAAO,IAAI,SAAS;EACjD,IAAI,QAAQ,QAAQ,OAAO,QAAQ,UAAU;GAC3C,IAAI,KAAK,IAAI,GAAG,GAAG,OAAO;GAC1B,KAAK,IAAI,GAAG;EACd;EACA,OAAO;CACT,CACY,KAAK;AACnB;;;;;;;;;;AAWA,SAAgB,mBAAmB,OAAyB;CAC1D,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU,OAAO;CACxD,IAAI,sBAAsB,KAAK,GAAG,OAAO;CACzC,OAAO,KAAK,MAAM,kBAAkB,KAAK,CAAC;AAC5C"}