const isPlainObject = (value: object): boolean => { const proto = Object.getPrototypeOf(value); return proto === Object.prototype || proto === null; }; const isTemporalValue = (value: object): boolean => { const tag = (value as { [Symbol.toStringTag]?: unknown })[Symbol.toStringTag]; return typeof tag === 'string' && tag.startsWith('Temporal.'); }; /** * Creates a deep clone of a value. Primarily used to snapshot Vue 3 reactive * data (e.g. fetched DTOs) so later edits can be diffed against the original. * * IMPORTANT: a bare `structuredClone(obj)` is NOT used as the primary path. The * structured-clone algorithm — and a naive key-by-key copy — both silently * degrade `Temporal.*` values (PlainDateTime, PlainDate, PlainTime, …) to an * empty object `{}`, because Temporal keeps its data in internal slots rather * than in enumerable own properties. In a Temporal-first codebase that * corruption is pervasive (e.g. validity dates blanked after a save round-trip). * * This implementation therefore: * - deep-copies plain objects and arrays, * - preserves immutable `Temporal.*` values by reference (a safe clone), * - tracks visited references so circular / shared graphs don't recurse forever, * - delegates other structured built-ins (Date, Map, Set, RegExp, typed arrays) * to `structuredClone`, which copies those faithfully. * * @param obj Value to-be cloned */ export const deepClone = (obj: T): T => { if (obj == null) { return null; } const seen = new WeakMap(); const cloneValue = (value: any): any => { // Primitives, functions and symbols clone by value/reference. if (value === null || typeof value !== 'object') { return value; } // Preserve identity within a single clone so circular / shared references // are not duplicated and do not recurse forever. if (seen.has(value)) { return seen.get(value); } // Temporal values are immutable and store their data in internal slots, so a // structural copy collapses them to `{}`. Sharing the reference is a correct clone. if (isTemporalValue(value)) { return value; } if (Array.isArray(value)) { const cloned: any[] = []; seen.set(value, cloned); for (let i = 0; i < value.length; i++) { cloned[i] = cloneValue(value[i]); } return cloned; } if (isPlainObject(value)) { const cloned: Record = {}; seen.set(value, cloned); for (const key in value) { if (Object.prototype.hasOwnProperty.call(value, key)) { cloned[key] = cloneValue(value[key]); } } return cloned; } // Other structured built-ins (Date, Map, Set, RegExp, typed arrays, …): // structuredClone copies these faithfully. Fall back to the original // reference when the runtime lacks structuredClone or the value is not // structurally cloneable. if (typeof structuredClone === 'function') { try { return structuredClone(value); } catch { return value; } } return value; }; return cloneValue(obj); };