From e9e8804de8249f58340e3810721cd5c2a0f78fcd Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Sat, 11 Jul 2015 20:18:01 -0700 Subject: [PATCH] fixed a bunch of issues around graph mapping --- gun.js | 118 +++++++++++++++++++++----------------------- package.json | 2 +- test/common.js | 131 +++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 183 insertions(+), 68 deletions(-) diff --git a/gun.js b/gun.js index be75f1d0..349b27fa 100644 --- a/gun.js +++ b/gun.js @@ -367,7 +367,7 @@ function load(key){ if(Gun.fns.is(ctx.hook = gun.__.opt.hooks.get)){ ctx.hook(key, function(err, data){ // will be called multiple times. - console.log("chain.get ", key, "from hook", err, data); + console.log("chain.get ", key, "from hook", err, data, '\n'); if(err){ return cb.call(gun, err, null) } if(!data){ if(ctx.data){ return } @@ -418,7 +418,7 @@ index({soul: opt.soul}); } else { // will be injected via a put (gun.__.flag.start[key] = gun._.at('soul')).once(function($){ - console.log("chain.key"); + console.log("chain.key", key, '\n'); (gun.__.key.s[key] = gun.__.key.s[key] || {})[$.soul] = gun.__.graph[$.soul]; delete gun.__.flag.start[key]; }, -1); @@ -477,44 +477,39 @@ Path ultimately should call .val each time, individually, for what it finds. Things that wait and merge many things together should be an abstraction ontop of path. */ - Chain.path = function(path, cb){ + Chain.path = function(path, cb, opt){ var gun = this.chain(); cb = cb || function(){}; + opt = opt || {}; + if((path !== null && !path) || !Gun.text.is(path = path.join? path.join('.') : path + '')){ return } if(!gun.back._.at){ return cb.call(gun, {err: Gun.log("No context!")}), gun } - // TODO: Hmmm once also? figure it out later. - gun.back._.at('soul').event(function($){ - var ctx = {path: (Gun.text.ify(path) || '').split('.')}; - (function trace($){ // TODO: Check for field as well and merge? - if(!ctx.path.length){ return } - var node = gun.__.graph[$.soul], field = Gun.text.ify(ctx.path.shift()), soul, val; - if(ctx.path.length){ - if(soul = Gun.is.soul(val = node[field])){ - gun.get(val, function(err, data){ - data = (data || {})[soul]; - if(err || !data || Gun.obj.empty(data, Gun._.meta)){ return cb.call(gun, err) } - trace({soul: soul}); - }); - } else { - cb.call(gun, null); - } - } else - if(!Gun.obj.has(node, field)){ // TODO: THIS MAY NOT BE CORRECT BEHAVIOR!!!! - cb.call(gun, null, null, field); - gun._.at('soul').emit({soul: $.soul, field: field, PATH: 'SOUL', WAS: 'ON'}); // if .put is after, makes sense. If anything else, makes sense to wait. - } else - if(soul = Gun.is.soul(val = node[field])){ - gun.get(val, function(err, data){ - data = (data || {})[soul]; - cb.call(gun, err, data, field); // TODO: Should we attach field here, does map? - }); - ctx.node = gun.__.graph[ctx.soul] = gun.__.graph[ctx.soul] || Gun.union.pseudo(soul); - gun._.at('soul').emit({soul: soul, field: null, from: $.soul, at: field, PATH: 'SOUL'}); - } else { - cb.call(gun, null, val, field); - gun._.at('soul').emit({soul: $.soul, field: field, PATH: 'SOUL'}); + + gun.back.on(function($, node){ + if(!(node = node || gun.__.graph[$.soul])){ return } + var chain = this || gun, src = opt.src || gun; + var ctx = {path: path.split('.')}, field = Gun.text.ify(ctx.path.shift()); + var val = node[field], soul = Gun.is.soul(val); + console.log("chain.path", field, node, '\n'); + if(!field && !ctx.path.length){ + cb.call(chain, null, node, field); + return opt.step? src._.at('soul').emit({soul: $.soul, field: null, from: opt.step.soul, at: opt.step.field, gun: chain, PATH: 'SOUL'}) + : src._.at('soul').emit({soul: $.soul, field: null, gun: chain, PATH: 'SOUL'}); + } + if(!Gun.obj.has(node, field)){ + if(opt.end || (!ctx.path.length && gun.__.meta($.soul).end)){ // TODO: Make it so you can adjust how many terminations! + cb.call(chain, null, null, field); + src._.at('soul').emit({soul: $.soul, field: field, gun: chain, PATH: 'SOUL'}); } - }($)); - }); + return; + } + if(soul){ + return gun.get(val, function(err, data){ + if(err){ return cb.call(chain, err) } + }).path(ctx.path, cb, {src: src, step: {soul: $.soul, field: field}}); + } + cb.call(chain, null, val, field); + return src._.at('soul').emit({soul: $.soul, field: field, gun: chain, PATH: 'SOUL'}); + }, {raw: true, once: true}); return gun; } @@ -536,11 +531,11 @@ if($.field){ if(ctx[$.soul + $.field]){ return } ctx[$.soul + $.field] = true; // TODO: unregister instead? - return cb.call(gun, node[$.field], $.field || $.at); + return cb.call($.gun || gun, node[$.field], $.field || $.at); } if(ctx[$.soul] || ($.key && ctx[$.key]) || !gun.__.meta($.soul).end){ return } // TODO: Add opt to change number of terminations. ctx[$.soul] = ctx[$.key] = true; // TODO: unregister instead? - cb.call(gun, Gun.obj.copy(node), $.field || $.at); + cb.call($.gun || gun, Gun.obj.copy(node), $.field || $.at); }, {raw: true}); return gun; @@ -551,7 +546,6 @@ var gun = this, ctx = {}; opt = Gun.obj.is(opt)? opt : {change: opt}; cb = cb || function(){}; - gun._.at('soul').event(function($){ // TODO: once per soul on graph. (?) if(ctx[$.soul]){ if(opt.raw){ @@ -559,13 +553,15 @@ } } else { (ctx[$.soul] = function(delta, $$){ - var $$ = $$ || $, node = gun.__.graph[$$.soul]; - if(opt.raw){ return cb.call(gun, $$, delta, this) } + $$ = $$ || $; var node = gun.__.graph[$$.soul]; + if(delta && $.soul != Gun.is.soul.on(delta)){ return } + if(opt.raw){ return cb.call($$.gun || gun, $$, delta, this) } if(!opt.end && Gun.obj.empty(delta, Gun._.meta)){ return } if($$.key){ node = Gun.union.pseudo($.key, gun.__.key.s[$.key]) || node } - cb.call(gun, Gun.obj.copy(opt.change? delta || node : node), $$.field || $$.at); + if(opt.change){ node = delta || node } + cb.call($$.gun || gun, Gun.obj.copy($$.field? node[$$.field] : node), $$.field || $$.at); })(gun.__.graph[$.soul], $); - gun.__.on($.soul).event(ctx[$.soul]); + if(!opt.once){ gun.__.on($.soul).event(ctx[$.soul]) } } }); @@ -595,8 +591,9 @@ if(gun.back.not){ gun.back.not(call) } gun.back._.at('soul').event(function($){ // TODO: maybe once per soul? + var chain = $.gun || gun; var ctx = {}, obj = val, $ = Gun.obj.copy($); - console.log("chain.put", val); + console.log("chain.put", val, '\n'); if(Gun.is.value(obj)){ if($.from && $.at){ $.soul = $.from; @@ -636,7 +633,7 @@ env.graph[at.node._[Gun._.soul] = at.soul] = at.node; cb(at, at.soul); }; - $.empty? path() : gun.back.path(at.path.join('.'), path); // TODO: clean this up. + $.empty? path() : gun.back.path(at.path, path, {once: true, end: true}); // TODO: clean this up. } } if(!at.node._[Gun._.HAM]){ @@ -645,7 +642,7 @@ if(!at.field){ return } at.node._[Gun._.HAM][at.field] = drift; })(function(err, ify){ - console.log("chain.put PUT <----", ify.graph); + console.log("chain.put PUT <----", ify.graph, '\n'); if(err || ify.err){ return cb.call(gun, err || ify.err) } if(err = Gun.union(gun, ify.graph).err){ return cb.call(gun, err) } if($.from = Gun.is.soul(ify.root[$.field])){ $.soul = $.from; $.field = null } @@ -670,26 +667,23 @@ opt = (Gun.obj.is(opt)? opt : (opt? {node: true} : {})); cb = cb || function(){}; - gun.back.on(function(node){ // oo what if this gets TODO: BUG! retriggered? + gun.back.on(function(node){ var soul = Gun.is.soul.on(node); + console.log("chain.map", node, '\n'); Gun.obj.map(node, function(val, field){ // maybe filter against known fields. if(Gun._.meta == field){ return } var s = Gun.is.soul(val); if(s){ - gun.get(val, function(err, data){ - data = (data || {})[s]; // TODO: should map have support for `.not`? error? - if(err || !data || Gun.obj.empty(data, Gun._.meta)){ return } - cb.call(this, Gun.obj.copy(data), field); - }); - gun.__.graph[s] = gun.__.graph[s] || Gun.union.pseudo(s); - gun._.at('soul').emit({soul: s, field: null, from: soul, at: field, MAP: 'SOUL'}); + gun.get(val).on(function(d, f){ + cb.call(this, d, f || field); + gun._.at('soul').emit({soul: s, field: null, from: soul, at: field, MAP: 'SOUL', gun: this}) + }); //, {once: true}); } else { if(opt.node){ return } // {node: true} maps over only sub nodes. - console.log("trigger next thing", field, val); - cb.call(gun, val, field); + cb.call(this, val, field); gun._.at('soul').emit({soul: soul, field: field, MAP: 'SOUL'}); } - }); + }, this || gun); }, true); return gun; @@ -700,11 +694,11 @@ opt = opt || {}; if(!gun.back){ gun = gun.put({}) } - gun = gun.not(function(next, key){ return key? this.put({}).key(key) : this.put({}) }); + gun = gun.not(function(key){ return key? this.put({}).key(key) : this.put({}) }); if(!val && !Gun.is.value(val)){ return gun } - var obj = {}; - obj['I' + drift + 'R' + Gun.text.random(5)] = val; - return gun.put(obj, cb); + var obj = {}, index = 'I' + drift + 'R' + Gun.text.random(5); + obj[index] = val; + return Gun.is.value(val)? gun.put(obj, cb) : gun.put(obj, cb).path(index); } Chain.not = function(cb){ var gun = this, ctx = {}; @@ -716,7 +710,7 @@ var kick = function(next){ if(++c){ return Gun.log("Warning! Multiple `not` resumes!"); } next._.at('soul').once(function($){ $.N0T = 'KICK SOUL'; gun._.at('soul').emit($) }); - }, chain = gun.chain(), next = cb.call(chain, kick, key), c = -1; + }, chain = gun.chain(), next = cb.call(chain, key, kick), c = -1; if(Gun.is(next)){ kick(next) } chain._.at('soul').emit({soul: Gun.roulette.call(chain), empty: true, key: key, N0T: 'SOUL', WAS: 'ON'}); // WAS ON }); diff --git a/package.json b/package.json index 63eb2169..87d809df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gun", - "version": "0.2.0-alpha-2", + "version": "0.2.0-alpha-3", "description": "Graph engine", "main": "index.js", "scripts": { diff --git a/test/common.js b/test/common.js index 284e5acf..dbcc5f10 100644 --- a/test/common.js +++ b/test/common.js @@ -7,6 +7,7 @@ Gun.log.squelch = true; describe('Gun', function(){ var t = {}; + describe('Utility', function(){ it('verbose console.log debugging', function(done) { console.log("TURN THIS BACK ON the DEBUGGING TEST"); done(); return; @@ -829,7 +830,7 @@ describe('Gun', function(){ var gun = Gun(); it('put', function(done){ - gun.put("hello", function(err){ + gun.put("hello", function(err, ok){ expect(err).to.be.ok(); done(); }); @@ -953,12 +954,13 @@ describe('Gun', function(){ }, 500); }); - /* + /* // not sure what this is suppose to do. Review later it('get key no data put', function(done){ gun.get('this/key/definitely/does/not/exist', function(err, data){ expect(err).to.not.be.ok(); expect(data).to.not.be.ok(); }).put({testing: 'stuff'}, function(err, ok){ + console.log("what?", err, ok); expect(err).to.not.be.ok(); var node = gun.__.graph[done.soul]; expect(node.hello).to.be('key'); @@ -1082,7 +1084,7 @@ describe('Gun', function(){ }); }); - /* + /* // Future feature! it('put gun node', function(done){ var mark = gun.put({age: 23, name: "Mark Nadal"}); var amber = gun.put({age: 23, name: "Amber Nadal"}); @@ -1548,7 +1550,7 @@ describe('Gun', function(){ }); it('double not', function(done){ // from the thought tutorial - var gun = Gun().get('thoughts').not(function(n, key){ + var gun = Gun().get('thoughts').not(function(key){ return this.put({}).key(key); }); @@ -1584,7 +1586,7 @@ describe('Gun', function(){ }); gun.set(1).set(2).set(3).set(4); // if you set an object you'd have to do a `.back` - gun.map().val(function(val){ + gun.map().val(function(val, field){ i += 1; expect(val).to.be(i); if(i % 4 === 0){ @@ -1592,6 +1594,7 @@ describe('Gun', function(){ done.i = 0; Gun.obj.map(gun.__.graph, function(){ done.i++ }); expect(done.i).to.be(1); // make sure there isn't double. + Gun.log.verbose = false; done() },10); } @@ -1715,6 +1718,124 @@ describe('Gun', function(){ },10); },10); }); + + it("get map val -> map val", function(done){ // Terje's bug + var gun = Gun(); // we can test GUN locally. + var passengers = gun.get('passengers'); // this is now a list of passengers that we will map over. + var ctx = {n: 0, d: 0, l: 0}; + passengers.map().val(function(passenger, id){ + this.map().val(function(change, field){ + //console.log("Passenger", passenger.name, "had", field, "change to:", change, '\n\n'); + if('name' == field){ expect(change).to.be(passenger.name); ctx.n++ } + if('direction' == field){ expect(change).to.be(passenger.direction); ctx.d++ } + if('location' == field){ + delete change._; ctx.l++; + if('Bob' == passenger.name){ + expect(change).to.eql({'lat': '37.6159', 'lng': '-128.5'}); + } else { + expect(change).to.eql({'lat': 'f37.6159', 'lng': 'f-128.5'}); + } + } + if(ctx.n == 2 && ctx.d == 2 && ctx.l == 2){ done() } + }); + }); + var bob = passengers.set({ + name: "Bob", + location: {'lat': '37.6159', 'lng': '-128.5'}, + direction: '128.2' + }); + var fred = passengers.set({ + name: "Fred", + location: {'lat': 'f37.6159', 'lng': 'f-128.5'}, + direction: 'f128.2' + }); + }); + + it("get map map val", function(done){ // Terje's bug + var gun = Gun(); // we can test GUN locally. + var passengers = gun.get('passengers/map'); // this is now a list of passengers that we will map over. + var ctx = {n: 0, d: 0, l: 0}; + passengers.map().map().val(function(val, field){ + if('name' == field){ expect(val).to.be(!ctx.n? 'Bob' : 'Fred'); ctx.n++ } + if('direction' == field){ expect(val).to.be(!ctx.d? '128.2' : 'f128.2'); ctx.d++ } + if('location' == field){ + delete val._; + if(!ctx.l){ + expect(val).to.eql({'lat': '37.6159', 'lng': '-128.5'}); + } else { + expect(val).to.eql({'lat': 'f37.6159', 'lng': 'f-128.5'}); + } + ctx.l++; + } + if(ctx.n == 2 && ctx.d == 2 && ctx.l == 2){ done() } + }); + var bob = passengers.set({ + name: "Bob", + location: {'lat': '37.6159', 'lng': '-128.5'}, + direction: '128.2' + }); + setTimeout(function(){ + var fred = passengers.set({ + name: "Fred", + location: {'lat': 'f37.6159', 'lng': 'f-128.5'}, + direction: 'f128.2' + }); + },100); + }); + + it("get map path val", function(done){ // Terje's bug + var gun = Gun(); + var ctx = {l: -1, d: 0}; + var passengers = gun.get('passengers/path'); + passengers.map().path('location.lng').val(function(val, field){ + expect(field).to.be('lng'); + if(ctx.l){ + expect(val).to.be('-128.5'); + } else { + expect(val).to.eql('f-128.5'); + } + ctx.l++; + if(ctx.l){ done() } + }); + var bob = passengers.set({ + name: "Bob", + location: {'lat': '37.6159', 'lng': '-128.5'}, + direction: '128.2' + }); + setTimeout(function(){ + var fred = passengers.set({ + name: "Fred", + location: {'lat': 'f37.6159', 'lng': 'f-128.5'}, + direction: 'f128.2' + }); + },100); + }); + + it("put path deep val -> path val", function(done){ // Terje's bug + var gun = Gun(); + gun.put({you: {have: {got: {to: {be: {kidding: "me!"}}}}}}).path('you.have.got.to.be').val(function(val, field){ + expect(val.kidding).to.be('me!'); + this.path('kidding').val(function(val){ + expect(val).to.be('me!'); + done(); + }); + }); + }); + + it("get set path put, map path val -> path val", function(done){ // Terje's bug + var gun = Gun(); + var ctx = {l: -1, d: 0}; + var passengers = gun.get('passengers/set/path'); + passengers.set({name: 'Bob'}).path('direction').put({lol: {just: 'kidding', dude: '!'}}, function(err, ok){}); + passengers.map().path('direction.lol').val(function(val){ + this.path('just').val(function(val){ + expect(val).to.be('kidding'); + }).back.path('dude').val(function(val){ + expect(val).to.be('!'); + done(); + }); + }) + }); }); describe('Streams', function(){