Comparing version 0.7.0 to 0.8.0
350
browser.js
@@ -19,32 +19,2 @@ /* hugov@runbox.com | https://github.com/hville/attostore.git | license:MIT */ | ||
function pathKeys(path) { | ||
var ct = cType(path); | ||
return ct === Array ? path : ct === Number ? [path] : !path ? [] : path.split('/') | ||
} | ||
/** | ||
* 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) { | ||
@@ -54,131 +24,8 @@ if (isObj(obj)) return obj[key] | ||
/** | ||
* @param {Object|Array<Object>} ops | ||
* @return {Error|void} | ||
*/ | ||
function patch(ops) { | ||
var data = Array.isArray(ops) ? ops.reduce(setRed, this.data) : setRed(this.data, ops); | ||
return data instanceof Error ? data : update(this, data, '') | ||
function pathKeys(path) { | ||
var ct = cType(path); | ||
return ct === Array ? path : ct === Number ? [path] : !path ? [] : path.split('/') | ||
} | ||
/** | ||
* @param {*} res | ||
* @param {Object} op | ||
* @returns {*} | ||
*/ | ||
function setRed(res, op) { | ||
return res instanceof Error ? res : setKeys(res, pathKeys(op.p), op.v, 0) | ||
} | ||
/** | ||
* @param {!Object} store | ||
* @param {*} val | ||
* @param {string} key | ||
* @return {void|Error} | ||
*/ | ||
function update(store, val, key) { | ||
if (val instanceof Error) return val | ||
if (val !== store.data) { | ||
var old = store.data; | ||
store.data = val; | ||
// fire kids first... | ||
store._ks.forEach(updateKid, val); | ||
// ...then self | ||
for (var i=0, fs=store._fs; i<fs.length; ++i) { | ||
fs[i].f.call(fs[i].c === undefined ? store : fs[i].c, val, key, old); | ||
} | ||
} | ||
} | ||
function updateKid(kid, k) { | ||
update(kid, getKey(this, k), k); | ||
} | ||
/** | ||
* @param {*} obj | ||
* @param {!Array} keys | ||
* @param {*} val | ||
* @param {number} idx | ||
* @return {*} | ||
*/ | ||
function setKeys(obj, keys, val, idx) { | ||
// 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 = setKeys(o, keys, val, idx+1); | ||
return v instanceof Error ? v : 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 | ||
} | ||
/** | ||
* @param {null|string|Array} path | ||
* @param {*} [data] | ||
* @return {Object} | ||
*/ | ||
function setOperation(path, data) { | ||
if (path === undefined || data === undefined) throw Error('undefined argument') | ||
return {p: path, v: data} | ||
} | ||
/** | ||
* @param {null|string|Array} path | ||
* @return {Object} | ||
*/ | ||
function delOperation(path) { | ||
if (path === undefined) throw Error('undefined argument') | ||
return {p: path == null ? '' : path} | ||
} | ||
/** | ||
* @param {string} name | ||
* @param {*} [data] | ||
* @return {Error|void} | ||
*/ | ||
function run(name, data) { | ||
var ops = this._cs[name] && this._cs[name](data); | ||
if (!ops) return Error('invalid command ' + name) | ||
return this.run(ops) | ||
} | ||
/** | ||
* @constructor | ||
@@ -193,2 +40,3 @@ * @param {*} [data] | ||
/** | ||
@@ -200,3 +48,3 @@ * @param {Array|string|number} key | ||
*/ | ||
function on(key, fcn, ctx) { | ||
Trie.prototype.on = function(key, fcn, ctx) { | ||
var leaf = setLeaf(this, pathKeys(key)), | ||
@@ -206,3 +54,3 @@ list = leaf._fs; | ||
return this | ||
} | ||
}; | ||
@@ -215,3 +63,3 @@ /** | ||
*/ | ||
function off(key, fcn, ctx) { | ||
Trie.prototype.off = function(key, fcn, ctx) { | ||
var keys = pathKeys(key), | ||
@@ -226,3 +74,3 @@ itm = getLeaf(this, keys), | ||
return this | ||
} | ||
}; | ||
@@ -235,3 +83,3 @@ /** | ||
*/ | ||
function once(key, fcn, ctx) { | ||
Trie.prototype.once = function(key, fcn, ctx) { | ||
var store = this; | ||
@@ -243,4 +91,5 @@ function wrap(v,k,o) { | ||
return this.on(key, wrap, ctx) | ||
} | ||
}; | ||
/** | ||
@@ -299,2 +148,27 @@ * @param {Trie} root | ||
/** | ||
* 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 | ||
} | ||
/** | ||
* @constructor | ||
@@ -304,17 +178,7 @@ * @param {*} [data] | ||
*/ | ||
function Store(data, commands) { | ||
this._ks = new Map; | ||
this._fs = []; | ||
this._cs = commands || {}; | ||
function State(data) { | ||
this.data = data; | ||
} | ||
Store.prototype.on = on; | ||
Store.prototype.off = off; | ||
Store.prototype.once = once; | ||
Store.prototype.patch = patch; | ||
Store.prototype.run = run; | ||
Store.prototype.get = function(path) { | ||
State.prototype.get = function(path) { | ||
var keys = pathKeys(path); | ||
@@ -328,2 +192,130 @@ for (var i=0, itm = this.data; i<keys.length; ++i) { | ||
/** | ||
* @param {null|string|Array} path | ||
* @param {*} data | ||
* @return {Object} | ||
*/ | ||
State.prototype.set = function(path, data) { | ||
if (!(this.data instanceof Error)) this.data = setKeys(this.data, pathKeys(path), data, 0); | ||
return this | ||
}; | ||
/** | ||
* @param {null|string|Array} path | ||
* @return {Object} | ||
*/ | ||
State.prototype.delete = function(path) { | ||
return this.set(path, undefined) | ||
}; | ||
/** | ||
* @param {*} obj | ||
* @param {!Array} keys | ||
* @param {*} val | ||
* @param {number} idx | ||
* @return {*} | ||
*/ | ||
function setKeys(obj, keys, val, idx) { | ||
// 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 = setKeys(o, keys, val, idx+1); | ||
return v instanceof Error ? v : 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 | ||
} | ||
/** | ||
* @constructor | ||
* @param {*} initValue | ||
* @param {Object} commands | ||
*/ | ||
function Store(initValue, commands) { | ||
this._ks = new Map; | ||
this._fs = []; | ||
this._cs = commands || {}; | ||
this.data = initValue; | ||
} | ||
Store.prototype.on = Trie.prototype.on; | ||
Store.prototype.off = Trie.prototype.off; | ||
Store.prototype.once = Trie.prototype.once; | ||
Store.prototype.get = State.prototype.get; | ||
/** | ||
* @param {string} name | ||
* @param {...*} [param] | ||
* @return {Error|void} | ||
*/ | ||
Store.prototype.run = function(name, param) { //eslint-disable-line no-unused-vars | ||
var state = new State(this.data), | ||
cmd = this._cs[name]; | ||
if (!cmd) return Error('invalid command ' + name) | ||
for (var i=1, args=[]; i<arguments.length; ++i) args[i-1] = arguments[i]; | ||
cmd.apply(state, args); | ||
return state.data instanceof Error ? state.data : update(this, state.data, null) | ||
}; | ||
/** | ||
* @param {!Object} trie | ||
* @param {*} val | ||
* @param {string} key | ||
* @return {void|Error} | ||
*/ | ||
function update(trie, val, key) { | ||
if (val instanceof Error) return val | ||
if (val !== trie.data) { | ||
var old = trie.data; | ||
trie.data = val; | ||
// fire kids first... | ||
trie._ks.forEach(updateKid, val); | ||
// ...then self | ||
for (var i=0, fs=trie._fs; i<fs.length; ++i) { | ||
fs[i].f.call(fs[i].c, val, key, old); | ||
} | ||
} | ||
} | ||
function updateKid(kid, k) { | ||
update(kid, getKey(this, k), k); | ||
} | ||
function missingKeys(reference, value) { | ||
@@ -351,8 +343,8 @@ var keys = isObj(reference) ? Object.keys(reference) : []; | ||
/** | ||
* @param {*} [initialValue] | ||
* @param {Object} [commands] | ||
* @param {*} initValue | ||
* @param {Object} commands | ||
* @return {Store} | ||
*/ | ||
function createStore(initialValue, commands) { | ||
return new Store(initialValue, commands) | ||
function createStore(initValue, commands) { | ||
return new Store(initValue, commands) | ||
} | ||
@@ -364,5 +356,3 @@ | ||
exports.Store = Store; | ||
exports.setOperation = setOperation; | ||
exports.delOperation = delOperation; | ||
}((this.attostore = this.attostore || {}))); |
350
index.js
@@ -20,32 +20,2 @@ /* hugov@runbox.com | https://github.com/hville/attostore.git | license:MIT */ | ||
function pathKeys(path) { | ||
var ct = cType(path); | ||
return ct === Array ? path : ct === Number ? [path] : !path ? [] : path.split('/') | ||
} | ||
/** | ||
* 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) { | ||
@@ -55,131 +25,8 @@ if (isObj(obj)) return obj[key] | ||
/** | ||
* @param {Object|Array<Object>} ops | ||
* @return {Error|void} | ||
*/ | ||
function patch(ops) { | ||
var data = Array.isArray(ops) ? ops.reduce(setRed, this.data) : setRed(this.data, ops); | ||
return data instanceof Error ? data : update(this, data, '') | ||
function pathKeys(path) { | ||
var ct = cType(path); | ||
return ct === Array ? path : ct === Number ? [path] : !path ? [] : path.split('/') | ||
} | ||
/** | ||
* @param {*} res | ||
* @param {Object} op | ||
* @returns {*} | ||
*/ | ||
function setRed(res, op) { | ||
return res instanceof Error ? res : setKeys(res, pathKeys(op.p), op.v, 0) | ||
} | ||
/** | ||
* @param {!Object} store | ||
* @param {*} val | ||
* @param {string} key | ||
* @return {void|Error} | ||
*/ | ||
function update(store, val, key) { | ||
if (val instanceof Error) return val | ||
if (val !== store.data) { | ||
var old = store.data; | ||
store.data = val; | ||
// fire kids first... | ||
store._ks.forEach(updateKid, val); | ||
// ...then self | ||
for (var i=0, fs=store._fs; i<fs.length; ++i) { | ||
fs[i].f.call(fs[i].c === undefined ? store : fs[i].c, val, key, old); | ||
} | ||
} | ||
} | ||
function updateKid(kid, k) { | ||
update(kid, getKey(this, k), k); | ||
} | ||
/** | ||
* @param {*} obj | ||
* @param {!Array} keys | ||
* @param {*} val | ||
* @param {number} idx | ||
* @return {*} | ||
*/ | ||
function setKeys(obj, keys, val, idx) { | ||
// 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 = setKeys(o, keys, val, idx+1); | ||
return v instanceof Error ? v : 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 | ||
} | ||
/** | ||
* @param {null|string|Array} path | ||
* @param {*} [data] | ||
* @return {Object} | ||
*/ | ||
function setOperation(path, data) { | ||
if (path === undefined || data === undefined) throw Error('undefined argument') | ||
return {p: path, v: data} | ||
} | ||
/** | ||
* @param {null|string|Array} path | ||
* @return {Object} | ||
*/ | ||
function delOperation(path) { | ||
if (path === undefined) throw Error('undefined argument') | ||
return {p: path == null ? '' : path} | ||
} | ||
/** | ||
* @param {string} name | ||
* @param {*} [data] | ||
* @return {Error|void} | ||
*/ | ||
function run(name, data) { | ||
var ops = this._cs[name] && this._cs[name](data); | ||
if (!ops) return Error('invalid command ' + name) | ||
return this.run(ops) | ||
} | ||
/** | ||
* @constructor | ||
@@ -194,2 +41,3 @@ * @param {*} [data] | ||
/** | ||
@@ -201,3 +49,3 @@ * @param {Array|string|number} key | ||
*/ | ||
function on(key, fcn, ctx) { | ||
Trie.prototype.on = function(key, fcn, ctx) { | ||
var leaf = setLeaf(this, pathKeys(key)), | ||
@@ -207,3 +55,3 @@ list = leaf._fs; | ||
return this | ||
} | ||
}; | ||
@@ -216,3 +64,3 @@ /** | ||
*/ | ||
function off(key, fcn, ctx) { | ||
Trie.prototype.off = function(key, fcn, ctx) { | ||
var keys = pathKeys(key), | ||
@@ -227,3 +75,3 @@ itm = getLeaf(this, keys), | ||
return this | ||
} | ||
}; | ||
@@ -236,3 +84,3 @@ /** | ||
*/ | ||
function once(key, fcn, ctx) { | ||
Trie.prototype.once = function(key, fcn, ctx) { | ||
var store = this; | ||
@@ -244,4 +92,5 @@ function wrap(v,k,o) { | ||
return this.on(key, wrap, ctx) | ||
} | ||
}; | ||
/** | ||
@@ -300,2 +149,27 @@ * @param {Trie} root | ||
/** | ||
* 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 | ||
} | ||
/** | ||
* @constructor | ||
@@ -305,17 +179,7 @@ * @param {*} [data] | ||
*/ | ||
function Store(data, commands) { | ||
this._ks = new Map; | ||
this._fs = []; | ||
this._cs = commands || {}; | ||
function State(data) { | ||
this.data = data; | ||
} | ||
Store.prototype.on = on; | ||
Store.prototype.off = off; | ||
Store.prototype.once = once; | ||
Store.prototype.patch = patch; | ||
Store.prototype.run = run; | ||
Store.prototype.get = function(path) { | ||
State.prototype.get = function(path) { | ||
var keys = pathKeys(path); | ||
@@ -329,2 +193,130 @@ for (var i=0, itm = this.data; i<keys.length; ++i) { | ||
/** | ||
* @param {null|string|Array} path | ||
* @param {*} data | ||
* @return {Object} | ||
*/ | ||
State.prototype.set = function(path, data) { | ||
if (!(this.data instanceof Error)) this.data = setKeys(this.data, pathKeys(path), data, 0); | ||
return this | ||
}; | ||
/** | ||
* @param {null|string|Array} path | ||
* @return {Object} | ||
*/ | ||
State.prototype.delete = function(path) { | ||
return this.set(path, undefined) | ||
}; | ||
/** | ||
* @param {*} obj | ||
* @param {!Array} keys | ||
* @param {*} val | ||
* @param {number} idx | ||
* @return {*} | ||
*/ | ||
function setKeys(obj, keys, val, idx) { | ||
// 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 = setKeys(o, keys, val, idx+1); | ||
return v instanceof Error ? v : 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 | ||
} | ||
/** | ||
* @constructor | ||
* @param {*} initValue | ||
* @param {Object} commands | ||
*/ | ||
function Store(initValue, commands) { | ||
this._ks = new Map; | ||
this._fs = []; | ||
this._cs = commands || {}; | ||
this.data = initValue; | ||
} | ||
Store.prototype.on = Trie.prototype.on; | ||
Store.prototype.off = Trie.prototype.off; | ||
Store.prototype.once = Trie.prototype.once; | ||
Store.prototype.get = State.prototype.get; | ||
/** | ||
* @param {string} name | ||
* @param {...*} [param] | ||
* @return {Error|void} | ||
*/ | ||
Store.prototype.run = function(name, param) { //eslint-disable-line no-unused-vars | ||
var state = new State(this.data), | ||
cmd = this._cs[name]; | ||
if (!cmd) return Error('invalid command ' + name) | ||
for (var i=1, args=[]; i<arguments.length; ++i) args[i-1] = arguments[i]; | ||
cmd.apply(state, args); | ||
return state.data instanceof Error ? state.data : update(this, state.data, null) | ||
}; | ||
/** | ||
* @param {!Object} trie | ||
* @param {*} val | ||
* @param {string} key | ||
* @return {void|Error} | ||
*/ | ||
function update(trie, val, key) { | ||
if (val instanceof Error) return val | ||
if (val !== trie.data) { | ||
var old = trie.data; | ||
trie.data = val; | ||
// fire kids first... | ||
trie._ks.forEach(updateKid, val); | ||
// ...then self | ||
for (var i=0, fs=trie._fs; i<fs.length; ++i) { | ||
fs[i].f.call(fs[i].c, val, key, old); | ||
} | ||
} | ||
} | ||
function updateKid(kid, k) { | ||
update(kid, getKey(this, k), k); | ||
} | ||
function missingKeys(reference, value) { | ||
@@ -352,8 +344,8 @@ var keys = isObj(reference) ? Object.keys(reference) : []; | ||
/** | ||
* @param {*} [initialValue] | ||
* @param {Object} [commands] | ||
* @param {*} initValue | ||
* @param {Object} commands | ||
* @return {Store} | ||
*/ | ||
function createStore(initialValue, commands) { | ||
return new Store(initialValue, commands) | ||
function createStore(initValue, commands) { | ||
return new Store(initValue, commands) | ||
} | ||
@@ -365,3 +357,1 @@ | ||
exports.Store = Store; | ||
exports.setOperation = setOperation; | ||
exports.delOperation = delOperation; |
@@ -5,8 +5,8 @@ // @ts-check | ||
/** | ||
* @param {*} [initialValue] | ||
* @param {Object} [commands] | ||
* @param {*} initValue | ||
* @param {Object} commands | ||
* @return {Store} | ||
*/ | ||
export function createStore(initialValue, commands) { | ||
return new Store(initialValue, commands) | ||
export function createStore(initValue, commands) { | ||
return new Store(initValue, commands) | ||
} | ||
@@ -16,2 +16,1 @@ | ||
export {Store} from './src/_store' | ||
export {setOperation, delOperation} from './src/_store-ops' |
{ | ||
"name": "attostore", | ||
"version": "0.7.0", | ||
"version": "0.8.0", | ||
"description": "json data store", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -13,5 +13,5 @@ # attodom | ||
var store = create({}, { | ||
yell: function(name) { return setOperation('yell', name) }, | ||
sing: function(name) { return setOperation('sing', name) }, | ||
stop: function() { return [delOperation('yell'), delOperation('sing')] }, | ||
yell: function(name) { this.set('yell', name) }, | ||
sing: function(name) { this.set('sing', name) }, | ||
stop: function() { this.delete('yell').delete('sing')] }, | ||
}) | ||
@@ -44,14 +44,10 @@ | ||
createStore(initValue: `any`, commands: `{cmdName: Command}`): `Store` | ||
setOperation(path, data): `Operation` | ||
delOperation(path): `Operation` | ||
createStore(initValue: `any`, commands: `{commandName: Command}`): `Store` | ||
### Store | ||
.patch(`Operation|Operations`): `Error|void` | ||
.on(path: `Path`, handler: `(val, key, old, key)=>void`, [, context: `any`]): `Ref` | ||
.once(path: `Path`, handler: `(val, key, old, key)=>void`, [, context: `any`]): `Ref` | ||
.off(path: `Path`, handler: `(val, key, old, key)=>void`, [, context: `any`]): `Ref` | ||
.run(commandName: `string`, ...args: `any`): `Error|void` | ||
@@ -61,3 +57,3 @@ | ||
`any => Operation|Operations` | ||
`(this:{set, delete, get}, any) => void` | ||
@@ -64,0 +60,0 @@ |
@@ -1,33 +0,60 @@ | ||
import {pathKeys} from './path-keys' | ||
import {isObj} from './type' | ||
import {patch} from './_store-edits' | ||
import {run} from './_store-ops' | ||
import {on, off, once} from './_store-events' | ||
import {Trie} from './_trie' | ||
import {State} from './_state' | ||
import {getKey} from './get-key' | ||
/** | ||
* @constructor | ||
* @param {*} [data] | ||
* @param {Object} [commands] | ||
* @param {*} initValue | ||
* @param {Object} commands | ||
*/ | ||
export function Store(data, commands) { | ||
export function Store(initValue, commands) { | ||
this._ks = new Map | ||
this._fs = [] | ||
this._cs = commands || {} | ||
this.data = data | ||
this.data = initValue | ||
} | ||
Store.prototype.on = on | ||
Store.prototype.off = off | ||
Store.prototype.once = once | ||
Store.prototype.on = Trie.prototype.on | ||
Store.prototype.off = Trie.prototype.off | ||
Store.prototype.once = Trie.prototype.once | ||
Store.prototype.patch = patch | ||
Store.prototype.run = run | ||
Store.prototype.get = State.prototype.get | ||
Store.prototype.get = function(path) { | ||
var keys = pathKeys(path) | ||
for (var i=0, itm = this.data; i<keys.length; ++i) { | ||
if (isObj(itm)) itm = itm[keys[i]] | ||
else return | ||
/** | ||
* @param {string} name | ||
* @param {...*} [param] | ||
* @return {Error|void} | ||
*/ | ||
Store.prototype.run = function(name, param) { //eslint-disable-line no-unused-vars | ||
var state = new State(this.data), | ||
cmd = this._cs[name] | ||
if (!cmd) return Error('invalid command ' + name) | ||
for (var i=1, args=[]; i<arguments.length; ++i) args[i-1] = arguments[i] | ||
cmd.apply(state, args) | ||
return state.data instanceof Error ? state.data : update(this, state.data, null) | ||
} | ||
/** | ||
* @param {!Object} trie | ||
* @param {*} val | ||
* @param {string} key | ||
* @return {void|Error} | ||
*/ | ||
function update(trie, val, key) { | ||
if (val instanceof Error) return val | ||
if (val !== trie.data) { | ||
var old = trie.data | ||
trie.data = val | ||
// fire kids first... | ||
trie._ks.forEach(updateKid, val) | ||
// ...then self | ||
for (var i=0, fs=trie._fs; i<fs.length; ++i) { | ||
fs[i].f.call(fs[i].c, val, key, old) | ||
} | ||
} | ||
return itm | ||
} | ||
function updateKid(kid, k) { | ||
update(kid, getKey(this, k), k) | ||
} |
103
src/_trie.js
@@ -0,1 +1,4 @@ | ||
import {getKey} from './get-key' | ||
import {pathKeys} from './path-keys' | ||
/** | ||
@@ -10,1 +13,101 @@ * @constructor | ||
} | ||
/** | ||
* @param {Array|string|number} key | ||
* @param {Function} fcn | ||
* @param {*} [ctx] | ||
* @return {!Object} | ||
*/ | ||
Trie.prototype.on = function(key, fcn, ctx) { | ||
var leaf = setLeaf(this, pathKeys(key)), | ||
list = leaf._fs | ||
if (indexOf(list, fcn, ctx) === -1) list.push({f: fcn, c:ctx}) | ||
return this | ||
} | ||
/** | ||
* @param {Array|string|number} key | ||
* @param {Function} fcn | ||
* @param {*} [ctx] | ||
* @return {!Object} | ||
*/ | ||
Trie.prototype.off = function(key, fcn, ctx) { | ||
var keys = pathKeys(key), | ||
itm = getLeaf(this, keys), | ||
arr = itm && itm._fs, | ||
idx = indexOf(arr, fcn, ctx) | ||
if (idx !== -1) { | ||
arr.splice(idx, 1) | ||
if (!arr.length && !itm._ks.size) delLeaf(this, keys, 0) | ||
} | ||
return this | ||
} | ||
/** | ||
* @param {Array|string|number} key | ||
* @param {Function} fcn | ||
* @param {*} [ctx] | ||
* @return {!Object} | ||
*/ | ||
Trie.prototype.once = function(key, fcn, ctx) { | ||
var store = this | ||
function wrap(v,k,o) { | ||
store.off(key, wrap, ctx); | ||
fcn.call(this, v,k,o) | ||
} | ||
return this.on(key, wrap, ctx) | ||
} | ||
/** | ||
* @param {Trie} root | ||
* @param {Array<string>} keys | ||
* @return {Trie} | ||
*/ | ||
function getLeaf(root, keys) { | ||
for (var i=0, itm = root; i<keys.length; ++i) { | ||
if (itm !== undefined) itm = itm._ks.get(''+keys[i]) | ||
} | ||
return itm | ||
} | ||
/** | ||
* @param {Trie} root | ||
* @param {Array<string>} keys | ||
* @return {Trie} | ||
*/ | ||
function setLeaf(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(getKey(itm.data, key))) | ||
itm = itm._ks.get(key) | ||
} | ||
return itm | ||
} | ||
/** | ||
* @param {Trie} trie | ||
* @param {Array<string>} keys | ||
* @param {number} idx | ||
* @return {void} | ||
*/ | ||
function delLeaf(trie, keys, idx) { | ||
var key = keys[idx++], | ||
kid = trie._ks.get(key) | ||
if (kid instanceof Trie) { | ||
if (idx !== keys.length) delLeaf(kid, keys, idx) | ||
if (!kid._ks.size && !kid._fs.length) trie._ks.delete(key) | ||
} | ||
} | ||
/** | ||
* @param {Array} arr | ||
* @param {Function} fcn | ||
* @param {*} ctx | ||
* @return {number} | ||
*/ | ||
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 | ||
} |
@@ -6,9 +6,7 @@ var S = require( '../index' ), | ||
changed = S.changedKeys, | ||
missing = S.missingKeys, | ||
setOp = S.setOperation, | ||
delOp = S.delOperation | ||
missing = S.missingKeys | ||
function compare(v,k,o) { | ||
t('!==', v, o, 'child event only if value changed: ') | ||
t('===', k === null || typeof k === 'string', true, 'child event only if value changed: ') | ||
t('===', k === null || typeof k === 'string', true, 'child event only if value changed: '+k) | ||
if (this) { | ||
@@ -22,3 +20,5 @@ t('{==}', missing(v,o), this[0], 'added keys') | ||
t('ref - added keys', function() { | ||
var store = create() | ||
var store = create({}, { | ||
init: function(v) { this.set('', v) } | ||
}) | ||
@@ -30,3 +30,3 @@ store.once('aa',compare, [['bb','b'],[],[]]) | ||
t('!', store.patch(setOp(null, {aa: {bb:{cc:{}, c: 'c'}, b: 'b'}, a:'a'})), 'no errors') | ||
t('!', store.run('init', {aa: {bb:{cc:{}, c: 'c'}, b: 'b'}, a:'a'}), 'no errors') | ||
t('{===}', store.data, {aa: {bb:{cc:{}, c: 'c'}, b: 'b'}, a:'a'}) | ||
@@ -37,3 +37,7 @@ }) | ||
t('ref - del keys', function() { | ||
var store = create({aa: {bb:{cc:{}, c: 'c'}, b: 'b'}, a:'a'}) | ||
var store = create(null, { | ||
init: function(v) { this.set([], v) }, | ||
clear: function() { this.delete() } | ||
}) | ||
store.run('init', {aa: {bb:{cc:{}, c: 'c'}, b: 'b'}, a:'a'}) | ||
@@ -45,3 +49,3 @@ store.once('aa', compare, [[],[],['bb','b']]) | ||
t('!', store.patch(delOp(''))) | ||
t('!', store.run('clear')) | ||
t('{===}', store.data, undefined) | ||
@@ -51,3 +55,7 @@ }) | ||
t('auto purge trie', function() { | ||
var store = create() | ||
var store = create(null, { | ||
init: function(v) { this.set(null, v) }, | ||
set: function(p,v) { this.set(p,v) }, | ||
clear: function() { this.delete() } | ||
}) | ||
function noop(){} | ||
@@ -64,9 +72,14 @@ t('===', store._ks.size, 0) | ||
t('db - query', function() { | ||
var store = create({data: {list: [1,0]}, view:{}, acts:{}, done:[]}) | ||
var store = create(null, { | ||
init: function(v) { this.set(null, v) }, | ||
set: function(p,v) { this.set(p,v) }, | ||
clear: function() { this.delete() } | ||
}) | ||
store.run('init', {data: {list: [1,0]}, view:{}, acts:{}, done:[]}) | ||
store.on('data/list', function(v) { | ||
store.patch(setOp('view/sort', v.slice().sort())) | ||
store.run('set', 'view/sort', v.slice().sort()) | ||
}) | ||
store.on('acts/push', function(v) { | ||
store.patch(setOp('data/list', store.get('data/list').concat(v))) | ||
store.run('set', 'data/list', store.get('data/list').concat(v)) | ||
}) | ||
@@ -78,3 +91,3 @@ | ||
t('!', store.patch(setOp('acts/push', 9))) | ||
t('!', store.run('set', 'acts/push', 9)) | ||
t('{===}', store.data.data.list, [1,0,9])//[1,0] | ||
@@ -88,6 +101,6 @@ t('{===}', store.data.view.sort, [0,1,9])//undefined | ||
var commands = { | ||
init: function() { return setOp('', {}) }, | ||
yell: function(x) { return setOp('yell', !!x) }, | ||
sing: function(x) { return setOp('sing', !!x) }, | ||
stop: function() { return [delOp('yell'), delOp('sing')] } | ||
init: function() { this.set('', {}) }, | ||
yell: function(x) { this.set('yell', !!x) }, | ||
sing: function(x) { this.set('sing', !!x) }, | ||
stop: function() { this.delete('yell').delete('sing') } | ||
}, | ||
@@ -103,3 +116,3 @@ store = create(null, commands), | ||
t('{===}', store.data, expected.newVal) | ||
}) | ||
}, store) | ||
store.run('init') | ||
@@ -110,3 +123,3 @@ expected = {newVal:{yell:true}, oldVal:{}} | ||
store.run('sing', 2) | ||
expected = {newVal:{yell:false, sing:false}, oldVal:{yell:true, sing:true}} | ||
expected = {newVal:{}, oldVal:{yell:true, sing:true}} | ||
store.run('stop') | ||
@@ -117,4 +130,6 @@ }) | ||
t('store - errors', function() { | ||
var initVal = {}, | ||
store = create(initVal) | ||
var store = create(null, { | ||
set: function(p,v) { this.set(p,v) }, | ||
del: function(p) { this.delete(p) } | ||
}) | ||
@@ -125,4 +140,6 @@ store.on('', function() { | ||
t('===', store.patch(setOp('a/b', 9)) instanceof Error, true) | ||
t('===', store.patch(delOp('a/b')) instanceof Error, true) | ||
t('===', store.run('set', 'a/b', 9) instanceof Error, true) | ||
t('===', store.run('del', 'a/b') instanceof Error, true) | ||
var res = store.run('set', 'a/b', 9) | ||
t('===', res instanceof Error && res.message, 'invalid path: a/b') | ||
}) |
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
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
31330
17
1053
68