JSON.stringify with stable key ordering at all nesting levels.
(value: unknown)
| 106 | |
| 107 | /** JSON.stringify with stable key ordering at all nesting levels. */ |
| 108 | function stableStringify(value: unknown): string { |
| 109 | return JSON.stringify(value, (_key: string, val: unknown) => { |
| 110 | if (val && typeof val === "object" && !Array.isArray(val)) { |
| 111 | const sorted: Record<string, unknown> = {}; |
| 112 | for (const k of Object.keys(val as object).sort()) { |
| 113 | sorted[k] = (val as any)[k]; |
| 114 | } |
| 115 | return sorted; |
| 116 | } |
| 117 | // Serialize non-JSON-safe primitives to stable strings. |
| 118 | // evaluate() can pull arbitrary values from store/ref state, |
| 119 | // including undefined in object literals and edge-case numbers. |
| 120 | if (val === undefined) return "__undefined__"; |
| 121 | if (typeof val === "number") { |
| 122 | if (Number.isNaN(val)) return "__NaN__"; |
| 123 | if (val === Infinity) return "__Inf__"; |
| 124 | if (val === -Infinity) return "__-Inf__"; |
| 125 | } |
| 126 | return val; |
| 127 | }); |
| 128 | } |
| 129 | |
| 130 | function buildCacheKey(toolName: string, args: unknown, deps: unknown): string { |
| 131 | const depsKey = deps != null ? "::" + stableStringify(deps) : ""; |