synapse-storage
Version:
Набор инструментов для управления состоянием и апи-запросами
115 lines (113 loc) • 4.46 kB
JavaScript
/**
* 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