/** * Metadata flattening — turn a nested record (envelope, payload, error) into a * flat list of dot-notation `key → value` entries for the inspector. Pure + * unit-tested; the component renders + copies what this returns. */ /** A primitive leaf value the inspector can render + copy. */ export type MetaValue = string | number | boolean | null; /** One flattened entry — `a.b.c` → value, with the value's JS type. */ export interface MetaEntry { readonly path: string; readonly value: MetaValue; readonly type: "string" | "number" | "boolean" | "null"; } function typeOf(v: MetaValue): MetaEntry["type"] { if (v === null) return "null"; if (typeof v === "number") return "number"; if (typeof v === "boolean") return "boolean"; return "string"; } /** * Flatten an arbitrary value into dot-notation leaf entries. Objects recurse by * key; arrays recurse by index (`items.0.id`). Primitives become a single * entry. `undefined` and functions are dropped. Cycles are guarded. */ export function flattenMetadata(input: unknown, prefix = ""): MetaEntry[] { return walk(input, prefix, new WeakSet()); } function walk(value: unknown, prefix: string, seen: WeakSet): MetaEntry[] { if (value === null) return [{ path: prefix || "value", value: null, type: "null" }]; if (value === undefined || typeof value === "function") return []; if (typeof value !== "object") { const v = value as MetaValue; return [{ path: prefix || "value", value: v, type: typeOf(v) }]; } if (seen.has(value as object)) return [{ path: prefix || "value", value: "[circular]", type: "string" }]; seen.add(value as object); const out: MetaEntry[] = []; const entries: [string, unknown][] = Array.isArray(value) ? value.map((v, i) => [String(i), v]) : Object.entries(value as Record); for (const [k, v] of entries) { out.push(...walk(v, prefix ? `${prefix}.${k}` : k, seen)); } return out; }