Comparing version 0.2.0 to 0.3.0
323
browser.js
@@ -19,25 +19,4 @@ /* hugov@runbox.com | https://github.com/hville/attostore.git | license:MIT */ | ||
/** | ||
* deep Equal check on JSON-like objects | ||
* @function | ||
* @param {*} obj - object to check | ||
* @param {*} ref - the reference | ||
* @return {boolean|void} true if equal | ||
*/ | ||
function isEqual(obj, ref) { | ||
var cO = cType(obj), | ||
cR = cType(ref); | ||
if (cO !== cR) return false | ||
if (cO === Array) { | ||
if (obj.length !== ref.length) return false | ||
for (var i=0; i<obj.length; ++i) if (!isEqual(obj[i], ref[i])) return false | ||
return true | ||
} | ||
if (cO === Object) { | ||
var ko = Object.keys(obj); //TODO type annotation obj: Object | ||
if (ko.length !== Object.keys(ref).length) return false //TODO type annotation typeof ref: Object | ||
for (i=0; i<ko.length; ++i) if (!isEqual(obj[ko[i]], ref[ko[i]])) return false | ||
return true | ||
} | ||
else return obj === ref | ||
function getKey(obj, key) { | ||
if (isObj(obj)) return obj[key] | ||
} | ||
@@ -49,2 +28,9 @@ | ||
function once(key, fcn, ctx) { | ||
var wrap = fcn.length > 2 | ||
? function(a,b,c,d,e) { this.off(key, wrap, this); fcn.call(ctx, a,b,c,d,e); } | ||
: function(a,b) { this.off(key, wrap, this); fcn.call(ctx, a,b); }; | ||
return this.on(key, wrap, this) | ||
} | ||
/* | ||
@@ -72,12 +58,60 @@ patchAsync: function(patch, ondone) { | ||
function once(key, fcn, ctx) { | ||
function wrapped(a,b,c,d) { | ||
this.off(key, wrapped, this); | ||
fcn.call(ctx || this, a,b,c,d); | ||
} | ||
return this.on(key, wrapped, this) | ||
/** | ||
* @constructor | ||
* @param {!Object} root | ||
* @param {!Array} keys | ||
*/ | ||
function Ref(root, keys) { | ||
this.store = root; | ||
this._ks = keys; | ||
} | ||
function getKey(obj, key) { | ||
if (isObj(obj)) return obj[key] | ||
Ref.prototype = { | ||
get parent() { return new Ref(this.store, this._ks.slice(0,-1)) }, | ||
get root() { return new Ref(this.store, []) }, | ||
keys: function(path) { | ||
return this._ks.concat(pathKeys(path)) | ||
}, | ||
/** | ||
* @memberof Ref | ||
* @param {Array|string} [path] | ||
* @return {!Object} | ||
*/ | ||
ref: function(path) { | ||
return new Ref(this.store, this.keys(path)) | ||
}, | ||
on: function(path, fcn, ctx) { | ||
this.store.on(this.keys(path), fcn, ctx); | ||
return this | ||
}, | ||
off: function(path, fcn, ctx) { | ||
this.store.off(this.keys(path), fcn, ctx); | ||
return this | ||
}, | ||
once: once, | ||
set: function(path, val, ondone) { | ||
return promisify(setTimeout, [storeSet, 0, this.store, this.keys(path), val], ondone) | ||
}, | ||
del: function(path, ondone) { | ||
return promisify(setTimeout, [storeSet, 0, this.store, this.keys(path), undefined], ondone) | ||
}, | ||
query: function(transform) { | ||
var query = new Trie; | ||
query._set(transform(this.store.data)); | ||
this.store.on(this._ks, function(v) { query._set(transform(v)); }); | ||
return query | ||
} | ||
}; | ||
function storeSet(src, key, val, cb) { | ||
return src.patch([val === undefined ? {k:key} : {k:key, v:val}], cb) | ||
} | ||
@@ -94,46 +128,60 @@ | ||
Trie.prototype.on = function(key, fcn, ctx) { | ||
var leaf = set(this, pathKeys(key)), | ||
list = leaf._fs; | ||
if (indexOf(list, fcn, ctx) === -1) list.push({f: fcn, c:ctx||null}); | ||
return this | ||
}; | ||
/** | ||
* @memberof Store | ||
* @param {Array|string|number} [path] | ||
* @return {!Object} | ||
*/ | ||
Trie.prototype = { | ||
Trie.prototype.off = function(key, fcn, ctx) { | ||
var keys = pathKeys(key), | ||
itm = get(this, keys), | ||
arr = itm && itm._fs, | ||
idx = indexOf(arr, fcn, ctx); | ||
if (idx !== -1) { | ||
arr.splice(idx, 1); | ||
if (!arr.length && !itm._ks.size) del(this, keys, 0); | ||
} | ||
return this | ||
}; | ||
ref: function(path) { | ||
return new Ref(this, pathKeys(path)) | ||
}, | ||
Trie.prototype.once = once; | ||
on: function(key, fcn, ctx) { | ||
var leaf = set(this, pathKeys(key)), | ||
list = leaf._fs; | ||
if (indexOf(list, fcn, ctx) === -1) list.push({f: fcn, c:ctx||null}); | ||
return this | ||
}, | ||
/** | ||
* @param {*} val | ||
* @param {*} old | ||
* @param {string} [key] | ||
* @param {Object|Array} [obj] | ||
* @return {void} | ||
*/ | ||
Trie.prototype.emit = function(val, old, key, obj) { | ||
this._ks.forEach(function(kid, k) { | ||
if (k === '*') { | ||
var keys = filterKeys(val, old); | ||
for (var i=0; i<keys.length; ++i) kid.emit(getKey(val, keys[i]), getKey(old, keys[i]), keys[i], val); | ||
off: function(key, fcn, ctx) { | ||
var keys = pathKeys(key), | ||
itm = get(this, keys), | ||
arr = itm && itm._fs, | ||
idx = indexOf(arr, fcn, ctx); | ||
if (idx !== -1) { | ||
arr.splice(idx, 1); | ||
if (!arr.length && !itm._ks.size) del(this, keys, 0); | ||
} | ||
else { | ||
var v = getKey(val, k), | ||
o = getKey(old, k); | ||
if (v !== o) kid.emit(v,o,k,val); | ||
return this | ||
}, | ||
once: once, | ||
/** | ||
* @param {*} val | ||
* @return {void} | ||
*/ | ||
_set: function(val) { | ||
if (val !== this.data) { | ||
var old = this.data, | ||
dif = null; | ||
// update kids first | ||
this._ks.forEach(updateKid, val); | ||
// update self | ||
this.data = val; | ||
for (var i=0, fs=this._fs; i<fs.length; ++i) { | ||
var fcn = fs[i].f; | ||
//compute changes only once and only if required | ||
if (fcn.length > 2) { | ||
if (!dif) dif = compare(val, old); | ||
fcn.call(fs[i].c, val, old, dif[0], dif[1], dif[2]); | ||
} | ||
else fcn.call(fs[i].c, val, old); | ||
} | ||
} | ||
}); | ||
for (var i=0, fs=this._fs; i<fs.length; ++i) fs[i].f.call(fs[i].c, val, old, key, obj); | ||
} | ||
}; | ||
function get(root, keys) { | ||
@@ -169,98 +217,57 @@ for (var i=0, itm = root; i<keys.length; ++i) { | ||
function filterKeys(val, old) { | ||
var res = [], | ||
kvs = isObj(val) ? Object.keys(val) : [], | ||
function compare(val, old) { | ||
var kvs = isObj(val) ? Object.keys(val) : [], | ||
kos = isObj(old) ? Object.keys(old) : []; | ||
if (!kvs.length) return kos | ||
if (!kos.length) return kvs | ||
for (var i=0; i<kvs.length; ++i) if (val[kvs[i]] !== old[kvs[i]]) res.push(kvs[i]); | ||
for (var j=0; j<kos.length; ++j) if (val[kos[j]] === undefined) res.push(kos[j]); | ||
return res | ||
if (!kvs.length || !kos.length) return [kvs, [], kos] | ||
var dif = [[],[],[]]; | ||
for (var i=0; i<kvs.length; ++i) { | ||
if (old[kvs[i]] === undefined) dif[0].push(kvs[i]); // added | ||
if (val[kvs[i]] !== old[kvs[i]]) dif[1].push(kvs[i]); // changed | ||
} | ||
for (var j=0; j<kos.length; ++j) { | ||
if (val[kos[j]] === undefined) dif[2].push(kos[j]); // removed | ||
} | ||
return dif | ||
} | ||
/** | ||
* @constructor | ||
* @param {!Object} root | ||
* @param {!Array} keys | ||
*/ | ||
function Ref(root, keys) { | ||
this._db = root; | ||
this._ks = keys; | ||
function updateKid(kid, k) { | ||
kid._set(getKey(this, k)); | ||
} | ||
Ref.prototype = { | ||
constructor: Ref, | ||
get parent() { return new Ref(this._db, this._ks.slice(0,-1)) }, | ||
get root() { return new Ref(this._db, []) }, | ||
keys: function(path) { | ||
return this._ks.concat(pathKeys(path)) | ||
}, | ||
/** | ||
* @memberof Ref | ||
* @param {Array|string} [path] | ||
* @return {!Object} | ||
*/ | ||
ref: function(path) { | ||
return new Ref(this._db, this.keys(path)) | ||
}, | ||
set: function(path, val, ondone) { | ||
return promisify(setTimeout, [storeSet, 0, this._db, this.keys(path), val], ondone) | ||
}, | ||
del: function(path, ondone) { | ||
return promisify(setTimeout, [storeSet, 0, this._db, this.keys(path)], ondone) | ||
}, | ||
on: function(path, fcn, ctx) { | ||
this._db.trie.on(this.keys(path), fcn, ctx); | ||
return this | ||
}, | ||
off: function(path, fcn, ctx) { | ||
this._db.trie.off(this.keys(path), fcn, ctx); | ||
return this | ||
}, | ||
once: once, | ||
query: function(transform) { | ||
var query = new Trie, | ||
last; | ||
this._db.trie.on(this._ks, function(v,k,n) { | ||
var next = transform(v,k,n); | ||
query.emit(next, last); | ||
}); | ||
return query | ||
} | ||
}; | ||
function storeSet(src, key, val, cb) { | ||
return src.patch([val === undefined ? {k:key} : {k:key, v:val}], cb) | ||
} | ||
/** | ||
* @constructor | ||
* @param {*} [data] | ||
* deep Equal check on JSON-like objects | ||
* @function | ||
* @param {*} obj - object to check | ||
* @param {*} ref - the reference | ||
* @return {boolean|void} true if equal | ||
*/ | ||
function Store(data) { | ||
this.trie = new Trie; | ||
this.data = data; | ||
function isEqual(obj, ref) { | ||
var cO = cType(obj), | ||
cR = cType(ref); | ||
if (cO !== cR) return false | ||
if (cO === Array) { | ||
if (obj.length !== ref.length) return false | ||
for (var i=0; i<obj.length; ++i) if (!isEqual(obj[i], ref[i])) return false | ||
return true | ||
} | ||
if (cO === Object) { | ||
var ko = Object.keys(obj); //TODO type annotation obj: Object | ||
if (ko.length !== Object.keys(ref).length) return false //TODO type annotation typeof ref: Object | ||
for (i=0; i<ko.length; ++i) if (!isEqual(obj[ko[i]], ref[ko[i]])) return false | ||
return true | ||
} | ||
else return obj === ref | ||
} | ||
/** | ||
* @memberof Store | ||
* @param {Array|string|number} [path] | ||
* @return {!Object} | ||
*/ | ||
Store.prototype.ref = function(path) { | ||
return new Ref(this, pathKeys(path)) | ||
// @ts-check | ||
var module$1 = function (initValue) { | ||
var root = new Trie(); | ||
root.patch = patch; | ||
root._set(initValue); | ||
return new Ref(root, []) | ||
}; | ||
Store.prototype.patch = function(acts, done) { | ||
var oldV = this.data, | ||
newV = oldV; | ||
function patch(acts, done) { | ||
var newV = this.data; | ||
for (var i=0; i<acts.length; ++i) { | ||
@@ -273,9 +280,8 @@ newV = setPath(newV, acts[i].k, acts[i].v, 0); | ||
} | ||
if (newV !== oldV) { | ||
this.data = newV; | ||
this.trie.emit(newV, oldV); | ||
if (newV !== this.data) { | ||
this._set(newV); | ||
done(null, acts); | ||
} | ||
else done(null, null); | ||
}; | ||
} | ||
@@ -338,9 +344,4 @@ /** | ||
// @ts-check | ||
var module$1 = function () { | ||
return new Store() | ||
}; | ||
return module$1; | ||
}()); |
323
index.js
@@ -18,25 +18,4 @@ /* hugov@runbox.com | https://github.com/hville/attostore.git | license:MIT */ | ||
/** | ||
* deep Equal check on JSON-like objects | ||
* @function | ||
* @param {*} obj - object to check | ||
* @param {*} ref - the reference | ||
* @return {boolean|void} true if equal | ||
*/ | ||
function isEqual(obj, ref) { | ||
var cO = cType(obj), | ||
cR = cType(ref); | ||
if (cO !== cR) return false | ||
if (cO === Array) { | ||
if (obj.length !== ref.length) return false | ||
for (var i=0; i<obj.length; ++i) if (!isEqual(obj[i], ref[i])) return false | ||
return true | ||
} | ||
if (cO === Object) { | ||
var ko = Object.keys(obj); //TODO type annotation obj: Object | ||
if (ko.length !== Object.keys(ref).length) return false //TODO type annotation typeof ref: Object | ||
for (i=0; i<ko.length; ++i) if (!isEqual(obj[ko[i]], ref[ko[i]])) return false | ||
return true | ||
} | ||
else return obj === ref | ||
function getKey(obj, key) { | ||
if (isObj(obj)) return obj[key] | ||
} | ||
@@ -48,2 +27,9 @@ | ||
function once(key, fcn, ctx) { | ||
var wrap = fcn.length > 2 | ||
? function(a,b,c,d,e) { this.off(key, wrap, this); fcn.call(ctx, a,b,c,d,e); } | ||
: function(a,b) { this.off(key, wrap, this); fcn.call(ctx, a,b); }; | ||
return this.on(key, wrap, this) | ||
} | ||
/* | ||
@@ -71,12 +57,60 @@ patchAsync: function(patch, ondone) { | ||
function once(key, fcn, ctx) { | ||
function wrapped(a,b,c,d) { | ||
this.off(key, wrapped, this); | ||
fcn.call(ctx || this, a,b,c,d); | ||
} | ||
return this.on(key, wrapped, this) | ||
/** | ||
* @constructor | ||
* @param {!Object} root | ||
* @param {!Array} keys | ||
*/ | ||
function Ref(root, keys) { | ||
this.store = root; | ||
this._ks = keys; | ||
} | ||
function getKey(obj, key) { | ||
if (isObj(obj)) return obj[key] | ||
Ref.prototype = { | ||
get parent() { return new Ref(this.store, this._ks.slice(0,-1)) }, | ||
get root() { return new Ref(this.store, []) }, | ||
keys: function(path) { | ||
return this._ks.concat(pathKeys(path)) | ||
}, | ||
/** | ||
* @memberof Ref | ||
* @param {Array|string} [path] | ||
* @return {!Object} | ||
*/ | ||
ref: function(path) { | ||
return new Ref(this.store, this.keys(path)) | ||
}, | ||
on: function(path, fcn, ctx) { | ||
this.store.on(this.keys(path), fcn, ctx); | ||
return this | ||
}, | ||
off: function(path, fcn, ctx) { | ||
this.store.off(this.keys(path), fcn, ctx); | ||
return this | ||
}, | ||
once: once, | ||
set: function(path, val, ondone) { | ||
return promisify(setTimeout, [storeSet, 0, this.store, this.keys(path), val], ondone) | ||
}, | ||
del: function(path, ondone) { | ||
return promisify(setTimeout, [storeSet, 0, this.store, this.keys(path), undefined], ondone) | ||
}, | ||
query: function(transform) { | ||
var query = new Trie; | ||
query._set(transform(this.store.data)); | ||
this.store.on(this._ks, function(v) { query._set(transform(v)); }); | ||
return query | ||
} | ||
}; | ||
function storeSet(src, key, val, cb) { | ||
return src.patch([val === undefined ? {k:key} : {k:key, v:val}], cb) | ||
} | ||
@@ -93,46 +127,60 @@ | ||
Trie.prototype.on = function(key, fcn, ctx) { | ||
var leaf = set(this, pathKeys(key)), | ||
list = leaf._fs; | ||
if (indexOf(list, fcn, ctx) === -1) list.push({f: fcn, c:ctx||null}); | ||
return this | ||
}; | ||
/** | ||
* @memberof Store | ||
* @param {Array|string|number} [path] | ||
* @return {!Object} | ||
*/ | ||
Trie.prototype = { | ||
Trie.prototype.off = function(key, fcn, ctx) { | ||
var keys = pathKeys(key), | ||
itm = get(this, keys), | ||
arr = itm && itm._fs, | ||
idx = indexOf(arr, fcn, ctx); | ||
if (idx !== -1) { | ||
arr.splice(idx, 1); | ||
if (!arr.length && !itm._ks.size) del(this, keys, 0); | ||
} | ||
return this | ||
}; | ||
ref: function(path) { | ||
return new Ref(this, pathKeys(path)) | ||
}, | ||
Trie.prototype.once = once; | ||
on: function(key, fcn, ctx) { | ||
var leaf = set(this, pathKeys(key)), | ||
list = leaf._fs; | ||
if (indexOf(list, fcn, ctx) === -1) list.push({f: fcn, c:ctx||null}); | ||
return this | ||
}, | ||
/** | ||
* @param {*} val | ||
* @param {*} old | ||
* @param {string} [key] | ||
* @param {Object|Array} [obj] | ||
* @return {void} | ||
*/ | ||
Trie.prototype.emit = function(val, old, key, obj) { | ||
this._ks.forEach(function(kid, k) { | ||
if (k === '*') { | ||
var keys = filterKeys(val, old); | ||
for (var i=0; i<keys.length; ++i) kid.emit(getKey(val, keys[i]), getKey(old, keys[i]), keys[i], val); | ||
off: function(key, fcn, ctx) { | ||
var keys = pathKeys(key), | ||
itm = get(this, keys), | ||
arr = itm && itm._fs, | ||
idx = indexOf(arr, fcn, ctx); | ||
if (idx !== -1) { | ||
arr.splice(idx, 1); | ||
if (!arr.length && !itm._ks.size) del(this, keys, 0); | ||
} | ||
else { | ||
var v = getKey(val, k), | ||
o = getKey(old, k); | ||
if (v !== o) kid.emit(v,o,k,val); | ||
return this | ||
}, | ||
once: once, | ||
/** | ||
* @param {*} val | ||
* @return {void} | ||
*/ | ||
_set: function(val) { | ||
if (val !== this.data) { | ||
var old = this.data, | ||
dif = null; | ||
// update kids first | ||
this._ks.forEach(updateKid, val); | ||
// update self | ||
this.data = val; | ||
for (var i=0, fs=this._fs; i<fs.length; ++i) { | ||
var fcn = fs[i].f; | ||
//compute changes only once and only if required | ||
if (fcn.length > 2) { | ||
if (!dif) dif = compare(val, old); | ||
fcn.call(fs[i].c, val, old, dif[0], dif[1], dif[2]); | ||
} | ||
else fcn.call(fs[i].c, val, old); | ||
} | ||
} | ||
}); | ||
for (var i=0, fs=this._fs; i<fs.length; ++i) fs[i].f.call(fs[i].c, val, old, key, obj); | ||
} | ||
}; | ||
function get(root, keys) { | ||
@@ -168,98 +216,57 @@ for (var i=0, itm = root; i<keys.length; ++i) { | ||
function filterKeys(val, old) { | ||
var res = [], | ||
kvs = isObj(val) ? Object.keys(val) : [], | ||
function compare(val, old) { | ||
var kvs = isObj(val) ? Object.keys(val) : [], | ||
kos = isObj(old) ? Object.keys(old) : []; | ||
if (!kvs.length) return kos | ||
if (!kos.length) return kvs | ||
for (var i=0; i<kvs.length; ++i) if (val[kvs[i]] !== old[kvs[i]]) res.push(kvs[i]); | ||
for (var j=0; j<kos.length; ++j) if (val[kos[j]] === undefined) res.push(kos[j]); | ||
return res | ||
if (!kvs.length || !kos.length) return [kvs, [], kos] | ||
var dif = [[],[],[]]; | ||
for (var i=0; i<kvs.length; ++i) { | ||
if (old[kvs[i]] === undefined) dif[0].push(kvs[i]); // added | ||
if (val[kvs[i]] !== old[kvs[i]]) dif[1].push(kvs[i]); // changed | ||
} | ||
for (var j=0; j<kos.length; ++j) { | ||
if (val[kos[j]] === undefined) dif[2].push(kos[j]); // removed | ||
} | ||
return dif | ||
} | ||
/** | ||
* @constructor | ||
* @param {!Object} root | ||
* @param {!Array} keys | ||
*/ | ||
function Ref(root, keys) { | ||
this._db = root; | ||
this._ks = keys; | ||
function updateKid(kid, k) { | ||
kid._set(getKey(this, k)); | ||
} | ||
Ref.prototype = { | ||
constructor: Ref, | ||
get parent() { return new Ref(this._db, this._ks.slice(0,-1)) }, | ||
get root() { return new Ref(this._db, []) }, | ||
keys: function(path) { | ||
return this._ks.concat(pathKeys(path)) | ||
}, | ||
/** | ||
* @memberof Ref | ||
* @param {Array|string} [path] | ||
* @return {!Object} | ||
*/ | ||
ref: function(path) { | ||
return new Ref(this._db, this.keys(path)) | ||
}, | ||
set: function(path, val, ondone) { | ||
return promisify(setTimeout, [storeSet, 0, this._db, this.keys(path), val], ondone) | ||
}, | ||
del: function(path, ondone) { | ||
return promisify(setTimeout, [storeSet, 0, this._db, this.keys(path)], ondone) | ||
}, | ||
on: function(path, fcn, ctx) { | ||
this._db.trie.on(this.keys(path), fcn, ctx); | ||
return this | ||
}, | ||
off: function(path, fcn, ctx) { | ||
this._db.trie.off(this.keys(path), fcn, ctx); | ||
return this | ||
}, | ||
once: once, | ||
query: function(transform) { | ||
var query = new Trie, | ||
last; | ||
this._db.trie.on(this._ks, function(v,k,n) { | ||
var next = transform(v,k,n); | ||
query.emit(next, last); | ||
}); | ||
return query | ||
} | ||
}; | ||
function storeSet(src, key, val, cb) { | ||
return src.patch([val === undefined ? {k:key} : {k:key, v:val}], cb) | ||
} | ||
/** | ||
* @constructor | ||
* @param {*} [data] | ||
* deep Equal check on JSON-like objects | ||
* @function | ||
* @param {*} obj - object to check | ||
* @param {*} ref - the reference | ||
* @return {boolean|void} true if equal | ||
*/ | ||
function Store(data) { | ||
this.trie = new Trie; | ||
this.data = data; | ||
function isEqual(obj, ref) { | ||
var cO = cType(obj), | ||
cR = cType(ref); | ||
if (cO !== cR) return false | ||
if (cO === Array) { | ||
if (obj.length !== ref.length) return false | ||
for (var i=0; i<obj.length; ++i) if (!isEqual(obj[i], ref[i])) return false | ||
return true | ||
} | ||
if (cO === Object) { | ||
var ko = Object.keys(obj); //TODO type annotation obj: Object | ||
if (ko.length !== Object.keys(ref).length) return false //TODO type annotation typeof ref: Object | ||
for (i=0; i<ko.length; ++i) if (!isEqual(obj[ko[i]], ref[ko[i]])) return false | ||
return true | ||
} | ||
else return obj === ref | ||
} | ||
/** | ||
* @memberof Store | ||
* @param {Array|string|number} [path] | ||
* @return {!Object} | ||
*/ | ||
Store.prototype.ref = function(path) { | ||
return new Ref(this, pathKeys(path)) | ||
// @ts-check | ||
var module$1 = function (initValue) { | ||
var root = new Trie(); | ||
root.patch = patch; | ||
root._set(initValue); | ||
return new Ref(root, []) | ||
}; | ||
Store.prototype.patch = function(acts, done) { | ||
var oldV = this.data, | ||
newV = oldV; | ||
function patch(acts, done) { | ||
var newV = this.data; | ||
for (var i=0; i<acts.length; ++i) { | ||
@@ -272,9 +279,8 @@ newV = setPath(newV, acts[i].k, acts[i].v, 0); | ||
} | ||
if (newV !== oldV) { | ||
this.data = newV; | ||
this.trie.emit(newV, oldV); | ||
if (newV !== this.data) { | ||
this._set(newV); | ||
done(null, acts); | ||
} | ||
else done(null, null); | ||
}; | ||
} | ||
@@ -337,7 +343,2 @@ /** | ||
// @ts-check | ||
var module$1 = function () { | ||
return new Store() | ||
}; | ||
module.exports = module$1; |
// @ts-check | ||
import {Store} from './src/_store' | ||
import {Trie} from './src/_trie' | ||
import {Ref} from './src/_ref' | ||
import {isEqual} from './src/is-eq' | ||
import {isObj} from './src/type' | ||
export default function () { | ||
return new Store() | ||
export default function (initValue) { | ||
var root = new Trie() | ||
root.patch = patch | ||
root._set(initValue) | ||
return new Ref(root, []) | ||
} | ||
function patch(acts, done) { | ||
var newV = this.data | ||
for (var i=0; i<acts.length; ++i) { | ||
newV = setPath(newV, acts[i].k, acts[i].v, 0) | ||
if (newV instanceof Error) { | ||
done(newV) | ||
return | ||
} | ||
} | ||
if (newV !== this.data) { | ||
this._set(newV) | ||
done(null, acts) | ||
} | ||
else done(null, null) | ||
} | ||
/** | ||
* @param {*} obj | ||
* @param {!Array} keys | ||
* @param {*} val | ||
* @param {number} idx | ||
* @return {*} | ||
*/ | ||
function setPath(obj, keys, val, idx) { | ||
if (val instanceof Error) return val | ||
// last key reached => close | ||
if (idx === keys.length) return isEqual(obj, val) ? obj : val | ||
// recursive calls to end of path | ||
if (!isObj(obj)) return Error('invalid path ' + keys.join('/')) | ||
var k = keys[idx], | ||
o = obj[k], | ||
v = setPath(o, keys, val, idx+1) | ||
return v === o ? obj | ||
: Array.isArray(obj) ? aSet(obj, +k, v) | ||
: oSet(obj, k, v) | ||
} | ||
/** | ||
* @param {!Array} arr | ||
* @param {number} key | ||
* @param {*} val | ||
* @return {!Array|Error} | ||
*/ | ||
function aSet(arr, key, val) { | ||
var tgt = arr.slice() | ||
if (val === undefined) { | ||
if (key !== arr.length-1) return Error('only the last array item can be deleted') | ||
tgt.length = key | ||
return tgt | ||
} | ||
if (key < arr.length) { | ||
tgt[key] = val | ||
return tgt | ||
} | ||
return Error('invalid array index: ' + key) | ||
} | ||
/** | ||
* @param {!Object} obj | ||
* @param {string} key | ||
* @param {*} val | ||
* @return {!Object} | ||
*/ | ||
function oSet(obj, key, val) { | ||
for (var i=0, ks=Object.keys(obj), res={}; i<ks.length; ++i) if (ks[i] !== key) res[ks[i]] = obj[ks[i]] | ||
if (val !== undefined) res[key] = val | ||
return res | ||
} |
{ | ||
"name": "attostore", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"description": "async json data store", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -46,4 +46,4 @@ # attodom | ||
* only the last item of an Array can be deleted to avoid shifting of keys | ||
* only JSON types supported (Array, Object, string, number, boolean, null) | ||
## API | ||
@@ -50,0 +50,0 @@ |
@@ -6,3 +6,2 @@ import {pathKeys} from './path-keys' | ||
/** | ||
@@ -14,3 +13,3 @@ * @constructor | ||
export function Ref(root, keys) { | ||
this._db = root | ||
this.store = root | ||
this._ks = keys | ||
@@ -20,6 +19,5 @@ } | ||
Ref.prototype = { | ||
constructor: Ref, | ||
get parent() { return new Ref(this._db, this._ks.slice(0,-1)) }, | ||
get root() { return new Ref(this._db, []) }, | ||
get parent() { return new Ref(this.store, this._ks.slice(0,-1)) }, | ||
get root() { return new Ref(this.store, []) }, | ||
@@ -29,2 +27,3 @@ keys: function(path) { | ||
}, | ||
/** | ||
@@ -36,15 +35,7 @@ * @memberof Ref | ||
ref: function(path) { | ||
return new Ref(this._db, this.keys(path)) | ||
return new Ref(this.store, this.keys(path)) | ||
}, | ||
set: function(path, val, ondone) { | ||
return promisify(setTimeout, [storeSet, 0, this._db, this.keys(path), val], ondone) | ||
}, | ||
del: function(path, ondone) { | ||
return promisify(setTimeout, [storeSet, 0, this._db, this.keys(path)], ondone) | ||
}, | ||
on: function(path, fcn, ctx) { | ||
this._db.trie.on(this.keys(path), fcn, ctx) | ||
this.store.on(this.keys(path), fcn, ctx) | ||
return this | ||
@@ -54,3 +45,3 @@ }, | ||
off: function(path, fcn, ctx) { | ||
this._db.trie.off(this.keys(path), fcn, ctx) | ||
this.store.off(this.keys(path), fcn, ctx) | ||
return this | ||
@@ -61,9 +52,14 @@ }, | ||
set: function(path, val, ondone) { | ||
return promisify(setTimeout, [storeSet, 0, this.store, this.keys(path), val], ondone) | ||
}, | ||
del: function(path, ondone) { | ||
return promisify(setTimeout, [storeSet, 0, this.store, this.keys(path), undefined], ondone) | ||
}, | ||
query: function(transform) { | ||
var query = new Trie, | ||
last | ||
this._db.trie.on(this._ks, function(v,k,n) { | ||
var next = transform(v,k,n) | ||
query.emit(next, last) | ||
}) | ||
var query = new Trie | ||
query._set(transform(this.store.data)) | ||
this.store.on(this._ks, function(v) { query._set(transform(v)) }) | ||
return query | ||
@@ -70,0 +66,0 @@ } |
113
src/_trie.js
@@ -5,2 +5,3 @@ import {isObj} from './type' | ||
import {once} from './once' | ||
import {Ref} from './_ref' | ||
@@ -16,46 +17,60 @@ /** | ||
Trie.prototype.on = function(key, fcn, ctx) { | ||
var leaf = set(this, pathKeys(key)), | ||
list = leaf._fs | ||
if (indexOf(list, fcn, ctx) === -1) list.push({f: fcn, c:ctx||null}) | ||
return this | ||
} | ||
/** | ||
* @memberof Store | ||
* @param {Array|string|number} [path] | ||
* @return {!Object} | ||
*/ | ||
Trie.prototype = { | ||
Trie.prototype.off = function(key, fcn, ctx) { | ||
var keys = pathKeys(key), | ||
itm = get(this, keys), | ||
arr = itm && itm._fs, | ||
idx = indexOf(arr, fcn, ctx) | ||
if (idx !== -1) { | ||
arr.splice(idx, 1) | ||
if (!arr.length && !itm._ks.size) del(this, keys, 0) | ||
} | ||
return this | ||
} | ||
ref: function(path) { | ||
return new Ref(this, pathKeys(path)) | ||
}, | ||
Trie.prototype.once = once | ||
on: function(key, fcn, ctx) { | ||
var leaf = set(this, pathKeys(key)), | ||
list = leaf._fs | ||
if (indexOf(list, fcn, ctx) === -1) list.push({f: fcn, c:ctx||null}) | ||
return this | ||
}, | ||
/** | ||
* @param {*} val | ||
* @param {*} old | ||
* @param {string} [key] | ||
* @param {Object|Array} [obj] | ||
* @return {void} | ||
*/ | ||
Trie.prototype.emit = function(val, old, key, obj) { | ||
this._ks.forEach(function(kid, k) { | ||
if (k === '*') { | ||
var keys = filterKeys(val, old) | ||
for (var i=0; i<keys.length; ++i) kid.emit(getKey(val, keys[i]), getKey(old, keys[i]), keys[i], val) | ||
off: function(key, fcn, ctx) { | ||
var keys = pathKeys(key), | ||
itm = get(this, keys), | ||
arr = itm && itm._fs, | ||
idx = indexOf(arr, fcn, ctx) | ||
if (idx !== -1) { | ||
arr.splice(idx, 1) | ||
if (!arr.length && !itm._ks.size) del(this, keys, 0) | ||
} | ||
else { | ||
var v = getKey(val, k), | ||
o = getKey(old, k) | ||
if (v !== o) kid.emit(v,o,k,val) | ||
return this | ||
}, | ||
once: once, | ||
/** | ||
* @param {*} val | ||
* @return {void} | ||
*/ | ||
_set: function(val) { | ||
if (val !== this.data) { | ||
var old = this.data, | ||
dif = null | ||
// update kids first | ||
this._ks.forEach(updateKid, val) | ||
// update self | ||
this.data = val | ||
for (var i=0, fs=this._fs; i<fs.length; ++i) { | ||
var fcn = fs[i].f | ||
//compute changes only once and only if required | ||
if (fcn.length > 2) { | ||
if (!dif) dif = compare(val, old) | ||
fcn.call(fs[i].c, val, old, dif[0], dif[1], dif[2]) | ||
} | ||
else fcn.call(fs[i].c, val, old) | ||
} | ||
} | ||
}) | ||
for (var i=0, fs=this._fs; i<fs.length; ++i) fs[i].f.call(fs[i].c, val, old, key, obj) | ||
} | ||
} | ||
function get(root, keys) { | ||
@@ -91,12 +106,20 @@ for (var i=0, itm = root; i<keys.length; ++i) { | ||
function filterKeys(val, old) { | ||
var res = [], | ||
kvs = isObj(val) ? Object.keys(val) : [], | ||
function compare(val, old) { | ||
var kvs = isObj(val) ? Object.keys(val) : [], | ||
kos = isObj(old) ? Object.keys(old) : [] | ||
if (!kvs.length) return kos | ||
if (!kos.length) return kvs | ||
for (var i=0; i<kvs.length; ++i) if (val[kvs[i]] !== old[kvs[i]]) res.push(kvs[i]) | ||
for (var j=0; j<kos.length; ++j) if (val[kos[j]] === undefined) res.push(kos[j]) | ||
return res | ||
if (!kvs.length || !kos.length) return [kvs, [], kos] | ||
var dif = [[],[],[]] | ||
for (var i=0; i<kvs.length; ++i) { | ||
if (old[kvs[i]] === undefined) dif[0].push(kvs[i]) // added | ||
if (val[kvs[i]] !== old[kvs[i]]) dif[1].push(kvs[i]) // changed | ||
} | ||
for (var j=0; j<kos.length; ++j) { | ||
if (val[kos[j]] === undefined) dif[2].push(kos[j]) // removed | ||
} | ||
return dif | ||
} | ||
function updateKid(kid, k) { | ||
kid._set(getKey(this, k)) | ||
} |
export function once(key, fcn, ctx) { | ||
function wrapped(a,b,c,d) { | ||
this.off(key, wrapped, this) | ||
fcn.call(ctx || this, a,b,c,d) | ||
} | ||
return this.on(key, wrapped, this) | ||
var wrap = fcn.length > 2 | ||
? function(a,b,c,d,e) { this.off(key, wrap, this); fcn.call(ctx, a,b,c,d,e) } | ||
: function(a,b) { this.off(key, wrap, this); fcn.call(ctx, a,b) } | ||
return this.on(key, wrap, this) | ||
} |
@@ -5,6 +5,7 @@ // @ts-check | ||
function addChangesL(v,o,k) { | ||
t('!==', v, o, 'child event only if value changed: '+k) | ||
//t('!==', v, o && o[k], 'child event only if v[k] changed: '+k) | ||
this.push(k) | ||
function compare(v,o,as,ms,ds) { | ||
t('!==', v, o, 'child event only if value changed: ') | ||
t('{===}', as, this[0]) | ||
t('{===}', ms, this[1]) | ||
t('{===}', ds, this[2]) | ||
} | ||
@@ -14,14 +15,12 @@ | ||
t('db - promise', function(end) { | ||
var root = DB(), | ||
ref_ = root.ref(), | ||
changed = [] | ||
ref_.on('*', addChangesL, changed) | ||
ref_.on('aa/bb/*', addChangesL, changed) | ||
ref_.ref('aa').on('*', addChangesL, changed) | ||
var ref = DB() | ||
ref_.set('', {aa: {bb:{cc:{}, c: 'c'}, b: 'b'}, a:'a'}).then(function() { | ||
t('{===}', root.data, {aa: {bb:{cc:{}, c: 'c'}, b: 'b'}, a:'a'}) | ||
t('{===}', changed, ['aa', 'a', 'cc', 'c', 'bb', 'b']) | ||
ref.once('',compare, [['aa', 'a'],[],[]]) | ||
ref.ref('aa').once('bb',compare, [['cc', 'c'],[],[]]) | ||
ref.once('aa',compare, [['bb', 'b'],[],[]]) | ||
ref.set('',{aa: {bb:{cc:{}, c: 'c'}, b: 'b'}, a:'a'}).then(function() { | ||
t('{===}', ref.store.data, {aa: {bb:{cc:{}, c: 'c'}, b: 'b'}, a:'a'}) | ||
end() | ||
}) | ||
}) |
@@ -5,59 +5,35 @@ // @ts-check | ||
function addChangesL(v,o,k) { | ||
t('!==', v, o, 'child event only if value changed: '+k) | ||
this.push(k) | ||
function compare(v,o,as,ms,ds) { | ||
t('!==', v, o, 'child event only if value changed: ') | ||
t('{===}', as, this[0]) | ||
t('{===}', ms, this[1]) | ||
t('{===}', ds, this[2]) | ||
} | ||
function addChangesU(v,o,k) { | ||
t('!==', v, o, 'child event only if value changed') | ||
this.push(k.toUpperCase()) | ||
} | ||
function addChangesV(v,o,k) { | ||
t('!==', v, o, 'child event only if value changed') | ||
this.push(k+v) | ||
} | ||
t('db - callback', function(end) { | ||
var root = DB(), | ||
ref_ = root.ref(), | ||
refa = root.ref('aa'), | ||
changed = [] | ||
t('ref - added keys', function(end) { | ||
var ref = DB() | ||
ref_.once('aa', addChangesU, changed) | ||
refa.on('*', addChangesL, changed) | ||
ref_.once('aa/bb', addChangesU, changed) | ||
ref_.on(['aa','bb','*'], addChangesL, changed) | ||
refa.once('bb/cc', addChangesU, changed) | ||
ref_.on('*', addChangesL, changed) | ||
ref.ref('aa').once('',compare, [['bb','b'],[],[]]) | ||
ref.once('aa/bb', compare, [['cc','c'],[],[]]) | ||
ref.ref('aa').once('bb/cc', compare, [[],[],[]]) | ||
ref.on('',compare, [['aa','a'],[],[]]) | ||
ref_.set('', {aa: {bb:{cc:{}, c: 'c'}, b: 'b'}, a:'a'}, function(err) { | ||
ref.set('', {aa: {bb:{cc:{}, c: 'c'}, b: 'b'}, a:'a'}, function(err) { | ||
t('!', err) | ||
t('{===}', root.data, {aa: {bb:{cc:{}, c: 'c'}, b: 'b'}, a:'a'}) | ||
t('{===}', changed, ['bb', 'b', 'cc', 'c', 'CC', 'BB', 'AA', 'aa', 'a']) | ||
changed.length = 0 | ||
ref_.set('aa/bb', {}, function(e) { | ||
t('!', e) | ||
t('{===}', root.data, {aa: {bb:{}, b: 'b'}, a:'a'}) | ||
t('{===}', changed, ['bb', 'cc', 'c', 'aa']) | ||
refa.off('*', addChangesL, changed) | ||
ref_.off('*', addChangesL, changed) | ||
refa.off(['bb','*'], addChangesL, changed) | ||
t('===', root.trie._ks.size, 0, 'dereferencing dtree') | ||
end() | ||
}) | ||
t('{===}', ref.store.data, {aa: {bb:{cc:{}, c: 'c'}, b: 'b'}, a:'a'}) | ||
end() | ||
}) | ||
}) | ||
t('db - wildCard', function(end) { | ||
var root = DB(), | ||
ref_ = root.ref(), | ||
changed = [] | ||
t('ref - del keys', function(end) { | ||
var ref = DB({aa: {bb:{cc:{}, c: 'c'}, b: 'b'}, a:'a'}) | ||
ref_.on('*/x', addChangesV, changed) | ||
ref.once('aa', compare, [[],[],['bb','b']]) | ||
ref.once('aa/bb', compare, [[],[],['cc','c']]) | ||
ref.ref('aa/bb/cc').once('',compare, [[],[],[]]) | ||
ref.on('',compare, [[],[],['aa','a']]) | ||
ref_.set('', {A: {a:'Aa', x:'Ax'}, B: {b:'Bb'}, C: {x:'Cx'}}, function(err) { | ||
ref.del('',function(err) { | ||
t('!', err) | ||
t('{===}', root.data, {A: {a:'Aa', x:'Ax'}, B: {b:'Bb'}, C: {x:'Cx'}}) | ||
t('{===}', changed, ['xAx', 'xCx']) | ||
t('{===}', ref.store.data, undefined) | ||
end() | ||
@@ -68,24 +44,12 @@ }) | ||
t('db - query', function(end) { | ||
var src = DB(), | ||
ref = src.ref(), | ||
var ref = DB([1,2,3,0]), | ||
xfo = ref.query(function(v) { return v.slice().sort() }) | ||
xfo.on('', function(v,o,k,s) { | ||
t('===', k, undefined) | ||
t('===', s, undefined) | ||
t('!==', v, o, 'child event only if value changed') | ||
t('{==}', v, [0,1,2,3]) | ||
}) | ||
xfo.on('*', function(v,o,k,s) { | ||
t('!==', v, o, 'child event only if value changed') | ||
t('!!', Array.isArray(s)) | ||
t('!!', typeof k, 'number') | ||
t('{==}', s, [0,1,2,3]) | ||
}) | ||
ref.set('', [1,2,3,0], function(err) { | ||
xfo.once('',compare, [[],[0,3],[]]) | ||
ref.set('',[1,2,3,0], function(err) { | ||
t('!', err) | ||
t('{===}', src.data, [1,2,3,0]) | ||
t('{===}', ref.store.data, [1,2,3,0]) | ||
t('{===}', xfo.data, [0,1,2,3]) | ||
end() | ||
}) | ||
}) |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
0
30221
18
954