built.io-browserify
Version:
SDK for Built.io Backend which is compatible with Browserify
304 lines (277 loc) • 8.94 kB
JavaScript
/*
This is a patched version of the "deep-diff" project:
https://github.com/flitbit/diff
Specifically, we have patched the library with shorter names
in the resulting diff object:
kind -> k
path -> p
index -> i
item -> m
The `lhs` portion of the diff object has been removed (it was unnecessary).
A new function is part of the API, `applyPatch`:
var Diff = require('diff')
var rhs = Diff.applyPatch(lhs, differences)
The function takes an lhs and the differences calculated, and returns the rhs
as a modified lhs.
*/
;(function(undefined) {
"use strict";
var $scope
, conflict, conflictResolution = [];
if (typeof global == 'object' && global) {
$scope = global;
} else if (typeof window !== 'undefined'){
$scope = window;
} else {
$scope = {};
}
conflict = $scope.DeepDiff;
if (conflict) {
conflictResolution.push(
function() {
if ('undefined' !== typeof conflict && $scope.DeepDiff === accumulateDiff) {
$scope.DeepDiff = conflict;
conflict = undefined;
}
});
}
// nodejs compatible on server side and in the browser.
function inherits(ctor, superCtor) {
ctor.super_ = superCtor;
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
}
function Diff(kind, path) {
Object.defineProperty(this, 'k', { value: kind, enumerable: true });
if (path && path.length) {
Object.defineProperty(this, 'p', { value: path, enumerable: true });
}
}
function DiffEdit(path, origin, value) {
DiffEdit.super_.call(this, 'E', path);
// Object.defineProperty(this, 'lhs', { value: origin, enumerable: true });
Object.defineProperty(this, 'rhs', { value: value, enumerable: true });
}
inherits(DiffEdit, Diff);
function DiffNew(path, value) {
DiffNew.super_.call(this, 'N', path);
Object.defineProperty(this, 'rhs', { value: value, enumerable: true });
}
inherits(DiffNew, Diff);
function DiffDeleted(path, value) {
DiffDeleted.super_.call(this, 'D', path);
// Object.defineProperty(this, 'lhs', { value: value, enumerable: true });
}
inherits(DiffDeleted, Diff);
function DiffArray(path, index, item) {
DiffArray.super_.call(this, 'A', path);
Object.defineProperty(this, 'i', { value: index, enumerable: true });
Object.defineProperty(this, 'm', { value: item, enumerable: true });
}
inherits(DiffArray, Diff);
function arrayRemove(arr, from, to) {
var rest = arr.slice((to || from) + 1 || arr.length);
arr.length = from < 0 ? arr.length + from : from;
arr.push.apply(arr, rest);
return arr;
}
function deepDiff(lhs, rhs, changes, path, key, stack) {
path = path || [];
var currentPath = path.slice(0);
if (key) { currentPath.push(key); }
var ltype = typeof lhs;
var rtype = typeof rhs;
if (ltype === 'undefined') {
if (rtype !== 'undefined') {
changes(new DiffNew(currentPath, rhs ));
}
} else if (rtype === 'undefined') {
changes(new DiffDeleted(currentPath, lhs));
} else if (ltype !== rtype) {
changes(new DiffEdit(currentPath, lhs, rhs));
} else if (lhs instanceof Date && rhs instanceof Date && ((lhs-rhs) != 0) ) {
changes(new DiffEdit(currentPath, lhs, rhs));
} else if (ltype === 'object' && lhs != null && rhs != null) {
stack = stack || [];
if (stack.indexOf(lhs) < 0) {
stack.push(lhs);
if (Array.isArray(lhs)) {
var i
, len = lhs.length
, ea = function(d) {
changes(new DiffArray(currentPath, i, d));
};
for(i = 0; i < lhs.length; i++) {
if (i >= rhs.length) {
changes(new DiffArray(currentPath, i, new DiffDeleted(undefined, lhs[i])));
} else {
deepDiff(lhs[i], rhs[i], ea, [], null, stack);
}
}
while(i < rhs.length) {
changes(new DiffArray(currentPath, i, new DiffNew(undefined, rhs[i++])));
}
} else {
var akeys = Object.keys(lhs);
var pkeys = Object.keys(rhs);
akeys.forEach(function(k) {
var i = pkeys.indexOf(k);
if (i >= 0) {
deepDiff(lhs[k], rhs[k], changes, currentPath, k, stack);
pkeys = arrayRemove(pkeys, i);
} else {
deepDiff(lhs[k], undefined, changes, currentPath, k, stack);
}
});
pkeys.forEach(function(k) {
deepDiff(undefined, rhs[k], changes, currentPath, k, stack);
});
}
stack.length = stack.length - 1;
}
} else if (lhs !== rhs) {
if(!(ltype === "number" && isNaN(lhs) && isNaN(rhs))) {
changes(new DiffEdit(currentPath, lhs, rhs));
}
}
}
function accumulateDiff(lhs, rhs, accum) {
accum = accum || [];
deepDiff(lhs, rhs, function(diff) {
if (diff) {
accum.push(diff);
}
});
return (accum.length) ? accum : undefined;
}
function applyArrayChange(arr, index, change) {
if (change.p && change.p.length) {
// the structure of the object at the index has changed...
var it = arr[index], i, u = change.p.length - 1;
for(i = 0; i < u; i++){
it = it[change.p[i]];
}
switch(change.k) {
case 'A':
// Array was modified...
// it will be an array...
applyArrayChange(it[change.p[i]], change.i, change.m);
break;
case 'D':
// Item was deleted...
delete it[change.p[i]];
break;
case 'E':
case 'N':
// Item was edited or is new...
it[change.p[i]] = change.rhs;
break;
}
} else {
// the array item is different...
switch(change.k) {
case 'A':
// Array was modified...
// it will be an array...
applyArrayChange(arr[index], change.i, change.m);
break;
case 'D':
// Item was deleted...
arr = arrayRemove(arr, index);
break;
case 'E':
case 'N':
// Item was edited or is new...
arr[index] = change.rhs;
break;
}
}
return arr;
}
function applyChange(target, source, change) {
// if (!(change instanceof Diff)) {
// throw new TypeError('[Object] change must be instanceof Diff');
// }
if (target && source && change) {
var it = target, i, u;
u = change.p.length - 1;
for(i = 0; i < u; i++){
if (typeof it[change.p[i]] === 'undefined') {
it[change.p[i]] = {};
}
it = it[change.p[i]];
}
switch(change.k) {
case 'A':
// Array was modified...
// it will be an array...
applyArrayChange(it[change.p[i]], change.i, change.m);
break;
case 'D':
// Item was deleted...
delete it[change.p[i]];
break;
case 'E':
case 'N':
// Item was edited or is new...
it[change.p[i]] = change.rhs;
break;
}
}
}
function applyDiff(target, source, filter) {
if (target && source) {
var onChange = function(change) {
if (!filter || filter(target, source, change)) {
applyChange(target, source, change);
}
};
deepDiff(target, source, onChange);
}
}
function applyPatch(lhs, diff) {
var arrOps = []
for (var i = 0; i < diff.length; i++) {
var d = diff[i]
if (d.i || (d.i === 0))
arrOps.push(d)
else
applyChange(lhs, {}, d)
};
arrOps.sort(function(a, b) {return a.i < b.i})
for (var i = 0; i < arrOps.length; i++) {
var d = arrOps[i]
applyChange(lhs, {}, d)
};
return lhs
}
Object.defineProperties(accumulateDiff, {
diff: { value: accumulateDiff, enumerable:true },
observableDiff: { value: deepDiff, enumerable:true },
applyDiff: { value: applyDiff, enumerable:true },
applyChange: { value: applyChange, enumerable:true },
applyPatch: {value: applyPatch, enumerable:true },
isConflict: { get: function() { return 'undefined' !== typeof conflict; }, enumerable: true },
noConflict: {
value: function () {
if (conflictResolution) {
conflictResolution.forEach(function (it) { it(); });
conflictResolution = null;
}
return accumulateDiff;
},
enumerable: true
}
});
if (typeof module != 'undefined' && module && typeof exports == 'object' && exports && module.exports === exports) {
module.exports = accumulateDiff; // nodejs
} else {
$scope.DeepDiff = accumulateDiff; // other... browser?
}
}());