UNPKG

@bluelovers/deep-diff

Version:

Javascript utility for calculating deep difference, capturing changes, and applying changes across objects; for nodejs and the browser.

537 lines (443 loc) 13.9 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.BlueloversDeepDiff = {})); })(this, (function (exports) { 'use strict'; exports.EnumKinds = void 0; (function (EnumKinds) { EnumKinds["DiffEdit"] = "E"; EnumKinds["DiffNew"] = "N"; EnumKinds["DiffDeleted"] = "D"; EnumKinds["DiffArray"] = "A"; })(exports.EnumKinds || (exports.EnumKinds = {})); const validKinds = ["N", "E", "A", "D"]; class Diff { constructor(kind, path) { this.kind = kind; if (path !== null && path !== void 0 && path.length) { this.path = path; } } } class DiffEdit extends Diff { constructor(path, lhs, rhs) { super("E", path); this.lhs = lhs; this.rhs = rhs; } } class DiffNew extends Diff { constructor(path, rhs) { super("N", path); this.rhs = rhs; } } class DiffDeleted extends Diff { constructor(path, lhs) { super("D", path); this.lhs = lhs; } } class DiffArray extends Diff { constructor(path, index, item) { super("A", path); this.index = index; this.item = item; } } function arrayRemove(arr, from) { return arr.splice(from, 1); } function realTypeOf(subject) { const type = typeof subject; if (type !== 'object') { return type; } if (subject === Math) { return 'math'; } else if (subject === null) { return 'null'; } else if (Array.isArray(subject)) { return 'array'; } else if (Object.prototype.toString.call(subject) === '[object Date]') { return 'date'; } else if (typeof subject.toString === 'function' && /^\/.*\//.test(subject.toString())) { return 'regexp'; } return 'object'; } function hashThisString(string) { let hash = 0; if (string.length === 0) { return hash; } for (let i = 0; i < string.length; i++) { const char = string.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; } return hash; } function getOrderIndependentHash(object) { let accum = 0; const type = realTypeOf(object); if (type === 'array') { object.forEach(function (item) { accum += getOrderIndependentHash(item); }); const arrayString = normalizeHashDesc(type, { hash: accum }); return accum + hashThisString(arrayString); } else if (type === 'object') { for (const key in object) { if (object.hasOwnProperty(key)) { const keyValueString = normalizeHashDesc(type, { key, hash: getOrderIndependentHash(object[key]) }); accum += hashThisString(keyValueString); } } return accum; } const stringToHash = normalizeHashDesc(type, { value: object }); return accum + hashThisString(stringToHash); } function normalizeHashDesc(type, options) { switch (type) { case 'array': return `[ type: ${type} , hash: ${options.hash}]`; case 'object': return `[ type: ${type}, key: ${options.key} , hash: ${options.hash}]`; default: return `[ type: ${type} , value: ${options.value}]`; } } function _deepDiff(lhs, rhs, changes, prefilter, path, key, stack, orderIndependent) { changes = changes || []; path = path || []; stack = stack || []; const currentPath = path.slice(0); if (typeof key !== 'undefined' && key !== null) { if (prefilter) { if (typeof prefilter === 'function' && prefilter(currentPath, key)) { return; } else if (typeof prefilter === 'object') { if (prefilter.prefilter && prefilter.prefilter(currentPath, key)) { return; } if (prefilter.normalize) { const alt = prefilter.normalize(currentPath, key, lhs, rhs); if (alt) { lhs = alt[0]; rhs = alt[1]; } } } } currentPath.push(key); } if (realTypeOf(lhs) === 'regexp' && realTypeOf(rhs) === 'regexp') { lhs = lhs.toString(); rhs = rhs.toString(); } const ltype = typeof lhs; const rtype = typeof rhs; let i, j, k, other; const ldefined = ltype !== 'undefined' || stack && stack.length > 0 && stack[stack.length - 1].lhs && Object.getOwnPropertyDescriptor(stack[stack.length - 1].lhs, key); const rdefined = rtype !== 'undefined' || stack && stack.length > 0 && stack[stack.length - 1].rhs && Object.getOwnPropertyDescriptor(stack[stack.length - 1].rhs, key); if (!ldefined && rdefined) { changes.push(new DiffNew(currentPath, rhs)); } else if (!rdefined && ldefined) { changes.push(new DiffDeleted(currentPath, lhs)); } else if (realTypeOf(lhs) !== realTypeOf(rhs)) { changes.push(new DiffEdit(currentPath, lhs, rhs)); } else if (realTypeOf(lhs) === 'date' && lhs - rhs !== 0) { changes.push(new DiffEdit(currentPath, lhs, rhs)); } else if (ltype === 'object' && lhs !== null && rhs !== null) { for (i = stack.length - 1; i > -1; --i) { if (stack[i].lhs === lhs) { other = true; break; } } if (!other) { stack.push({ lhs: lhs, rhs: rhs }); if (Array.isArray(lhs)) { if (orderIndependent) { lhs.sort(function (a, b) { return getOrderIndependentHash(a) - getOrderIndependentHash(b); }); rhs.sort(function (a, b) { return getOrderIndependentHash(a) - getOrderIndependentHash(b); }); } i = rhs.length - 1; j = lhs.length - 1; while (i > j) { changes.push(new DiffArray(currentPath, i, new DiffNew(undefined, rhs[i--]))); } while (j > i) { changes.push(new DiffArray(currentPath, j, new DiffDeleted(undefined, lhs[j--]))); } for (; i >= 0; --i) { _deepDiff(lhs[i], rhs[i], changes, prefilter, currentPath, i, stack, orderIndependent); } } else { const akeys = Object.keys(lhs).concat(Object.getOwnPropertySymbols(lhs)); const pkeys = Object.keys(rhs).concat(Object.getOwnPropertySymbols(rhs)); for (i = 0; i < akeys.length; ++i) { k = akeys[i]; other = pkeys.indexOf(k); if (other >= 0) { _deepDiff(lhs[k], rhs[k], changes, prefilter, currentPath, k, stack, orderIndependent); pkeys[other] = null; } else { _deepDiff(lhs[k], undefined, changes, prefilter, currentPath, k, stack, orderIndependent); } } for (i = 0; i < pkeys.length; ++i) { k = pkeys[i]; if (k) { _deepDiff(undefined, rhs[k], changes, prefilter, currentPath, k, stack, orderIndependent); } } } stack.length = stack.length - 1; } else if (lhs !== rhs) { changes.push(new DiffEdit(currentPath, lhs, rhs)); } } else if (lhs !== rhs) { if (!(ltype === 'number' && isNaN(lhs) && isNaN(rhs))) { changes.push(new DiffEdit(currentPath, lhs, rhs)); } } } function observableDiff(lhs, rhs, observer, prefilter, orderIndependent) { const changes = []; _deepDiff(lhs, rhs, changes, prefilter, null, null, null, orderIndependent); if (observer) { for (let i = 0; i < changes.length; ++i) { observer(changes[i]); } } return changes; } function orderIndependentObservableDiff(lhs, rhs, changes, prefilter, path, key, stack) { return _deepDiff(lhs, rhs, changes, prefilter, path, key, stack, true); } function deepDiff(lhs, rhs, prefilter, accum) { const observer = accum ? function (difference) { if (difference) { accum.push(difference); } } : undefined; const changes = observableDiff(lhs, rhs, observer, prefilter); return accum ? accum : changes.length ? changes : undefined; } function orderIndependentDiff(lhs, rhs, prefilter, accum) { const observer = accum ? function (difference) { if (difference) { accum.push(difference); } } : undefined; const changes = observableDiff(lhs, rhs, observer, prefilter, true); return accum ? accum : changes.length ? changes : undefined; } function _traversalObject(target, path) { path = path.slice(); const key = path.pop(); let it = target; path.reduce((target, key) => { return it = target[key]; }, it); return [it, key]; } function _applyArrayChange(arr, index, change) { var _change$path; if ((_change$path = change.path) !== null && _change$path !== void 0 && _change$path.length) { let [it, key] = _traversalObject(arr[index], change.path); switch (change.kind) { case 'A': _applyArrayChange(it[key], change.index, change.item); break; case 'D': delete it[key]; break; case 'E': case 'N': it[key] = change.rhs; break; } } else { switch (change.kind) { case 'A': _applyArrayChange(arr[index], change.index, change.item); break; case 'D': arr = arrayRemove(arr, index); break; case 'E': case 'N': arr[index] = change.rhs; break; } } return arr; } function isIDiffNode(source) { return (source !== null && source !== void 0 ? source : false) && validKinds.includes(source.kind); } function applyChange(target, source, change) { if (typeof change === 'undefined' && isIDiffNode(source)) { change = source; } if (target && change && change.kind) { let it = target, i = -1, last = change.path ? change.path.length - 1 : 0; while (++i < last) { if (typeof it[change.path[i]] === 'undefined') { it[change.path[i]] = typeof change.path[i + 1] !== 'undefined' && typeof change.path[i + 1] === 'number' ? [] : {}; } it = it[change.path[i]]; } switch (change.kind) { case 'A': if (change.path && typeof it[change.path[i]] === 'undefined') { it[change.path[i]] = []; } _applyArrayChange(change.path ? it[change.path[i]] : it, change.index, change.item); break; case 'D': delete it[change.path[i]]; break; case 'E': case 'N': it[change.path[i]] = change.rhs; break; } } } function revertArrayChange(arr, index, change) { if (change.path && change.path.length) { let it = arr[index], i, u = change.path.length - 1; for (i = 0; i < u; i++) { it = it[change.path[i]]; } switch (change.kind) { case "A": revertArrayChange(it[change.path[i]], change.index, change.item); break; case "D": it[change.path[i]] = change.lhs; break; case "E": it[change.path[i]] = change.lhs; break; case "N": delete it[change.path[i]]; break; } } else { switch (change.kind) { case "A": revertArrayChange(arr[index], change.index, change.item); break; case "D": arr[index] = change.lhs; break; case "E": arr[index] = change.lhs; break; case "N": arr = arrayRemove(arr, index); break; } } return arr; } function revertChange(target, source, change) { if (target && source && change !== null && change !== void 0 && change.kind) { let it = target, i, u; u = change.path.length - 1; for (i = 0; i < u; i++) { if (typeof it[change.path[i]] === 'undefined') { it[change.path[i]] = {}; } it = it[change.path[i]]; } switch (change.kind) { case "A": revertArrayChange(it[change.path[i]], change.index, change.item); break; case "D": it[change.path[i]] = change.lhs; break; case "E": it[change.path[i]] = change.lhs; break; case "N": delete it[change.path[i]]; break; } } } function applyDiff(target, source, filter) { if (target && source) { const onChange = function (change) { if (!filter || filter(target, source, change)) { applyChange(target, source, change); } }; observableDiff(target, source, onChange); } return target; } function _applyDiffChangeCore(lhs, differences, fn) { differences.forEach(it => { fn(lhs, true, it); }); return true; } function applyDiffChange(lhs, differences) { if (_applyDiffChangeCore(lhs, differences, applyChange)) { return lhs; } } function revertDiffChange(lhs, differences) { if (_applyDiffChangeCore(lhs, differences, revertChange)) { return lhs; } } exports.Diff = Diff; exports.DiffArray = DiffArray; exports.DiffDeleted = DiffDeleted; exports.DiffEdit = DiffEdit; exports.DiffNew = DiffNew; exports.applyChange = applyChange; exports.applyDiff = applyDiff; exports.applyDiffChange = applyDiffChange; exports.deepDiff = deepDiff; exports["default"] = deepDiff; exports.diff = deepDiff; exports.getOrderIndependentHash = getOrderIndependentHash; exports.isIDiffNode = isIDiffNode; exports.observableDiff = observableDiff; exports.orderIndependentDiff = orderIndependentDiff; exports.orderIndependentObservableDiff = orderIndependentObservableDiff; exports.revertChange = revertChange; exports.revertDiffChange = revertDiffChange; Object.defineProperty(exports, '__esModule', { value: true }); })); //# sourceMappingURL=index.umd.development.cjs.map