kick-blanking, context variables, and bidirectional key/set set/key load/set working!

This commit is contained in:
Mark Nadal 2015-01-22 04:08:31 -07:00
parent 1a55cf4e35
commit 1ded3ca5eb
2 changed files with 127 additions and 125 deletions

164
gun.js
View File

@ -216,7 +216,7 @@
gun.__.opt.peers = opt.peers || gun.__.opt.peers || {};
gun.__.opt.uuid = opt.uuid || gun.__.opt.uuid || {};
gun.__.opt.hooks = gun.__.opt.hooks || {};
gun.__.hook = Gun.shot('next','end');
gun.__.hook = Gun.shot('then','end');
Gun.obj.map(opt.hooks, function(h, f){
if(!Gun.fns.is(h)){ return }
gun.__.opt.hooks[f] = h;
@ -230,9 +230,7 @@
gun.back = from;
gun.__ = from.__;
gun._ = {};
Gun.obj.map(from._, function(val, field){
gun._[field] = val;
});
//Gun.obj.map(from._, function(val, field){ gun._[field] = val });
return gun;
}
Chain.load = function(key, cb, opt){
@ -241,37 +239,36 @@
gun.shot.done(function(){
opt = opt || {};
cb = cb || function(){};
cb.soul = (key||{})[Gun._.soul];
if(cb.soul){
cb.node = gun.__.graph[cb.soul];
} else {
//gun._.key = key;
cb.node = gun.__.keys[key];
cb.soul = (key||{})[Gun._.soul]; // is this a key or a soul?
if(cb.soul){ // if a soul...
cb.node = gun.__.graph[cb.soul]; // then attempt to grab it directly from cache in the graph
} else { // if not...
cb.node = gun.__.keys[key]; // attempt to grab it directly from our key cache
(gun._.keys = gun._.keys || {})[key] = cb.node? 1 : 0; // set a key marker on it
}
if(!opt.force && cb.node){ // set this to the current node, too!
Gun.log.call(gun, "load via gun"); // remember to do all the same stack stuff here also!
gun._.node = cb.node;
if(Gun.fns.is(cb)){ cb.call(gun, null, Gun.obj.copy(gun._.node)) } // frozen copy
gun.shot('then').fire(gun._.node); // freezing for internal use is NOT okay.
return; // TODO: BUG: This needs to react the same as below! I think this is all done/resolved/clear now.
if(!opt.force && cb.node){ // if it was in cache, then...
Gun.log.call(gun, "load via gun");
gun._.node = cb.node; // assign it to this context
cb.call(gun, null, Gun.obj.copy(gun._.node)); // frozen copy
gun.shot('then').fire();
return;
}
cb.fn = function(){}
// missing: hear shots! I now hook this up in other places, but we could get async/latency issues?
// We need to subscribe early? Or the transport layer handle this for us?
if(Gun.fns.is(gun.__.opt.hooks.load)){
gun.__.opt.hooks.load(key, function(err, data){
//console.log('loaded', err, data, gun);
gun._.loaded = (gun._.loaded || 0) + 1; // TODO: loading should be idempotent even if we got an err or no data
if(err){ return cb(err), (gun._.err||cb.fn).call(gun, err) }
if(!data){ return gun.shot('then').fire() }
if(!data){ return cb(null), (gun._.blank||cb.fn).call(gun) }
var context = gun.union(data); // safely transform the data
if(context.err){ return cb(context.err), (gun._.err||cb.fn).call(gun, context.err) }
gun._.node = gun.__.graph[data._[Gun._.soul]]; // don't wait for the union to be done because we want the immediate state not the intended state.
if(!cb.soul){ gun.__.keys[key] = gun._.node } // TODO: BUG: what if the key has changed since we were gone? What do we resolve to? Not sure yet.
if(Gun.fns.is(cb)){ cb.call(gun, null, Gun.obj.copy(gun._.node)) } // frozen copy
gun.shot('then').fire(gun._.node); // freezing for internal use is NOT okay.
if(!data){ return gun.shot('then').fire() } // if no data, emit without any contexxt change
var context = gun.union(data); // safely transform the data into the current context
if(context.err){ return cb(context.err), (gun._.err||cb.fn).call(gun, context.err) } // but validate it in case of errors
gun._.node = gun.__.graph[data._[Gun._.soul]]; // immediately use the state in cache, no waiting for union to be done.
if(!cb.soul){ // and if we had loaded with a key rather than a soul
gun._.keys[key] = 1; // then set a marker that this key matches
gun.__.keys[key] = gun._.node; // and cache a pointer to the node
}
cb.call(gun, null, Gun.obj.copy(gun._.node)); // frozen copy
gun.shot('then').fire();
}, opt);
} else {
Gun.log.call(gun, "Warning! You have no persistence layer to load from!");
@ -282,17 +279,22 @@
}
Chain.key = function(key, cb){
var gun = this;
cb = cb || function(){};
gun.shot.then(function(){
if(Gun.obj.is(key)){ // if key is an object then we get the soul directly from it.
Gun.obj.map(key, function(soul, field){ return key = field, cb.soul = soul });
cb.node = gun.__.keys[key] = gun.__.graph[cb.soul]; // if it is cached, then it is important to reference it.
} else { // else let's get the soul directly from the node, plus link the key.
cb.node = gun.__.keys[key] = gun._.node;
cb = cb || function(){};
if(Gun.obj.is(key)){ // if key is an object then we get the soul directly from it
Gun.obj.map(key, function(soul, field){ return cb.key = field, cb.soul = soul });
} else { // or else
cb.key = key; // the key is the key
}
if(gun._.node){ // if it is in cache
cb.soul = gun._.node._[Gun._.soul];
(gun._.keys = gun._.keys || {})[cb.key] = 1; // clear the marker in this context
(gun.__.keys = gun.__.keys || {})[cb.key] = gun._.node; // and create the pointer
} else { // if it is not in cache
(gun._.keys = gun._.keys || {})[cb.key] = 0; // then set a marker on this context
}
if(Gun.fns.is(gun.__.opt.hooks.key)){
gun.__.opt.hooks.key(key, cb.soul || (cb.node||{_:{}})._[Gun._.soul], function(err, data){
gun.__.keys[key] = cb.node || gun._.node; // once more for good luck.
gun.__.opt.hooks.key(cb.key, cb.soul, function(err, data){
if(err){ return cb(err) }
return cb(null);
});
@ -300,11 +302,6 @@
Gun.log.call(gun, "Warning! You have no key hook!");
}
});
if(!gun.back){
//console.log("what what?");
(gun._.key = gun._.key || {})[key] = true;
//cb({err: "There exists nothing for the key to reference."});
}
return gun;
}
/*
@ -325,56 +322,56 @@
Chain.path = function(path){ // The focal point follows the path
var gun = this.chain();
path = (path || '').split('.');
gun.back.shot.then(function trace(node){ // should handle blank and err! Err already handled?
if(!node){ return gun.shot('then').fire() } // TODO: BUG? ERROR? MAYBE? MARK?
gun.field = null;
gun._.node = node;
gun.back.shot.then(function trace(){ // should handle blank and err! Err already handled?
if(!gun._.node && !gun.back._.node){ return gun.shot('then').fire() } // TODO: BUG? ERROR? MAYBE? MARK?
gun._.field = null;
gun._.node = gun._.node || gun.back._.node;
if(!path.length){ // if the path resolves to another node, we finish here.
return gun.shot('then').fire(node); // this is not frozen yet, but it is still used for internals so keep it unfrozen.
return gun.shot('then').fire(); // this is not frozen yet, but it is still used for internals so keep it unfrozen.
}
var field = Gun.text.ify(path.shift())
, val = node[field];
gun.field = field;
//console.log('path');
, val = gun._.node[field];
gun._.field = field;
if(Gun.is.soul(val)){ // we might end on a link, so we must resolve
return gun.load(val).shot.then(trace);
} else
if(path.length){ // we cannot go any further, despite the fact there is more path, which means the thing we wanted does not exist.
gun.shot('then').fire();
} else { // we are done, and this should be the value we wanted.
gun.shot('then').fire(node, field); // js copies primitive values, thus we must pass by reference.
gun.shot('then').fire();
}
});
return gun;
}
Chain.get = function(cb){
var gun = this;
gun.shot.then(function(node){
gun.shot.then(function(){
cb = cb || function(){};
cb.call(gun, gun.field? node[gun.field] : Gun.obj.copy(node)); // frozen copy
// TODO! BUG! Maybe? Should a field that is null trigger a blank instead?
if(!gun._.node){ return }
cb.call(gun, gun._.field? gun._.node[gun._.field] : Gun.obj.copy(gun._.node), gun._.field); // frozen copy
// TODO: BUG! Maybe? Interesting, if delta is an object, I might have to .load!
});
return gun;
}
Chain.on = function(cb){
var gun = this;
gun.get(function(node){
var get = this;
gun.shot.then(function(){
cb = cb || function(){};
cb.call(get, Gun.obj.copy(node)); // frozen copy
get.__.on(get._.node._[Gun._.soul]).event(function(delta){
if(!gun._.node){ return }
cb.call(gun, gun._.field? gun._.node[gun._.field] : Gun.obj.copy(gun._.node), gun._.field); // frozen copy
gun.__.on(gun._.node._[Gun._.soul]).event(function(delta){
if(!delta){ return }
if(!get.field){
cb.call(get, Gun.obj.copy(get._.node)); // frozen copy
if(!gun._.field){
cb.call(gun, Gun.obj.copy(gun._.node)); // frozen copy
return;
}
if(Gun.obj.has(delta, get.field)){
delta = delta[get.field];
cb.call(get, Gun.obj.is(delta)? Gun.obj.copy(delta) : delta); // frozen copy
if(Gun.obj.has(delta, gun._.field)){
delta = delta[gun._.field];
cb.call(gun, Gun.obj.is(delta)? Gun.obj.copy(delta) : delta, gun._.field); // frozen copy
// TODO: BUG! Maybe? Interesting, if delta is an object, I might have to .load!
}
})
})
});
return gun;
}
/*
@ -392,11 +389,10 @@
opt = opt || {};
var gun = this, set = {};
gun.shot.then(function(){
console.log("meow?", val);
cb = Gun.fns.is(cb)? cb : function(){};
if(gun.field){ // if a field exists, it should always be a string
if(gun._.field){ // if a field exists, it should always be a string
var partial = {}; // in case we are doing a set on a field, not on a node
partial[gun.field] = val; // we create a blank node with the field/value to be set
partial[gun._.field] = val; // we create a blank node with the field/value to be set
val = partial;
} else
if(!Gun.obj.is(val)){
@ -407,13 +403,18 @@
val._ = Gun.ify.soul.call(gun, {}, gun._.node || val); // set their souls to be the same that way they will merge correctly for us during the union!
set = Gun.ify.call(gun, val, set);
cb.root = set.root;
if(set.err || !cb.root){ return cb(set.err || {err: "No root object!"}), gun }
if(set.err || !cb.root){ return cb(set.err || {err: "No root object!"}) }
set = Gun.ify.state(set.nodes, Gun.time.is()); // set time state on nodes?
if(set.err){ return cb(set.err), gun }
if(set.err){ return cb(set.err) }
gun.union(set.nodes); // while this maybe should return a list of the nodes that were changed, we want to send the actual delta
gun._.node = gun.__.graph[cb.root._[Gun._.soul]] || cb.root;
//console.log("Did we set?", cb.root, gun._.key);
// TODO? ^ Maybe BUG! if val is a new node on a field, _.node should now be that! Or will that happen automatically?
if(!gun._.field){
Gun.obj.map(gun._.keys, function(yes, key){
if(yes){ return }
console.log("setting fields", key, yes);
gun.key(key);
});
}
if(Gun.fns.is(gun.__.opt.hooks.set)){
gun.__.opt.hooks.set(set.nodes, function(err, data){ // now iterate through those nodes to a persistence layer and get a callback once all are saved
if(err){ return cb(err) }
@ -424,7 +425,7 @@
}
});
if(!gun.back){
gun.shot('then').fire(set);
gun.shot('then').fire();
}
return gun;
}
@ -438,7 +439,6 @@
}).get(function(val){
if(error){ return cb(error) } // which in case it is, allows us to fail fast.
var list = {}, soul = Gun.is.soul.on(val);
//console.log(val, 'has', soul);
if(!soul){ return cb({err: "No soul!"}) }
list[soul] = val; // other wise, let's then
gun.set(list, cb); // merge with the graph node.
@ -447,9 +447,9 @@
}
Chain.map = function(cb, opt){
var gun = this;
gun.shot.then(function(val){
cb = cb || function(){};
gun.get(function(val){
opt = opt || {};
cb = cb || function(){};
Gun.obj.map(val, function(val, field){ // by default it only maps over nodes
if(Gun._.meta == field){ return }
if(Gun.is.soul(val)){
@ -499,16 +499,18 @@
return context;
}
Chain.blank = function(blank){
var tmp = this.chain();
var tmp = this;
var gun = this.chain();
gun.back.shot.then(function(node){
if(node){ return gun.shot('then').fire(node); }
console.log("WE GOT BLANKNESS!!!");
blank.call(tmp);
tmp.shot.then(function(val){
console.log("tmp after blank", val);
tmp.shot.then(function(){
if(tmp._.node){ // if it does indeed exist
gun._ = tmp._; // switch back to the original context
return gun.shot('then').fire(); // yet fire off the chain
}
blank.call(tmp); // call the blank function with the original context
tmp.shot.then(function(){ // then after the blank logic is done...
gun._ = tmp._; // inherit those changes
gun.shot('then').fire(); // and fire off the chain
});
tmp.shot('then').fire();
});
return gun;
}

View File

@ -318,7 +318,7 @@ describe('Gun', function(){
require('../lib/file');
var gun = Gun({file: 'data.json'});
/*
it('set key get', function(done){
gun.set({hello: "world"}).key('hello/world').get(function(val){
expect(val.hello).to.be('world');
@ -353,60 +353,60 @@ describe('Gun', function(){
done();
});
});
*/
it('key set get', function(done){
gun.key('world/hello').set({world: "hello"}).get(function(val){
expect(val.world).to.be('hello');
done();
});
});
it('load', function(done){
gun.load('world/hello').get(function(val){
expect(val.world).to.be('hello');
done();
});
});
it('load blank kick get', function(done){ // it would be cool with GUN
console.log("test blank kicking");
gun.load("some/empty/thing").blank(function(){ // that if you call blank first
console.log("blank happened");
this.set({now: 'exists'}); // you can set stuff
}).get(function(val){ // and THEN still retrieve it.
console.log("get happened");
expect(val.now).to.be('exists');
done();
});
});
/*
it.skip('var set key path', function(done){ // contexts should be able to be saved to a variable
var foo = gun.set({foo: 'bar'}).key('foo/bar');
foo.path('hello.world.nowhere')// this should only change the context temporarily
setTimeout(function(){
foo.path('foo').get(function(val){ // and then be reused from the same original context
console.log('?', val);
expect(val).to.be('bar'); // this should work
done();
});
}, 100);
});
it.skip('var load path', function(done){ // contexts should be able to be saved to a variable
var foo = gun.load('foo/bar');
foo.path('hello.world.nowhere')// this should only change the context temporarily
setTimeout(function(){
foo.path('foo').get(function(val){ // and then be reused from the same original context
expect(val).to.be('bar'); // this should work
done();
});
}, 100);
});
it('path', function(done){
console.log("fix path!");
return done(); // TODO: FIX! This is broken because of API changes, fix it!
this.timeout(9000);
var gun = require('gun')({
s3: require('./shotgun') // replace this with your own keys!
});
gun.load('d1ed426098eae2bba8c60605e1e4552f66281770', null, {id: true}) // get Rodney Morris
.path('parent.parent.first') // Rodney's parent is Juan Colon, whose parent is Francis Peters
.get(function(val){
console.log("val!", val);
it('load blank kick get when it already exists', function(done){
gun.load("some/empty/thing").blank(function(){
this.set({now: 'THIS SHOULD NOT HAPPEN'});
}).get(function(val){
expect(val.now).to.be('exists');
done();
});
console.log("________________________");
});
*/
it('var set key path', function(done){ // contexts should be able to be saved to a variable
var foo = gun.set({foo: 'bar'}).key('foo/bar');
foo.path('hello.world.nowhere'); // this should become a sub-context, that doesn't alter the original
setTimeout(function(){
foo.path('foo').get(function(val){ // and then the original should be able to be reused later
expect(val).to.be('bar'); // this should work
done();
});
}, 100);
});
it('var load path', function(done){ // contexts should be able to be saved to a variable
var foo = gun.load('foo/bar');
foo.path('hello.world.nowhere'); // this should become a sub-context, that doesn't alter the original
setTimeout(function(){
foo.path('foo').get(function(val){ // and then the original should be able to be reused later
expect(val).to.be('bar'); // this should work
done();
});
}, 100);
});
});
});