Comparing version
274
lib/delta.js
var walk = require('./walk'); | ||
var flatten = require('./flatten'); | ||
var traverse = require('./traverse'); | ||
var del = require('./del'); | ||
var deepCompare = require('./deep-compare'); | ||
@@ -25,2 +27,3 @@ exports.apply = function(data, delta) { | ||
'$inc', | ||
'$push', | ||
'$pull' | ||
@@ -34,3 +37,3 @@ ].indexOf(prop) < 0) { | ||
if(delta.$unset) { | ||
var unset = flatten(delta.$unset); | ||
var unset = delta.$unset; | ||
for(var path in unset) { | ||
@@ -43,12 +46,33 @@ del(path, data); | ||
if(delta.$pull) { | ||
var pull = flatten(delta.$pull); | ||
var pull = delta.$pull; | ||
for(var path in pull) { | ||
var arr = traverse(path, data); | ||
arr.splice(arr.indexOf(pull[path]), 1); | ||
if(pull[path].$each) { | ||
for(var i = 0; i < pull[path].$each.length; i += 1) { | ||
arr.splice(arr.indexOf(pull[path].$each[i]), 1); | ||
} | ||
} else { | ||
arr.splice(arr.indexOf(pull[path]), 1); | ||
} | ||
} | ||
} | ||
// pull. | ||
if(delta.$push) { | ||
var push = delta.$push; | ||
for(var path in push) { | ||
var arr = traverse(path, data); | ||
if(push[path].$each) { | ||
for(var i = 0; i < push[path].$each.length; i += 1) { | ||
arr.push(push[path].$each[i]); | ||
} | ||
} else { | ||
arr.push(push[path]); | ||
} | ||
} | ||
} | ||
// rename. | ||
if(delta.$rename) { | ||
var rename = flatten(delta.$rename); | ||
var rename = delta.$rename; | ||
for(var path in rename) { | ||
@@ -63,3 +87,3 @@ var val = traverse(path, data); | ||
if(delta.$setOnInsert) { | ||
var setOnInsert = flatten(delta.$setOnInsert); | ||
var setOnInsert = delta.$setOnInsert; | ||
for(var path in setOnInsert) { | ||
@@ -74,3 +98,3 @@ if(traverse(path, data) === undefined) { | ||
if(delta.$set) { | ||
var set = flatten(delta.$set); | ||
var set = delta.$set; | ||
for(var path in set) { | ||
@@ -83,3 +107,3 @@ traverse(path, data, set[path]); | ||
if(delta.$inc) { | ||
var inc = flatten(delta.$inc); | ||
var inc = delta.$inc; | ||
for(var path in inc) { | ||
@@ -96,3 +120,2 @@ var val = traverse(path, data) + inc[path]; | ||
exports.create = function(original, current) { | ||
var delta = {}; | ||
@@ -107,42 +130,155 @@ // throw on non objects. | ||
// flatten the data. | ||
var o = flatten(original); | ||
var c = flatten(current); | ||
var delta = { $unset: {}, $set: {}, $push: {}, $pull: {}, $rename: {} }; | ||
var arrayPaths = []; | ||
var handledPaths = []; | ||
// compare the original object to the new | ||
// one. | ||
for(var path in o) { | ||
var oVal = o[path]; | ||
var cVal = c[path]; | ||
// walk through the original. | ||
walk(original, function(oVal, path) { | ||
var cVal = traverse(path, current); | ||
// removals. | ||
// if the current path points to an array in | ||
// both the original object and the current | ||
// then look for array diffs | ||
if( | ||
typeof cVal == 'object' && typeof oVal == 'object' && | ||
cVal.constructor == Array && oVal.constructor == Array | ||
) { | ||
var oRArr = oVal; | ||
var cRArr = cVal; | ||
var oArr = oRArr.slice(0); | ||
var cArr = cRArr.slice(0); | ||
// loop through the original array | ||
while(oArr.length > 0) { | ||
oVal = oArr.shift(); | ||
// look for the oVal within the current | ||
// array. | ||
for(var i = 0; i < cRArr.length; i += 1) { | ||
cVal = cRArr[i]; | ||
// if the oVal and cVal are in both | ||
// arrays, then move on. | ||
if(deepCompare(oVal, cVal)) { oVal = undefined; break; } | ||
} | ||
// if the oVal is no longer in the current | ||
// array, then it has been removed. | ||
if(oVal) { | ||
if(!delta.$pull) { delta.$pull = {}; } | ||
if(!delta.$pull[path]) { delta.$pull[path] = {}; } | ||
if(!delta.$pull[path].$each) { delta.$pull[path].$each = []; } | ||
delta.$pull[path].$each.push(oVal); | ||
} | ||
} | ||
// loop through the current array | ||
while(cArr.length > 0) { | ||
cVal = cArr.shift(); | ||
// look for the cVal within the original | ||
// array. | ||
for(var i = 0; i < oRArr.length; i += 1) { | ||
oVal = oRArr[i]; | ||
// if the oVal and cVal are in both | ||
// arrays, then move on. | ||
if(deepCompare(oVal, cVal)) { cVal = undefined; break; } | ||
} | ||
// if the cVal is not in the original | ||
// array, then it has been added. | ||
if(cVal) { | ||
if(!delta.$push) { delta.$push = {}; } | ||
if(!delta.$push[path]) { delta.$push[path] = {}; } | ||
if(!delta.$push[path].$each) { delta.$push[path].$each = []; } | ||
delta.$push[path].$each.push(cVal); | ||
} | ||
} | ||
handledPaths.push(path); | ||
} | ||
// if both the current and the original | ||
// value at the current path are both objects, | ||
// or they are the same value, then do | ||
// nothing. | ||
if( | ||
typeof cVal == 'object' && typeof oVal == 'object' || | ||
cVal === oVal | ||
) { | ||
return; | ||
} | ||
// if the path is a decendent of a handled | ||
// path, then do nothing. | ||
for(var i = 0; i < handledPaths.length; i += 1) { | ||
if(path.slice(0, handledPaths[i].length) == handledPaths[i]) { | ||
return; | ||
} | ||
} | ||
// if the path is a decendent of an array | ||
// path, then do nothing. | ||
for(var i = 0; i < arrayPaths.length; i += 1) { | ||
if(path.slice(0, arrayPaths[i].length) == arrayPaths[i]) { | ||
return; | ||
} | ||
} | ||
// If oVal is defined and cVal is not then | ||
// a deletion has occured. Save the original | ||
// value temporarity so we can track renames. | ||
if(cVal === undefined) { | ||
if(!delta.$unset) { delta.$unset = {}; } | ||
delta.$unset[path] = oVal; | ||
handledPaths.push(path); | ||
} | ||
// additions. | ||
else if(cVal !== oVal) { | ||
// if oVal does not equal cVal then note an | ||
// addition was made. | ||
else { | ||
if(!delta.$set) { delta.$set = {}; } | ||
delta.$set[path] = cVal; | ||
handledPaths.push(path); | ||
} | ||
// remove the path. | ||
delete o[path]; | ||
delete c[path]; | ||
} | ||
}); | ||
// whatever is left in c become additions. | ||
for(var path in c) { | ||
if(!delta.$set) { delta.$set = {}; } | ||
delta.$set[path] = c[path]; | ||
delete c[path]; | ||
} | ||
// walk through the current | ||
walk(current, function(cVal, path) { | ||
var oVal = traverse(path, original); | ||
// if both the current and the original | ||
// value at the current path are both objects, | ||
// or they are equal then do nothing. | ||
if(typeof cVal == typeof oVal == 'object' || cVal == oVal) { | ||
return; | ||
} | ||
// if the path is a decendent of a handled | ||
// path, then do nothing. | ||
for(var i = 0; i < handledPaths.length; i += 1) { | ||
var handledPath = handledPaths[i]; | ||
if(path.slice(0, handledPath.length) == handledPath) { | ||
return; | ||
} | ||
} | ||
// if the oVal is undefined then an addition | ||
// was made. | ||
if(oVal === undefined) { | ||
if(!delta.$set) { delta.$set = {}; } | ||
delta.$set[path] = cVal; | ||
handledPaths.push(path); | ||
} | ||
}); | ||
// look for renames. | ||
for(var uPath in delta.$unset) { | ||
var uVal = delta.$unset[uPath]; | ||
for(var sPath in delta.$set) { | ||
var sVal = delta.$set[sPath]; | ||
walk(delta.$unset, function(uVal, uPath) { | ||
if(typeof uVal == 'object') { return; } | ||
walk(delta.$set, function(sVal, sPath) { | ||
if(typeof sVal == 'object') { return; } | ||
// if the values match, assume a rename. | ||
@@ -152,51 +288,39 @@ if(uVal == sVal) { | ||
delta.$rename[uPath] = sPath; | ||
delete delta.$unset[uPath]; | ||
delete delta.$set[sPath]; | ||
// delete the delta.$unset and/or | ||
// delta.$set objects if they are empty. | ||
for(var ok in delta.$unset) { break; } | ||
if(ok === undefined) { delete delta.$unset; } | ||
else { ok = undefined; } | ||
for(var ok in delta.$set) { break; } | ||
if(ok === undefined) { delete delta.$set; } | ||
else { ok = undefined; } | ||
del(uPath, delta.$unset); | ||
del(sPath, delta.$set); | ||
} | ||
} | ||
} | ||
}); | ||
}); | ||
// replace array unsets with $pull commands | ||
if(delta.$unset) { | ||
for(var uPath in delta.$unset) { | ||
// replace all unset values with 1. | ||
walk(delta.$unset, function(cVal, uPath) { | ||
if(typeof cVal == 'object') { return; } | ||
traverse(uPath, delta.$unset, 1); | ||
}); | ||
// get the path chunks and check for index | ||
// property denoting existance of an array. | ||
var uPathChunks = uPath.split('.'); | ||
var prop = uPathChunks.pop(); | ||
if((prop|0) != prop) { continue; } | ||
var aPath = uPathChunks.join('.'); | ||
// delete set if its empty | ||
for(var ok in delta.$set) { break; } | ||
if(ok === undefined) { delete delta.$set; } | ||
else { ok = undefined; } | ||
// convert all paths to the same array to | ||
// pull commands. | ||
for(var uPath in delta.$unset) { | ||
if(uPath.slice(0, aPath.length) != aPath) { continue; } | ||
if(!delta.$pull) { delta.$pull = {}; } | ||
delta.$pull[aPath] = delta.$unset[uPath]; | ||
delete delta.$unset[uPath]; | ||
// delete unset if its empty | ||
for(var ok in delta.$unset) { break; } | ||
if(ok === undefined) { delete delta.$unset; } | ||
else { ok = undefined; } | ||
// delete $unset if its empty | ||
for(var ok in delta.$unset) { break; } | ||
if(ok === undefined) { delete delta.$unset; } | ||
else { ok = undefined; } | ||
} | ||
} | ||
} | ||
// delete push if its empty | ||
for(var ok in delta.$push) { break; } | ||
if(ok === undefined) { delete delta.$push; } | ||
else { ok = undefined; } | ||
// set the value of unset commands to 1 | ||
if(delta.$unset) { | ||
for(var uPath in delta.$unset) { | ||
delta.$unset[uPath] = 1; | ||
} | ||
} | ||
// delete pull if its empty | ||
for(var ok in delta.$pull) { break; } | ||
if(ok === undefined) { delete delta.$pull; } | ||
else { ok = undefined; } | ||
// delete rename if its empty | ||
for(var ok in delta.$rename) { break; } | ||
if(ok === undefined) { delete delta.$rename; } | ||
else { ok = undefined; } | ||
// if the delta is empty return undefined. | ||
@@ -203,0 +327,0 @@ for(var ok in delta) { break; } |
@@ -11,3 +11,3 @@ | ||
while(i < s.length) { | ||
if(s[i].match(/[A-Z]/)) { | ||
if(s[i].match(/[A-Z\d]/)) { | ||
o += '-' + s[i].toLowerCase(); | ||
@@ -14,0 +14,0 @@ } else if(s[i].match(/[ _]/)) { |
@@ -8,4 +8,4 @@ | ||
var objs = Array.prototype.slice.call(arguments, 1); | ||
var deppMerge = objs[objs.length - 1] == true; | ||
if(deppMerge) { objs.pop(); } | ||
var deepMerge = objs[objs.length - 1] == true; | ||
if(deepMerge) { objs.pop(); } | ||
while(objs[0]) { | ||
@@ -17,6 +17,7 @@ var obj = objs.shift(); | ||
if( | ||
deppMerge && typeof obj[key] == 'object' && ( | ||
deepMerge && typeof obj[key] == 'object' && ( | ||
typeof targetObj[key] == 'object' || | ||
typeof targetObj[key] == undefined | ||
)) { | ||
) | ||
) { | ||
if(typeof targetObj[key] == undefined) { | ||
@@ -23,0 +24,0 @@ targetObj[key] = {}; |
@@ -11,3 +11,3 @@ | ||
while(i < s.length) { | ||
if(s[i].match(/[A-Z]/)) { | ||
if(s[i].match(/[A-Z\d]/)) { | ||
o += '_' + s[i].toLowerCase(); | ||
@@ -14,0 +14,0 @@ } else { |
{ | ||
"name": "primitive", | ||
"version": "0.1.7", | ||
"version": "0.2.0", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "main": "./lib/primitive.js", |
@@ -37,3 +37,3 @@ | ||
var d = delta.create({}, { a: { b: 'c' }}); | ||
d.$set['a.b'].should.equal('c'); | ||
d.$set.a.b.should.equal('c'); | ||
}); | ||
@@ -48,3 +48,3 @@ | ||
var d = delta.create({ a: { b: 'c' }}, {}); | ||
d.$unset['a.b'].should.equal(1); | ||
d.$unset.a.b.should.equal(1); | ||
}); | ||
@@ -58,28 +58,21 @@ | ||
it('adds the new and original path to $rename', function() { | ||
var d = delta.create({ a: { b: 'c' }}, { a: 'c' }); | ||
d.$rename['a.b'].should.equal('a'); | ||
var d = delta.create({ a: { b: 'c' } }, { a: { c: 'c' } }); | ||
d.$rename['a.b'].should.equal('a.c'); | ||
}); | ||
it('can handle very complex data', function() { | ||
it('can arrays', function() { | ||
var d = delta.create({ | ||
a: { | ||
b: 'a', | ||
c: 'c', | ||
}, | ||
z: 'z', | ||
g: ['i', 'g', 'w'] | ||
arr: ['i', 'g', 'w'] | ||
}, { | ||
a: 'a', | ||
b: 'b', | ||
x: { y: 'y' }, | ||
g: ['g', 'h'] | ||
arr: ['g', 'h'] | ||
}); | ||
d.$unset['a.c'].should.equal(1); | ||
d.$unset['z'].should.equal(1); | ||
d.$set['g.0'].should.equal('g'); | ||
d.$set['g.1'].should.equal('h'); | ||
d.$set['b'].should.equal('b'); | ||
d.$set['x.y'].should.equal('y'); | ||
d.$rename['a.b'].should.equal('a'); | ||
d.$pull['g'].should.equal('w'); | ||
d.$push.should.be.OK; | ||
d.$push.arr.should.be.OK; | ||
d.$push.arr.$each.should.be.OK; | ||
d.$push.arr.$each[0].should.equal('h'); | ||
d.$pull.should.be.OK; | ||
d.$pull.arr.should.be.OK; | ||
d.$pull.arr.$each.should.be.OK; | ||
d.$pull.arr.$each[0].should.equal('i'); | ||
d.$pull.arr.$each[1].should.equal('w'); | ||
}); | ||
@@ -92,17 +85,21 @@ | ||
before(function() { | ||
this.data = { | ||
a: 'a', | ||
b: 'b', | ||
e: ['e', 'x'], | ||
x: 'x' | ||
this.original = { | ||
a: { | ||
b: { | ||
c: '1' | ||
} | ||
}, | ||
d: '2', | ||
e: '3', | ||
f: [1, 2, 3] | ||
}; | ||
this.d = delta.create(this.data, { | ||
a: 'a', | ||
b: { | ||
c: 'c' | ||
this.current = { | ||
a: { | ||
b: '1' | ||
}, | ||
d: { | ||
e: ['e', 'y'] | ||
} | ||
}); | ||
c: '2', | ||
d: '3', | ||
f: [2, 5, 3] | ||
}; | ||
this.delta = delta.create(this.original, this.current); | ||
}); | ||
@@ -136,17 +133,17 @@ | ||
it('adds values to the correct path from $set', function() { | ||
var obj = delta.apply(this.data, this.d); | ||
obj.b.c.should.equal('c'); | ||
var obj = delta.apply(this.original, this.delta); | ||
obj.c.should.equal('2'); | ||
}); | ||
it('removes paths from $unset', function() { | ||
var obj = delta.apply(this.data, this.d); | ||
(obj.x === undefined).should.be.true; | ||
var obj = delta.apply(this.original, this.delta); | ||
(obj.e === undefined).should.be.true; | ||
}); | ||
it('renames paths from $rename', function() { | ||
var obj = delta.apply(this.data, this.d); | ||
(obj.e[0] === undefined).should.be.true; | ||
obj.d.e[0].should.equal('e'); | ||
var obj = delta.apply(this.original, this.delta); | ||
(obj.e === undefined).should.be.true; | ||
obj.d.should.equal('3'); | ||
}); | ||
}); |
24857
17.13%803
14.88%