mirror of
https://github.com/amark/gun.git
synced 2025-06-07 06:36:46 +00:00
val end fix & comments
This commit is contained in:
parent
a36dd37fef
commit
13b8ff9ada
@ -8,7 +8,7 @@
|
|||||||
}
|
}
|
||||||
, "dependencies": {
|
, "dependencies": {
|
||||||
"express": "~>4.9.0",
|
"express": "~>4.9.0",
|
||||||
"gun": "~>0.2.3"
|
"gun": "~>0.2.4"
|
||||||
}
|
}
|
||||||
, "scripts": {
|
, "scripts": {
|
||||||
"start": "node http.js",
|
"start": "node http.js",
|
||||||
|
305
gun.js
305
gun.js
@ -1,80 +1,79 @@
|
|||||||
;(function(){
|
;(function(){
|
||||||
function Gun(opt){
|
function Gun(opt){ // the starting point to using GUN! Should be called once per page load.
|
||||||
var gun = this;
|
var gun = this;
|
||||||
if(!Gun.is(gun)){ // if this is not a GUN instance,
|
if(!Gun.is(gun)){ // if this is not a GUN instance,
|
||||||
return new Gun(opt); // then make it so.
|
return new Gun(opt); // then make it so.
|
||||||
}
|
}
|
||||||
gun.opt(opt);
|
if(Gun.is(opt)){ // if opt is a GUN instance,
|
||||||
|
return gun; // then return a new chain, reusing the existing options.
|
||||||
|
}
|
||||||
|
return gun.opt(opt); // update the instance's options.
|
||||||
}
|
}
|
||||||
Gun._ = { // some reserved key words, these are not the only ones.
|
Gun._ = { // some reserved key words, these are not the only ones.
|
||||||
soul: '#'
|
soul: '#' // a soul is a UUID of a node but it always points to the "latest" data known.
|
||||||
,meta: '_'
|
,meta: '_' // all metadata of the node is stored in the meta property on the node.
|
||||||
,HAM: '>'
|
,HAM: '>' // other than the soul, we store HAM metadata.
|
||||||
}
|
}
|
||||||
;(function(Gun){ // GUN specific utilities
|
;(function(Gun){ // GUN specific utilities.
|
||||||
Util(Gun); // initialize standard utilities
|
Util(Gun); // initialize standard javascript utilities.
|
||||||
Gun.version = 0.2; // TODO: When Mark (or somebody) does a push/publish, dynamically update package.json
|
Gun.version = 0.2; // does Travis allow us to dynamically update package.json and npm publish?
|
||||||
Gun.is = function(gun){ return (gun instanceof Gun)? true : false }
|
Gun.is = function(gun){ return (gun instanceof Gun)? true : false } // check to see if it is a GUN instance.
|
||||||
Gun.is.value = function(v){ // null, binary, number (!Infinity), text, or a rel (soul).
|
Gun.is.value = function(v){ // Valid values are a subset of JSON: null, binary, number (!Infinity), text, or a soul relation. Arrays need special algorithms to handle concurrency, so they are not supported directly. Use an extension that supports them if needed but research their problems first.
|
||||||
if(v === null){ return true } // deletes
|
if(v === null){ return true } // "deletes", nulling out fields.
|
||||||
if(v === Infinity){ return false } // we want this to be, but JSON does not support it, sad face.
|
if(v === Infinity){ return false } // we want this to be, but JSON does not support it, sad face.
|
||||||
if(Gun.bi.is(v)
|
if(Gun.bi.is(v) // by "binary" we mean boolean.
|
||||||
|| Gun.num.is(v)
|
|| Gun.num.is(v)
|
||||||
|| Gun.text.is(v)){
|
|| Gun.text.is(v)){ // by "text" we mean strings.
|
||||||
return true; // simple values
|
return true; // simple values are valid.
|
||||||
}
|
}
|
||||||
var id;
|
return Gun.is.soul(v) || false; // is the value a soul relation? Then it is valid and return it. If not, everything else remaining is an invalid data type. Custom extensions can be built on top of these primitives to support other types.
|
||||||
if(id = Gun.is.soul(v)){
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
Gun.is.value.as = function(v){
|
Gun.is.value.as = function(v){ // check if it is a valid value and return the value if so,
|
||||||
return Gun.is.value(v)? v : null;
|
return Gun.is.value(v)? v : null; // else return null.
|
||||||
}
|
}
|
||||||
Gun.is.soul = function(v){
|
Gun.is.soul = function(v){ // this defines whether an object is a soul relation or not, they look like this: {'#': 'UUID'}
|
||||||
if(Gun.obj.is(v)){
|
if(Gun.obj.is(v)){ // must be an object.
|
||||||
var id;
|
var id;
|
||||||
Gun.obj.map(v, function(soul, field){
|
Gun.obj.map(v, function(soul, field){ // map over the object...
|
||||||
if(id){ return id = false } // if ID is already defined AND we're still looping through the object, it is invalid.
|
if(id){ return id = false } // if ID is already defined AND we're still looping through the object, it is considered invalid.
|
||||||
if(field == Gun._.soul && Gun.text.is(soul)){
|
if(field == Gun._.soul && Gun.text.is(soul)){ // the field should be '#' and have a text value.
|
||||||
id = soul; // we found the soul!
|
id = soul; // we found the soul!
|
||||||
} else {
|
} else {
|
||||||
return id = false; // if there exists anything else on the object, that isn't the soul, then it is invalid.
|
return id = false; // if there exists anything else on the object that isn't the soul, then it is considered invalid.
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if(id){
|
if(id){ // a valid id was found.
|
||||||
return id;
|
return id; // yay! Return it.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false; // the value was not a valid soul relation.
|
||||||
}
|
}
|
||||||
Gun.is.soul.on = function(n){ return (n && n._ && n._[Gun._.soul]) || false }
|
Gun.is.soul.on = function(n){ return (n && n._ && n._[Gun._.soul]) || false } // convenience function to check to see if there is a soul on a node and return it.
|
||||||
Gun.is.node = function(node, cb){
|
Gun.is.node = function(node, cb, t){ // checks to see if an object is a valid node.
|
||||||
var soul;
|
var soul;
|
||||||
if(!Gun.obj.is(node)){ return false }
|
if(!Gun.obj.is(node)){ return false } // must be an object.
|
||||||
if(soul = Gun.is.soul.on(node)){
|
if(soul = Gun.is.soul.on(node)){ // must have a soul on it.
|
||||||
return !Gun.obj.map(node, function(value, field){ // need to invert this, because the way we check for this is via a negation.
|
return !Gun.obj.map(node, function(value, field){ // we invert this because the way we check for this is via a negation.
|
||||||
if(field == Gun._.meta){ return } // skip this.
|
if(field == Gun._.meta){ return } // skip over the metadata.
|
||||||
if(!Gun.is.value(value)){ return true } // it is true that this is an invalid node.
|
if(!Gun.is.value(value)){ return true } // it is true that this is an invalid node.
|
||||||
if(cb){ cb(value, field, node._, soul) }
|
if(cb){ cb.call(t, value, field, node._, soul) } // optionally callback each field/value.
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return false;
|
return false; // nope! This was not a valid node.
|
||||||
}
|
}
|
||||||
Gun.is.graph = function(graph, cb, fn){
|
Gun.is.graph = function(graph, cb, fn, t){ // checks to see if an object is a valid graph.
|
||||||
var exist = false;
|
var exist = false;
|
||||||
if(!Gun.obj.is(graph)){ return false }
|
if(!Gun.obj.is(graph)){ return false } // must be an object.
|
||||||
return !Gun.obj.map(graph, function(node, soul){ // need to invert this, because the way we check for this is via a negation.
|
return !Gun.obj.map(graph, function(node, soul){ // we invert this because the way we check for this is via a negation.
|
||||||
if(!node || soul !== Gun.is.soul.on(node) || !Gun.is.node(node, fn)){ return true } // it is true that this is an invalid graph.
|
if(!node || soul !== Gun.is.soul.on(node) || !Gun.is.node(node, fn)){ return true } // it is true that this is an invalid graph.
|
||||||
(cb || function(){})(node, soul, function(fn){
|
(cb || function(){}).call(t, node, soul, function(fn){ // optional callback for each node.
|
||||||
if(fn){ Gun.is.node(node, fn) }
|
if(fn){ Gun.is.node(node, fn, t) } // where we then have an optional callback for each field/value.
|
||||||
});
|
});
|
||||||
exist = true;
|
exist = true;
|
||||||
}) && exist;
|
}) && exist; // makes sure it wasn't an empty object.
|
||||||
}
|
}
|
||||||
// Gun.ify // the serializer is too long for right here, it has been relocated towards the bottom.
|
// Gun.ify // the serializer is too long for right here, it has been relocated towards the bottom.
|
||||||
Gun.union = function(gun, prime, cb){
|
Gun.union = function(gun, prime, cb){ // merge two graphs into the first.
|
||||||
var ctx = {count: 0, cb: function(){ cb = cb? cb() && null : null }};
|
var ctx = {count: 0, cb: function(){ cb = cb? cb() && null : null }};
|
||||||
ctx.graph = gun.__.graph;
|
ctx.graph = gun.__.graph;
|
||||||
if(!ctx.graph){ ctx.err = {err: Gun.log("No graph!") } }
|
if(!ctx.graph){ ctx.err = {err: Gun.log("No graph!") } }
|
||||||
@ -99,6 +98,7 @@
|
|||||||
var vertex = graph[soul];
|
var vertex = graph[soul];
|
||||||
if(!vertex){ // disjoint // TODO: Maybe not correct? BUG, probably.
|
if(!vertex){ // disjoint // TODO: Maybe not correct? BUG, probably.
|
||||||
Gun.on('union').emit(gun, graph[soul] = node);
|
Gun.on('union').emit(gun, graph[soul] = node);
|
||||||
|
gun.__.on('union').emit(node);
|
||||||
gun.__.on(soul).emit(node);
|
gun.__.on(soul).emit(node);
|
||||||
ctx.count -= 1;
|
ctx.count -= 1;
|
||||||
return;
|
return;
|
||||||
@ -116,7 +116,8 @@
|
|||||||
//context.nodes[change._[Gun._.soul]] = change;
|
//context.nodes[change._[Gun._.soul]] = change;
|
||||||
//context('change').fire(change);
|
//context('change').fire(change);
|
||||||
Gun.on('union').emit(gun, change);
|
Gun.on('union').emit(gun, change);
|
||||||
gun.__.on(Gun.is.soul.on(change)).emit(change);
|
gun.__.on('union').emit(change);
|
||||||
|
gun.__.on(Gun.is.soul.on(change)).emit(change);
|
||||||
}, function(){})(function(){
|
}, function(){})(function(){
|
||||||
if(!(ctx.count -= 1)){ ctx.cb() }
|
if(!(ctx.count -= 1)){ ctx.cb() }
|
||||||
});
|
});
|
||||||
@ -174,7 +175,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if(now.end){ now.call({}, vertex) } // TODO: Should HAM handle empty updates?
|
if(now.end){ now.call({}, vertex) } // TODO: Should HAM handle empty updates? YES.
|
||||||
function HAM(machineState, incomingState, currentState, incomingValue, currentValue){ // TODO: Lester's comments on roll backs could be vulnerable to divergence, investigate!
|
function HAM(machineState, incomingState, currentState, incomingValue, currentValue){ // TODO: Lester's comments on roll backs could be vulnerable to divergence, investigate!
|
||||||
if(machineState < incomingState){
|
if(machineState < incomingState){
|
||||||
// the incoming value is outside the boundary of the machine's state, it must be reprocessed in another state.
|
// the incoming value is outside the boundary of the machine's state, it must be reprocessed in another state.
|
||||||
@ -282,7 +283,6 @@
|
|||||||
var gun = this;
|
var gun = this;
|
||||||
gun._ = gun._ || {};
|
gun._ = gun._ || {};
|
||||||
gun.__ = gun.__ || {};
|
gun.__ = gun.__ || {};
|
||||||
if(opt === null){ return gun }
|
|
||||||
opt = opt || {};
|
opt = opt || {};
|
||||||
gun.__.opt = gun.__.opt || {};
|
gun.__.opt = gun.__.opt || {};
|
||||||
gun.__.flag = gun.__.flag || {start: {}, end: {}};
|
gun.__.flag = gun.__.flag || {start: {}, end: {}};
|
||||||
@ -306,14 +306,14 @@
|
|||||||
return gun;
|
return gun;
|
||||||
}
|
}
|
||||||
Gun.chain.chain = function(from){
|
Gun.chain.chain = function(from){
|
||||||
var gun = Gun(null);
|
|
||||||
from = from || this;
|
from = from || this;
|
||||||
gun.back = from;
|
var gun = Gun(from); // create a gun chain from this GUN instance.
|
||||||
gun.__ = from.__;
|
gun.back = from; // back link it.
|
||||||
gun._ = {on: Gun.on.create()};
|
gun.__ = from.__; // inherit the instance level configurations.
|
||||||
|
gun._ = {on: Gun.on.create()}; // create an event emitter for this new chain.
|
||||||
gun._.at = function(e){
|
gun._.at = function(e){
|
||||||
var proxy = function(cb, i, chain){
|
var proxy = function(cb, i, chain){
|
||||||
var on = gun._.on(e), at;
|
var on = gun._.on(e), at; // TODO: BUG! the on event emitter should be passed as the this, apparently it isn't. :(
|
||||||
if(at = ((on = gun._.on(e)).e = on.e || {})[e]){ setTimeout(function(){cb.call(on, at)},0) }
|
if(at = ((on = gun._.on(e)).e = on.e || {})[e]){ setTimeout(function(){cb.call(on, at)},0) }
|
||||||
on[chain](function(at){
|
on[chain](function(at){
|
||||||
cb.call(on, at);
|
cb.call(on, at);
|
||||||
@ -321,80 +321,75 @@
|
|||||||
}
|
}
|
||||||
proxy.event = function(cb, i){ return proxy(cb, i, 'event') };
|
proxy.event = function(cb, i){ return proxy(cb, i, 'event') };
|
||||||
proxy.once = function(cb, i){ return proxy(cb, i, 'once') };
|
proxy.once = function(cb, i){ return proxy(cb, i, 'once') };
|
||||||
proxy.emit = function(at, on){//setTimeout(function(){
|
proxy.emit = function(at, on){ // emit immediately, not async.
|
||||||
((on = gun._.on(e)).e = on.e || {})[e] = at;
|
((on = gun._.on(e)).e = on.e || {})[e] = at;
|
||||||
gun._.on(e).emit(at);
|
gun._.on(e).emit(at);
|
||||||
//},0)
|
|
||||||
};
|
};
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
return gun;
|
return gun;
|
||||||
}
|
}
|
||||||
Chain.get = function(key, cb, opt){ // get opens up a reference to a node and loads it.
|
Chain.get = function(key, cb, opt){ // get opens up a reference to a node and loads it.
|
||||||
var gun = this.chain(), ctx = {};
|
var gun = this.chain(), ctx = {}; // create the new chain and have a scoped context.
|
||||||
if(!key){ return cb.call(gun, {err: Gun.log("No key or relation to get!") }), gun }
|
if(!key){ return cb.call(gun, {err: Gun.log("No key or relation to get!") }), gun }
|
||||||
ctx.key = Gun.text.is(key) && key; // if key is text, then key, else false.
|
ctx.key = Gun.text.is(key) && key; // if key is text, then key, else false.
|
||||||
ctx.soul = Gun.is.soul(key); // if key is a soul, then the soul, else false.
|
ctx.soul = Gun.is.soul(key); // if key is a soul, then the soul, else false.
|
||||||
cb = cb || function(){};
|
cb = cb || function(){};
|
||||||
opt = opt || {};
|
opt = opt || {};
|
||||||
|
|
||||||
if(opt.force){ load(key) } else
|
if(opt.force){ load(key) } else // force a load regardless of whether it is a key or soul!
|
||||||
if(ctx.soul){
|
if(ctx.soul){ // if we have a soul that is...
|
||||||
if(ctx.node = gun.__.graph[ctx.soul]){ // in memory
|
if(ctx.node = gun.__.graph[ctx.soul]){ // in memory, then
|
||||||
(ctx.graph = {})[ctx.soul] = ctx.node;
|
(ctx.graph = {})[ctx.soul] = ctx.node; // add it to our graph context.
|
||||||
cb.call(gun, null, Gun.obj.copy(ctx.graph));
|
cb.call(gun, null, Gun.obj.copy(ctx.graph)); // call the callback with a copy of the graph.
|
||||||
(ctx.graph = {})[ctx.soul] = Gun.union.pseudo(ctx.soul);
|
(ctx.graph = {})[ctx.soul] = Gun.union.pseudo(ctx.soul); // override our context with empty nodes as end markers, per the wire protocol.
|
||||||
cb.call(gun, null, ctx.graph); cb.call(gun, null, {}); // end;
|
cb.call(gun, null, ctx.graph); cb.call(gun, null, {}); // call the end nodes, and wire end.
|
||||||
} else { load(key) } // not in memory
|
} else { load(key) } // not in memory, then load it.
|
||||||
ctx.node = gun.__.graph[ctx.soul] = gun.__.graph[ctx.soul] || Gun.union.pseudo(ctx.soul);
|
ctx.node = gun.__.graph[ctx.soul] = gun.__.graph[ctx.soul] || Gun.union.pseudo(ctx.soul); // either way we know the soul, so make sure it is in our graph so it can be referenced.
|
||||||
gun._.at('soul').emit({soul: ctx.soul, GET: 'SOUL'});
|
gun._.at('soul').emit({soul: ctx.soul, GET: 'SOUL'}); // emit the soul to the chain!
|
||||||
} else
|
} else
|
||||||
if(ctx.key){
|
if(ctx.key){ // if it is a key, then
|
||||||
function get(soul){
|
function get(soul){ // once we have a soul
|
||||||
var graph = gun.__.key.s[ctx.key], end = {};
|
var graph = gun.__.key.s[ctx.key], end = {}; // grab the graph corresponding to the key.
|
||||||
Gun.is.graph(graph, function(node, soul){
|
Gun.is.graph(graph, function(node, soul){ // iterate over each node
|
||||||
end[soul] = Gun.union.pseudo(soul);
|
end[soul] = Gun.union.pseudo(soul); // put empty nodes as an end marker, per the wire protocol.
|
||||||
gun._.at('soul').emit({soul: soul, key: ctx.key, GET: 'SOUL'});
|
gun._.at('soul').emit({soul: soul, key: ctx.key, GET: 'SOUL'}); // emit each soul to the chain!
|
||||||
});
|
});
|
||||||
cb.call(gun, null, Gun.obj.copy(graph)); cb.call(gun, null, end); cb.call(gun, null, {}); // end.
|
cb.call(gun, null, Gun.obj.copy(graph)); cb.call(gun, null, end); cb.call(gun, null, {}); // and finally, call the graph, the end nodes, and the wire end.
|
||||||
}
|
}
|
||||||
if(gun.__.key.s[ctx.key]){ get() } // in memory
|
if(gun.__.key.s[ctx.key]){ get() } // check if it is in memory, else
|
||||||
else if(ctx.flag = gun.__.flag.start[ctx.key]){ // will be in memory
|
else if(ctx.flag = gun.__.flag.start[ctx.key]){ // if it will be in memory, then TODO: convert this to use the meta system instead if possible, seems cleaner.
|
||||||
ctx.flag.once(get);
|
ctx.flag.once(get); // subscribe to when that happens.
|
||||||
} else { load(key) } // not in memory
|
} else { load(key) } // else it is not in memory, load it.
|
||||||
} else { cb.call(gun, {err: Gun.log("No key or relation to get!")}) }
|
} else { cb.call(gun, {err: Gun.log("No key or relation to get!")}) }
|
||||||
|
|
||||||
function load(key){
|
function load(key){ // load a key or soul.
|
||||||
if(Gun.fns.is(ctx.hook = gun.__.opt.hooks.get)){
|
if(Gun.fns.is(ctx.hook = gun.__.opt.hooks.get)){ // if we have a hook...
|
||||||
ctx.hook(key, function(err, data){ // will be called multiple times.
|
ctx.hook(key, function(err, data){ // listen to callbacks, which will be called multiple times.
|
||||||
console.log("chain.get ", key, "from hook", err, data, '\n');
|
console.log("chain.get ", key, "from hook", err, data, '\n');
|
||||||
if(err){ return cb.call(gun, err, null) }
|
if(err){ return cb.call(gun, err, null) } // if error, call it and be done. TODO: emit error!
|
||||||
if(!data){
|
if(!data){ // if there is no data...
|
||||||
if(ctx.data){ return }
|
if(ctx.data){ return } // make sure we don't have context data, if we do, don't go further.
|
||||||
cb.call(gun, null, null);
|
cb.call(gun, null, null); // call that we have null data, since nodejs callback style does not differentiate between error, null, and data.
|
||||||
if(!ctx.key || ctx.soul){ return } // TODO: Maybe want to do a .not on a soul directly?
|
return gun.__.flag.end[ctx.key || ctx.soul] = gun.__.flag.end[ctx.key || ctx.soul] || function($){ // set the end marker. TODO: convert this to use the meta system instead if possible, seems cleaner.
|
||||||
return gun.__.flag.end[ctx.key] = gun.__.flag.end[ctx.key] || function($){
|
|
||||||
// TODO: cover all edge cases, uniqueness?
|
// TODO: cover all edge cases, uniqueness?
|
||||||
delete gun.__.flag.end[ctx.key];
|
delete gun.__.flag.end[ctx.key || ctx.soul]; // once called, clear out our flag.
|
||||||
gun._.at('soul').emit($);
|
gun._.at('soul').emit($); // emit the soul to the chain.
|
||||||
}, gun._.at('null').emit({key: ctx.key, GET: 'NULL'});
|
}, gun._.at('null').emit({key: ctx.key, soul: ctx.soul, GET: 'NULL'}); // emit the null to the chain!
|
||||||
}
|
}
|
||||||
var dat = ctx.data = {};
|
var dat = ctx.data = {}; // create a data context.
|
||||||
if(Gun.obj.empty(data)){ return cb.call(gun, null, data) }
|
if(Gun.obj.empty(data)){ return cb.call(gun, null, data) } // call the wire end and be done.
|
||||||
if(!Gun.is.graph(data, function(node, soul, map){
|
if(!Gun.is.graph(data, function(node, soul, map){ // iterate over each node in the data graph
|
||||||
if(err = Gun.union(gun, node).err){ return cb.call(gun, err, data) }
|
if(err = Gun.union(gun, node).err){ return cb.call(gun, err, data) } // union it, or error.
|
||||||
/*dat[soul] = Gun.union.pseudo(soul); map(function(val, field){
|
if(ctx.key){ (gun.__.key.s[ctx.key] = gun.__.key.s[ctx.key] || {})[soul] = gun.__.graph[soul] } // if it was on a key, then add this node to the key graph.
|
||||||
(dat[soul]._[Gun._.HAM] = dat[soul]._[Gun._.HAM] || {})[field] = gun.__.graph[soul]._[Gun._.HAM]
|
gun._.at('soul').emit({soul: soul, key: ctx.key, GET: 'SOUL'}); // and emit each soul to the chain!
|
||||||
});*/
|
})){ return cb.call(gun, {err: Gun.log('Not a valid graph!') }, data) } // if the data isn't a graph, error out.
|
||||||
if(ctx.key){ (gun.__.key.s[ctx.key] = gun.__.key.s[ctx.key] || {})[soul] = gun.__.graph[soul] }
|
cb.call(gun, null, data); // TODO: Hmm, this should be called before the chain emit, but I don't want to do 2 map operations. As long as union is called before, does this matter that it is after?
|
||||||
gun._.at('soul').emit({soul: soul, key: ctx.key, GET: 'SOUL'}); // TODO: Implications?
|
|
||||||
})){ return cb.call(gun, {err: Gun.log('Not a valid graph!') }, data) }
|
|
||||||
cb.call(gun, null, data);
|
|
||||||
}, opt);
|
}, opt);
|
||||||
} else {
|
} else { // if there is no hook...
|
||||||
console.Log("Warning! You have no persistence layer to get from!");
|
console.Log("Warning! You have no persistence layer to get from!"); // politely notify people.
|
||||||
cb.call(gun, null, null); // Technically no error, but no way we can get data.
|
cb.call(gun, null, null); // Technically no error, but no way we can get data.
|
||||||
gun._.at('null').emit({key: ctx.key, GET: 'NULL'});
|
gun._.at('null').emit({key: ctx.key, GET: 'NULL'}); // and emit the null to the chain!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return gun;
|
return gun;
|
||||||
@ -403,11 +398,11 @@
|
|||||||
var gun = this, ctx = {};
|
var gun = this, ctx = {};
|
||||||
if(!key){ return cb.call(gun, {err: Gun.log('No key!')}), gun }
|
if(!key){ return cb.call(gun, {err: Gun.log('No key!')}), gun }
|
||||||
if(!gun.back){ gun = gun.chain() }
|
if(!gun.back){ gun = gun.chain() }
|
||||||
if(gun.__.key.s[key]){ console.Log("Warning! Key already used!") } // TODO! Have opt that will aggregate.
|
if(gun.__.key.s[key]){ console.Log("Warning! Key already used!") } // TODO: Have opt that will aggregate.
|
||||||
cb = cb || function(){};
|
cb = cb || function(){};
|
||||||
opt = Gun.text.is(opt)? {soul: opt} : opt || {};
|
opt = Gun.text.is(opt)? {soul: opt} : opt || {};
|
||||||
opt.soul = opt.soul || opt[Gun._.soul];
|
opt.soul = opt.soul || opt[Gun._.soul];
|
||||||
if(opt.soul){ // force inject // TODO! BUG! WRITE A TEST FOR THIS!
|
if(opt.soul){ // force inject // TODO: BUG! WRITE A TEST FOR THIS!
|
||||||
if(!gun.__.graph[opt.soul]){
|
if(!gun.__.graph[opt.soul]){
|
||||||
((gun.__.graph[opt.soul] = {})._ = {})[Gun._.soul] = opt.soul;
|
((gun.__.graph[opt.soul] = {})._ = {})[Gun._.soul] = opt.soul;
|
||||||
}
|
}
|
||||||
@ -514,9 +509,10 @@
|
|||||||
return gun;
|
return gun;
|
||||||
}
|
}
|
||||||
Chain.val = (function(){
|
Chain.val = (function(){
|
||||||
Gun.on('union').event(function(gun, data){
|
Gun.on('union').event(function(gun, data, end){
|
||||||
if(!Gun.obj.empty(data, Gun._.meta)){ return }
|
if(!Gun.obj.empty(data, Gun._.meta)){ return }
|
||||||
(data = gun.__.meta(Gun.is.soul.on(data))).end = (data.end || 0) + 1;
|
(end = gun.__.meta(Gun.is.soul.on(data))).end = (end.end || 0) + 1;
|
||||||
|
gun.__.on(Gun.is.soul.on(data) + '.end').emit(data);
|
||||||
});
|
});
|
||||||
return function(cb, opt){
|
return function(cb, opt){
|
||||||
var gun = this, ctx = {};
|
var gun = this, ctx = {};
|
||||||
@ -524,53 +520,62 @@
|
|||||||
opt = opt || {};
|
opt = opt || {};
|
||||||
|
|
||||||
gun.on(function($, delta, on){
|
gun.on(function($, delta, on){
|
||||||
var node = gun.__.graph[$.soul];
|
var node = gun.__.graph[$.soul], hash = $.soul + ($.field || '');
|
||||||
if($.key){
|
if(!$.soul || !node || ctx[hash + '.end']){ return }
|
||||||
node = Gun.union.pseudo($.key, gun.__.key.s[$.key]) || node;
|
//(on = on || {off:function(){}}).off();
|
||||||
|
ctx[hash + '.end'] = function(data){
|
||||||
|
if(data && $.soul != Gun.is.soul.on(data)){ return }
|
||||||
|
var node = gun.__.graph[$.soul] || node; //on = (this || {off:function(){}}); // TODO: BUG? is var node = thing || node safe in old IE?
|
||||||
|
if($.key){
|
||||||
|
// TODO: BUG! Shouldn't `.val` pseudo union check that each node in the key graph is ended? Current thought: Not necessarily! Since `.val` is first come first serve until we provide configurable end options.
|
||||||
|
node = Gun.union.pseudo($.key, gun.__.key.s[$.key]) || node;
|
||||||
|
}
|
||||||
|
if($.field){
|
||||||
|
if(!Gun.obj.has(node, $.field) || ctx[hash] || Gun.is.soul(node[$.field])){ return }
|
||||||
|
ctx[hash] = true; //on.off(); // TODO: Fix the bug with at for this to be on.
|
||||||
|
return cb.call($.gun || gun, node[$.field], $.field || $.at);
|
||||||
|
}
|
||||||
|
if(!gun.__.meta($.soul).end || (ctx[$.soul] || ($.key && ctx[$.key]))){ return } // TODO: Add opt to change number of terminations.
|
||||||
|
ctx[$.soul] = ctx[$.key] = true; //on.off(); // TODO: Fix the bug with at for this to be on.
|
||||||
|
cb.call($.gun || gun, Gun.obj.copy(node), $.field || $.at);
|
||||||
}
|
}
|
||||||
if($.field){
|
if(gun.__.meta($.soul).end){
|
||||||
if(!Gun.obj.has(node, $.field) || ctx[$.soul + $.field]){ return }
|
if(!$.field || Gun.obj.has(node, $.field)){ return ctx[hash + '.end']() }
|
||||||
ctx[$.soul + $.field] = true; // TODO: unregister instead?
|
|
||||||
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.
|
gun.__.on($.soul + '.end').event(ctx[hash + '.end']);
|
||||||
ctx[$.soul] = ctx[$.key] = true; // TODO: unregister instead?
|
|
||||||
cb.call($.gun || gun, Gun.obj.copy(node), $.field || $.at);
|
|
||||||
}, {raw: true});
|
}, {raw: true});
|
||||||
|
|
||||||
return gun;
|
return gun;
|
||||||
}
|
}
|
||||||
}());
|
}());
|
||||||
// .on(fn) gives you back the object, .on(fn, true) gives you delta pair.
|
Chain.on = function(cb, opt){ // on subscribes to any changes related to the chain.
|
||||||
Chain.on = function(cb, opt){
|
var gun = this, ctx = {}; // reuse the current chain and have a scoped context.
|
||||||
var gun = this, ctx = {};
|
opt = Gun.obj.is(opt)? opt : {change: opt}; // .on(fn, true) gives you delta pairs.
|
||||||
opt = Gun.obj.is(opt)? opt : {change: opt};
|
|
||||||
cb = cb || function(){};
|
cb = cb || function(){};
|
||||||
gun._.at('soul').event(function($){ // TODO: once per soul on graph. (?)
|
gun._.at('soul').event(function($){ // subscribe to soul events on the chain.
|
||||||
if(ctx[$.soul]){
|
if(ctx[$.soul]){ // check to see if we have a root level listener for changes on this soul.
|
||||||
if(opt.raw){
|
if(opt.raw){ // then do nothing, unless the options request all raw events.
|
||||||
ctx[$.soul](gun.__.graph[$.soul], $); // TODO: we get duplicate ons, once here and once from HAM.
|
ctx[$.soul].call(this, gun.__.graph[$.soul], $); // call it with the node and the event. TODO: we get duplicate ons, once here and once from HAM.
|
||||||
}
|
}
|
||||||
} else {
|
} else { // if there isn't a root level listener for changes on the soul, then
|
||||||
(ctx[$.soul] = function(delta, $$){
|
(ctx[$.soul] = function(delta, $$){ // mark that we are adding one.
|
||||||
$$ = $$ || $; var node = gun.__.graph[$$.soul], soul;
|
$$ = $$ || $; var node = gun.__.graph[$$.soul], soul; // use the pass event or reuse the the chain's event.
|
||||||
if(delta && $$.soul != Gun.is.soul.on(delta)){ return }
|
if(delta && $$.soul != Gun.is.soul.on(delta)){ return } // just make sure that things match for safety purposes.
|
||||||
if($$.field && (soul = Gun.is.soul(node[$$.field]))){
|
if($$.field && (soul = Gun.is.soul(node[$$.field]))){ // if the chain is on a field, then check if the field value is a relation.
|
||||||
(ctx[$$.soul + $$.field] || {off:function(){}}).off();
|
(ctx[$$.soul + $$.field] || {off:function(){}}).off(); // why do we do this again? To prevent recursion or something I think?
|
||||||
ctx[$$.soul + $$.field] = gun.__.on(soul).event(function(delta){
|
ctx[$$.soul + $$.field] = gun.__.on(soul).event(function(delta){ // subscribe to root level emitter for the soul.
|
||||||
ctx[$.soul](delta, {soul: soul, field: null, at: $.field});
|
ctx[$$.soul](delta, {soul: soul, field: null, from: $$.soul, at: $$.field}); // call ourselves.
|
||||||
});
|
});
|
||||||
// TODO: do we need to load it? what about that $.gun context?
|
// TODO: do we need to load it? what about that $.gun context?
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if(opt.raw){ return cb.call($$.gun || gun, $$, delta, this) } // if raw events are requested, call it with all the pieces we have.
|
||||||
if(opt.raw){ return cb.call($$.gun || gun, $$, delta, this) }
|
if(!opt.end && Gun.obj.empty(delta, Gun._.meta)){ return } // ignore end nodes unless otherwise requested.
|
||||||
if(!opt.end && Gun.obj.empty(delta, Gun._.meta)){ return }
|
if($$.key){ node = Gun.union.pseudo($.key, gun.__.key.s[$.key]) || node } // if there are multiple nodes on the key, then do a pseudo union across all nodes in the key graph to create a temporary abstract form of the data.
|
||||||
if($$.key){ node = Gun.union.pseudo($.key, gun.__.key.s[$.key]) || node }
|
if(opt.change){ node = delta || node } // if we want only the change, prioritize the delta over the node.
|
||||||
if(opt.change){ node = delta || node }
|
cb.call($$.gun || gun, Gun.obj.copy($$.field? node[$$.field] : node), $$.field || $$.at); // call the callback with a snapshot of the data!
|
||||||
cb.call($$.gun || gun, Gun.obj.copy($$.field? node[$$.field] : node), $$.field || $$.at);
|
}).call(this, gun.__.graph[$.soul], $); // call ourselves immediately!
|
||||||
})(gun.__.graph[$.soul], $);
|
if(!opt.once){ gun.__.on($.soul).event(ctx[$.soul]) } // and finally, actually subscribe to the root level emitter for this soul.
|
||||||
if(!opt.once){ gun.__.on($.soul).event(ctx[$.soul]) }
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -642,7 +647,7 @@
|
|||||||
env.graph[at.node._[Gun._.soul] = at.soul] = at.node;
|
env.graph[at.node._[Gun._.soul] = at.soul] = at.node;
|
||||||
cb(at, at.soul);
|
cb(at, at.soul);
|
||||||
};
|
};
|
||||||
($.empty && !$.field)? path() : chain.back.path(at.path, path, {once: true, end: true}); // TODO: clean this up.
|
($.empty && !$.field)? path() : chain.back.path(at.path || [], path, {once: true, end: true}); // TODO: clean this up.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!at.node._[Gun._.HAM]){
|
if(!at.node._[Gun._.HAM]){
|
||||||
@ -680,14 +685,14 @@
|
|||||||
gun.back.on(function(node){
|
gun.back.on(function(node){
|
||||||
var soul = Gun.is.soul.on(node);
|
var soul = Gun.is.soul.on(node);
|
||||||
console.log("chain.map", node, '\n');
|
console.log("chain.map", node, '\n');
|
||||||
Gun.obj.map(node, function(val, field){ // maybe filter against known fields.
|
Gun.is.node(node, function(val, field){ // maybe filter against known fields. Huh?
|
||||||
if(Gun._.meta == field){ return }
|
|
||||||
var s = Gun.is.soul(val);
|
var s = Gun.is.soul(val);
|
||||||
if(s){
|
if(s){
|
||||||
gun.get(val).on(function(d, f){
|
// TODO: BUG? What if we re-assign a field to a different soul or value? Shouldn't we disable the previous listener? Do we need to check if we already made a listener so we don't recursively add up more and more listeners that get called? Etc. ?
|
||||||
|
ctx[s] = gun.get(val).on(function(d, f){
|
||||||
cb.call(this, d, f || field);
|
cb.call(this, d, f || field);
|
||||||
gun._.at('soul').emit({soul: s, field: null, from: soul, at: field, MAP: 'SOUL', gun: this})
|
gun._.at('soul').emit({soul: s, field: null, from: soul, at: field, MAP: 'SOUL', gun: this})
|
||||||
}); //, {once: true});
|
});
|
||||||
} else {
|
} else {
|
||||||
if(opt.node){ return } // {node: true} maps over only sub nodes.
|
if(opt.node){ return } // {node: true} maps over only sub nodes.
|
||||||
cb.call(this, val, field);
|
cb.call(this, val, field);
|
||||||
@ -718,13 +723,13 @@
|
|||||||
gun._.at('null').once(function($){
|
gun._.at('null').once(function($){
|
||||||
if($.key && ($.soul || gun.__.key.s[$.key])){ return }
|
if($.key && ($.soul || gun.__.key.s[$.key])){ return }
|
||||||
if($.field && Gun.obj.has(gun.__.graph[$.soul], $.field)){ return }
|
if($.field && Gun.obj.has(gun.__.graph[$.soul], $.field)){ return }
|
||||||
// TODO! BUG? Removed a start flag check and tests passed, but is that an edge case?
|
// TODO: BUG? Removed a start flag check and tests passed, but is that an edge case?
|
||||||
var kick = function(next){
|
var kick = function(next){
|
||||||
if(++c){ return Gun.log("Warning! Multiple `not` resumes!"); }
|
if(++c){ return Gun.log("Warning! Multiple `not` resumes!"); }
|
||||||
next._.at('soul').once(function($){ $.N0T = 'KICK SOUL'; gun._.at('soul').emit($) });
|
next._.at('soul').once(function($){ $.N0T = 'KICK SOUL'; gun._.at('soul').emit($) });
|
||||||
}, chain = gun.chain(), next = cb.call(chain, opt.raw? $ : ($.field || $.key), kick), c = -1;
|
}, chain = gun.chain(), next = cb.call(chain, opt.raw? $ : ($.field || $.key), kick), c = -1;
|
||||||
if(Gun.is(next)){ kick(next) }
|
if(Gun.is(next)){ kick(next) }
|
||||||
gun.__.graph[kick.soul = Gun.roulette.call(chain)] = gun.__.graph[kick.soul] || Gun.union.pseudo(kick.soul); // TODO: refactor Gun.roulette
|
gun.__.graph[kick.soul = $.soul || Gun.roulette.call(chain)] = gun.__.graph[kick.soul] || Gun.union.pseudo(kick.soul); // TODO: refactor Gun.roulette
|
||||||
chain._.at('soul').emit({soul: kick.soul, empty: true, key: $.key, field: $.field, N0T: 'SOUL', WAS: 'ON'}); // WAS ON!
|
chain._.at('soul').emit({soul: kick.soul, empty: true, key: $.key, field: $.field, N0T: 'SOUL', WAS: 'ON'}); // WAS ON!
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1100,7 +1105,7 @@
|
|||||||
// TODO: Re-emit message to other peers if we have any non-overlaping ones.
|
// TODO: Re-emit message to other peers if we have any non-overlaping ones.
|
||||||
if(tab.server.put.key(req, cb)){ return }
|
if(tab.server.put.key(req, cb)){ return }
|
||||||
if(Gun.is.node(req.body) || Gun.is.graph(req.body, function(node, soul){
|
if(Gun.is.node(req.body) || Gun.is.graph(req.body, function(node, soul){
|
||||||
gun.__.flag.end[soul] = true; // TODO! Put this in CORE not in TAB driver?
|
gun.__.flag.end[soul] = true; // TODO: Put this in CORE not in TAB driver?
|
||||||
})){
|
})){
|
||||||
//console.log("tran.put", req.body);
|
//console.log("tran.put", req.body);
|
||||||
if(req.err = Gun.union(gun, req.body, function(err, ctx){
|
if(req.err = Gun.union(gun, req.body, function(err, ctx){
|
||||||
@ -1227,7 +1232,7 @@
|
|||||||
cb(res);
|
cb(res);
|
||||||
cb.id = js.id;
|
cb.id = js.id;
|
||||||
js.parentNode.removeChild(js);
|
js.parentNode.removeChild(js);
|
||||||
window[cb.id] = null; // TODO! BUG: This needs to handle chunking!
|
window[cb.id] = null; // TODO: BUG: This needs to handle chunking!
|
||||||
try{delete window[cb.id];
|
try{delete window[cb.id];
|
||||||
}catch(e){}
|
}catch(e){}
|
||||||
}
|
}
|
||||||
|
@ -127,12 +127,12 @@
|
|||||||
return cb({headers: reply.headers, body: (err? (err.err? err : {err: err || "Unknown error."}) : null)});
|
return cb({headers: reply.headers, body: (err? (err.err? err : {err: err || "Unknown error."}) : null)});
|
||||||
}
|
}
|
||||||
if(Gun.obj.empty(graph)){ return cb({headers: reply.headers, body: graph}) } // we're out of stuff!
|
if(Gun.obj.empty(graph)){ return cb({headers: reply.headers, body: graph}) } // we're out of stuff!
|
||||||
|
|
||||||
/*
|
/*
|
||||||
(function(chunks){// FEATURE! Stream chunks if the nodes are large!
|
(function(chunks){// FEATURE! Stream chunks if the nodes are large!
|
||||||
var max = 10;
|
var max = 10;
|
||||||
Gun.is.graph(graph, function(node, soul){
|
Gun.is.graph(graph, function(node, soul){
|
||||||
var chunk = {};
|
var chunk = {};
|
||||||
console.log("node big enough?", Object.keys(node).length);
|
|
||||||
if(Object.keys(node).length > max){
|
if(Object.keys(node).length > max){
|
||||||
var count = 0, n = Gun.union.pseudo(soul);
|
var count = 0, n = Gun.union.pseudo(soul);
|
||||||
Gun.obj.map(node, function(val, field){
|
Gun.obj.map(node, function(val, field){
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gun",
|
"name": "gun",
|
||||||
"version": "0.2.3",
|
"version": "0.2.4",
|
||||||
"description": "Graph engine",
|
"description": "Graph engine",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -2145,5 +2145,29 @@ describe('Gun', function(){
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('val emits all data', function(done){ // bug in chat app
|
||||||
|
var chat = Gun().get('example/chat/data').not(function(){
|
||||||
|
return this.put({1: {who: 'Welcome', what: "to the chat app!", when: 0}}).key('example/chat/data');
|
||||||
|
});
|
||||||
|
chat.set({who: 'mark', what: "1", when: 1});
|
||||||
|
chat.set({who: 'mark', what: "2", when: 2});
|
||||||
|
chat.set({who: 'mark', what: "3", when: 3});
|
||||||
|
chat.set({who: 'mark', what: "4", when: 4});
|
||||||
|
chat.set({who: 'mark', what: "5", when: 5});
|
||||||
|
var seen = {1: false, 2: false, 3: false, 4: false, 5: false}
|
||||||
|
setTimeout(function(){
|
||||||
|
chat.map(function(m){ /*console.log("MAP:", m)*/ }).val(function(msg, field){
|
||||||
|
var msg = Gun.obj.copy(msg);
|
||||||
|
if(msg.what){
|
||||||
|
expect(msg.what).to.be.ok();
|
||||||
|
seen[msg.when] = true;
|
||||||
|
}
|
||||||
|
if(!Gun.obj.map(seen, function(boo){ if(!boo){ return true } })){
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
11702
test/mocha.js
11702
test/mocha.js
File diff suppressed because it is too large
Load Diff
@ -1,29 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
|
|
||||||
<script src="../gun.js"></script>
|
|
||||||
<script src="../lib/nts.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="now" style="text-align: center; font-size: 10vmin; line-height: 95vh;">
|
|
||||||
Hello!
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
var gun = Gun('http://localhost:8080/gun');
|
|
||||||
var $now = $('#now');
|
|
||||||
|
|
||||||
window.frame = function(cb){
|
|
||||||
var requestAnimationFrame = window.requestAnimationFrame || function(cb){setTimeout(cb,0)}
|
|
||||||
requestAnimationFrame(cb);
|
|
||||||
}
|
|
||||||
window.frame(function frame(){
|
|
||||||
//window.frame(frame);
|
|
||||||
var d = new Date()
|
|
||||||
var s = d.getFullYear() + '/' + (d.getMonth() + 1) + '/' + d.getDate() + '/' +
|
|
||||||
(d.getHours() + 1) + ':' + d.getSeconds() + '.' + d.getMilliseconds();
|
|
||||||
$now.text(s);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Loading…
x
Reference in New Issue
Block a user