Comparing version 0.0.1 to 0.2.0
473
browser.js
@@ -5,58 +5,174 @@ /* hugov@runbox.com | https://github.com/hville/attostore.git | license:MIT */ | ||
function on(typ, fcn, ctx) { | ||
var evts = this._db.event, | ||
leaf = evts.setLeaf(this.keys), | ||
list = evts[typ].get(leaf), | ||
evtO = {f: fcn, c:ctx||null}; | ||
if (!list) evts[typ].set(leaf, [evtO]); | ||
else if (indexOfEvt(list, fcn, ctx) === -1) list.push(evtO); | ||
return this | ||
/** | ||
* @function | ||
* @param {*} v - object to test | ||
* @return {*} null|undefined|Constructor | ||
*/ | ||
function cType(v) { | ||
//null, String, Boolean, Number, Object, Array | ||
return v == null ? v : v.constructor || Object | ||
} | ||
function off(typ, fcn, ctx) { | ||
var evts = this._db.event, | ||
leaf = evts.getLeaf(this.keys), | ||
list = leaf && evts[typ].get(leaf); | ||
if (list) { | ||
var idx = indexOfEvt(list, fcn, ctx); | ||
if (idx !== -1) list.splice(idx, 1); | ||
if (!list.length) { | ||
evts[typ].delete(leaf); | ||
evts.delLeaf(this.keys); | ||
} | ||
function isObj(v) { | ||
return v && typeof v === 'object' | ||
} | ||
/** | ||
* 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 | ||
} | ||
return this | ||
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 once(etyp, fcn, ctx) { | ||
function wrapped(data, last, ks) { | ||
this.off(etyp, wrapped, this); | ||
fcn.call(ctx || this, data, last, ks); | ||
function pathKeys(path) { | ||
return Array.isArray(path) ? path : (path && path.split) ? path.split('/') : cType(path) === Number ? [path] : [] | ||
} | ||
/* | ||
patchAsync: function(patch, ondone) { | ||
return promisify(setTimeout, [patchSync, 0, this, patch], ondone) | ||
}, | ||
patchSync: function(patch, ondone) { | ||
return promisify(patchSync, [this, patch], ondone) | ||
} | ||
return this.on(etyp, wrapped, this) | ||
*/ | ||
function promisify(fcn, args, cb) { | ||
// avoids promises and return void if a callback is provided | ||
if (cb) fcn.apply(null, args.concat(cb)); | ||
// return a promise only if no callback provided | ||
else return new Promise(function(done, fail) { | ||
fcn.apply(null, args.concat(function(err, res) { | ||
if (err) fail(err); | ||
else done(res); | ||
})); | ||
}) | ||
} | ||
function indexOfEvt(lst, fcn, ctx) { | ||
for (var i=0; i<lst.length; ++i) if (lst[i].f === fcn && lst[i].c === ctx) return i | ||
return -1 | ||
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) | ||
} | ||
function getKey(obj, key) { | ||
if (isObj(obj)) return obj[key] | ||
} | ||
/** | ||
* @function | ||
* @param {*} v - object to test | ||
* @return {*} null|undefined|Constructor | ||
* @constructor | ||
*/ | ||
function cType(v) { | ||
//null, String, Boolean, Number, Object, Array | ||
return v == null ? v : v.constructor || Object | ||
function Trie() { | ||
this._ks = new Map; | ||
this._fs = []; | ||
this.data = undefined; | ||
} | ||
function isObj(v) { | ||
return typeof v === 'object' | ||
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 | ||
}; | ||
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 | ||
}; | ||
Trie.prototype.once = once; | ||
/** | ||
* @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); | ||
} | ||
else { | ||
var v = getKey(val, k), | ||
o = getKey(old, k); | ||
if (v !== o) kid.emit(v,o,k,val); | ||
} | ||
}); | ||
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) { | ||
for (var i=0, itm = root; i<keys.length; ++i) { | ||
if (itm !== undefined) itm = itm._ks.get(''+keys[i]); | ||
} | ||
return itm | ||
} | ||
function pathKeys(path) { | ||
return Array.isArray(path) ? path : (path && path.split) ? path.split('/') : cType(path) === Number ? [path] : [] | ||
function set(root, keys) { | ||
for (var i=0, itm = root; i<keys.length; ++i) { | ||
var key = ''+keys[i]; | ||
if (!itm._ks.has(key)) itm._ks.set(key, new Trie); | ||
itm = itm._ks.get(key); | ||
} | ||
return itm | ||
} | ||
function del(trie, keys, idx) { | ||
var key = keys[idx++], | ||
kid = trie._ks.get(key); | ||
if (kid) { | ||
if (idx !== keys.length) del(kid, keys, idx); | ||
if (!kid._ks.size && !kid._fs.length) trie._ks.delete(key); | ||
} | ||
} | ||
function indexOf(arr, fcn, ctx) { | ||
if (arr) for (var i=0; i<arr.length; ++i) if (arr[i].f === fcn && arr[i].c === ctx) return i | ||
return -1 | ||
} | ||
function filterKeys(val, old) { | ||
var res = [], | ||
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 | ||
} | ||
/** | ||
@@ -69,11 +185,16 @@ * @constructor | ||
this._db = root; | ||
this.keys = keys; | ||
this._ks = keys; | ||
} | ||
Ref.prototype = { | ||
get path() { return this.keys.join('/') }, | ||
get parent() { return new Ref(this._db, this.keys.slice(0,-1)) }, | ||
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] | ||
@@ -83,139 +204,77 @@ * @return {!Object} | ||
ref: function(path) { | ||
return new Ref(this._db, this.keys.concat(pathKeys(path))) | ||
return new Ref(this._db, this.keys(path)) | ||
}, | ||
set: function(val, ondone) { | ||
this._db.set(this.keys, val, ondone); | ||
return this | ||
set: function(path, val, ondone) { | ||
return promisify(setTimeout, [storeSet, 0, this._db, this.keys(path), val], ondone) | ||
}, | ||
on: on, | ||
off: off, | ||
once: once | ||
}; | ||
del: function(path, ondone) { | ||
return promisify(setTimeout, [storeSet, 0, this._db, this.keys(path)], ondone) | ||
}, | ||
function reducePath(keys, obj, onKid, onTip, onKin, res, ctx) { //cb(this:ctx, res, kid, key, kin) | ||
var kin = obj, | ||
kids = []; | ||
for (var i=0; i<keys.length; ++i) { | ||
if (!isObj(kin)) return Error('invalid path') | ||
var key = keys[i]; | ||
if (onKid) res = onKid.call(ctx, res, kin[key], key, kin); | ||
kin = kids[i] = kin[key]; | ||
} | ||
if (onTip) res = onTip.call(ctx, res, kin); | ||
while(i--) { | ||
if (onKin) res = onKin.call(ctx, res, kids[i], keys[i], i ? kids[i-1] : obj); | ||
} | ||
return res | ||
} | ||
function reduceTree(obj, onKid, onTip, onKin, res, ctx) { //cb(this:ctx, res, kid, key, kin) | ||
if (isObj(obj)) for (var i=0, ks=Object.keys(obj); i<ks.length; ++i) { | ||
var key = ks[i], | ||
kid = obj[key]; | ||
if (onKid) res = onKid.call(ctx, res, kid, key, obj); | ||
res = reduceTree(kid, onKid, onTip, onKin, res, ctx); | ||
if (onKin) res = onKin.call(ctx, res, kid, key, obj); | ||
} | ||
else if (onTip) res = onTip(res, obj); | ||
return res | ||
} | ||
function Event() { | ||
this.dtree = Object.create(null); | ||
this.child = new WeakMap; | ||
this.value = new WeakMap; | ||
} | ||
Event.prototype = { | ||
setLeaf: function(keys) { | ||
for (var i=0, leaf=this.dtree; i<keys.length; ++i) { | ||
leaf = leaf[keys[i]] || (leaf[keys[i]] = Object.create(null)); | ||
} | ||
return leaf | ||
on: function(path, fcn, ctx) { | ||
this._db.trie.on(this.keys(path), fcn, ctx); | ||
return this | ||
}, | ||
getLeaf: function(keys) { | ||
for (var i=0, leaf=this.dtree; i<keys.length; ++i) { | ||
if (!(leaf = leaf[keys[i]])) return | ||
} | ||
return leaf | ||
off: function(path, fcn, ctx) { | ||
this._db.trie.off(this.keys(path), fcn, ctx); | ||
return this | ||
}, | ||
delLeaf: function(keys) { | ||
reducePath(keys, this.dtree, null, onTip, delLeaf, null, 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 onTip(res, tip) { | ||
return reduceTree(tip, null, null, delLeaf, this) | ||
function storeSet(src, key, val, cb) { | ||
return src.patch([val === undefined ? {k:key} : {k:key, v:val}], cb) | ||
} | ||
function delLeaf(res, kid, key, kin) { | ||
var eVals = res.value.get(kid), | ||
eKids = res.child.get(kid); | ||
if (!Object.keys(kid).length && !(eVals && eVals.length) && !(eKids && eKids.length)) delete kin[key]; | ||
return res | ||
/** | ||
* @constructor | ||
* @param {*} [data] | ||
*/ | ||
function Store(data) { | ||
this.trie = new Trie; | ||
this.data = data; | ||
} | ||
/** | ||
* deep Equal check on JSON-like objects | ||
* @function | ||
* @param {*} obj - object to check | ||
* @param {*} ref - the reference | ||
* @return {boolean|void} true if equal | ||
* @memberof Store | ||
* @param {Array|string|number} [path] | ||
* @return {!Object} | ||
*/ | ||
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 | ||
} | ||
//TODO g(a,b) vs o(a)?a[b]:void 0 | ||
function getKey(obj, key) { | ||
if (isObj(obj)) return obj[key] | ||
} | ||
//import {reduceTree, reducePath} from './reduce' | ||
function Store(initValue) { | ||
this.state = initValue || {}; | ||
this.event = new Event; | ||
} | ||
Store.prototype.set = function(keys, val, ondone) { | ||
setTimeout(set, 0, this, keys, val, ondone); | ||
return this | ||
Store.prototype.ref = function(path) { | ||
return new Ref(this, pathKeys(path)) | ||
}; | ||
//TODO patch: set all, only fire if good | ||
function set(root, keys, value, ondone) { | ||
var last = root.state, | ||
evts = root.event; | ||
var data = setUp(evts.dtree, last, keys, value, 0, evts.child); | ||
if (data instanceof Error) { | ||
if (ondone) ondone(data.message); | ||
Store.prototype.patch = function(acts, done) { | ||
var oldV = this.data, | ||
newV = oldV; | ||
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 | ||
} | ||
} | ||
else if (data !== last) { | ||
root.state = data; | ||
fireV(evts.dtree, data, last, evts.value); //TODO manualy fire path keys instead of all refs | ||
if (ondone) ondone(null, data); | ||
if (newV !== oldV) { | ||
this.data = newV; | ||
this.trie.emit(newV, oldV); | ||
done(null, acts); | ||
} | ||
} | ||
else done(null, null); | ||
}; | ||
/** | ||
* @param {Object} ref | ||
* @param {*} obj | ||
@@ -225,71 +284,22 @@ * @param {!Array} keys | ||
* @param {number} idx | ||
* @param {!WeakMap} evtC | ||
* @return {*} | ||
*/ | ||
function setUp(ref, obj, keys, val, idx, evtC) { | ||
if (idx === keys.length) { | ||
if (isEqual(obj, val)) return obj | ||
if (ref) fireC(ref, val, obj, evtC); | ||
return val | ||
} | ||
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 key = keys[idx], | ||
oldK = obj[key], | ||
newK = setUp(ref && ref[key], oldK, keys, val, idx+1, evtC); | ||
if (newK === oldK) return obj | ||
var res = Array.isArray(obj) ? aSet(obj, key, newK) : oSet(obj, key, newK); //TODO obj type annotation | ||
if (!(res instanceof Error)) fireList(evtC.get(ref), res, obj, key); | ||
return res | ||
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 {!Object} ref | ||
* @param {*} val | ||
* @param {*} old | ||
* @param {!WeakMap} evtC | ||
* @return {void} | ||
*/ | ||
function fireC(ref, val, old, evtC) { | ||
if (val !== old) { | ||
// fire children first | ||
for (var i=0, ks=Object.keys(ref); i<ks.length; ++i) { | ||
var k = ks[i]; | ||
fireC(ref[k], getKey(val, k), getKey(old, k), evtC); //TODO typeDef ref[k] is an Object | ||
} | ||
// fire parent after | ||
var evts = evtC.get(ref); | ||
if (evts) { | ||
if (isObj(val)) { | ||
if (isObj(old)) { | ||
fireKeys(val, '!=', evts, val, old); | ||
fireKeys(old, '!A', evts, val, old); | ||
} | ||
else fireKeys(val, '', evts, val, old); | ||
} | ||
else if (isObj(old)) fireKeys(old, '', evts, val, old); | ||
} | ||
} | ||
} | ||
/** | ||
* @param {!Object} ref | ||
* @param {*} val | ||
* @param {*} old | ||
* @param {!WeakMap} evtV | ||
* @return {void} | ||
*/ | ||
function fireV(ref, val, old, evtV) { | ||
if (val !== old) { | ||
// fire parent first | ||
var evts = evtV.get(ref); | ||
if (evts) fireList(evts, val, old); | ||
// fire children after | ||
for (var i=0, ks=Object.keys(ref); i<ks.length; ++i) { | ||
var k = ks[i]; | ||
fireV(ref[k], getKey(val, k), getKey(old, k), evtV); | ||
} | ||
} | ||
} | ||
/** | ||
* @param {!Array} arr | ||
@@ -307,3 +317,3 @@ * @param {number} key | ||
} | ||
if (key <= arr.length) { | ||
if (key < arr.length) { | ||
tgt[key] = val; | ||
@@ -327,30 +337,9 @@ return tgt | ||
/** | ||
* @param {Array} list | ||
* @param {*} data | ||
* @param {*} last | ||
* @param {string|number} [key] | ||
* @return {void} | ||
*/ | ||
function fireList(list, data, last, key) { | ||
if (list) for (var i=0; i<list.length; ++i) list[i].f.call(list[i].c, data, last, key); | ||
} | ||
function fireKeys(src, tst, evts, val, old) { | ||
for (var i=0, ks=Object.keys(src); i<ks.length; ++i) { | ||
var k = ks[i], | ||
cond = tst === '!=' ? val[k] !== old[k] : tst === '!A' ? val[k] === undefined : true; | ||
if(cond) fireList(evts, val, old, k); | ||
} | ||
} | ||
// @ts-check | ||
function db(initValue) { | ||
return new Ref(new Store(initValue), []) | ||
} | ||
var module$1 = function () { | ||
return new Store() | ||
}; | ||
return db; | ||
return module$1; | ||
}()); |
473
index.js
/* hugov@runbox.com | https://github.com/hville/attostore.git | license:MIT */ | ||
'use strict'; | ||
function on(typ, fcn, ctx) { | ||
var evts = this._db.event, | ||
leaf = evts.setLeaf(this.keys), | ||
list = evts[typ].get(leaf), | ||
evtO = {f: fcn, c:ctx||null}; | ||
if (!list) evts[typ].set(leaf, [evtO]); | ||
else if (indexOfEvt(list, fcn, ctx) === -1) list.push(evtO); | ||
return this | ||
/** | ||
* @function | ||
* @param {*} v - object to test | ||
* @return {*} null|undefined|Constructor | ||
*/ | ||
function cType(v) { | ||
//null, String, Boolean, Number, Object, Array | ||
return v == null ? v : v.constructor || Object | ||
} | ||
function off(typ, fcn, ctx) { | ||
var evts = this._db.event, | ||
leaf = evts.getLeaf(this.keys), | ||
list = leaf && evts[typ].get(leaf); | ||
if (list) { | ||
var idx = indexOfEvt(list, fcn, ctx); | ||
if (idx !== -1) list.splice(idx, 1); | ||
if (!list.length) { | ||
evts[typ].delete(leaf); | ||
evts.delLeaf(this.keys); | ||
} | ||
function isObj(v) { | ||
return v && typeof v === 'object' | ||
} | ||
/** | ||
* 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 | ||
} | ||
return this | ||
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 once(etyp, fcn, ctx) { | ||
function wrapped(data, last, ks) { | ||
this.off(etyp, wrapped, this); | ||
fcn.call(ctx || this, data, last, ks); | ||
function pathKeys(path) { | ||
return Array.isArray(path) ? path : (path && path.split) ? path.split('/') : cType(path) === Number ? [path] : [] | ||
} | ||
/* | ||
patchAsync: function(patch, ondone) { | ||
return promisify(setTimeout, [patchSync, 0, this, patch], ondone) | ||
}, | ||
patchSync: function(patch, ondone) { | ||
return promisify(patchSync, [this, patch], ondone) | ||
} | ||
return this.on(etyp, wrapped, this) | ||
*/ | ||
function promisify(fcn, args, cb) { | ||
// avoids promises and return void if a callback is provided | ||
if (cb) fcn.apply(null, args.concat(cb)); | ||
// return a promise only if no callback provided | ||
else return new Promise(function(done, fail) { | ||
fcn.apply(null, args.concat(function(err, res) { | ||
if (err) fail(err); | ||
else done(res); | ||
})); | ||
}) | ||
} | ||
function indexOfEvt(lst, fcn, ctx) { | ||
for (var i=0; i<lst.length; ++i) if (lst[i].f === fcn && lst[i].c === ctx) return i | ||
return -1 | ||
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) | ||
} | ||
function getKey(obj, key) { | ||
if (isObj(obj)) return obj[key] | ||
} | ||
/** | ||
* @function | ||
* @param {*} v - object to test | ||
* @return {*} null|undefined|Constructor | ||
* @constructor | ||
*/ | ||
function cType(v) { | ||
//null, String, Boolean, Number, Object, Array | ||
return v == null ? v : v.constructor || Object | ||
function Trie() { | ||
this._ks = new Map; | ||
this._fs = []; | ||
this.data = undefined; | ||
} | ||
function isObj(v) { | ||
return typeof v === 'object' | ||
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 | ||
}; | ||
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 | ||
}; | ||
Trie.prototype.once = once; | ||
/** | ||
* @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); | ||
} | ||
else { | ||
var v = getKey(val, k), | ||
o = getKey(old, k); | ||
if (v !== o) kid.emit(v,o,k,val); | ||
} | ||
}); | ||
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) { | ||
for (var i=0, itm = root; i<keys.length; ++i) { | ||
if (itm !== undefined) itm = itm._ks.get(''+keys[i]); | ||
} | ||
return itm | ||
} | ||
function pathKeys(path) { | ||
return Array.isArray(path) ? path : (path && path.split) ? path.split('/') : cType(path) === Number ? [path] : [] | ||
function set(root, keys) { | ||
for (var i=0, itm = root; i<keys.length; ++i) { | ||
var key = ''+keys[i]; | ||
if (!itm._ks.has(key)) itm._ks.set(key, new Trie); | ||
itm = itm._ks.get(key); | ||
} | ||
return itm | ||
} | ||
function del(trie, keys, idx) { | ||
var key = keys[idx++], | ||
kid = trie._ks.get(key); | ||
if (kid) { | ||
if (idx !== keys.length) del(kid, keys, idx); | ||
if (!kid._ks.size && !kid._fs.length) trie._ks.delete(key); | ||
} | ||
} | ||
function indexOf(arr, fcn, ctx) { | ||
if (arr) for (var i=0; i<arr.length; ++i) if (arr[i].f === fcn && arr[i].c === ctx) return i | ||
return -1 | ||
} | ||
function filterKeys(val, old) { | ||
var res = [], | ||
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 | ||
} | ||
/** | ||
@@ -67,11 +183,16 @@ * @constructor | ||
this._db = root; | ||
this.keys = keys; | ||
this._ks = keys; | ||
} | ||
Ref.prototype = { | ||
get path() { return this.keys.join('/') }, | ||
get parent() { return new Ref(this._db, this.keys.slice(0,-1)) }, | ||
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] | ||
@@ -81,139 +202,77 @@ * @return {!Object} | ||
ref: function(path) { | ||
return new Ref(this._db, this.keys.concat(pathKeys(path))) | ||
return new Ref(this._db, this.keys(path)) | ||
}, | ||
set: function(val, ondone) { | ||
this._db.set(this.keys, val, ondone); | ||
return this | ||
set: function(path, val, ondone) { | ||
return promisify(setTimeout, [storeSet, 0, this._db, this.keys(path), val], ondone) | ||
}, | ||
on: on, | ||
off: off, | ||
once: once | ||
}; | ||
del: function(path, ondone) { | ||
return promisify(setTimeout, [storeSet, 0, this._db, this.keys(path)], ondone) | ||
}, | ||
function reducePath(keys, obj, onKid, onTip, onKin, res, ctx) { //cb(this:ctx, res, kid, key, kin) | ||
var kin = obj, | ||
kids = []; | ||
for (var i=0; i<keys.length; ++i) { | ||
if (!isObj(kin)) return Error('invalid path') | ||
var key = keys[i]; | ||
if (onKid) res = onKid.call(ctx, res, kin[key], key, kin); | ||
kin = kids[i] = kin[key]; | ||
} | ||
if (onTip) res = onTip.call(ctx, res, kin); | ||
while(i--) { | ||
if (onKin) res = onKin.call(ctx, res, kids[i], keys[i], i ? kids[i-1] : obj); | ||
} | ||
return res | ||
} | ||
function reduceTree(obj, onKid, onTip, onKin, res, ctx) { //cb(this:ctx, res, kid, key, kin) | ||
if (isObj(obj)) for (var i=0, ks=Object.keys(obj); i<ks.length; ++i) { | ||
var key = ks[i], | ||
kid = obj[key]; | ||
if (onKid) res = onKid.call(ctx, res, kid, key, obj); | ||
res = reduceTree(kid, onKid, onTip, onKin, res, ctx); | ||
if (onKin) res = onKin.call(ctx, res, kid, key, obj); | ||
} | ||
else if (onTip) res = onTip(res, obj); | ||
return res | ||
} | ||
function Event() { | ||
this.dtree = Object.create(null); | ||
this.child = new WeakMap; | ||
this.value = new WeakMap; | ||
} | ||
Event.prototype = { | ||
setLeaf: function(keys) { | ||
for (var i=0, leaf=this.dtree; i<keys.length; ++i) { | ||
leaf = leaf[keys[i]] || (leaf[keys[i]] = Object.create(null)); | ||
} | ||
return leaf | ||
on: function(path, fcn, ctx) { | ||
this._db.trie.on(this.keys(path), fcn, ctx); | ||
return this | ||
}, | ||
getLeaf: function(keys) { | ||
for (var i=0, leaf=this.dtree; i<keys.length; ++i) { | ||
if (!(leaf = leaf[keys[i]])) return | ||
} | ||
return leaf | ||
off: function(path, fcn, ctx) { | ||
this._db.trie.off(this.keys(path), fcn, ctx); | ||
return this | ||
}, | ||
delLeaf: function(keys) { | ||
reducePath(keys, this.dtree, null, onTip, delLeaf, null, 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 onTip(res, tip) { | ||
return reduceTree(tip, null, null, delLeaf, this) | ||
function storeSet(src, key, val, cb) { | ||
return src.patch([val === undefined ? {k:key} : {k:key, v:val}], cb) | ||
} | ||
function delLeaf(res, kid, key, kin) { | ||
var eVals = res.value.get(kid), | ||
eKids = res.child.get(kid); | ||
if (!Object.keys(kid).length && !(eVals && eVals.length) && !(eKids && eKids.length)) delete kin[key]; | ||
return res | ||
/** | ||
* @constructor | ||
* @param {*} [data] | ||
*/ | ||
function Store(data) { | ||
this.trie = new Trie; | ||
this.data = data; | ||
} | ||
/** | ||
* deep Equal check on JSON-like objects | ||
* @function | ||
* @param {*} obj - object to check | ||
* @param {*} ref - the reference | ||
* @return {boolean|void} true if equal | ||
* @memberof Store | ||
* @param {Array|string|number} [path] | ||
* @return {!Object} | ||
*/ | ||
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 | ||
} | ||
//TODO g(a,b) vs o(a)?a[b]:void 0 | ||
function getKey(obj, key) { | ||
if (isObj(obj)) return obj[key] | ||
} | ||
//import {reduceTree, reducePath} from './reduce' | ||
function Store(initValue) { | ||
this.state = initValue || {}; | ||
this.event = new Event; | ||
} | ||
Store.prototype.set = function(keys, val, ondone) { | ||
setTimeout(set, 0, this, keys, val, ondone); | ||
return this | ||
Store.prototype.ref = function(path) { | ||
return new Ref(this, pathKeys(path)) | ||
}; | ||
//TODO patch: set all, only fire if good | ||
function set(root, keys, value, ondone) { | ||
var last = root.state, | ||
evts = root.event; | ||
var data = setUp(evts.dtree, last, keys, value, 0, evts.child); | ||
if (data instanceof Error) { | ||
if (ondone) ondone(data.message); | ||
Store.prototype.patch = function(acts, done) { | ||
var oldV = this.data, | ||
newV = oldV; | ||
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 | ||
} | ||
} | ||
else if (data !== last) { | ||
root.state = data; | ||
fireV(evts.dtree, data, last, evts.value); //TODO manualy fire path keys instead of all refs | ||
if (ondone) ondone(null, data); | ||
if (newV !== oldV) { | ||
this.data = newV; | ||
this.trie.emit(newV, oldV); | ||
done(null, acts); | ||
} | ||
} | ||
else done(null, null); | ||
}; | ||
/** | ||
* @param {Object} ref | ||
* @param {*} obj | ||
@@ -223,71 +282,22 @@ * @param {!Array} keys | ||
* @param {number} idx | ||
* @param {!WeakMap} evtC | ||
* @return {*} | ||
*/ | ||
function setUp(ref, obj, keys, val, idx, evtC) { | ||
if (idx === keys.length) { | ||
if (isEqual(obj, val)) return obj | ||
if (ref) fireC(ref, val, obj, evtC); | ||
return val | ||
} | ||
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 key = keys[idx], | ||
oldK = obj[key], | ||
newK = setUp(ref && ref[key], oldK, keys, val, idx+1, evtC); | ||
if (newK === oldK) return obj | ||
var res = Array.isArray(obj) ? aSet(obj, key, newK) : oSet(obj, key, newK); //TODO obj type annotation | ||
if (!(res instanceof Error)) fireList(evtC.get(ref), res, obj, key); | ||
return res | ||
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 {!Object} ref | ||
* @param {*} val | ||
* @param {*} old | ||
* @param {!WeakMap} evtC | ||
* @return {void} | ||
*/ | ||
function fireC(ref, val, old, evtC) { | ||
if (val !== old) { | ||
// fire children first | ||
for (var i=0, ks=Object.keys(ref); i<ks.length; ++i) { | ||
var k = ks[i]; | ||
fireC(ref[k], getKey(val, k), getKey(old, k), evtC); //TODO typeDef ref[k] is an Object | ||
} | ||
// fire parent after | ||
var evts = evtC.get(ref); | ||
if (evts) { | ||
if (isObj(val)) { | ||
if (isObj(old)) { | ||
fireKeys(val, '!=', evts, val, old); | ||
fireKeys(old, '!A', evts, val, old); | ||
} | ||
else fireKeys(val, '', evts, val, old); | ||
} | ||
else if (isObj(old)) fireKeys(old, '', evts, val, old); | ||
} | ||
} | ||
} | ||
/** | ||
* @param {!Object} ref | ||
* @param {*} val | ||
* @param {*} old | ||
* @param {!WeakMap} evtV | ||
* @return {void} | ||
*/ | ||
function fireV(ref, val, old, evtV) { | ||
if (val !== old) { | ||
// fire parent first | ||
var evts = evtV.get(ref); | ||
if (evts) fireList(evts, val, old); | ||
// fire children after | ||
for (var i=0, ks=Object.keys(ref); i<ks.length; ++i) { | ||
var k = ks[i]; | ||
fireV(ref[k], getKey(val, k), getKey(old, k), evtV); | ||
} | ||
} | ||
} | ||
/** | ||
* @param {!Array} arr | ||
@@ -305,3 +315,3 @@ * @param {number} key | ||
} | ||
if (key <= arr.length) { | ||
if (key < arr.length) { | ||
tgt[key] = val; | ||
@@ -325,28 +335,7 @@ return tgt | ||
/** | ||
* @param {Array} list | ||
* @param {*} data | ||
* @param {*} last | ||
* @param {string|number} [key] | ||
* @return {void} | ||
*/ | ||
function fireList(list, data, last, key) { | ||
if (list) for (var i=0; i<list.length; ++i) list[i].f.call(list[i].c, data, last, key); | ||
} | ||
function fireKeys(src, tst, evts, val, old) { | ||
for (var i=0, ks=Object.keys(src); i<ks.length; ++i) { | ||
var k = ks[i], | ||
cond = tst === '!=' ? val[k] !== old[k] : tst === '!A' ? val[k] === undefined : true; | ||
if(cond) fireList(evts, val, old, k); | ||
} | ||
} | ||
// @ts-check | ||
function db(initValue) { | ||
return new Ref(new Store(initValue), []) | ||
} | ||
var module$1 = function () { | ||
return new Store() | ||
}; | ||
module.exports = db; | ||
module.exports = module$1; |
// @ts-check | ||
import {Ref} from './src/_ref' | ||
import {Store} from './src/_store' | ||
export default function db(initValue) { | ||
return new Ref(new Store(initValue), []) | ||
export default function () { | ||
return new Store() | ||
} |
{ | ||
"name": "attostore", | ||
"version": "0.0.1", | ||
"version": "0.2.0", | ||
"description": "async json data store", | ||
@@ -9,3 +9,4 @@ "keywords": [ | ||
"async", | ||
"cursor" | ||
"cursor", | ||
"in-memory" | ||
], | ||
@@ -12,0 +13,0 @@ "author": "Hugo Villeneuve", |
@@ -1,4 +0,7 @@ | ||
import {on, off, once} from './_ref-event' | ||
import {pathKeys} from './path-keys' | ||
import {promisify} from './promisify' | ||
import {once} from './once' | ||
import {Trie} from './_trie' | ||
/** | ||
@@ -11,11 +14,16 @@ * @constructor | ||
this._db = root | ||
this.keys = keys | ||
this._ks = keys | ||
} | ||
Ref.prototype = { | ||
get path() { return this.keys.join('/') }, | ||
get parent() { return new Ref(this._db, this.keys.slice(0,-1)) }, | ||
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] | ||
@@ -25,13 +33,38 @@ * @return {!Object} | ||
ref: function(path) { | ||
return new Ref(this._db, this.keys.concat(pathKeys(path))) | ||
return new Ref(this._db, this.keys(path)) | ||
}, | ||
set: function(val, ondone) { | ||
this._db.set(this.keys, val, ondone) | ||
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 | ||
}, | ||
on: on, | ||
off: off, | ||
once: once | ||
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) | ||
} |
@@ -1,35 +0,43 @@ | ||
import {Event} from './_event' | ||
//import {reduceTree, reducePath} from './reduce' | ||
import {isEqual} from './is-eq' | ||
import {getKey} from './get' | ||
import {isObj} from './type' | ||
export function Store(initValue) { | ||
this.state = initValue || {} | ||
this.event = new Event | ||
import {pathKeys} from './path-keys' | ||
import {Ref} from './_ref' | ||
import {Trie} from './_trie' | ||
/** | ||
* @constructor | ||
* @param {*} [data] | ||
*/ | ||
export function Store(data) { | ||
this.trie = new Trie | ||
this.data = data | ||
} | ||
Store.prototype.set = function(keys, val, ondone) { | ||
setTimeout(set, 0, this, keys, val, ondone) | ||
return this | ||
/** | ||
* @memberof Store | ||
* @param {Array|string|number} [path] | ||
* @return {!Object} | ||
*/ | ||
Store.prototype.ref = function(path) { | ||
return new Ref(this, pathKeys(path)) | ||
} | ||
//TODO patch: set all, only fire if good | ||
function set(root, keys, value, ondone) { | ||
var last = root.state, | ||
evts = root.event | ||
var data = setUp(evts.dtree, last, keys, value, 0, evts.child) | ||
if (data instanceof Error) { | ||
if (ondone) ondone(data.message) | ||
Store.prototype.patch = function(acts, done) { | ||
var oldV = this.data, | ||
newV = oldV | ||
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 | ||
} | ||
} | ||
else if (data !== last) { | ||
root.state = data | ||
fireV(evts.dtree, data, last, evts.value) //TODO manualy fire path keys instead of all refs | ||
if (ondone) ondone(null, data) | ||
if (newV !== oldV) { | ||
this.data = newV | ||
this.trie.emit(newV, oldV) | ||
done(null, acts) | ||
} | ||
else done(null, null) | ||
} | ||
/** | ||
* @param {Object} ref | ||
* @param {*} obj | ||
@@ -39,71 +47,22 @@ * @param {!Array} keys | ||
* @param {number} idx | ||
* @param {!WeakMap} evtC | ||
* @return {*} | ||
*/ | ||
function setUp(ref, obj, keys, val, idx, evtC) { | ||
if (idx === keys.length) { | ||
if (isEqual(obj, val)) return obj | ||
if (ref) fireC(ref, val, obj, evtC) | ||
return val | ||
} | ||
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 key = keys[idx], | ||
oldK = obj[key], | ||
newK = setUp(ref && ref[key], oldK, keys, val, idx+1, evtC) | ||
if (newK === oldK) return obj | ||
var res = Array.isArray(obj) ? aSet(obj, key, newK) : oSet(obj, key, newK) //TODO obj type annotation | ||
if (!(res instanceof Error)) fireList(evtC.get(ref), res, obj, key) | ||
return res | ||
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 {!Object} ref | ||
* @param {*} val | ||
* @param {*} old | ||
* @param {!WeakMap} evtC | ||
* @return {void} | ||
*/ | ||
function fireC(ref, val, old, evtC) { | ||
if (val !== old) { | ||
// fire children first | ||
for (var i=0, ks=Object.keys(ref); i<ks.length; ++i) { | ||
var k = ks[i] | ||
fireC(ref[k], getKey(val, k), getKey(old, k), evtC) //TODO typeDef ref[k] is an Object | ||
} | ||
// fire parent after | ||
var evts = evtC.get(ref) | ||
if (evts) { | ||
if (isObj(val)) { | ||
if (isObj(old)) { | ||
fireKeys(val, '!=', evts, val, old) | ||
fireKeys(old, '!A', evts, val, old) | ||
} | ||
else fireKeys(val, '', evts, val, old) | ||
} | ||
else if (isObj(old)) fireKeys(old, '', evts, val, old) | ||
} | ||
} | ||
} | ||
/** | ||
* @param {!Object} ref | ||
* @param {*} val | ||
* @param {*} old | ||
* @param {!WeakMap} evtV | ||
* @return {void} | ||
*/ | ||
function fireV(ref, val, old, evtV) { | ||
if (val !== old) { | ||
// fire parent first | ||
var evts = evtV.get(ref) | ||
if (evts) fireList(evts, val, old) | ||
// fire children after | ||
for (var i=0, ks=Object.keys(ref); i<ks.length; ++i) { | ||
var k = ks[i] | ||
fireV(ref[k], getKey(val, k), getKey(old, k), evtV) | ||
} | ||
} | ||
} | ||
/** | ||
* @param {!Array} arr | ||
@@ -121,3 +80,3 @@ * @param {number} key | ||
} | ||
if (key <= arr.length) { | ||
if (key < arr.length) { | ||
tgt[key] = val | ||
@@ -140,22 +99,1 @@ return tgt | ||
} | ||
/** | ||
* @param {Array} list | ||
* @param {*} data | ||
* @param {*} last | ||
* @param {string|number} [key] | ||
* @return {void} | ||
*/ | ||
function fireList(list, data, last, key) { | ||
if (list) for (var i=0; i<list.length; ++i) list[i].f.call(list[i].c, data, last, key) | ||
} | ||
function fireKeys(src, tst, evts, val, old) { | ||
for (var i=0, ks=Object.keys(src); i<ks.length; ++i) { | ||
var k = ks[i], | ||
cond = tst === '!=' ? val[k] !== old[k] : tst === '!A' ? val[k] === undefined : true | ||
if(cond) fireList(evts, val, old, k) | ||
} | ||
} |
import {isObj} from './type' | ||
//TODO g(a,b) vs o(a)?a[b]:void 0 | ||
export function getKey(obj, key) { | ||
if (isObj(obj)) return obj[key] | ||
} |
@@ -12,3 +12,3 @@ /** | ||
export function isObj(v) { | ||
return typeof v === 'object' | ||
return v && typeof v === 'object' | ||
} |
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
No README
QualityPackage does not have a README. This may indicate a failed publish or a low quality package.
Found 1 instance in 1 package
30929
19
993
1
104
1