diff --git a/gun.js b/gun.js index 18922b8c..9ba1d3c8 100644 --- a/gun.js +++ b/gun.js @@ -27,7 +27,7 @@ var Type = {}; 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.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.ify = function(t){ if(Type.text.is(t)){ return t } @@ -289,10 +289,13 @@ var proto = create.prototype; var on = create.on = On.scope(); - proto.chain = function(tmp){ - var chain = new this.constructor(); - tmp = chain._ || (chain._ = {}); - tmp.back = this; + proto.chain = function(){ + var chain = new this.constructor(), _, tmp; + _ = chain._ || (chain._ = {}); + _.back = this; + if(tmp = opt.extend){ + _[tmp] = this._[tmp]; + } return chain; } proto.back = function(){ @@ -681,10 +684,10 @@ } function node(env, at){ var 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; return at; - } + }*/ if(Node.ify(at.obj, map, {env: env, at: at})){ env.graph[at.soul = Node.soul(at.node)] = at.node; } @@ -692,6 +695,9 @@ } function map(v,f,n){ 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){ env.map(v,f,n); } @@ -707,7 +713,12 @@ } } 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){ return v; @@ -757,7 +768,7 @@ } }()); 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; /*setTimeout(function(){ // test var g = Graph.ify({ @@ -776,12 +787,13 @@ ;module(function(module){ function Gun(o){ - if(!(this instanceof Gun)){ return new Gun.create(o) } + if(!(this instanceof Gun)){ return Gun.create(o) } 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. meta: '_' // all metadata of the node is stored in the meta property on the node. @@ -813,37 +825,95 @@ Gun.state = require('./state'); 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.chain.opt = opt; - Gun.chain.opt = function(opt, stun){ - opt = opt || {}; - var gun = this, at = gun._, u; - if(!at.graph){ - at.graph = {}; - at.opt = at.opt || {}; + ;(function(){ + + Gun.chain.opt = function(opt){ + opt = opt || {}; + var gun = this, at = gun._, tmp, u; + 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; - at.opt.state = opt.state || Gun.state; - at.opt.peers = at.opt.peers || {}; - if(text_is(opt)){ opt = {peers: opt} } - 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.peers, function(v, f){ - at.opt.peers[f] = v; - }); - obj_map(['key', 'path', 'map', 'not', 'init'], function(f){ - if(!opt[f]){ return } - at.opt[f] = opt[f] || at.opt[f]; - }); - if(!stun){ Gun.on('opt', {gun: gun, opt: opt}) } - return gun; - } + function root(at){ + var gun = at.gun; + at.root = gun; + at.graph = {}; + gun.on('in', input, at); + gun.on('out', output, at); + } + function output(at){ + var cat = this, gun = cat.gun, tmp; + console.debug(7, 'out!', at, cat); + if(at.put){ + cat.on('in', at); + } + if(!at.gun){ + at = Gun.obj.to(at, {gun: gun}); // TODO: BUG! Maybe we shouldn't do this yet since it will effect the redirected input stream? + } + 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 list_is = Type.list.is; - var obj_map = Type.obj.map; + var list = Type.list, list_is = list.is; + 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 }; @@ -964,7 +1034,7 @@ } } 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.union = Gun.obj.copy(vertex); opt.vertex = vertex; @@ -975,28 +1045,6 @@ } 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 num = Type.num, num_is = num.is; var obj = Type.obj, obj_has = obj.has, obj_put = obj.put, obj_map = obj.map; @@ -1016,12 +1064,8 @@ ;(function(){ var obj = {}, u; Gun.chain.Back = function(n, opt){ var tmp; - if(-1 === n){ - if(tmp = this.Back('graph', this)){ - return tmp; - } - this._.graph = {}; - return this; + if(-1 === n || Infinity === n){ + return this._.root; } var gun = this, at = gun._; if(typeof n === 'string'){ @@ -1044,53 +1088,119 @@ }()) ;(function(){ - Gun.chain.put = function(data, cb, opt){ - var gun = this, at = gun._, env, tmp; - if(!obj_is(data)){ - cb({err: Gun.log("No node exists to put ", (typeof data), '"' + data + '" in!')}); - return gun; - if(!from.back || !from.get || !from.get.field || !back.Back('get')){ - return back; + Gun.chain.puut = function(data, cb, opt){ + var gun = this, at = gun._, root = gun.Back(-1); + if(at.err){ return gun } + opt = opt || {}; + opt.gun = gun; + opt.any = opt.any || cb; + 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. - return back; + if(opt.soul){ + 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.gun = gun; opt.any = opt.any || cb; opt.data = opt.data || 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(at.get && (tmp = at.back) && (tmp = tmp._) && !tmp.get){ - opt.soul = at.get; - } else - if(!gun.Back('get')){ + if(root === gun){ opt.soul = (opt.uuid || gun.Back('opt.uuid') || Gun.text.random)(); - } else { - opt.next = Gun.on.next(gun); - gun.on(save, {as: opt}); + } else + if(at.get && root === at.back){ + if(!(opt.init || gun.Back('opt.init'))){ + opt.soul = at.get; + } } } 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.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){ cb({err: Gun.log(env.err)}); 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. if(!opt.any){ return } opt.any(ack.err, ack.ok); }, opt) }); }; - function save(){ - console.log("spooky"); + function path(gun, data, cb, opt){ + 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; if(!n){ @@ -1214,12 +1324,14 @@ if(!opt || !opt.path){ var back = this.Back(-1); } // TODO: CHANGING API! Remove this line! var gun, back = back || this; var path = back._.path || empty, tmp; + console.debug(1, 'get', lex); if(typeof lex === 'string'){ if(!(gun = path[lex])){ + console.debug(2, 'get', lex); gun = cache(lex, back); } } else - if(!lex && 0 != lex){ // TODO: BUG!? + if(!lex && 0 != lex){ (gun = back.chain())._.err = {err: Gun.log('Invalid get request!', lex)}; if(cb){ cb.call(gun, gun._.err) } return gun; @@ -1247,55 +1359,53 @@ } if(cb && cb instanceof Function){ (opt = opt || {}).any = cb; - opt.gun = opt.gun || gun; - Gun.chain.get.any(opt); + (opt.gun = opt.gun || gun).get.any(opt); } return gun; } function cache(key, back){ var cat = back._, path = cat.path, gun = back.chain(), at = gun._; - at.get = key; - if(!path){ - if(cat.back && !cat.back._.path && !cat.graph){ - cat.graph = {}; // TODO: DEPRECATE? - } - path = cat.path = {}; - back.on('in', input, cat); // WAITS FOR DATA. - back.on('out', output, cat); - } - return path[at.get] = gun; + if(!path){ path = cat.path = {} } + path[at.get = key] = gun; + Gun.on('path', at); + 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. + console.debug(3, 'cache', key); + return gun; } - function output(at){ var cat = this, tmp; - if(cat.graph){ - if(at.put){ - cat.on('in', at); - } - } - if(at.get){ - var get = at.get; + function output(at){ + var cat = this, gun = cat.gun, root = gun.Back(-1), put, get, tmp; + if((get = at.get)){ at = Gun.obj.to(at, {}); - if(cat.graph){ - if(typeof get === 'string'){ - at.get = Gun.obj.put({}, Gun._.soul, get); + if(typeof get === 'string'){ + get = at.get = Gun.obj.put({}, (root === cat.back? _soul : _field), 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); } - function input(at){ var cat = this; - cat.put = Gun.HAM.union(cat.put, cat.change = at.put) || cat.put || at.put; - if(!Gun.obj.is(cat.change)){ return } - Gun.obj.map(at.put, map, {at: at, cat: this}); - } - 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; + function input(at, ev){ var cat = this, tmp; + cat.put = at.put; + if(Gun.val.is(cat.change = cat.change || at.put)){ + value.call(cat, at, ev); + return; } - 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',{ put: data, get: key, @@ -1303,18 +1413,40 @@ via: this.at }); } - Gun.chain.get.any = function(opt){ - opt.gun.on('in', any, opt); - if(opt.run){ return } // Useless if data is already known to be successful. We don't need to ask for anything. - opt.gun.on('out', {'#': Gun.on.ask(anything, opt), get: opt.gun._.get}) + function value(at, ev){ + var cat = this, rel = Gun.val.rel.is(cat.change), tmp, u; + if(!rel || rel === cat.rel){ return } + 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); } - function anything(ack, ev){ var opt = this; + function ack(ack, ev){ var opt = this; any.call(opt, {err: ack.err, put: ack.put}, ev); } var empty = {}, u; + var _soul = Gun._.soul, _field = Gun._.field; }()); ;(function(){ @@ -1354,60 +1486,60 @@ return s; } keyed._ = '##'; - var get = Gun.chain.get; - Gun.chain.get = function(lex, cb, opt){ - var gun = get.call(this, lex, null, opt), at = gun._, cat = this._, tmp; - if(!at.key && cat.path && (tmp = cat.back) && !tmp._.path){ - at.key = true; - gun.on('in', pseudo, at); + Gun.on('path', function(at){ + var gun = at.gun; + if(gun.Back(-1) !== at.back){ return } + gun.on('in', pseudo, gun._); + gun.on('out', normalize, gun._); + }); + function normalize(at){ var cat = this; + if(!at.put){ + if(at.get){ + search.call(cat, at); + } + return; } - if(cb && cb instanceof Function){ - (opt = opt || {}).any = cb; - opt.gun = opt.gun || gun; - Gun.chain.get.any(opt); - } - return gun; - } - 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(at.opt && at.opt.key){ return } + var put = at.put; + Gun.graph.is(put, function(node, soul){ // TODO: CLEAN ALL OF THIS UP! + 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. + Gun.node.is(key.node, function each(rel, s){ 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? - if(obj_has(n, keyed.on)){ - is_node(n, each); + 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._)){ + Gun.node.is(n, each); return; } - rel = env.graph[s] = env.graph[s] || is_node_soul_ify({}, s); - is_node(node, function(v,f){ - is_node_state_ify(rel, {field: f, value: v, state: is_node_state(node, f) }); + rel = put[s] = put[s] || Gun.node.soul.ify({}, s); + Gun.node.is(node, function(v,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; - var put = at.put; - if(!put){ return } - if(!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 change = cat.change; + if(!change || !at.put){ return } + if(!at.put[keyed._] && !cat.pseudo){ return } + 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){ cat.pseudo = Gun.node.ify({}, soul); cat.pseudo._.key = 'pseudo'; // TODO: CLEAN THIS UP! cat.pseudo._['>'] = {}; // TODO: CLEAN THIS UP! + cat.put = cat.pseudo; } Gun.node.is(change, function(v, f){ var key; if(key = keyed(f)){ - if(seen[key]){ return} + if(seen[key]){ return } + already = false; 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; } if(keyed._ === f){ return } @@ -1416,17 +1548,29 @@ Gun.state.ify(data, f, Gun.state.is(change, f)); }); function on(put){ + // TODO: PERF? Only use change! + //put = this._.change || put; cat.pseudo = Gun.HAM.union(cat.pseudo, put) || cat.pseudo; - cat.put = cat.pseudo; - resume(cat); + cat.change = put; + resume({ + put: cat.pseudo, + get: soul + //via: this.at + }); + } + if(!data){ + if(already){ + on(cat.pseudo); + } + return; } - if(!data){ return } var graph = {}; Gun.obj.map(seen, function(t,s){ graph[s] = Gun.node.soul.ify(Gun.obj.copy(data), s); }); cat.gun.Back(-1).on('in', {put: graph}); } + var obj = Gun.obj, obj_has = obj.has; }()); Gun.chain.path = function(field, cb, opt){ @@ -1461,23 +1605,34 @@ ;(function(){ Gun.chain.chain.opt.on = function(cb, opt){ if(!cb){ return this } - opt = opt || {}; + var tmp; + opt = (true === opt)? {change: 1} : opt || {}; opt.gun = opt.gun || this; 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){ - opt.gun.on('out', obj_to(opt.gun._)); + opt.gun._.on('out', obj_to(opt.gun._)); } 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? - 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){ opt.ok.call(opt.as, cat, ev); } 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){ (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; } @@ -1510,7 +1665,32 @@ ev.off(); 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(){ @@ -1542,75 +1722,43 @@ ;(function(){ 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){ var root, noop = function(){}; if(typeof window !== 'undefined'){ root = window } var store = root.localStorage || {setItem: noop, removeItem: noop, getItem: noop}; - Gun.chain.local = function(opt){ - var back = this, gun, chain; - 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; + function put(at){ var err, id, opt; + (opt = at.opt || {}).prefix = opt.prefix || 'gun/'; Gun.graph.is(at.put, function(node, soul){ try{store.setItem(opt.prefix + soul, Gun.text.ify(node)); }catch(e){ err = e } }); 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){ - var lex = at.get, soul, data; + function get(at){ + var gun = at.gun, lex = at.get, soul, data, opt; + (opt = at.opt || {}).prefix = opt.prefix || 'gun/'; if(!lex || !(soul = lex[Gun._.soul])){ return } data = Gun.obj.ify(store.getItem(opt.prefix + soul) || null); 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)}); } - - var create = Gun.create; - function local(o){ - return new create(o).local(); - } - Gun.create = local; + Gun.on('put', put); + Gun.on('get', get); })(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; function get(err, data, at){ 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); }); }()); + return; Gun.on('put', function(at){ var opt = at.opt, graph = at.graph, gun = at.gun; diff --git a/test/common.js b/test/common.js index 2271edf1..f2a8f94c 100644 --- a/test/common.js +++ b/test/common.js @@ -840,6 +840,44 @@ describe('Gun', function(){ //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(){ @@ -998,7 +1036,6 @@ describe('Gun', function(){ expect(gun.__.graph['asdf']).to.not.be.ok(); var ctx = Gun.HAM.graph(gun, prime); - console.log("????", ctx); expect(ctx).to.not.be.ok(); });return; @@ -1614,18 +1651,14 @@ describe('Gun', function(){ } it('get node put node merge', function(done){ - console.debug.i = 1;console.log('-------------------------'); gun.get('hello/key', function(err, node){ if(done.soul){ return } - console.log(4, "get", err, node); expect(err).to.not.be.ok(); expect(node.hello).to.be('key'); done.soul = Gun.node.soul(node); }).put({hi: 'you'}, function(err, ok){ - console.debug(12, "pat", err, ok); expect(err).to.not.be.ok(); var keynode = gun.Back(-1)._.graph[done.soul], soul; - console.log("OH NO", done.soul, keynode) expect(keynode.hi).to.not.be.ok(); var c = soulnode(gun, keynode), soul = c[0]; expect(c.length).to.be(1); @@ -1633,14 +1666,13 @@ describe('Gun', function(){ expect(node.hello).to.be('key'); expect(node.hi).to.be('you'); }).on(function(node){ - console.log("******************", node); if(done.c){ return } //expect(done.soul).to.be(Gun.is.node.soul(node)); // TODO: DISCUSSION! This has changed? expect(node.hi).to.be('you'); expect(node.hello).to.be('key'); done(); done.c = 1; }); - });return; + }); it('get null put node never', function(done){ // TODO: GET returns nothing, and then doing a PUT? gun.get(null, function(err, ok){ @@ -1663,6 +1695,7 @@ describe('Gun', function(){ done.err = err; expect(err).to.not.be.ok(); expect(data).to.not.be.ok(); + console.debug(10, '******', err, data); }).put({testing: 'stuff'}, function(err, ok){ done.flag = true; }); @@ -1680,14 +1713,14 @@ describe('Gun', function(){ expect(err).to.not.be.ok(); expect(node.hello).to.be('key'); 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){ if(done.c){ return } 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]; 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.hi).to.be('overwritten'); 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 }; }); }); - + it('get key path put', function(done){ var gun = Gun().put({foo:'lol', extra: 'yes'}).key('key/path/put'); var data = gun.get('key/path/put'); data.path('foo').put('epic'); data.val(function(val, field){ 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(); }); }); it('put node path', function(done){ + var gun = Gun(); 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. expect(err).to.not.be.ok(); @@ -1741,6 +1775,7 @@ describe('Gun', function(){ done(); done.end = true; }); }); + it('get node path', function(done){ 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. @@ -1767,14 +1802,14 @@ describe('Gun', function(){ expect(err).to.not.be.ok(); if(done.soul){ return } 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){ if(done.c){ return } 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]; 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.hi).to.be('again'); 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){ - 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 } expect(err).to.not.be.ok(); expect(node.hi).to.be('again'); 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){ if(done.c){ return } 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]; 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.yay).to.not.be.ok(); - expect(Gun.is.rel(root.hi)).to.be.ok(); - expect(Gun.is.rel(root.hi)).to.not.be(soul); - var node = gun.__.graph[Gun.is.rel(root.hi)]; + expect(Gun.val.rel.is(root.hi)).to.be.ok(); + expect(Gun.val.rel.is(root.hi)).to.not.be(soul); + var node = gun.Back(-1)._.graph[Gun.val.rel.is(root.hi)]; expect(node.yay).to.be('value'); - if(done.sub){ expect(done.sub).to.be(Gun.is.rel(root.hi)) } - else { done.sub = Gun.is.rel(root.hi) } + if(done.sub){ expect(done.sub).to.be(Gun.val.rel.is(root.hi)) } + else { done.sub = Gun.val.rel.is(root.hi) } done.w = 1; if(done.r){ done(); done.c = 1 }; }).on(function(node, field){ + console.log("******************", field, node);return; if(done.c){ return } expect(field).to.be('hi'); expect(node.yay).to.be('value'); - if(done.sub){ expect(done.sub).to.be(Gun.is.node.soul(node)) } - else { done.sub = Gun.is.node.soul(node) } + if(done.sub){ expect(done.sub).to.be(Gun.node.soul(node)) } + else { done.sub = Gun.node.soul(node) } done.r = 1; if(done.w){ done(); done.c = 1 }; }); - }); + });return; it('get path wire', function(done){ var gun = Gun();