Merge pull request #15 from amark/async

Async
This commit is contained in:
Mark Nadal 2015-02-13 20:15:30 -07:00
commit 4475b8582e
11 changed files with 367 additions and 182 deletions

View File

@ -0,0 +1,6 @@
<html>
<body>
<script src="../../gun.js"></script>
<script src="../../lib/list.js"></script>
</body>
</html>

307
gun.js
View File

@ -28,6 +28,9 @@
} }
return false; return false;
} }
Gun.is.value.as = function(v){
return Gun.is.value(v)? v : null;
}
Gun.is.soul = function(v){ Gun.is.soul = function(v){
if(Gun.obj.is(v)){ if(Gun.obj.is(v)){
var id; var id;
@ -198,6 +201,7 @@
gun._ = gun._ || {}; gun._ = gun._ || {};
gun.__ = gun.__ || {}; gun.__ = gun.__ || {};
gun.shot = Gun.shot('then', 'err'); gun.shot = Gun.shot('then', 'err');
gun.shot.next = Gun.next();
if(opt === null){ return gun } if(opt === null){ return gun }
opt = opt || {}; opt = opt || {};
gun.__.opt = gun.__.opt || {}; gun.__.opt = gun.__.opt || {};
@ -210,6 +214,7 @@
if(Gun.list.is(opt.peers)){ opt.peers = Gun.obj.map(opt.peers, function(n,f,m){ m(n,{}) }) } if(Gun.list.is(opt.peers)){ opt.peers = Gun.obj.map(opt.peers, function(n,f,m){ m(n,{}) }) }
gun.__.opt.peers = opt.peers || gun.__.opt.peers || {}; gun.__.opt.peers = opt.peers || gun.__.opt.peers || {};
gun.__.opt.uuid = opt.uuid || gun.__.opt.uuid || {}; gun.__.opt.uuid = opt.uuid || gun.__.opt.uuid || {};
gun.__.opt.cb = gun.__.opt.cb || function(){};
gun.__.opt.hooks = gun.__.opt.hooks || {}; gun.__.opt.hooks = gun.__.opt.hooks || {};
gun.__.hook = Gun.shot('then','end'); gun.__.hook = Gun.shot('then','end');
Gun.obj.map(opt.hooks, function(h, f){ Gun.obj.map(opt.hooks, function(h, f){
@ -230,11 +235,10 @@
} }
Chain.load = function(key, cb, opt){ Chain.load = function(key, cb, opt){
var gun = this.chain(); var gun = this.chain();
gun.shot('done'); gun.shot.next(function(next){
gun.shot.done(function(){
opt = opt || {}; opt = opt || {};
cb = cb || function(){}; cb = cb || function(){};
cb.soul = (key||{})[Gun._.soul]; // is this a key or a soul? cb.soul = Gun.is.soul(key); // is this a key or a soul?
if(cb.soul){ // if 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 cb.node = gun.__.graph[cb.soul]; // then attempt to grab it directly from cache in the graph
} else { // if not... } else { // if not...
@ -244,37 +248,36 @@
if(!opt.force && cb.node){ // if it was in cache, then... if(!opt.force && cb.node){ // if it was in cache, then...
console.log("load via gun"); console.log("load via gun");
gun._.node = cb.node; // assign it to this context gun._.node = cb.node; // assign it to this context
cb.call(gun, null, Gun.obj.copy(gun._.node)); // frozen copy return cb.call(gun, null, Gun.obj.copy(gun._.node)), next(); // 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? // 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? // We need to subscribe early? Or the transport layer handle this for us?
if(Gun.fns.is(gun.__.opt.hooks.load)){ if(Gun.fns.is(gun.__.opt.hooks.load)){
gun.__.opt.hooks.load(key, function(err, data){ gun.__.opt.hooks.load(key, function(err, data){
if(err){ return cb.call(gun, err), (gun._.err||cb.fn).call(gun, err) } if(err){ return cb.call(gun, err) }
if(!data){ return cb.call(gun, err, data), gun.shot('then').fire() } // if no data, emit without any contexxt change if(!data){ return cb.call(gun, null, data), next() }
var context = gun.union(data); // safely transform the data into the current context var context = gun.union(data); // safely transform the data into the current context
if(context.err){ return cb.call(gun, context.err), (gun._.err||cb.fn).call(gun, context.err) } // but validate it in case of errors if(context.err){ return cb.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. gun._.node = gun.__.graph[Gun.is.soul.on(data)]; // immediately use the state in cache.
if(!cb.soul){ // and if we had loaded with a key rather than a soul 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] = 1; // then set a marker that this key matches
gun.__.keys[key] = gun._.node; // and cache a pointer to the node gun.__.keys[key] = gun._.node; // and cache a pointer to the node
} }
cb.call(gun, null, Gun.obj.copy(gun._.node)); // frozen copy return cb.call(gun, null, Gun.obj.copy(gun._.node)), next(); // frozen copy
gun.shot('then').fire();
}, opt); }, opt);
} else { } else {
cb.call(gun, {err: Gun.log("Warning! You have no persistence layer to load from!")}); root.console.log("Warning! You have no persistence layer to load from!");
return cb.call(gun), next();
} }
}); });
gun.shot('done').fire(); // because we are loading, we always fire!
return gun; return gun;
} }
Chain.key = function(key, cb){ Chain.key = function(key, cb){
var gun = this; var gun = this;
gun.shot.then(function(){ if(!gun.back){ // TODO: BUG? Does this maybe introduce bugs other than the test that it fixes?
gun = gun.chain(); // create a new context
}
gun.shot.next(function(next){
cb = cb || function(){}; cb = cb || function(){};
if(Gun.obj.is(key)){ // if key is an object then we get the soul directly from it 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 }); Gun.obj.map(key, function(soul, field){ return cb.key = field, cb.soul = soul });
@ -282,25 +285,26 @@
cb.key = key; // the key is the key cb.key = key; // the key is the key
} }
if(gun._.node){ // if it is in cache if(gun._.node){ // if it is in cache
cb.soul = gun._.node._[Gun._.soul]; cb.soul = Gun.is.soul.on(gun._.node);
(gun._.keys = gun._.keys || {})[cb.key] = 1; // clear the marker in this context (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 (gun.__.keys = gun.__.keys || {})[cb.key] = gun._.node; // and create the pointer
} else { // if it is not in cache } else { // if it is not in cache
(gun._.keys = gun._.keys || {})[cb.key] = 0; // then set a marker on this context (gun._.keys = gun._.keys || {})[cb.key] = 0; // then set a marker on this context
} }
if(Gun.fns.is(gun.__.opt.hooks.key)){ if(Gun.fns.is(gun.__.opt.hooks.key)){
gun.__.opt.hooks.key(cb.key, cb.soul, function(err, data){ gun.__.opt.hooks.key(cb.key, cb.soul, function(err, data){ // call the hook
if(err){ return cb.call(gun, err) } return cb.call(gun, err, data); // and notify how it went.
return cb.call(gun, null);
}); });
} else { } else {
cb.call(gun, {err: Gun.log("Warning! You have no key hook!")}); root.console.log("Warning! You have no key hook!");
cb.call(gun);
} }
next(); // continue regardless
}); });
return gun; return gun;
} }
/* /*
how many different ways can we get something? how many different ways can we get something? ONLY THE FIRST ONE IS SUPPORTED, the others might become plugins.
Find via a singular path Find via a singular path
.path('blah').get(blah); .path('blah').get(blah);
Find via multiple paths with the callback getting called many times Find via multiple paths with the callback getting called many times
@ -314,66 +318,71 @@
Path ultimately should call .get each time, individually, for what it finds. Path ultimately should call .get each time, individually, for what it finds.
Things that wait and merge many things together should be an abstraction ontop of path. Things that wait and merge many things together should be an abstraction ontop of path.
*/ */
Chain.path = function(path){ // The focal point follows the path Chain.path = function(path, cb){ // Follow the path into the field.
var gun = this.chain(); var gun = this.chain(); // create a new context, changing the focal point.
cb = cb || function(){};
path = (path || '').split('.'); path = (path || '').split('.');
gun.back.shot.then(function trace(){ // should handle blank and err! Err already handled? gun.shot.next(cb.done = function(next){ // let the previous promise resolve.
if(!gun._.node && !gun.back._.node){ return gun.shot('then').fire() } // TODO: BUG? ERROR? MAYBE? MARK? if(next){ cb.next = next }
gun._.field = null; if(!cb.next || !cb.back){ return }
gun._.node = gun._.node || gun.back._.node; cb = cb || function(){}; // fail safe our function.
if(!path.length){ // if the path resolves to another node, we finish here. (function trace(){ // create a recursive function, and immediately call it.
return gun.shot('then').fire(); // this is not frozen yet, but it is still used for internals so keep it unfrozen. gun._.field = Gun.text.ify(path.shift()); // where are we at? Figure it out.
} if(gun._.node && path.length && Gun.is.soul(gun._.node[gun._.field])){ // if we need to recurse more
var field = Gun.text.ify(path.shift()) return gun.load(val, function(err){ // and the recursion happens to be on a relation, then load it.
, val = gun._.node[field]; if(err){ return cb.call(gun, err) }
gun._.field = field; trace(gun._ = this._); // follow the context down the chain.
if(Gun.is.soul(val)){ // we might end on a link, so we must resolve
return gun.load(val, function(){
gun._ = this._;
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();
} }
cb.call(gun, null, Gun.obj.copy(gun._.node), gun._.field); // frozen copy
cb.next(); // and be done, fire our gun with the context.
}(gun._.node = gun.back._.node)); // immediately call trace, setting the new context with the previous node.
});
gun.back.shot.next(function(next){
cb.back = true;
cb.done();
next();
}); });
return gun; return gun;
} }
Chain.get = function(cb){ Chain.get = function(cb){
var gun = this; var gun = this; // keep using the existing context.
gun.shot.then(function(){ gun.shot.next(function(next){ // let the previous promise resolve.
cb = cb || function(){}; next(); // continue with the chain.
if(!gun._.node){ return } cb = cb || function(){}; // fail safe our function.
cb.call(gun, gun._.field? gun._.node[gun._.field] : Gun.obj.copy(gun._.node), gun._.field); // frozen copy if(!gun._.node){ return } // if no node, then abandon and let blank handle it.
// TODO: BUG! Maybe? Interesting, if delta is an object, I might have to .load! var field = gun._.field, val = gun._.node[field]; // else attempt to get the value at the field, if we have a field.
if(field && Gun.is.soul(val)){ // if we have a field, then check to see if it is a relation
return gun.load(val, function(err, val){ // and load it.
if(err || !this._.node){ return } // handle error?
cb.call(this, val, field); // already frozen copy
});
}
cb.call(gun, gun._.field? Gun.is.value.as(val) : Gun.obj.copy(gun._.node), gun._.field); // frozen copy
}); });
return gun; return gun;
} }
Chain.on = function(cb){ Chain.on = function(cb){ // get and then subscribe to subsequent changes.
var gun = this; var gun = this; // keep using the existing context.
gun.shot.then(function(){ gun.get(function(val, field){
cb = cb || function(){}; cb = cb || function(){}; // fail safe our function.
if(!gun._.node){ return } cb.call(gun, val, field);
cb.call(gun, gun._.field? gun._.node[gun._.field] : Gun.obj.copy(gun._.node), gun._.field); // frozen copy gun.__.on(Gun.is.soul.on(gun._.node)).event(function(delta){ // then subscribe to subsequent changes.
gun.__.on(gun._.node._[Gun._.soul]).event(function(delta){ if(!delta || !gun._.node){ return }
if(!delta){ return } if(!gun._.field){ // if we were listening to changes on the node as a whole
if(!gun._.field){ return cb.call(gun, Gun.obj.copy(gun._.node)); // frozen copy
cb.call(gun, Gun.obj.copy(gun._.node)); // frozen copy
return;
} }
if(Gun.obj.has(delta, gun._.field)){ if(Gun.obj.has(delta, gun._.field)){ // else changes on an individual property
delta = delta[gun._.field]; delta = delta[gun._.field]; // grab it and
cb.call(gun, Gun.obj.is(delta)? Gun.obj.copy(delta) : delta, gun._.field); // frozen copy cb.call(gun, Gun.obj.is(delta)? Gun.obj.copy(delta) : Gun.is.value.as(delta), gun._.field); // frozen copy
// TODO: BUG! Maybe? Interesting, if delta is an object, I might have to .load! // TODO! BUG: If delta is an object, that would suggest it is a relation which needs to get loaded.
} }
}) });
}); });
return gun; return gun;
} }
/* /*
ACID compliant, unfortunately the vocabulary is vague, as such the following is an explicit definition: ACID compliant? Unfortunately the vocabulary is vague, as such the following is an explicit definition:
A - Atomic, if you set a full node, or nodes of nodes, if any value is in error then nothing will be set. A - Atomic, if you set a full node, or nodes of nodes, if any value is in error then nothing will be set.
If you want sets to be independent of each other, you need to set each piece of the data individually. If you want sets to be independent of each other, you need to set each piece of the data individually.
C - Consistency, if you use any reserved symbols or similar, the operation will be rejected as it could lead to an invalid read and thus an invalid state. C - Consistency, if you use any reserved symbols or similar, the operation will be rejected as it could lead to an invalid read and thus an invalid state.
@ -384,25 +393,41 @@
If this causes any application-level concern, it can compare against the live data by immediately reading it, or accessing the logs if enabled. If this causes any application-level concern, it can compare against the live data by immediately reading it, or accessing the logs if enabled.
*/ */
Chain.set = function(val, cb, opt){ // TODO: need to turn deserializer into a trampolining function so stackoverflow doesn't happen. Chain.set = function(val, cb, opt){ // TODO: need to turn deserializer into a trampolining function so stackoverflow doesn't happen.
var gun = this, set = {}; var gun = this;
gun.shot.then(function(){ if(!gun.back){
gun = gun.chain(); // create a new context
}
gun.shot.next(function(next){
opt = opt || {}; opt = opt || {};
cb = cb || function(){}; cb = cb || function(){};
if(!gun._.node){
if(Gun.is.value(val) || !Gun.obj.is(val)){
return cb.call(gun, {err: Gun.log("No field exists to set the " + (typeof val) + " on.")});
}
} else
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 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; val = partial;
} else
if(!Gun.obj.is(val)){
return cb.call(gun, {err: Gun.log("No field exists to set the " + (typeof val) + " on.")});
} }
cb.states = Gun.time.is();
Gun.ify(val, function(raw, context, sub, soul){
if(val === raw){ return soul(Gun.is.soul.on(gun._.node)) }
if(gun._.node && sub && sub.path){
return gun.path(sub.path, function(err, node, field){
if(err){ cb.err = err + " (while doing a set)" } // let .done handle calling this, it may be slower but is more consistent.
if(this._.node && (field = Gun.is.soul(this._.node[this._.field]))){
return soul(field);
} soul(); // else call it anyways
});
} soul(); // else call it anyways
}).done(function(err, set){
// TODO: should be able to handle val being a relation or a gun context or a gun promise. // TODO: should be able to handle val being a relation or a gun context or a gun promise.
// TODO: BUG: IF we are setting an object, doing a partial merge, and they are reusing a frozen copy, we need to do a DIFF to update the HAM! Or else we'll get "old" HAM. // TODO: BUG: IF we are setting an object, doing a partial merge, and they are reusing a frozen copy, we need to do a DIFF to update the HAM! Or else we'll get "old" HAM.
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; cb.root = set.root;
set.err = set.err || cb.err;
if(set.err || !cb.root){ return cb.call(gun, set.err || {err: Gun.log("No root object!")}) } if(set.err || !cb.root){ return cb.call(gun, set.err || {err: Gun.log("No root object!")}) }
set = Gun.ify.state(set.nodes, Gun.time.is()); // set time state on nodes? set = Gun.ify.state(set.nodes, cb.states); // set time state on nodes?
if(set.err){ return cb.call(gun, set.err) } if(set.err){ return cb.call(gun, 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.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; gun._.node = gun.__.graph[cb.root._[Gun._.soul]] || cb.root;
@ -415,30 +440,14 @@
if(Gun.fns.is(gun.__.opt.hooks.set)){ 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 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.call(gun, err) } if(err){ return cb.call(gun, err) }
return cb.call(gun, null); return cb.call(gun, data);
}); });
} else { } else {
cb.call(gun, {err: Gun.log("Warning! You have no persistence layer to save to!")}) root.console.log("Warning! You have no persistence layer to save to!");
return cb.call(gun);
} }
next();
}); });
if(!gun.back){
gun.shot('then').fire();
}
return gun;
}
Chain.insert = function(obj, cb, opt){
var gun = this;
opt = opt || {};
cb = cb || function(){};
gun = gun.set({}); // insert assumes a graph node. So either create it or merge with the existing one.
var error, item = Gun(null).set(obj, function(err){ // create the new item in its own context.
error = err; // if this happens, it should get called before the .get
}).get(function(val){
if(error){ return cb.call(gun, error) } // which in case it is, allows us to fail fast.
var list = {}, soul = Gun.is.soul.on(val);
if(!soul){ return cb.call(gun, {err: Gun.log("No soul!")}) }
list[soul] = val; // other wise, let's then
gun.set(list, cb); // merge with the graph node.
}); });
return gun; return gun;
} }
@ -466,7 +475,7 @@
// Meaning it is more low level, such that even set uses union internally. // Meaning it is more low level, such that even set uses union internally.
Chain.union = function(prime, cb){ Chain.union = function(prime, cb){
var tmp = {}, gun = this, context = Gun.shot(); var tmp = {}, gun = this, context = Gun.shot();
cb = cb || function(){} cb = cb || function(){};
context.nodes = {}; context.nodes = {};
if(!prime){ if(!prime){
context.err = {err: Gun.log("No data to merge!")}; context.err = {err: Gun.log("No data to merge!")};
@ -496,18 +505,14 @@
return context; return context;
} }
Chain.blank = function(blank){ Chain.blank = function(blank){
var tmp = this; var gun = this;
var gun = this.chain(); blank = blank || function(){};
tmp.shot.then(function(){ gun.shot.next(function(next){
if(tmp._.node){ // if it does indeed exist if(gun._.node){ // if it does indeed exist
gun._ = tmp._; // switch back to the original context return next(); // yet fire off the chain
return gun.shot('then').fire(); // yet fire off the chain
} }
blank.call(tmp); // call the blank function with the original context blank.call(gun); // call blank
tmp.shot.then(function(){ // then after the blank logic is done... next(); // fire off the chain
gun._ = tmp._; // inherit those changes
gun.shot('then').fire(); // and fire off the chain
});
}); });
return gun; return gun;
} }
@ -626,6 +631,20 @@
Util.time = {}; Util.time = {};
Util.time.is = function(t){ return t? t instanceof Date : (+new Date().getTime()) } Util.time.is = function(t){ return t? t instanceof Date : (+new Date().getTime()) }
}(Gun)); }(Gun));
;Gun.next = function(){
var fn = function(cb){
if(!fn.stack || !fn.stack.length){
setImmediate(function next(n){
return (n = (fn.stack||[]).shift() || function(){}), n.back = fn.stack, fn.stack = [], n(function(){
return (fn.stack = (fn.stack||[]).concat(n.back)), next();
});
});
} if(cb){
(fn.stack = fn.stack || []).push(cb);
} return fn;
}, setImmediate = setImmediate || function(cb){return setTimeout(cb,0)}
return fn;
}
;Gun.shot=(function(){ ;Gun.shot=(function(){
// I hate the idea of using setTimeouts in my code to do callbacks (promises and sorts) // I hate the idea of using setTimeouts in my code to do callbacks (promises and sorts)
// as there is no way to guarantee any type of state integrity or the completion of callback. // as there is no way to guarantee any type of state integrity or the completion of callback.
@ -765,14 +784,14 @@
} }
}({})); }({}));
;(function(Serializer){ ;(function(Serializer){
Gun.ify = function(data, bind){ // TODO: BUG: Modify lists to include HAM state Gun.ify = function(data, cb){ // TODO: BUG: Modify lists to include HAM state
if(Gun.obj.map(bind, function(){ return true })){ return {err: Gun.log("Bind must be an empty object.")} }
var gun = Gun.is(this)? this : {} var gun = Gun.is(this)? this : {}
, context = { , nothing, context = Gun.shot();
nodes: {} context.nodes = {};
,seen: [] context.seen = [];
,_seen: [] context.seen = [];
}, nothing; context('done');
cb = cb || function(){};
function ify(data, context, sub){ function ify(data, context, sub){
sub = sub || {}; sub = sub || {};
sub.path = sub.path || ''; sub.path = sub.path || '';
@ -782,10 +801,9 @@
return data; return data;
} else } else
if(Gun.obj.is(data)){ if(Gun.obj.is(data)){
var value = bind || {}, symbol = {}, seen var value = {}, meta = {}, seen
, err = {err: "Metadata does not support external or circular references at " + sub.path, meta: true}; , err = {err: "Metadata does not support external or circular references at " + sub.path, meta: true};
context.root = context.root || value; context.root = context.root || value;
bind = null;
if(seen = ify.seen(context._seen, data)){ if(seen = ify.seen(context._seen, data)){
//console.log("seen in _", sub._, sub.path, data); //console.log("seen in _", sub._, sub.path, data);
Gun.log(context.err = err); Gun.log(context.err = err);
@ -797,20 +815,26 @@
Gun.log(context.err = err); Gun.log(context.err = err);
return; return;
} }
symbol = Gun.ify.soul.call(gun, symbol, seen); meta = Gun.ify.soul.call(gun, meta, seen);
return symbol; return meta;
} else { } else {
//console.log("seen nowhere", sub._, sub.path, data); //console.log("seen nowhere", sub._, sub.path, data);
if(sub._){ if(sub._){
context.seen.push({data: data, node: value}); context.seen.push({data: data, node: value});
} else { } else {
value._ = Gun.ify.soul.call(gun, {}, data); value._ = {};
cb(data, context, sub, context.many.add(function(soul){
//console.log("What soul did we find?", soul || "random");
meta[Gun._.soul] = value._[Gun._.soul] = soul = Gun.is.soul.on(data) || soul || Gun.roulette();
context.nodes[soul] = value;
this.done();
}));
context.seen.push({data: data, node: value}); context.seen.push({data: data, node: value});
context.nodes[value._[Gun._.soul]] = value;
} }
} }
Gun.obj.map(data, function(val, field){ Gun.obj.map(data, function(val, field){
var subs = {path: sub.path + field + '.', _: sub._ || (field == Gun._.meta)? true : false }; var subs = {path: sub.path? sub.path + '.' + field : field,
_: sub._ || (field == Gun._.meta)? true : false };
val = ify(val, context, subs); val = ify(val, context, subs);
//console.log('>>>>', sub.path + field, 'is', val); //console.log('>>>>', sub.path + field, 'is', val);
if(context.err){ return true } if(context.err){ return true }
@ -819,9 +843,8 @@
value[field] = val; value[field] = val;
}); });
if(sub._){ return value } if(sub._){ return value }
if(!value._ || !value._[Gun._.soul]){ return } if(!value._){ return }
symbol[Gun._.soul] = value._[Gun._.soul]; return meta;
return symbol;
} else } else
if(Gun.list.is(data)){ if(Gun.list.is(data)){
var unique = {}, edges var unique = {}, edges
@ -855,7 +878,11 @@
if(check.data === data){ return check.node } if(check.data === data){ return check.node }
}); });
} }
context.many = Gun.fns.sum(function(err){ context('done').fire(context.err, context) });
context.many.add(function(){
ify(data, context); ify(data, context);
this.done();
})();
return context; return context;
} }
Gun.ify.state = function(nodes, now){ Gun.ify.state = function(nodes, now){
@ -903,7 +930,7 @@
window.tab = tab; // for debugging purposes window.tab = tab; // for debugging purposes
opt = opt || {}; opt = opt || {};
tab.headers = opt.headers || {}; tab.headers = opt.headers || {};
tab.headers['gun-tid'] = tab.headers['gun-tid'] || Gun.text.random(); tab.headers['gun-sid'] = tab.headers['gun-sid'] || Gun.text.random();
tab.prefix = tab.prefix || opt.prefix || 'gun/'; tab.prefix = tab.prefix || opt.prefix || 'gun/';
tab.prekey = tab.prekey || opt.prekey || ''; tab.prekey = tab.prekey || opt.prekey || '';
tab.prenode = tab.prenode || opt.prenode || '_/nodes/'; tab.prenode = tab.prenode || opt.prenode || '_/nodes/';
@ -923,20 +950,20 @@
var node, lkey = key[Gun._.soul]? tab.prefix + tab.prenode + key[Gun._.soul] var node, lkey = key[Gun._.soul]? tab.prefix + tab.prenode + key[Gun._.soul]
: tab.prefix + tab.prekey + key : tab.prefix + tab.prekey + key
if((node = store.get(lkey)) && node[Gun._.soul]){ return local(node, cb) } if((node = store.get(lkey)) && node[Gun._.soul]){ return local(node, cb) }
if(node){ setTimeout(function(){cb(null, node)},0) } if(cb.node = node){ setTimeout(function(){cb(null, node)},0) }
}(key, cb)); }(key, cb));
Gun.obj.map(gun.__.opt.peers, function(peer, url){ Gun.obj.map(gun.__.opt.peers, function(peer, url){
request(url, null, function(err, reply){ request(url, null, function(err, reply){
Gun.log('via', url, key, reply.body); Gun.log('via', url, key, reply.body);
if(err || !reply){ return } // handle reconnect? if(err || !reply || (err = reply.body && reply.body.err)){
if(reply.body && reply.body.err){ cb({err: Gun.log(err || "Error: Load failed through " + url) });
cb(reply.body.err);
} else { } else {
if(!key[Gun._.soul] && Gun.is.soul(reply.body)){ if(!key[Gun._.soul] && Gun.is.soul(reply.body)){
var meta = {}; var meta = {};
meta[Gun._.soul] = Gun.is.soul(reply.body); meta[Gun._.soul] = Gun.is.soul(reply.body);
store.set(tab.prefix + tab.prekey + key, meta); store.set(tab.prefix + tab.prekey + key, meta);
} }
if(cb.node){ return gun.__.on(Gun.is.soul(reply.body)).emit(reply.body) }
cb(null, reply.body); cb(null, reply.body);
} }
}, opt); }, opt);
@ -950,11 +977,11 @@
store.set(tab.prefix + tab.prekey + key, meta); store.set(tab.prefix + tab.prekey + key, meta);
Gun.obj.map(gun.__.opt.peers, function(peer, url){ Gun.obj.map(gun.__.opt.peers, function(peer, url){
request(url, meta, function(err, reply){ request(url, meta, function(err, reply){
if(err || !reply){ if(err || !reply || (err = reply.body && reply.body.err)){
// tab.key(key, soul, cb); // naive implementation of retry TODO: BUG: need backoff and anti-infinite-loop! // tab.key(key, soul, cb); // naive implementation of retry TODO: BUG: need backoff and anti-infinite-loop!
cb({err: Gun.log(err || "Error: Key failed to be made on " + url) }); cb({err: Gun.log(err || "Error: Key failed to be made on " + url) });
} else { } else {
cb(); cb(null, reply.body);
} }
}, {url: {pathname: '/' + key }, headers: tab.headers}); }, {url: {pathname: '/' + key }, headers: tab.headers});
cb.peers = true; cb.peers = true;
@ -969,7 +996,12 @@
store.set(tab.prefix + tab.prenode + soul, gun.__.graph[soul]); store.set(tab.prefix + tab.prenode + soul, gun.__.graph[soul]);
}); });
Gun.obj.map(gun.__.opt.peers, function(peer, url){ Gun.obj.map(gun.__.opt.peers, function(peer, url){
request(url, nodes, function respond(err, reply, id){ request(url, nodes, function(err, reply){
if(err || !reply || (err = reply.body && reply.body.err)){
return cb({err: Gun.log(err || "Error: Set failed on " + url) });
} else {
cb(null, reply.body);
}
}, {headers: tab.headers}); }, {headers: tab.headers});
cb.peers = true; cb.peers = true;
}); tab.peers(cb); }); tab.peers(cb);
@ -979,7 +1011,7 @@
} }
tab.peers = function(cb){ tab.peers = function(cb){
if(cb && !cb.peers){ // there are no peers! this is a local only instance if(cb && !cb.peers){ // there are no peers! this is a local only instance
setTimeout(function(){cb({err: Gun.log("Warning! You have no peers to connect to!")})},1); setTimeout(function(){console.log("Warning! You have no peers to connect to!");cb()},1);
} }
} }
tab.set.defer = {}; tab.set.defer = {};
@ -1012,6 +1044,7 @@
} }
r.createServer = function(fn){ (r.createServer = fn).on = true } r.createServer = function(fn){ (r.createServer = fn).on = true }
r.transport = function(opt, cb){ r.transport = function(opt, cb){
//Gun.log("TRANSPORT:", opt);
if(r.ws(opt, cb)){ return } if(r.ws(opt, cb)){ return }
r.jsonp(opt, cb); r.jsonp(opt, cb);
} }
@ -1024,8 +1057,9 @@
if(opt.headers){ req.headers = opt.headers } if(opt.headers){ req.headers = opt.headers }
if(opt.body){ req.body = opt.body } if(opt.body){ req.body = opt.body }
if(opt.url){ req.url = opt.url } if(opt.url){ req.url = opt.url }
r.ws.cbs[req.wsrid = 'WS' + (+ new Date()) + '.' + Math.floor((Math.random()*65535)+1)] = function(err,res){ req.headers = req.headers || {};
delete r.ws.cbs[req.wsrid]; r.ws.cbs[req.headers['ws-rid'] = 'WS' + (+ new Date()) + '.' + Math.floor((Math.random()*65535)+1)] = function(err,res){
delete r.ws.cbs[req.headers['ws-rid']];
cb(err,res); cb(err,res);
} }
ws.send(JSON.stringify(req)); ws.send(JSON.stringify(req));
@ -1049,8 +1083,9 @@
try{res = JSON.parse(m.data); try{res = JSON.parse(m.data);
}catch(e){ return } }catch(e){ return }
if(!res){ return } if(!res){ return }
if(res.wsrid){ (r.ws.cbs[res.wsrid]||function(){})(null, res) } res.headers = res.headers || {};
//Gun.log("We have a pushed message!", res); if(res.headers['ws-rid']){ return (r.ws.cbs[res.headers['ws-rid']]||function(){})(null, res) }
Gun.log("We have a pushed message!", res);
if(res.body){ r.createServer(res, function(){}) } // emit extra events. if(res.body){ r.createServer(res, function(){}) } // emit extra events.
}; };
ws.onerror = function(e){ Gun.log(e); }; ws.onerror = function(e){ Gun.log(e); };

View File

@ -389,7 +389,7 @@ border-top: 1em solid FireBrick;">
Gun is a persisted distributed cache, part of a NoDB movement. Gun is a persisted distributed cache, part of a NoDB movement.
It requires zero maintenance and runs on your own infrastructure. It requires zero maintenance and runs on your own infrastructure.
Think of it as <i>"Dropbox for Databases"</i> or a <i>"Self-hosted Firebase"</i>. Think of it as <i>"Dropbox for Databases"</i> or a <i>"Self-hosted Firebase"</i>.
This is an early preview, so check out the <a href="http://github.com/amark/gun">github</a> and read on. This is an early preview, so check out the <a href="https://github.com/amark/gun" target="_blank">github</a> and read on.
</p> </p>
<p> <p>
Everything gets cached, so your users experience lightning fast response times. Everything gets cached, so your users experience lightning fast response times.

View File

@ -22,13 +22,13 @@ Gun.on('opt').event(function(gun, opts){
} }
,set: function(graph, cb){ ,set: function(graph, cb){
all.nodes = gun.__.graph; all.nodes = gun.__.graph;
for(n in all.nodes){ /*for(n in all.nodes){ // this causes some divergence problems, so removed for now till later when it can be fixed.
for(k in all.nodes[n]){ for(k in all.nodes[n]){
if(all.nodes[n][k] === null){ if(all.nodes[n][k] === null){
delete all.nodes[n][k]; delete all.nodes[n][k];
} }
} }
} }*/
fs.writeFile(opts.file, Gun.text.ify(all), cb); fs.writeFile(opts.file, Gun.text.ify(all), cb);
} }
,key: function(key, soul, cb){ ,key: function(key, soul, cb){

22
lib/list.js Normal file
View File

@ -0,0 +1,22 @@
var Gun = Gun || require('../gun');
Gun.chain.list = function(cb, opt){
opt = opt || {};
cb = cb || function(){};
var gun = this.set({}); // insert assumes a graph node. So either create it or merge with the existing one.
gun.last = function(obj, cb){
var last = gun.path('last');
if(!arguments.length){ return last }
return gun.path('last').set(null).set(obj).get(function(){ // warning! these are not transactional! They could be.
last.path('next').set(this._.node, cb);
});
}
gun.first = function(obj, cb){
var first = gun.path('first');
if(!arguments.length){ return first }
return gun.path('first').set(null).set(obj).get(function(){ // warning! these are not transactional! They could be.
first.path('prev').set(this._.node, cb);
});
}
return gun;
};

View File

@ -24,7 +24,8 @@
s3.get(key, function(err, data, text, meta){ s3.get(key, function(err, data, text, meta){
Gun.log('via s3', key, err); Gun.log('via s3', key, err);
if(meta && meta[Gun._.soul]){ if(meta && meta[Gun._.soul]){
return s3.load(meta, cb); return s3.load(meta, cb); // SUPER SUPER IMPORTANT TODO!!!! Make this load via GUN in case soul is already cached!
// HUGE HUGE HUGE performance gains could come from the above line being updated! (not that this module is performant).
} }
if(err && err.statusCode == 404){ if(err && err.statusCode == 404){
err = null; // we want a difference between 'unfound' (data is null) and 'error' (auth is wrong). err = null; // we want a difference between 'unfound' (data is null) and 'error' (auth is wrong).

View File

@ -21,10 +21,13 @@ module.exports = function(wss, server){
msg.headers[i] = msg.headers[i] || val; // reattach headers msg.headers[i] = msg.headers[i] || val; // reattach headers
}); });
server.call(ws, msg, function(reply){ server.call(ws, msg, function(reply){
if(!ws || !ws.send){ return } if(!ws || !ws.send || !ws._socket || !ws._socket.writable){ return }
reply = reply || {}; reply = reply || {};
reply.wsrid = msg.wsrid; if(msg && msg.headers && msg.headers['ws-rid']){
ws.send(Gun.text.ify(reply)); (reply.headers = reply.headers || {})['ws-rid'] = msg.headers['ws-rid'];
}
try{ws.send(Gun.text.ify(reply));
}catch(e){} // juuuust in case.
}); });
}); });
ws.off = function(m){ ws.off = function(m){

View File

@ -17,12 +17,13 @@
gun.__.opt.ws.path = gun.__.opt.ws.path || opt.ws.path || '/gun'; gun.__.opt.ws.path = gun.__.opt.ws.path || opt.ws.path || '/gun';
require('./ws')(gun.server.websocket = gun.server.websocket || new ws(gun.__.opt.ws), function(req, res){ require('./ws')(gun.server.websocket = gun.server.websocket || new ws(gun.__.opt.ws), function(req, res){
var ws = this; var ws = this;
req.headers['gun-tid'] = ws.tid = ws.tid? ws.tid : req.headers['gun-tid']; req.headers['gun-sid'] = ws.sid = ws.sid? ws.sid : req.headers['gun-sid'];
ws.sub = ws.sub || gun.server.on('network').event(function(msg){ ws.sub = ws.sub || gun.server.on('network').event(function(msg){
if(!ws || !ws.send){ return this.off() } if(!ws || !ws.send || !ws._socket || !ws._socket.writable){ return this.off() }
if(!msg || (msg.headers && msg.headers['gun-tid'] === ws.tid)){ return } if(!msg || (msg.headers && msg.headers['gun-sid'] === ws.sid)){ return }
delete msg.wsrid; // depreciate this! if(msg && msg.headers){ delete msg.headers['ws-rid'] }
ws.send(Gun.text.ify(msg)); try{ws.send(Gun.text.ify(msg));
}catch(e){} // juuuust in case.
}); });
gun.__.opt.hooks.transport(req, res); gun.__.opt.hooks.transport(req, res);
}); });
@ -48,18 +49,18 @@
http(req, res, function(req, res){ http(req, res, function(req, res){
if(!req){ return next() } if(!req){ return next() }
var tab, cb = res = require('./jsonp')(req, res); var tab, cb = res = require('./jsonp')(req, res);
if(req.headers && (tab = req.headers['gun-tid'])){ if(req.headers && (tab = req.headers['gun-sid'])){
tab = (gun.server.peers = gun.server.peers || {})[tab] = gun.server.peers[tab] || {tid: tab}; tab = (gun.server.peers = gun.server.peers || {})[tab] = gun.server.peers[tab] || {sid: tab};
tab.sub = tab.sub || gun.server.on('network').event(function(req){ tab.sub = tab.sub || gun.server.on('network').event(function(req){
if(!tab){ return this.off() } // self cleans up after itself! if(!tab){ return this.off() } // self cleans up after itself!
if(!req || (req.headers && req.headers['gun-tid'] === tab.tid)){ return } if(!req || (req.headers && req.headers['gun-sid'] === tab.sid)){ return }
(tab.queue = tab.queue || []).push(req); (tab.queue = tab.queue || []).push(req);
tab.drain(tab.reply); tab.drain(tab.reply);
}); });
cb = function(r){ (r.headers||{}).poll = gun.__.opt.poll; res(r) } cb = function(r){ (r.headers||{}).poll = gun.__.opt.poll; res(r) }
tab.drain = tab.drain || function(res){ tab.drain = tab.drain || function(res){
if(!res || !tab || !tab.queue || !tab.queue.length){ return } if(!res || !tab || !tab.queue || !tab.queue.length){ return }
res({headers: {'gun-tid': tab.tid}, body: tab.queue }); res({headers: {'gun-sid': tab.sid}, body: tab.queue });
tab.off = setTimeout(function(){ tab = null }, gun.__.opt.pull); tab.off = setTimeout(function(){ tab = null }, gun.__.opt.pull);
tab.reply = tab.queue = null; tab.reply = tab.queue = null;
return true; return true;
@ -110,7 +111,6 @@
//Gun.log("transport.loading key ->", key, gun.__.graph, gun.__.keys); //Gun.log("transport.loading key ->", key, gun.__.graph, gun.__.keys);
gun.load(key, function(err, node){ gun.load(key, function(err, node){
//tran.sub.scribe(req.tab, node._[Gun._.soul]); //tran.sub.scribe(req.tab, node._[Gun._.soul]);
// TODO: MAJOR BUG!!!! This is crashing server if the socket gets disconnected or something.
cb({headers: reply.headers, body: (err? (err.err? err : {err: err || "Unknown error."}) : node || null)}); cb({headers: reply.headers, body: (err? (err.err? err : {err: err || "Unknown error."}) : node || null)});
}); });
} }

View File

@ -398,7 +398,7 @@ describe('Gun', function(){
}); });
}); });
it('load', function(done){ it('load again', function(done){
gun.load('world/hello').get(function(val){ gun.load('world/hello').get(function(val){
expect(val.world).to.be('hello'); expect(val.world).to.be('hello');
done(); done();
@ -423,6 +423,24 @@ describe('Gun', function(){
}); });
}); });
it('set path get sub', function(done){
gun.set({last: {some: 'object'}}).path('last').get(function(val){
expect(val.some).to.be('object');
done();
});
});
it('load set null', function(done){
gun.set({last: {some: 'object'}}).path('last').get(function(val){
expect(val.some).to.be('object');
}).set(null, function(err){
//console.log("ERR?", err);
}).get(function(val){
expect(val).to.be(null);
done();
});
});
it('var set key path', function(done){ // contexts should be able to be saved to a variable 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'); 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 foo.path('hello.world.nowhere'); // this should become a sub-context, that doesn't alter the original
@ -459,5 +477,29 @@ describe('Gun', function(){
done(); done();
}); });
}); });
it('set partial sub merge', function(done){
var mark = gun.set({name: "Mark", wife: { name: "Amber" }}).key('person/mark').get(function(mark){
expect(mark.name).to.be("Mark");
});
mark.set({age: 23, wife: {age: 23}});
setTimeout(function(){
mark.set({citizen: "USA", wife: {citizen: "USA"}}).get(function(mark){
expect(mark.name).to.be("Mark");
expect(mark.age).to.be(23);
expect(mark.citizen).to.be("USA");
this.path('wife').get(function(Amber){
expect(Amber.name).to.be("Amber");
expect(Amber.age).to.be(23);
expect(Amber.citizen).to.be("USA");
done();
});
});
}, 50);
});
}); });
}); });

View File

@ -55,7 +55,7 @@
, n = function(n){ return parseFloat(n) } , n = function(n){ return parseFloat(n) }
$(document).on('keyup', function(){ $(document).on('keyup', function(){
r.val( r.val(
(( n(sim.val()) / n(se.val()) ) * n(o.val()) / 1000 * 0.005 ).toFixed(2) ((( n(sim.val()) / n(se.val()) ) * n(o.val()) / 1000) * 0.005 ).toFixed(2)
); );
}) })
}) })

76
test/tmp.js Normal file
View File

@ -0,0 +1,76 @@
(function(){
return; // this file is for temporary testings and shouldn't get run.
var Gun = require('../gun');
var gun = Gun();
Gun.log.verbose = true;
/*
gun.set({foo: "bar"}).get(function(val){
console.log("POWR HOUSE", val);
this.set({lol: 'pancakes'}).get(function(v){
console.log("YEAH CAKES", v);
})
});
*/
gun.load('hello/world').set({hello: 'Mark'}).path('hello').get(function(val){
console.log("YO", val);
expect(val).to.be('Mark');
done();
});
return;
function Next(){
var fn = function(cb){
if(!fn.stack || !fn.stack.length){
setImmediate(function next(n){
return (n = (fn.stack||[]).shift() || function(){}), n.back = fn.stack, fn.stack = [], n(function(){
return (fn.stack = (fn.stack||[]).concat(n.back)), next();
});
});
} if(cb){
(fn.stack = fn.stack || []).push(cb);
} return fn;
}, setImmediate = setImmediate || function(cb){return setTimeout(cb,0)}
return fn;
}
module.exports = Next;
var next = Next();
var state = {};
next(function(n){
console.log(1);
setTimeout(n, 500);
});
next(function(n){
console.log(2);
setTimeout(function(){
//n(function(){
console.log(3);
next(function(n){
console.log(4);
setTimeout(function(){
console.log("before five");
n();
}, 5000);
})
next(function(n){
console.log(5);
setTimeout(n, 3000);
});
n();
//});
}, 1000);
});
next(function(n){
console.log(6);
n();
});
}());