import { isDeepStrictEqual } from 'node:util'; // Use row IDs so insertion/reordering does not masquerade as changed records. export function jsonChanges(before, after, location = '$') { if (isDeepStrictEqual(before, after)) return []; if (Array.isArray(before) && Array.isArray(after)) { const keyed = rows => rows.every(row => row && typeof row === 'object' && (row.id ?? row.data?.id) != null) && new Set(rows.map(row => row.id ?? row.data.id)).size === rows.length; if (keyed(before) && keyed(after)) { const index = rows => Object.fromEntries(rows.map(row => [row.id ?? row.data.id, row])); return jsonChanges(index(before), index(after), location); } } if (before && after && typeof before === 'object' && typeof after === 'object' && !Array.isArray(before) && !Array.isArray(after)) { return [...new Set([...Object.keys(before), ...Object.keys(after)])].flatMap(key => jsonChanges(before[key], after[key], `${location}[${JSON.stringify(key)}]`)); } return [{ path: location, type: before === undefined ? 'added' : after === undefined ? 'deleted' : 'modified', ...(before === undefined ? {} : { before }), ...(after === undefined ? {} : { after }) }]; }