UNPKG

synapse-storage

Version:

Набор инструментов для управления состоянием и апи-запросами

115 lines (113 loc) 4.46 kB
/** * Deep equality check for two values. */ function isEqual(a, b) { if (a === b) return true; if (a == null || b == null) return a === b; const typeA = typeof a; const typeB = typeof b; if (typeA !== typeB) return false; if (typeA !== 'object') return a === b; if (a instanceof Date && b instanceof Date) { return a.getTime() === b.getTime(); } if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) return false; for(let i = 0; i < a.length; i++){ if (!isEqual(a[i], b[i])) return false; } return true; } const keysA = Object.keys(a); const keysB = Object.keys(b); if (keysA.length !== keysB.length) return false; return keysA.every((key)=>Object.prototype.hasOwnProperty.call(b, key) && isEqual(a[key], b[key])); } /** * Shallow (one-level) equality check. Two values are equal when they are the * same reference, equal primitives, or objects with the same keys whose values * are strictly equal (`===`) — nested objects are NOT compared deeply. * * Shared default comparator for the shallow-compare middlewares (sync + async). */ function shallowEqual(prev, next) { if (prev === next) return true; if (typeof prev !== 'object' || typeof next !== 'object' || prev === null || next === null) { return prev === next; } const keysA = Object.keys(prev); const keysB = Object.keys(next); if (keysA.length !== keysB.length) return false; return keysA.every((key)=>Object.prototype.hasOwnProperty.call(next, key) && prev[key] === next[key]); } /** * Finds all changed paths between two objects. */ function findChangedPaths(oldObj, newObj, prefix = '', changedPaths = new Set(), visited = new WeakMap()) { if (oldObj === newObj) return changedPaths; if (typeof oldObj !== 'object' || typeof newObj !== 'object' || oldObj === null || newObj === null) { if (oldObj !== newObj) { changedPaths.add(prefix || ''); } return changedPaths; } // Cycle/dedup guard keyed by the (oldObj, newObj) PAIR, not by oldObj alone. // structuredClone deduplicates shared references, so a single `oldObj` can be // reached via different paths leading to DIFFERENT `newObj`s. Keying on oldObj // alone would skip the second, genuinely-changed branch entirely. const seenNew = visited.get(oldObj); if (seenNew) { if (seenNew.has(newObj)) return changedPaths; seenNew.add(newObj); } else { visited.set(oldObj, new WeakSet([ newObj ])); } const allKeys = new Set([ ...Object.keys(oldObj || {}), ...Object.keys(newObj || {}) ]); for (const key of allKeys){ const oldValue = oldObj[key]; const newValue = newObj[key]; if (oldValue === newValue) continue; const path = prefix ? `${prefix}.${key}` : key; if (oldValue && newValue && typeof oldValue === 'object' && typeof newValue === 'object' && !Array.isArray(oldValue) && !Array.isArray(newValue)) { findChangedPaths(oldValue, newValue, path, changedPaths, visited); } else if (Array.isArray(oldValue) && Array.isArray(newValue)) { if (!isEqual(oldValue, newValue)) { changedPaths.add(path); } } else if (!isEqual(oldValue, newValue)) { changedPaths.add(path); } } return changedPaths; } /** * Creates a lazy deep-clone of an object. * Shallow copy initially, structuredClone on first nested property access. */ function createLazyClone(source) { const cloned = new Set(); const shallow = { ...source }; return new Proxy(shallow, { get (target, prop, receiver) { if (typeof prop === 'string' && !cloned.has(prop)) { const val = target[prop]; if (val !== null && typeof val === 'object') { cloned.add(prop); target[prop] = structuredClone(val); } } return Reflect.get(target, prop, receiver); }, set (target, prop, value) { if (typeof prop === 'string') { cloned.add(prop); } return Reflect.set(target, prop, value); } }); } export { createLazyClone, findChangedPaths, isEqual, shallowEqual }; //# sourceMappingURL=state-diff.util.js.map