60+ tests passing

This commit is contained in:
Mark Nadal 2016-07-26 22:46:15 -07:00
parent 78460bf5a5
commit a609584eda
2 changed files with 446 additions and 259 deletions

613
gun.js
View File

@ -27,7 +27,7 @@
var Type = {}; var Type = {};
Type.fns = Type.fn = {is: function(fn){ return (!!fn && fn instanceof Function) }} Type.fns = Type.fn = {is: function(fn){ return (!!fn && fn instanceof Function) }}
Type.bi = {is: function(b){ return (b instanceof Boolean || typeof b == 'boolean') }} Type.bi = {is: function(b){ return (b instanceof Boolean || typeof b == 'boolean') }}
Type.num = {is: function(n){ return !list_is(n) && (Infinity === n || (n - parseFloat(n) + 1) >= 0) }} Type.num = {is: function(n){ return !list_is(n) && ((n - parseFloat(n) + 1) >= 0 || Infinity === n || -Infinity === n) }}
Type.text = {is: function(t){ return (typeof t == 'string') }} Type.text = {is: function(t){ return (typeof t == 'string') }}
Type.text.ify = function(t){ Type.text.ify = function(t){
if(Type.text.is(t)){ return t } if(Type.text.is(t)){ return t }
@ -289,10 +289,13 @@
var proto = create.prototype; var proto = create.prototype;
var on = create.on = On.scope(); var on = create.on = On.scope();
proto.chain = function(tmp){ proto.chain = function(){
var chain = new this.constructor(); var chain = new this.constructor(), _, tmp;
tmp = chain._ || (chain._ = {}); _ = chain._ || (chain._ = {});
tmp.back = this; _.back = this;
if(tmp = opt.extend){
_[tmp] = this._[tmp];
}
return chain; return chain;
} }
proto.back = function(){ proto.back = function(){
@ -681,10 +684,10 @@
} }
function node(env, at){ var tmp; function node(env, at){ var tmp;
if(tmp = seen(env, at)){ return tmp } if(tmp = seen(env, at)){ return tmp }
if(tmp = Node.soul(at.obj)){ /*if(tmp = Node.soul(at.obj)){ // TODO: You should probably delete this. Don't use it anymore. Maybe run one last test where a non-graphed document contains a graph and see if it has problems.
env.graph[at.soul = tmp] = at.node = at.obj; env.graph[at.soul = tmp] = at.node = at.obj;
return at; return at;
} }*/
if(Node.ify(at.obj, map, {env: env, at: at})){ if(Node.ify(at.obj, map, {env: env, at: at})){
env.graph[at.soul = Node.soul(at.node)] = at.node; env.graph[at.soul = Node.soul(at.node)] = at.node;
} }
@ -692,6 +695,9 @@
} }
function map(v,f,n){ function map(v,f,n){
var env = this.env, at = this.at, is = Val.is(v), tmp; var env = this.env, at = this.at, is = Val.is(v), tmp;
if(Node._ === f && obj_has(v,Val.rel._)){
return; // TODO: Bug?
}
if(env.map){ if(env.map){
env.map(v,f,n); env.map(v,f,n);
} }
@ -707,7 +713,12 @@
} }
} }
if(!f){ if(!f){
return at.node = Node.soul.ify({}, at.soul); at.node = at.node || {};
if(obj_has(v, Node._)){
at.node._ = Gun.obj.copy(v._);
}
at.node = Node.soul.ify(at.node, at.soul);
return at.node;
} }
if(is){ if(is){
return v; return v;
@ -757,7 +768,7 @@
} }
}()); }());
var fn_is = Type.fn.is; var fn_is = Type.fn.is;
var obj = Type.obj, obj_is = obj.is, obj_del = obj.del, obj_empty = obj.empty, obj_put = obj.put, obj_map = obj.map, obj_copy = obj.copy; var obj = Type.obj, obj_is = obj.is, obj_del = obj.del, obj_has = obj.has, obj_empty = obj.empty, obj_put = obj.put, obj_map = obj.map, obj_copy = obj.copy;
module.exports = Graph; module.exports = Graph;
/*setTimeout(function(){ // test /*setTimeout(function(){ // test
var g = Graph.ify({ var g = Graph.ify({
@ -776,12 +787,13 @@
;module(function(module){ ;module(function(module){
function Gun(o){ function Gun(o){
if(!(this instanceof Gun)){ return new Gun.create(o) } if(!(this instanceof Gun)){ return Gun.create(o) }
this._ = {gun: this}; this._ = {gun: this};
if(o){ this.opt(o) }
} }
Gun.create = Gun; Gun.create = function(o){
return new Gun().opt(o);
};
Gun._ = { // some reserved key words, these are not the only ones. Gun._ = { // some reserved key words, these are not the only ones.
meta: '_' // all metadata of the node is stored in the meta property on the node. meta: '_' // all metadata of the node is stored in the meta property on the node.
@ -813,37 +825,95 @@
Gun.state = require('./state'); Gun.state = require('./state');
Gun.graph = require('./graph'); Gun.graph = require('./graph');
var opt = {chain: 'in', back: 'out', id: Gun._.soul}; var opt = {chain: 'in', back: 'out', extend: 'root', id: Gun._.soul};
Gun.chain = require('./chain')(Gun, opt); Gun.chain = require('./chain')(Gun, opt);
Gun.chain.chain.opt = opt; Gun.chain.chain.opt = opt;
Gun.chain.opt = function(opt, stun){ ;(function(){
opt = opt || {};
var gun = this, at = gun._, u; Gun.chain.opt = function(opt){
if(!at.graph){ opt = opt || {};
at.graph = {}; var gun = this, at = gun._, tmp, u;
at.opt = at.opt || {}; if(!at.root){ root(at) }
tmp = at.opt = at.opt || {};
if(text_is(opt)){ opt = {peers: opt} }
else if(list_is(opt)){ opt = {peers: opt} }
if(text_is(opt.peers)){ opt.peers = [opt.peers] }
if(list_is(opt.peers)){ opt.peers = obj_map(opt.peers, function(n,f,m){m(n,{})}) }
obj_map(opt, function map(v,f){
if(obj_is(v)){
tmp = tmp[f] || (tmp[f] = {}); // TODO: Bug? Be careful of falsey values getting overwritten?
obj_map(v, map);
return;
}
tmp[f] = v;
});
return gun;
} }
at.opt.uuid = opt.uuid || text_random; function root(at){
at.opt.state = opt.state || Gun.state; var gun = at.gun;
at.opt.peers = at.opt.peers || {}; at.root = gun;
if(text_is(opt)){ opt = {peers: opt} } at.graph = {};
if(list_is(opt)){ opt = {peers: opt} } gun.on('in', input, at);
if(text_is(opt.peers)){ opt.peers = [opt.peers] } gun.on('out', output, at);
if(list_is(opt.peers)){ opt.peers = obj_map(opt.peers, function(n,f,m){m(n,{})}) } }
obj_map(opt.peers, function(v, f){ function output(at){
at.opt.peers[f] = v; var cat = this, gun = cat.gun, tmp;
}); console.debug(7, 'out!', at, cat);
obj_map(['key', 'path', 'map', 'not', 'init'], function(f){ if(at.put){
if(!opt[f]){ return } cat.on('in', at);
at.opt[f] = opt[f] || at.opt[f]; }
}); if(!at.gun){
if(!stun){ Gun.on('opt', {gun: gun, opt: opt}) } at = Gun.obj.to(at, {gun: gun}); // TODO: BUG! Maybe we shouldn't do this yet since it will effect the redirected input stream?
return gun; }
} if(at.put){ Gun.on('put', at) }
if(at.get){ get(at, cat) }
Gun.on('out', at);
if(!cat.back){ return }
cat.back.on('out', at);
}
function get(at, cat){
var soul = at.get[_soul], node = cat.graph[soul], field = at.get[_field];
if(node && (!field || obj_has(node, field))){
cat.on('in', {
put: Gun.graph.node(node), // TODO: BUG! Clone node!
get: soul
});
return;
}
Gun.on('get', at);
}
function input(at){ var cat = this;
if(!Gun.obj.is(at.put)){ return }
if(cat.graph){
Gun.obj.map(at.put, ham, {at: at, cat: this}); // all unions must happen first, sadly.
}
Gun.obj.map(at.put, map, {at: at, cat: this});
}
function ham(data, key){
var cat = this.cat, graph = cat.graph;
graph[key] = Gun.HAM.union(graph[key] || data, data) || graph[key];
}
function map(data, key){
var cat = this.cat, graph = cat.graph, path = cat.path || {}, gun, at;
if(!(gun = path[key])){ return }
(at = gun._).change = data;
if(graph){
data = graph[key]; // TODO! BUG/PERF! COPY!?
}
at.put = data; // TODO: Should be merged! Even for non-root level items.
gun.on('in',{
put: data,
get: key,
gun: gun,
via: this.at
});
}
}());
var text = Type.text, text_is = text.is, text_random = text.random; var text = Type.text, text_is = text.is, text_random = text.random;
var list_is = Type.list.is; var list = Type.list, list_is = list.is;
var obj_map = Type.obj.map; var obj = Type.obj, obj_is = obj.is, obj_has = obj.has, obj_map = obj.map;
var _soul = Gun._.soul, _field = Gun._.field;
console.debug = function(i, s){ return (console.debug.i && i === console.debug.i && console.debug.i++) && console.log.apply(console, arguments), s }; console.debug = function(i, s){ return (console.debug.i && i === console.debug.i && console.debug.i++) && console.log.apply(console, arguments), s };
@ -964,7 +1034,7 @@
} }
} }
Gun.HAM.union = function(vertex, node, opt){ Gun.HAM.union = function(vertex, node, opt){
if(!node._ || !vertex._){ return } if(!node || !vertex || !node._ || !vertex._){ return }
opt = num_is(opt)? {machine: opt} : {machine: (+new Date)}; opt = num_is(opt)? {machine: opt} : {machine: (+new Date)};
opt.union = Gun.obj.copy(vertex); opt.union = Gun.obj.copy(vertex);
opt.vertex = vertex; opt.vertex = vertex;
@ -975,28 +1045,6 @@
} }
return opt.union; return opt.union;
} }
Gun.HAM.node = function(gun, node, opt){
if(!node){ return }
opt = num_is(opt)? {state: opt} : opt || {};
if(gun instanceof Gun){
var soul = node_soul(node), root = gun.Back(-1);
var graph = root._.graph || (root._.graph = {});
if(!soul){ return }
var vertex = graph[soul];
if(vertex === node){ return vertex }
vertex = graph[soul] = vertex || node_ify({}, soul);
var machine = opt.state || ((root._.opt||opt).state || Gun.state)();
} else {
var soul = node_soul(gun);
if(!soul){ return }
var vertex = gun;
if(vertex === node){ return vertex }
var machine = opt.state;
}
obj_map(node._, meta, vertex);
if(!node_is(node, map, {node:node,vertex:vertex,machine:machine,opt:opt})){ return }
return vertex;
}
var Type = Gun; var Type = Gun;
var num = Type.num, num_is = num.is; var num = Type.num, num_is = num.is;
var obj = Type.obj, obj_has = obj.has, obj_put = obj.put, obj_map = obj.map; var obj = Type.obj, obj_has = obj.has, obj_put = obj.put, obj_map = obj.map;
@ -1016,12 +1064,8 @@
;(function(){ var obj = {}, u; ;(function(){ var obj = {}, u;
Gun.chain.Back = function(n, opt){ var tmp; Gun.chain.Back = function(n, opt){ var tmp;
if(-1 === n){ if(-1 === n || Infinity === n){
if(tmp = this.Back('graph', this)){ return this._.root;
return tmp;
}
this._.graph = {};
return this;
} }
var gun = this, at = gun._; var gun = this, at = gun._;
if(typeof n === 'string'){ if(typeof n === 'string'){
@ -1044,53 +1088,119 @@
}()) }())
;(function(){ ;(function(){
Gun.chain.put = function(data, cb, opt){ Gun.chain.puut = function(data, cb, opt){
var gun = this, at = gun._, env, tmp; var gun = this, at = gun._, root = gun.Back(-1);
if(!obj_is(data)){ if(at.err){ return gun }
cb({err: Gun.log("No node exists to put ", (typeof data), '"' + data + '" in!')}); opt = opt || {};
return gun; opt.gun = gun;
if(!from.back || !from.get || !from.get.field || !back.Back('get')){ opt.any = opt.any || cb;
return back; opt.data = opt.data || data;
opt.state = opt.state || (gun.Back('opt.state') || Gun.state)();
if(root !== at.back){
opt.soul = opt.soul || Gun.node.soul(data);
if(root === gun){
opt.soul = opt.soul || (opt.uuid || gun.Back('opt.uuid') || Gun.text.random)();
} }
from.back.put(obj.put({}, from.get.field, data), cb, opt); // TODO: Bug! Dynamic paths! Won't work. if(opt.soul){
return back; gun = root.get(opt.soul).put(data, cb, opt);
} else {
gun.on(save, {as: opt});
opt.next = Gun.on.next(gun);
}
return gun;
} else
if(!opt.soul){
if(!(opt.init || gun.Back('opt.init'))){
}
}
opt.soul = opt.soul || at.get || Gun.node.soul(data);
console.debug(11, 'put', root === at.back, opt.soul);
if(!opt.soul){
EXPLODE;
}
if(!obj_is(data)){
(cb||noop).call(gun, {err: Gun.log("No node exists to put ", (typeof data), '"' + data + '" in!')});
return gun;
}
env = opt.env = Gun.state.map(map, opt.state);
env.soul = opt.soul;
Gun.graph.ify(data, env, opt);
if(env.err){
cb({err: Gun.log(env.err)});
return gun;
}
if(opt.batch){
opt.batch(env.graph);
return gun;
}
return gun.on('out', {
gun: gun, put: env.graph, opt: opt,
'#': Gun.on.ask(function(ack, ev){ ev.off(); // One response is good enough for us currently. Later we may want to adjust this.
if(!opt.any){ return }
opt.any(ack.err, ack.ok);
}, opt)
});
}
Gun.chain.put = function(data, cb, opt){
var gun = this, at = gun._, env, root, tmp;
if(at.err){ return gun }
if(!obj_is(data)){
return path(gun, data, cb, opt);
} }
opt = opt || {}; opt = opt || {};
opt.gun = gun; opt.gun = gun;
opt.any = opt.any || cb; opt.any = opt.any || cb;
opt.data = opt.data || data; opt.data = opt.data || data;
opt.soul = opt.soul || Gun.node.soul(data); opt.soul = opt.soul || Gun.node.soul(data);
opt.state = (opt.state || gun.Back('opt.state') || Gun.state)(); opt.state = opt.state || (gun.Back('opt.state') || Gun.state)();
root = gun.Back(-1);
if(!opt.soul){ if(!opt.soul){
if(at.get && (tmp = at.back) && (tmp = tmp._) && !tmp.get){ if(root === gun){
opt.soul = at.get;
} else
if(!gun.Back('get')){
opt.soul = (opt.uuid || gun.Back('opt.uuid') || Gun.text.random)(); opt.soul = (opt.uuid || gun.Back('opt.uuid') || Gun.text.random)();
} else { } else
opt.next = Gun.on.next(gun); if(at.get && root === at.back){
gun.on(save, {as: opt}); if(!(opt.init || gun.Back('opt.init'))){
opt.soul = at.get;
}
} }
} }
if(opt.soul){ if(opt.soul){
gun = gun.Back(-1).get(opt.soul); gun = root.get(opt.soul);
} else {
gun.on(save, {as: opt});
opt.next = Gun.on.next(gun);
return gun;
} }
env = opt.env = Gun.state.map(map, opt.state); // TODO: Enforce states correctly! Something seemed to be lazy earlier. env = opt.env = Gun.state.map(map, opt.state); // TODO: Enforce states correctly! Something seemed to be lazy earlier.
env.soul = opt.soul; env.soul = opt.soul;
Gun.graph.ify(data, env, opt); // TODO: Enforce states correctly! Something seemed to be lazy earlier. Gun.graph.ify(data, env, opt);
if(env.err){ if(env.err){
cb({err: Gun.log(env.err)}); cb({err: Gun.log(env.err)});
return gun; return gun;
} }
return gun.on('out', {put: env.graph, return gun.on('out', {
gun: gun, put: env.graph, opt: opt,
'#': Gun.on.ask(function(ack, ev){ ev.off(); // One response is good enough for us currently. Later we may want to adjust this. '#': Gun.on.ask(function(ack, ev){ ev.off(); // One response is good enough for us currently. Later we may want to adjust this.
if(!opt.any){ return } if(!opt.any){ return }
opt.any(ack.err, ack.ok); opt.any(ack.err, ack.ok);
}, opt) }, opt)
}); });
}; };
function save(){ function path(gun, data, cb, opt){
console.log("spooky"); var at = gun._, tmp;
if(opt && opt.soul && at.get){
return gun.Back(-1).get(opt.soul).put(obj_put({}, at.get, data), cb, opt).get(at.get, null, {path: true});
}
if(!at.get || !(tmp = at.back) || !tmp._.get){ // TODO: BUG! Won't this fail on a `gun.get('foo').map().put('yes')` or with a `path('boo')` between map and put?
(cb||noop).call(gun, {err: Gun.log("No node exists to put ", (typeof data), '"' + data + '" in!')});
return gun;
}
if(at.get){
return at.back.put(obj_put({}, at.get, data), cb, opt).get(at.get, null, {path: true});
}
}
function save(at, ev){ var cat = this;
} }
function map(v,f,n){ var opt = this; function map(v,f,n){ var opt = this;
if(!n){ if(!n){
@ -1214,12 +1324,14 @@
if(!opt || !opt.path){ var back = this.Back(-1); } // TODO: CHANGING API! Remove this line! if(!opt || !opt.path){ var back = this.Back(-1); } // TODO: CHANGING API! Remove this line!
var gun, back = back || this; var gun, back = back || this;
var path = back._.path || empty, tmp; var path = back._.path || empty, tmp;
console.debug(1, 'get', lex);
if(typeof lex === 'string'){ if(typeof lex === 'string'){
if(!(gun = path[lex])){ if(!(gun = path[lex])){
console.debug(2, 'get', lex);
gun = cache(lex, back); gun = cache(lex, back);
} }
} else } else
if(!lex && 0 != lex){ // TODO: BUG!? if(!lex && 0 != lex){
(gun = back.chain())._.err = {err: Gun.log('Invalid get request!', lex)}; (gun = back.chain())._.err = {err: Gun.log('Invalid get request!', lex)};
if(cb){ cb.call(gun, gun._.err) } if(cb){ cb.call(gun, gun._.err) }
return gun; return gun;
@ -1247,55 +1359,53 @@
} }
if(cb && cb instanceof Function){ if(cb && cb instanceof Function){
(opt = opt || {}).any = cb; (opt = opt || {}).any = cb;
opt.gun = opt.gun || gun; (opt.gun = opt.gun || gun).get.any(opt);
Gun.chain.get.any(opt);
} }
return gun; return gun;
} }
function cache(key, back){ function cache(key, back){
var cat = back._, path = cat.path, gun = back.chain(), at = gun._; var cat = back._, path = cat.path, gun = back.chain(), at = gun._;
at.get = key; if(!path){ path = cat.path = {} }
if(!path){ path[at.get = key] = gun;
if(cat.back && !cat.back._.path && !cat.graph){ Gun.on('path', at);
cat.graph = {}; // TODO: DEPRECATE? gun.on('in', input, at); // For 'in' if I add my own listeners to each then I MUST do it before in gets called. If I listen globally for all incoming data instead though, regardless of individual listeners, I can transform the data there and then as well.
} gun.on('out', output, at); // However for output, there isn't really the global option. I must listen by adding my own listener individually BEFORE this one is ever called.
path = cat.path = {}; console.debug(3, 'cache', key);
back.on('in', input, cat); // WAITS FOR DATA. return gun;
back.on('out', output, cat);
}
return path[at.get] = gun;
} }
function output(at){ var cat = this, tmp; function output(at){
if(cat.graph){ var cat = this, gun = cat.gun, root = gun.Back(-1), put, get, tmp;
if(at.put){ if((get = at.get)){
cat.on('in', at);
}
}
if(at.get){
var get = at.get;
at = Gun.obj.to(at, {}); at = Gun.obj.to(at, {});
if(cat.graph){ if(typeof get === 'string'){
if(typeof get === 'string'){ get = at.get = Gun.obj.put({}, (root === cat.back? _soul : _field), get);
at.get = Gun.obj.put({}, Gun._.soul, get); } else {
if(root === cat.back){
if(!get[_soul]){
get[_soul] = cat.get;
}
} }
} }
if(cat.put){
// CACHED RESULT! REPLY NOW!
}
} }
console.debug(6, 'out', at, cat);
cat.back.on('out', at); cat.back.on('out', at);
} }
function input(at){ var cat = this; function input(at, ev){ var cat = this, tmp;
cat.put = Gun.HAM.union(cat.put, cat.change = at.put) || cat.put || at.put; cat.put = at.put;
if(!Gun.obj.is(cat.change)){ return } if(Gun.val.is(cat.change = cat.change || at.put)){
Gun.obj.map(at.put, map, {at: at, cat: this}); value.call(cat, at, ev);
} return;
function map(data, key){
var cat = this.cat, gun = cat.gun.get(key), at = gun._;
if(cat.graph){
cat.graph[key] = Gun.HAM.union(cat.graph[key] || data, data) || cat.graph[key] || data;
} }
at.put = (cat.graph||cat.put||empty)[key] || data; // TODO: BUG! Is this okay? if((tmp = cat.link) && (tmp = tmp.resume)){ // TODO: BUG!? Does this belong here or ABOVE the val.is check? I'm guessing here.
tmp(cat); // TODO: BUG! What about via? Do we need to clone this?
}
Gun.obj.map(cat.change, map, {at: at, cat: this, put: at.put});
}
function map(data, key){ // Map over only the changes on every update.
if(Gun._.meta === key){ return }
var cat = this.cat, path = cat.path || {}, gun;
if(!(gun = path[key])){ return }
if(this.put){ data = this.put[key] || data } // But use the actual data.
gun.on('in',{ gun.on('in',{
put: data, put: data,
get: key, get: key,
@ -1303,18 +1413,40 @@
via: this.at via: this.at
}); });
} }
Gun.chain.get.any = function(opt){ function value(at, ev){
opt.gun.on('in', any, opt); var cat = this, rel = Gun.val.rel.is(cat.change), tmp, u;
if(opt.run){ return } // Useless if data is already known to be successful. We don't need to ask for anything. if(!rel || rel === cat.rel){ return }
opt.gun.on('out', {'#': Gun.on.ask(anything, opt), get: opt.gun._.get}) if(tmp = cat.link){
if((tmp = tmp.opt) && (tmp = tmp.event)){
tmp.off();
}
// TODO: BUG! PERF! If there are no other uses of cat.link.gun then we need to cat.link.gun.off() to clean it up from memory. This requires a system that knows how many things point to it though. Maybe `gun.off` should handle this logic using info in the @$ event?
}
cat.rel = rel;
cat.change = u;
cat.link = {opt: {change: true, as: cat}, resume: ev.stun(true)};
cat.link.gun = cat.gun.Back(-1).get(rel).on(input, cat.link.opt); // TODO: BUG!? Don't use `gun.on` so that way get/put don't depend upon `.chain.on`?
} }
function any(at, ev){ var opt = this; opt.run = true; Gun.chain.get.any = function(opt){
if(!opt || !opt.any){ return }
console.debug(4, 'any', opt);
opt.gun.on('in', any, opt);
console.debug(5, 'any ran', opt.ran);
if(opt.ran){ return } // Useless if data is already known to be successful. We don't need to ask for anything.
opt.gun.on('out', {
gun: opt.gun, get: opt.gun._.get,
'#': Gun.on.ask(ack, opt),
})
}
function any(at, ev){ var opt = this; opt.ran = true;
console.debug(9, 'nothing', at);
opt.any.call(at.gun || opt.gun, at.err, at.put, at.get, at, ev); opt.any.call(at.gun || opt.gun, at.err, at.put, at.get, at, ev);
} }
function anything(ack, ev){ var opt = this; function ack(ack, ev){ var opt = this;
any.call(opt, {err: ack.err, put: ack.put}, ev); any.call(opt, {err: ack.err, put: ack.put}, ev);
} }
var empty = {}, u; var empty = {}, u;
var _soul = Gun._.soul, _field = Gun._.field;
}()); }());
;(function(){ ;(function(){
@ -1354,60 +1486,60 @@
return s; return s;
} }
keyed._ = '##'; keyed._ = '##';
var get = Gun.chain.get; Gun.on('path', function(at){
Gun.chain.get = function(lex, cb, opt){ var gun = at.gun;
var gun = get.call(this, lex, null, opt), at = gun._, cat = this._, tmp; if(gun.Back(-1) !== at.back){ return }
if(!at.key && cat.path && (tmp = cat.back) && !tmp._.path){ gun.on('in', pseudo, gun._);
at.key = true; gun.on('out', normalize, gun._);
gun.on('in', pseudo, at); });
function normalize(at){ var cat = this;
if(!at.put){
if(at.get){
search.call(cat, at);
}
return;
} }
if(cb && cb instanceof Function){ if(at.opt && at.opt.key){ return }
(opt = opt || {}).any = cb; var put = at.put;
opt.gun = opt.gun || gun; Gun.graph.is(put, function(node, soul){ // TODO: CLEAN ALL OF THIS UP!
Gun.chain.get.any(opt); var key = {node: cat.gun.Back(-1)._.graph[soul]}, tmp;
} if(!obj_has(key.node, keyed._)){ return } // TODO: BUG! Should iterate over it anyways to check for non #soul# properties to port.
return gun; Gun.node.is(key.node, function each(rel, s){
}
Gun.chain.get.any = get.any;
function normalize(cat){
if(!cat || !cat.put){ return }
//console.log("normalize", cat.put);
return;
var at = cat, env = at.env;
if(at.opt.key){ return }
is_graph(env.graph, function(node, soul){ // TODO: CLEAN ALL OF THIS UP!
var key = {node: at.gun.__.graph[soul]}, tmp;
if(!obj_has(key.node, keyed.on)){ return } // TODO: BUG! Should iterate over it anyways to check for non #soul# properties to port.
is_node(key.node, function each(rel, s){
if(!(s = keyed(s))){ return } if(!(s = keyed(s))){ return }
var n = at.gun.__.graph[s]; // TODO: BUG! Should we actually load the item or only use what is in memory? var n = cat.gun.Back(-1)._.graph[s]; // TODO: BUG! Should we actually load the item or only use what is in memory?
if(obj_has(n, keyed.on)){ if(obj_has(n, keyed._)){
is_node(n, each); Gun.node.is(n, each);
return; return;
} }
rel = env.graph[s] = env.graph[s] || is_node_soul_ify({}, s); rel = put[s] = put[s] || Gun.node.soul.ify({}, s);
is_node(node, function(v,f){ Gun.node.is(node, function(v,f){
is_node_state_ify(rel, {field: f, value: v, state: is_node_state(node, f) }); rel[f] = v;
Gun.state.ify(rel, f, Gun.state.is(node, f));
}); });
Gun.obj.del(env.graph, soul); Gun.obj.del(put, soul);
}); });
}); });
} }
function search(at){
delete at.get[Gun._.field]; // TODO: BUG!? Meh?
}
function pseudo(at, e){ var cat = this; function pseudo(at, e){ var cat = this;
var put = at.put; var change = cat.change;
if(!put){ return } if(!change || !at.put){ return }
if(!put[keyed._] && !cat.pseudo){ return } if(!at.put[keyed._] && !cat.pseudo){ return }
var soul = Gun.node.soul(put), resume = e.stun(resume), change = cat.change || at.put, seen = cat.seen || (cat.seen = {}), data; var soul = Gun.node.soul(at.put), resume = e.stun(resume), gun = cat.gun, seen = cat.seen || (cat.seen = {}), already = true, data;
if(!cat.pseudo){ if(!cat.pseudo){
cat.pseudo = Gun.node.ify({}, soul); cat.pseudo = Gun.node.ify({}, soul);
cat.pseudo._.key = 'pseudo'; // TODO: CLEAN THIS UP! cat.pseudo._.key = 'pseudo'; // TODO: CLEAN THIS UP!
cat.pseudo._['>'] = {}; // TODO: CLEAN THIS UP! cat.pseudo._['>'] = {}; // TODO: CLEAN THIS UP!
cat.put = cat.pseudo;
} }
Gun.node.is(change, function(v, f){ var key; Gun.node.is(change, function(v, f){ var key;
if(key = keyed(f)){ if(key = keyed(f)){
if(seen[key]){ return} if(seen[key]){ return }
already = false;
seen[key] = true; // TODO: Removing keys? seen[key] = true; // TODO: Removing keys?
cat.gun.Back(-1).get(key).on(on); cat.gun.Back(-1).get(key).on(on, true); // TODO: BUG! Perf! These are listeners that will become leaked! If we `gun.off()` on a pseudo we need to remember to also clean up these listeners.
return; return;
} }
if(keyed._ === f){ return } if(keyed._ === f){ return }
@ -1416,17 +1548,29 @@
Gun.state.ify(data, f, Gun.state.is(change, f)); Gun.state.ify(data, f, Gun.state.is(change, f));
}); });
function on(put){ function on(put){
// TODO: PERF? Only use change!
//put = this._.change || put;
cat.pseudo = Gun.HAM.union(cat.pseudo, put) || cat.pseudo; cat.pseudo = Gun.HAM.union(cat.pseudo, put) || cat.pseudo;
cat.put = cat.pseudo; cat.change = put;
resume(cat); resume({
put: cat.pseudo,
get: soul
//via: this.at
});
}
if(!data){
if(already){
on(cat.pseudo);
}
return;
} }
if(!data){ return }
var graph = {}; var graph = {};
Gun.obj.map(seen, function(t,s){ Gun.obj.map(seen, function(t,s){
graph[s] = Gun.node.soul.ify(Gun.obj.copy(data), s); graph[s] = Gun.node.soul.ify(Gun.obj.copy(data), s);
}); });
cat.gun.Back(-1).on('in', {put: graph}); cat.gun.Back(-1).on('in', {put: graph});
} }
var obj = Gun.obj, obj_has = obj.has;
}()); }());
Gun.chain.path = function(field, cb, opt){ Gun.chain.path = function(field, cb, opt){
@ -1461,23 +1605,34 @@
;(function(){ ;(function(){
Gun.chain.chain.opt.on = function(cb, opt){ Gun.chain.chain.opt.on = function(cb, opt){
if(!cb){ return this } if(!cb){ return this }
opt = opt || {}; var tmp;
opt = (true === opt)? {change: 1} : opt || {};
opt.gun = opt.gun || this; opt.gun = opt.gun || this;
opt.ok = cb; opt.ok = cb;
opt.gun.on('in', ok, opt); opt.event = opt.gun._.on('in', ok, opt);
if(opt.as && (tmp = opt.as.ons)){
(tmp['@$'] || (tmp['@$'] = {s: []})).s.push(opt.event);
}
if(!opt.run){ if(!opt.run){
opt.gun.on('out', obj_to(opt.gun._)); opt.gun._.on('out', obj_to(opt.gun._));
} }
return this; return this;
} }
function ok(cat, ev){ var opt = this; opt.run = true; function ok(cat, ev){ var opt = this, data, tmp; opt.run = true;
// TODO: BUG! Need to use at.put > cat.put for merged cache? // TODO: BUG! Need to use at.put > cat.put for merged cache?
if(!cat.put && !obj_has(cat, 'put')){ return } if(!(data = cat.put) && !obj_has(cat, 'put')){ return } // TODO: what if cat.put is undefined? This shouldn't happen but might from how get's anything is coded.
if(tmp = opt.change){
if(1 === tmp){
opt.change = true;
} else {
data = opt.gun._.change;
}
}
if(opt.as){ if(opt.as){
opt.ok.call(opt.as, cat, ev); opt.ok.call(opt.as, cat, ev);
} else { } else {
opt.ok.call(cat.gun || opt.gun, cat.put, cat.get, cat, ev); opt.ok.call(cat.gun || opt.gun, data, cat.get, cat, ev);
} }
} }
@ -1489,7 +1644,7 @@
} }
if(cb){ if(cb){
(opt = opt || {}).ok = cb; (opt = opt || {}).ok = cb;
at.on(val, {as: opt}); // LOADS DATA at.on(val, {as: opt}); // LOADS DATA // TODO: Have `.val` support an `opt.as` ability of its own?
} }
return gun; return gun;
} }
@ -1510,7 +1665,32 @@
ev.off(); ev.off();
opt.ok.call(cat.gun, data, cat.get); // TODO: BUG! opt.gun? opt.ok.call(cat.gun, data, cat.get); // TODO: BUG! opt.gun?
} }
var obj = Gun.obj, obj_has = obj.has, obj_to = obj.to;
Gun.chain.off = function(){
var gun = this, at = gun._, tmp;
var back = at.back || {}, cat = back._;
if(!cat){ return }
if(tmp = cat.path){
if(tmp[at.get]){
obj_del(tmp, at.get);
} else {
obj_map(tmp, function(path, key){
if(gun !== path){ return }
obj_del(tmp, key);
});
}
}
if((tmp = gun.Back(-1)) === back){
obj_del(tmp.graph, at.get);
}
if(at.ons && (tmp = at.ons['@$'])){
obj_map(tmp.s, function(ev){
ev.off();
});
}
return gun;
}
var obj = Gun.obj, obj_has = obj.has, obj_del = obj.del, obj_to = obj.to;
}()); }());
;(function(){ ;(function(){
@ -1542,75 +1722,43 @@
;(function(){ ;(function(){
var module = function(cb){ return function(){ cb({exports:{}}) } }; var module = function(cb){ return function(){ cb({exports:{}}) } };
;module(function(module){
Gun.chain.wsp = function(){
var back = this, gun;
gun = back.chain();
gun._.wsp = true;
gun.on('out', function(at){
back.on('out', at);
if(!at.get){ return }
var peers = (at.gun || gun).Back('opt.peers');
if(!peers){
Gun.log.once('peers', "Warning! You have no peers to connect to!");
Gun.on.ack(at['#'], {});
return;
}
});
return gun.chain();
}
var create = Gun.create;
function wsp(o){
return new create(o).wsp();
}
Gun.create = wsp;
})(module, './src/WebSocket');
;module(function(module){ ;module(function(module){
var root, noop = function(){}; var root, noop = function(){};
if(typeof window !== 'undefined'){ root = window } if(typeof window !== 'undefined'){ root = window }
var store = root.localStorage || {setItem: noop, removeItem: noop, getItem: noop}; var store = root.localStorage || {setItem: noop, removeItem: noop, getItem: noop};
Gun.chain.local = function(opt){ function put(at){ var err, id, opt;
var back = this, gun, chain; (opt = at.opt || {}).prefix = opt.prefix || 'gun/';
opt = opt || {};
opt.prefix = opt.prefix || 'gun/';
gun = back.chain();
gun._.local = true;
gun.on('out', function(at){
if(at.get){ get(chain, at, opt) }
if(at.put){ put(chain, at, opt) }
back.on('out', at);
});
back.on('in', function(at){
chain.on('in', at);
});
return chain = gun.chain();
}
function put(gun, at, opt){ var err, id;
Gun.graph.is(at.put, function(node, soul){ Gun.graph.is(at.put, function(node, soul){
try{store.setItem(opt.prefix + soul, Gun.text.ify(node)); try{store.setItem(opt.prefix + soul, Gun.text.ify(node));
}catch(e){ err = e } }catch(e){ err = e }
}); });
if(err){ explode } if(err){ explode }
Gun.on.ack(at['#'], {ok: 1}); Gun.on.ack(at, {ok: 1}); // TODO: Reliability! Are we sure we want to have localStorage ack?
} }
function get(gun, at, opt){ function get(at){
var lex = at.get, soul, data; var gun = at.gun, lex = at.get, soul, data, opt;
(opt = at.opt || {}).prefix = opt.prefix || 'gun/';
if(!lex || !(soul = lex[Gun._.soul])){ return } if(!lex || !(soul = lex[Gun._.soul])){ return }
data = Gun.obj.ify(store.getItem(opt.prefix + soul) || null); data = Gun.obj.ify(store.getItem(opt.prefix + soul) || null);
if(!data){ return } // localStorage isn't trustworthy to say "not found". if(!data){ return } // localStorage isn't trustworthy to say "not found".
gun.on('in', {put: Gun.graph.node(data)}); gun.Back(-1).on('in', {put: Gun.graph.node(data)});
} }
Gun.on('put', put);
var create = Gun.create; Gun.on('get', get);
function local(o){
return new create(o).local();
}
Gun.create = local;
})(module, './src/localStorage'); })(module, './src/localStorage');
;module(function(module){
Gun.on('get', function(at){
var peers = at.gun.Back('opt.peers');
if(!peers || Gun.obj.empty(peers)){
Gun.log.once('peers', "Warning! You have no peers to connect to!");
console.debug(8, 'wsp not');
Gun.on.ack(at, {});
return;
}
});
})(module, './src/WebSocket');
return; return;
function get(err, data, at){ function get(err, data, at){
if(!data && !Gun.obj.empty(at.opt.peers)){ return } // let the peers handle no data. if(!data && !Gun.obj.empty(at.opt.peers)){ return } // let the peers handle no data.
@ -1621,6 +1769,7 @@
Tab.store.get((opt.prefix || '') + lex.soul, get, at); Tab.store.get((opt.prefix || '') + lex.soul, get, at);
}); });
}()); }());
return;
Gun.on('put', function(at){ Gun.on('put', function(at){
var opt = at.opt, graph = at.graph, gun = at.gun; var opt = at.opt, graph = at.graph, gun = at.gun;

View File

@ -840,6 +840,44 @@ describe('Gun', function(){
//console.log("node/soul", n,s); //console.log("node/soul", n,s);
}); });
}); });
it('graph ify', function(done){
function map(v,f,n){
done.m = true;
}
var graph = Gun.graph.ify({
_: {'#': 'yay'},
a: 1
}, map);
expect(graph).to.eql({
yay: {
_: {'#': 'yay'},
a: 1
}
});
expect(done.m).to.be.ok();
var graph = Gun.graph.ify({
_: {'#': 'yay', '>': {a: 9}},
a: 1
}, map);
expect(graph).to.eql({
yay: {
_: {'#': 'yay', '>': {a: 9}},
a: 1
}
});
var map = Gun.state.map(map, 9);
var graph = Gun.graph.ify({
_: {'#': 'yay', '>': {a: 1}},
a: 1
}, map);
expect(graph).to.eql({
yay: {
_: {'#': 'yay', '>': {a: 9}},
a: 1
}
});
done();
});
}); });
}); });
describe('ify', function(){ describe('ify', function(){
@ -998,7 +1036,6 @@ describe('Gun', function(){
expect(gun.__.graph['asdf']).to.not.be.ok(); expect(gun.__.graph['asdf']).to.not.be.ok();
var ctx = Gun.HAM.graph(gun, prime); var ctx = Gun.HAM.graph(gun, prime);
console.log("????", ctx);
expect(ctx).to.not.be.ok(); expect(ctx).to.not.be.ok();
});return; });return;
@ -1614,18 +1651,14 @@ describe('Gun', function(){
} }
it('get node put node merge', function(done){ it('get node put node merge', function(done){
console.debug.i = 1;console.log('-------------------------');
gun.get('hello/key', function(err, node){ gun.get('hello/key', function(err, node){
if(done.soul){ return } if(done.soul){ return }
console.log(4, "get", err, node);
expect(err).to.not.be.ok(); expect(err).to.not.be.ok();
expect(node.hello).to.be('key'); expect(node.hello).to.be('key');
done.soul = Gun.node.soul(node); done.soul = Gun.node.soul(node);
}).put({hi: 'you'}, function(err, ok){ }).put({hi: 'you'}, function(err, ok){
console.debug(12, "pat", err, ok);
expect(err).to.not.be.ok(); expect(err).to.not.be.ok();
var keynode = gun.Back(-1)._.graph[done.soul], soul; var keynode = gun.Back(-1)._.graph[done.soul], soul;
console.log("OH NO", done.soul, keynode)
expect(keynode.hi).to.not.be.ok(); expect(keynode.hi).to.not.be.ok();
var c = soulnode(gun, keynode), soul = c[0]; var c = soulnode(gun, keynode), soul = c[0];
expect(c.length).to.be(1); expect(c.length).to.be(1);
@ -1633,14 +1666,13 @@ describe('Gun', function(){
expect(node.hello).to.be('key'); expect(node.hello).to.be('key');
expect(node.hi).to.be('you'); expect(node.hi).to.be('you');
}).on(function(node){ }).on(function(node){
console.log("******************", node);
if(done.c){ return } if(done.c){ return }
//expect(done.soul).to.be(Gun.is.node.soul(node)); // TODO: DISCUSSION! This has changed? //expect(done.soul).to.be(Gun.is.node.soul(node)); // TODO: DISCUSSION! This has changed?
expect(node.hi).to.be('you'); expect(node.hi).to.be('you');
expect(node.hello).to.be('key'); expect(node.hello).to.be('key');
done(); done.c = 1; done(); done.c = 1;
}); });
});return; });
it('get null put node never', function(done){ // TODO: GET returns nothing, and then doing a PUT? it('get null put node never', function(done){ // TODO: GET returns nothing, and then doing a PUT?
gun.get(null, function(err, ok){ gun.get(null, function(err, ok){
@ -1663,6 +1695,7 @@ describe('Gun', function(){
done.err = err; done.err = err;
expect(err).to.not.be.ok(); expect(err).to.not.be.ok();
expect(data).to.not.be.ok(); expect(data).to.not.be.ok();
console.debug(10, '******', err, data);
}).put({testing: 'stuff'}, function(err, ok){ }).put({testing: 'stuff'}, function(err, ok){
done.flag = true; done.flag = true;
}); });
@ -1680,14 +1713,14 @@ describe('Gun', function(){
expect(err).to.not.be.ok(); expect(err).to.not.be.ok();
expect(node.hello).to.be('key'); expect(node.hello).to.be('key');
expect(node.hi).to.be('you'); expect(node.hi).to.be('you');
done.soul = Gun.is.node.soul(node); done.soul = Gun.node.soul(node);
}).put({hi: 'overwritten'}, function(err, ok){ }).put({hi: 'overwritten'}, function(err, ok){
if(done.c){ return } if(done.c){ return }
expect(err).to.not.be.ok(); expect(err).to.not.be.ok();
var keynode = gun.__.graph[done.soul], soul; var keynode = gun.Back(-1)._.graph[done.soul], soul;
var c = soulnode(gun, keynode), soul = c[0]; var c = soulnode(gun, keynode), soul = c[0];
expect(c.length).to.be(1); expect(c.length).to.be(1);
var node = gun.__.graph[soul]; var node = gun.Back(-1)._.graph[soul];
expect(node.hello).to.be('key'); expect(node.hello).to.be('key');
expect(node.hi).to.be('overwritten'); expect(node.hi).to.be('overwritten');
done.w = 1; if(done.r){ done(); done.c = 1 }; done.w = 1; if(done.r){ done(); done.c = 1 };
@ -1699,19 +1732,20 @@ describe('Gun', function(){
done.r = 1; if(done.w){ done(); done.c = 1 }; done.r = 1; if(done.w){ done(); done.c = 1 };
}); });
}); });
it('get key path put', function(done){ it('get key path put', function(done){
var gun = Gun().put({foo:'lol', extra: 'yes'}).key('key/path/put'); var gun = Gun().put({foo:'lol', extra: 'yes'}).key('key/path/put');
var data = gun.get('key/path/put'); var data = gun.get('key/path/put');
data.path('foo').put('epic'); data.path('foo').put('epic');
data.val(function(val, field){ data.val(function(val, field){
expect(val.foo).to.be('epic'); expect(val.foo).to.be('epic');
expect(Gun.is.node.soul(val)).to.be('key/path/put'); expect(Gun.node.soul(val)).to.be('key/path/put');
done(); done();
}); });
}); });
it('put node path', function(done){ it('put node path', function(done){
var gun = Gun();
gun.put({hello: 'world'}).path('hello', function(err, val, field){ gun.put({hello: 'world'}).path('hello', function(err, val, field){
if(done.end){ return } // it is okay for path's callback to be called multiple times. if(done.end){ return } // it is okay for path's callback to be called multiple times.
expect(err).to.not.be.ok(); expect(err).to.not.be.ok();
@ -1741,6 +1775,7 @@ describe('Gun', function(){
done(); done.end = true; done(); done.end = true;
}); });
}); });
it('get node path', function(done){ it('get node path', function(done){
gun.get('hello/key').path('hi', function(err, val, field){ gun.get('hello/key').path('hi', function(err, val, field){
if(done.end){ return } // it is okay for path's callback to be called multiple times. if(done.end){ return } // it is okay for path's callback to be called multiple times.
@ -1767,14 +1802,14 @@ describe('Gun', function(){
expect(err).to.not.be.ok(); expect(err).to.not.be.ok();
if(done.soul){ return } if(done.soul){ return }
expect(node.hi).to.be('overwritten'); expect(node.hi).to.be('overwritten');
done.soul = Gun.is.node.soul(node); done.soul = Gun.node.soul(node);
}).path('hi').put('again', function(err, ok){ }).path('hi').put('again', function(err, ok){
if(done.c){ return } if(done.c){ return }
expect(err).to.not.be.ok(); expect(err).to.not.be.ok();
var keynode = gun.__.graph[done.soul], soul; var keynode = gun.Back(-1)._.graph[done.soul], soul;
var c = soulnode(gun, keynode), soul = c[0]; var c = soulnode(gun, keynode), soul = c[0];
expect(c.length).to.be(1); expect(c.length).to.be(1);
var node = gun.__.graph[done.sub = soul]; var node = gun.Back(-1)._.graph[done.sub = soul];
expect(node.hello).to.be('key'); expect(node.hello).to.be('key');
expect(node.hi).to.be('again'); expect(node.hi).to.be('again');
done.w = 1; if(done.r){ done(); done.c = 1 }; done.w = 1; if(done.r){ done(); done.c = 1 };
@ -1787,37 +1822,40 @@ describe('Gun', function(){
}); });
it('get node path put object', function(done){ it('get node path put object', function(done){
gun.get('hello/key', function(err, node){ console.debug.i=1;console.log("----------------------");
var foo = gun.get('hello/key', function(err, node){
if(done.soul){ return } if(done.soul){ return }
expect(err).to.not.be.ok(); expect(err).to.not.be.ok();
expect(node.hi).to.be('again'); expect(node.hi).to.be('again');
expect(node.hello).to.be('key'); expect(node.hello).to.be('key');
done.soul = Gun.is.node.soul(node); done.soul = Gun.node.soul(node);
console.debug(3, '****', err, node, done.soul);
}).path('hi').put({yay: "value"}, function(err, ok){ }).path('hi').put({yay: "value"}, function(err, ok){
if(done.c){ return } if(done.c){ return }
expect(err).to.not.be.ok(); expect(err).to.not.be.ok();
var keynode = gun.__.graph[done.soul], soul; var keynode = gun.Back(-1)._.graph[done.soul], soul;
var c = soulnode(gun, keynode), soul = c[0]; var c = soulnode(gun, keynode), soul = c[0];
expect(c.length).to.be(1); expect(c.length).to.be(1);
var root = gun.__.graph[soul]; var root = gun.Back(-1)._.graph[soul];
expect(root.hello).to.be('key'); expect(root.hello).to.be('key');
expect(root.yay).to.not.be.ok(); expect(root.yay).to.not.be.ok();
expect(Gun.is.rel(root.hi)).to.be.ok(); expect(Gun.val.rel.is(root.hi)).to.be.ok();
expect(Gun.is.rel(root.hi)).to.not.be(soul); expect(Gun.val.rel.is(root.hi)).to.not.be(soul);
var node = gun.__.graph[Gun.is.rel(root.hi)]; var node = gun.Back(-1)._.graph[Gun.val.rel.is(root.hi)];
expect(node.yay).to.be('value'); expect(node.yay).to.be('value');
if(done.sub){ expect(done.sub).to.be(Gun.is.rel(root.hi)) } if(done.sub){ expect(done.sub).to.be(Gun.val.rel.is(root.hi)) }
else { done.sub = Gun.is.rel(root.hi) } else { done.sub = Gun.val.rel.is(root.hi) }
done.w = 1; if(done.r){ done(); done.c = 1 }; done.w = 1; if(done.r){ done(); done.c = 1 };
}).on(function(node, field){ }).on(function(node, field){
console.log("******************", field, node);return;
if(done.c){ return } if(done.c){ return }
expect(field).to.be('hi'); expect(field).to.be('hi');
expect(node.yay).to.be('value'); expect(node.yay).to.be('value');
if(done.sub){ expect(done.sub).to.be(Gun.is.node.soul(node)) } if(done.sub){ expect(done.sub).to.be(Gun.node.soul(node)) }
else { done.sub = Gun.is.node.soul(node) } else { done.sub = Gun.node.soul(node) }
done.r = 1; if(done.w){ done(); done.c = 1 }; done.r = 1; if(done.w){ done(); done.c = 1 };
}); });
}); });return;
it('get path wire', function(done){ it('get path wire', function(done){
var gun = Gun(); var gun = Gun();