ready to be rolling soon

This commit is contained in:
Mark Nadal 2015-06-03 21:57:12 -07:00
parent e89c19cdd8
commit 3e6fc92949
10 changed files with 580 additions and 2260 deletions

View File

@ -10,7 +10,7 @@
} }
</style> </style>
<a href="todo/index.html"><iframe src="todo/index.html"></iframe></a> <a href="todo/index.html"><iframe src="todo/index.html"></iframe></a>
<a href="angular/index.html"><iframe src="angular/index.html"></iframe></a> <a href="json/index.html"><iframe src="json/index.html"></iframe></a>
<script src="../../gun.js"></script> <script src="../../gun.js"></script>
</body> </body>
</html> </html>

75
examples/json/index.html Normal file
View File

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="../../gun.js"></script>
</head>
<body>
<h3>Admin JSON Editor</h3>
This is a live view of your data, you can edit it in realtime or add new field/values.
<ul id="data">
</ul>
<li id="model">
<b>field</b>:
<span contenteditable="true">val</span>
</li>
<ul><li>
<form id="form" onsubmit="add()">
<label>
<input id="field" placeholder="field">
<button type="submit">add</button>
</label>
</form>
</li></ul>
<script>
var ref = Gun(location.origin + '/gun').get('example/json/data');
ref.not(function(){
return this.put({hello: "world!"}).key('example/json/data');
}).on(function(data){
for(var field in data){
if(field === '_'){ continue } // skip meta data!
var val = (data[field = field || ''] || '').toString(), id = field.replace(/[^A-z]/ig, ''), elem; // make data safe.
(elem = $('#' + id) || $('#data').appendChild($('#model').cloneNode(true))).id = id; // reuse or make element, set id.
$('b', elem).innerHTML = field.replace(/\</ig, '&lt;'); // escape and display field
$('span', elem).innerHTML = val.replace(/\</ig, '&lt;'); // escape and display value
}
});
var $ = function(s, elem){ return (elem || document).querySelector(s) } // make native look like jQuery.
document.onkeyup = function(e){
if(!e || !e.target){ return } // ignore if no element!
if(!e.target.attributes.contenteditable){ return } // ignore if element content isn't editable!
ref.path((e.target.previousElementSibling.innerHTML||'').toString().replace(/\</ig, '&lt;')) // grab the label, which is in the previous element.
.put( (e.target.innerHTML||'').toString().replace(/\</ig, '&lt;') ); // insert the value of the text in the current element.
}
$('#form').onsubmit = function add(e){
return ref.path($('#field').value || '').put("value"), false; // add a new field, and cancel the form submit.
}
</script>
<style>
html, body {
font-family: Verdana, Geneva, sans-serif;
}
a, button {
border: none;
color: skyblue;
background: transparent;
text-decoration: none;
cursor: poiner;
}
ul, li {
list-style-type: none;
}
ul:hover, li:hover {
list-style-type: inherit;
}
input {
border: none;
border-bottom: dashed 1px gainsboro;
}
.none, #model {
display: none;
}
</style>
</body>
</html>

View File

@ -9,10 +9,11 @@
<script> <script>
// by Forrest Tait! Edited by Mark Nadal. // by Forrest Tait! Edited by Mark Nadal.
function ready(){ function ready(){
Gun.log.verbose = true;
var $ = document.querySelector.bind(document); var $ = document.querySelector.bind(document);
var gun = Gun(location.origin + '/gun').get('example/todo/data'); var gun = Gun(location.origin + '/gun').get('example/todo/data');
gun.on(function renderToDo(val){ gun.not(function(){
return this.put({hello: "world!"}).key('example/todo/data');
}).on(function renderToDo(val){
var todoHTML = ''; var todoHTML = '';
for(key in val) { for(key in val) {
if(!val[key] || key == '_') continue; if(!val[key] || key == '_') continue;

248
gun.js
View File

@ -55,7 +55,7 @@
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){ // need to 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 this.
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) } if(cb){ cb(value, field, node._) }
}); });
} }
return false; return false;
@ -70,8 +70,8 @@
}) && exist; }) && exist;
} }
// 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){ Gun.union = function(gun, prime, cb){
var ctx = {}; 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!") } }
if(!prime){ ctx.err = {err: Gun.log("No data to merge!") } } if(!prime){ ctx.err = {err: Gun.log("No data to merge!") } }
@ -80,16 +80,22 @@
ctx.tmp[ctx.soul] = prime; ctx.tmp[ctx.soul] = prime;
prime = ctx.tmp; prime = ctx.tmp;
} }
Gun.is.graph(prime, null, function(val, field, meta){
if(!meta || !(meta = meta[Gun._.HAM]) || !Gun.num.is(meta[field])){
return ctx.err = {err: Gun.log("No state on " + field + "!") }
}
});
if(ctx.err){ return ctx } if(ctx.err){ return ctx }
(function union(graph, prime){ (function union(graph, prime){
Gun.obj.map(prime, function(node, soul){ Gun.obj.map(prime, function(node, soul){
soul = Gun.is.soul.on(node); soul = Gun.is.soul.on(node);
if(!soul){ return } if(!soul){ return }
var vertex = graph[soul]; var vertex = graph[soul];
if(!vertex){ // disjoint if(!vertex){ // disjoint // TODO: Maybe not correct? BUG, probably.
gun.__.on(soul).emit(graph[soul] = node); gun.__.on(soul).emit(graph[soul] = node);
return; return;
} }
ctx.count += 1;
Gun.HAM(vertex, node, function(){}, function(vertex, field, value){ Gun.HAM(vertex, node, function(){}, function(vertex, field, value){
if(!vertex){ return } if(!vertex){ return }
var change = {}; var change = {};
@ -101,27 +107,27 @@
//context.nodes[change._[Gun._.soul]] = change; //context.nodes[change._[Gun._.soul]] = change;
//context('change').fire(change); //context('change').fire(change);
gun.__.on(Gun.is.soul.on(change)).emit(change); gun.__.on(Gun.is.soul.on(change)).emit(change);
}, function(){ }, function(){})(function(){
if(!(ctx.count -= 1)){ ctx.cb() }
}); });
}); });
})(ctx.graph, prime); })(ctx.graph, prime);
if(!ctx.count){ ctx.cb() }
return ctx; return ctx;
} }
Gun.HAM = function(vertex, delta, lower, each, upper){ Gun.HAM = function(vertex, delta, lower, now, upper){
upper.max = -Infinity;
Gun.obj.map(delta, function update(incoming, field){ Gun.obj.map(delta, function update(incoming, field){
if(field === Gun._.meta){ return } if(field === Gun._.meta){ return }
if(!Gun.obj.has(vertex, field)){ // does not need to be applied through HAM
each.call({incoming: true, converge: true}, vertex, field, incoming);
}
var ctx = {incoming: {}, current: {}}, state; var ctx = {incoming: {}, current: {}}, state;
ctx.drift = Gun.time.is(); ctx.drift = Gun.time.is();
ctx.incoming.value = Gun.is.soul(incoming) || incoming; ctx.incoming.value = Gun.is.soul(incoming) || incoming;
ctx.current.value = Gun.is.soul(vertex[field]) || vertex[field] ctx.current.value = Gun.is.soul(vertex[field]) || vertex[field];
ctx.incoming.state = ((delta._||{})[Gun._.HAM]||{})[field] || 0; ctx.incoming.state = Gun.num.is(ctx.tmp = ((delta._||{})[Gun._.HAM]||{})[field])? ctx.tmp : -Infinity;
ctx.current.state = ((vertex._||{})[Gun._.HAM]||{})[field] || 0; ctx.current.state = Gun.num.is(ctx.tmp = ((vertex._||{})[Gun._.HAM]||{})[field])? ctx.tmp : -Infinity;
upper.max = ctx.incoming.state > upper.max? ctx.incoming.state : upper.max;
state = HAM(ctx.drift, ctx.incoming.state, ctx.current.state, ctx.incoming.value, ctx.current.value); state = HAM(ctx.drift, ctx.incoming.state, ctx.current.state, ctx.incoming.value, ctx.current.value);
//console.log("HAM:", ctx); //root.console.log("HAM:", ctx);
if(state.err){ if(state.err){
root.console.log(".!HYPOTHETICAL AMNESIA MACHINE ERR!.", state.err); // this error should never happen. root.console.log(".!HYPOTHETICAL AMNESIA MACHINE ERR!.", state.err); // this error should never happen.
return; return;
@ -131,13 +137,15 @@
return; return;
} }
if(state.incoming){ if(state.incoming){
each.call(state, vertex, field, incoming); now.call(state, vertex, field, incoming);
return; return;
} }
if(state.amnesiaQuarantine){ if(state.amnesiaQuarantine){
upper.wait = true;
upper.call(state, vertex, field, incoming); // signals that there are still future modifications.
Gun.schedule(ctx.incoming.state, function(){ Gun.schedule(ctx.incoming.state, function(){
update(incoming, field); update(incoming, field);
upper.call(state, vertex, field, incoming); if(ctx.incoming.state === upper.max){ upper.last() }
}); });
} }
}); });
@ -175,15 +183,10 @@
} }
return {err: "you have not properly handled recursion through your data or filtered it as JSON"}; return {err: "you have not properly handled recursion through your data or filtered it as JSON"};
} }
} return function(fn){
Gun.when = function(gun, cb){ // how much memory will this consume? upper.last = fn || function(){};
var setImmediate = setImmediate || function(cb){return setTimeout(cb,0)}; if(!upper.wait){ upper.last() }
Gun.obj.map(gun._.graph, function(on, soul){ }
setImmediate(function(){ cb.apply(on, on.args) });
});
gun._.on('soul').event(function(soul){
cb.apply((gun._.graph = gun._.graph || {})[this.soul = soul] = this, this.args = arguments);
});
} }
Gun.roulette = function(l, c){ Gun.roulette = function(l, c){
var gun = Gun.is(this)? this : {}; var gun = Gun.is(this)? this : {};
@ -227,7 +230,25 @@
from = from || this; from = from || this;
gun.back = from; gun.back = from;
gun.__ = from.__; gun.__ = from.__;
gun._ = {on: Gun.on.create() }; gun._ = {on: Gun.on.create()};
gun._.status = function(e){
var proxy = function(chain, cb){
return Gun.obj.map(gun._.graph, function(on, soul){
setImmediate(function(){ cb.call(on, on.status) });
return on; // TODO: BUG! What about plural graphs?
}) || gun._.on(e)[chain](function(status){
if(status){ (gun._.graph = gun._.graph || {})[status.soul] = this }
cb.call(this, this.status = status);
});
}
proxy.event = function(cb){ return proxy('event', cb) };
proxy.once = function(cb){ return proxy('once', cb) };
proxy.emit = function(){
var args = arguments;
setImmediate(function(me){ (me = gun._.on(e)).emit.apply(me, args) })
};
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.
@ -238,46 +259,46 @@
cb = cb || function(){}; cb = cb || function(){};
opt = opt || {}; opt = opt || {};
if(ctx.soul){ if(ctx.soul){
Gun.fns.async(function(){ open(ctx.soul) }); gun._.status('soul').emit({soul: ctx.soul});
if(ctx.node = gun.__.graph[ctx.soul]){ // in memory if(ctx.node = gun.__.graph[ctx.soul]){ // in memory
cb.call(gun, null, Gun.obj.copy(ctx.node)); cb.call(gun, null, Gun.obj.copy(ctx.node));
gun._.status('node').emit({soul: ctx.soul});
} else { load(key) } // not in memory } else { load(key) } // not in memory
} else } else
if(ctx.key){ if(ctx.key){
(function foo(){ // TODO: JANKY! UGLY!!!! (function foo(){ // TODO: JANKY! UGLY!!!! Can resolve as soon as the object exists.
if(ctx.node = gun.__.keys[ctx.key]){ // in memory, or from put.key if(ctx.node = gun.__.keys[ctx.key]){ // in memory, or from put.key
if(true === ctx.node){ if(true === ctx.node){
Gun.fns.async(foo); setTimeout(foo,0);
} else { } else {
cb.call(gun, null, Gun.obj.copy(ctx.node)); cb.call(gun, null, Gun.obj.copy(ctx.node));
Gun.fns.async(function(){ open(Gun.is.soul.on(ctx.node)) }); var soul = Gun.is.soul.on(ctx.node);
gun._.status('soul').emit({soul: soul});
gun._.status('node').emit({soul: soul});
} }
} else { load(key) } // not in memory } else { load(key) } // not in memory
})(); })();
} 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 open(soul){
if(!soul || ctx.open){ return }
ctx.open = true;
gun._.on('soul').emit(soul);
}
function load(key){ function load(key){
if(Gun.fns.is(ctx.hook = gun.__.opt.hooks.get)){ if(Gun.fns.is(ctx.hook = gun.__.opt.hooks.get)){
ctx.hook(key, function(err, data){ // multiple times potentially ctx.hook(key, function(err, data){ // multiple times potentially
//console.log("chain.get from load", err, data); //console.log("chain.get from load", err, data);
if(err){ return cb.call(gun, err, data) } if(err){ return cb.call(gun, err, data) }
if(!data){ return cb.call(gun, null) } // TODO: will have have `not` be based on open? if(!data){ return cb.call(gun, null, null), gun._.status('null').emit() }
if(ctx.soul = Gun.is.soul.on(data)){ if(ctx.soul = Gun.is.soul.on(data)){
open(ctx.soul); gun._.status('soul').emit({soul: ctx.soul});
} else { return cb.call(gun, {err: Gun.log('No soul on data!') }, data) } } else { return cb.call(gun, {err: Gun.log('No soul on data!') }, data) }
if(err = Gun.union(gun, data).err){ return cb.call(gun, err) } if(err = Gun.union(gun, data).err){ return cb.call(gun, err) }
cb.call(gun, null, data); cb.call(gun, null, data);
gun._.status('node').emit({soul: ctx.soul});
}, opt); }, opt);
} else { } else {
root.console.log("Warning! You have no persistence layer to get from!"); root.console.log("Warning! You have no persistence layer to get from!");
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._.status('null').emit();
} }
} }
return gun; return gun;
@ -288,14 +309,12 @@
cb = cb || function(){}; cb = cb || function(){};
opt = opt || {}; opt = opt || {};
gun.__.keys[key] = true; gun.__.keys[key] = true;
Gun.when(gun, function(soul){ gun._.status('soul').event(function($){ // TODO: once per soul in graph. (?)
Gun.fns.async(function wait(node){ // TODO: UGLY!!! JANKY!!! gun._.status('node').once(function($){
node = gun.__.graph[soul]; gun.__.keys[key] = gun.__.graph[$.soul];
if(true === node){ return Gun.fns.async(wait) }
gun.__.keys[key] = node;
}); });
if(Gun.fns.is(ctx.hook = gun.__.opt.hooks.key)){ if(Gun.fns.is(ctx.hook = gun.__.opt.hooks.key)){
ctx.hook(key, soul, function(err, data){ ctx.hook(key, $.soul, function(err, data){
return cb.call(gun, err, data); return cb.call(gun, err, data);
}, opt); }, opt);
} else { } else {
@ -307,6 +326,7 @@
} }
Chain.all = function(key, cb){ Chain.all = function(key, cb){
var gun = this.chain(); var gun = this.chain();
return gun; // TODO: BUG! We need to create all!
cb = cb || function(){}; cb = cb || function(){};
gun.shot.next(function(next){ gun.shot.next(function(next){
Gun.obj.map(gun.__.keys, function(node, key){ // TODO: BUG!! Need to handle souls too! Gun.obj.map(gun.__.keys, function(node, key){ // TODO: BUG!! Need to handle souls too!
@ -349,21 +369,16 @@
var gun = this.chain(), ctx = {}; var gun = this.chain(), ctx = {};
cb = cb || function(){}; cb = cb || function(){};
path = (Gun.text.ify(path) || '').split('.'); path = (Gun.text.ify(path) || '').split('.');
//gun.back._.on('soul').event(function trace(soul){ // TODO: Check for field as well and merge? // TODO: Hmmm once also? figure it out later.
Gun.when(gun.back, function trace(soul){ // TODO: Check for field as well and merge? gun.back._.status('node').event(function trace($){ // TODO: Check for field as well and merge?
var node = gun.__.graph[soul], field = node && Gun.text.ify(path.shift()), val; var node = gun.__.graph[$.soul], field = Gun.text.ify(path.shift()), val;
console.log("path...", soul, field, node);
if(!node){ // handle later
return Gun.fns.async(function(){ // TODO: UGLY!!! JANKY!!!
trace(soul);
});
} else
if(path.length){ if(path.length){
if(Gun.is.soul(val = node[field])){ if(Gun.is.soul(val = node[field])){
//root.console.log('path RECURSION', field);
gun.get(val, function(err, data){ gun.get(val, function(err, data){
if(err){ return cb.call(gun, err, data) } if(err){ return cb.call(gun, err, data) }
if(!data){ return cb.call(gun, null) } if(!data){ return cb.call(gun, null) }
trace(Gun.is.soul.on(data)); trace({soul: Gun.is.soul.on(data)});
}); });
} else { } else {
cb.call(gun, null); cb.call(gun, null);
@ -371,14 +386,20 @@
} else } else
if(!Gun.obj.has(node, field)){ // TODO: THIS MAY NOT BE CORRECT BEHAVIOR!!!! if(!Gun.obj.has(node, field)){ // TODO: THIS MAY NOT BE CORRECT BEHAVIOR!!!!
cb.call(gun, null, null, field); cb.call(gun, null, null, field);
gun._.on('soul').emit(soul, field); // if .put is after, makes sense. If anything else, makes sense to wait. gun._.on('soul').emit({soul: $.soul, field: field}); // if .put is after, makes sense. If anything else, makes sense to wait.
gun._.on('node').emit({soul: $.soul, field: field});
} else } else
if(Gun.is.soul(val = node[field])){ if(Gun.is.soul(val = node[field])){
gun.get(val, cb); gun.get(val, function(err, data){
gun._.on('soul').emit(Gun.is.soul(val), null, soul, field); cb.call(gun, err, data);
if(err || !data){ return }
gun._.status('node').emit({soul: Gun.is.soul(val)});
});
gun._.on('soul').emit({soul: Gun.is.soul(val), field: null, from: $.soul, at: field});
} else { } else {
cb.call(gun, null, val, field); cb.call(gun, null, val, field);
gun._.on('soul').emit(soul, field); gun._.on('soul').emit({soul: $.soul, field: field});
gun._.on('node').emit({soul: $.soul, field: field});
} }
}); });
@ -386,14 +407,11 @@
} }
Chain.val = function(cb){ Chain.val = function(cb){
var gun = this, ctx = {}; var gun = this, ctx = {};
cb = cb || function(){}; cb = cb || root.console.log.bind(root.console);
Gun.when(gun, function(soul, field){ gun._.status('node').event(function($){ // TODO: once per soul on graph. (?)
Gun.fns.async(function wait(node){ // TODO: UGLY!!! JANKY!!! var node = gun.__.graph[$.soul];
node = gun.__.graph[soul]; cb.call(gun, $.field? node[$.field] : Gun.obj.copy(node)); // TODO: at terminating
if(!node || true === node){ return Gun.fns.async(wait) }
cb.call(gun, field? node[field] : Gun.obj.copy(node));
});
}); });
return gun; return gun;
@ -403,10 +421,12 @@
var gun = this, ctx = {}; var gun = this, ctx = {};
cb = cb || function(){}; cb = cb || function(){};
Gun.when(gun, function(soul, field){ // TODO: below is also probably going to be on node.
gun.__.on(soul).event(function(delta){ gun.val(cb)._.status('soul').event(function($){ // TODO: once per soul on graph. (?)
var node = gun.__.graph[soul]; // TODO: Don't use val :(, but trigger callback now as well.
cb.call(gun, Gun.obj.copy(node), field); gun.__.on($.soul).event(function(delta){
var node = gun.__.graph[$.soul];
cb.call(gun, Gun.obj.copy(node), $.field);
}); });
}); });
@ -424,40 +444,37 @@
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.put = function(val, cb, opt){ // handle case where val is a gun context! Chain.put = function(val, cb, opt){ // handle case where val is a gun context!
var gun = this.chain(), drift = Gun.time.is(), flag; var gun = this.chain(), drift = Gun.time.is();
cb = cb || function(){}; cb = cb || function(){};
opt = opt || {}; opt = opt || {};
if(!gun.back.back){ if(!gun.back.back){
gun = gun.chain(); gun = gun.chain();
Gun.fns.async(function(){ gun.back._.status('soul').emit({soul: Gun.is.soul.on(val) || Gun.roulette.call(gun), empty: true});
flag = true;
gun.back._.on('soul').emit(Gun.is.soul.on(val) || Gun.roulette.call(gun));
});
} }
Gun.when(gun.back, function(soul, field, from, at){ gun.back._.status('soul').event(function($){ // TODO: maybe once per soul?
//console.log("chain.put", field, val, "on", soul, 'or', from, at); var ctx = {}, obj = val, $ = Gun.obj.copy($);
var ctx = {}, obj = val; console.log("chain.put", val, $);
if(Gun.is.value(obj)){ if(Gun.is.value(obj)){
if(from && at){ if($.from && $.at){
soul = from; $.soul = $.from;
field = at; $.field = $.at;
} // no else! } // no else!
if(!field){ if(!$.field){
return cb.call(gun, {err: Gun.log("No field exists for " + (typeof obj) + "!")}); return cb.call(gun, {err: Gun.log("No field exists for " + (typeof obj) + "!")});
} else } else
if(gun.__.graph[soul]){ if(gun.__.graph[$.soul]){
ctx.tmp = {}; ctx.tmp = {};
ctx.tmp[ctx.field = field] = obj; ctx.tmp[ctx.field = $.field] = obj;
obj = ctx.tmp; obj = ctx.tmp;
} else { } else {
return cb.call(gun, {err: Gun.log("No node exists to put " + (typeof obj) + " in!")}); return cb.call(gun, {err: Gun.log("No node exists to put " + (typeof obj) + " in!")});
} }
} }
if(Gun.obj.is(obj)){ if(Gun.obj.is(obj)){
if(field && !ctx.field){ if($.field && !ctx.field){
ctx.tmp = {}; ctx.tmp = {};
ctx.tmp[ctx.field = field] = obj; ctx.tmp[ctx.field = $.field] = obj;
obj = ctx.tmp; obj = ctx.tmp;
} }
Gun.ify(obj, function(env, cb){ Gun.ify(obj, function(env, cb){
@ -468,10 +485,10 @@
} }
if(!Gun.is.soul.on(at.node)){ if(!Gun.is.soul.on(at.node)){
if(obj === at.obj){ if(obj === at.obj){
env.graph[at.node._[Gun._.soul] = soul] = at.node; env.graph[at.node._[Gun._.soul] = $.soul] = at.node;
cb(at, soul); cb(at, $.soul);
} else { } else {
flag? path() : gun.back.path(at.path.join('.'), path); // TODO: clean this up. $.empty? path() : gun.back.path(at.path.join('.'), path); // TODO: clean this up.
function path(err, data){ function path(err, data){
var soul = Gun.is.soul.on(data) || Gun.roulette.call(gun); var soul = Gun.is.soul.on(data) || Gun.roulette.call(gun);
env.graph[at.node._[Gun._.soul] = soul] = at.node; env.graph[at.node._[Gun._.soul] = soul] = at.node;
@ -485,11 +502,12 @@
if(!at.field){ return } if(!at.field){ return }
at.node._[Gun._.HAM][at.field] = drift; at.node._[Gun._.HAM][at.field] = drift;
})(function(err, ify){ })(function(err, ify){
console.log("chain.put PUT", ify.graph); console.log("chain.put PUT <----", ify.graph);
if(err || ify.err){ return cb.call(gun, err || ify.err) } if(err || ify.err){ return cb.call(gun, err || ify.err) }
if(err = Gun.union(gun, ify.graph).err){ return cb.call(gun, err) } if(err = Gun.union(gun, ify.graph).err){ return cb.call(gun, err) }
if(from = Gun.is.soul(ify.root[field])){ soul = from; field = null } if($.from = Gun.is.soul(ify.root[$.field])){ $.soul = $.from; $.field = null }
gun._.on('soul').emit(soul, field); gun._.on('soul').emit({soul: $.soul, field: $.field, candy: true});
gun._.on('node').emit({soul: $.soul, field: $.field, barf: false});
if(Gun.fns.is(ctx.hook = gun.__.opt.hooks.put)){ if(Gun.fns.is(ctx.hook = gun.__.opt.hooks.put)){
ctx.hook(ify.graph, function(err, data){ // now iterate through those nodes to a persistence layer and get a callback once all are saved ctx.hook(ify.graph, 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) }
@ -524,16 +542,17 @@
var ahead = gun.chain(); return ahead; var ahead = gun.chain(); return ahead;
return gun; return gun;
} }
Chain.not = function(not){ Chain.not = function(cb){
var gun = this; var gun = this, ctx = {};
not = not || function(){}; cb = cb || function(){};
gun.shot.next(function(next){
if(gun._.node){ // if it does indeed exist gun._.status('null').once(function(){
return next(); // yet fire off the chain var chain = gun.chain(), next = cb.call(chain);
} next._.status('soul').event(function($){ gun._.on('soul').emit($) });
not.call(gun); // call not next._.status('node').event(function($){ gun._.on('node').emit($) });
next(); // fire off the chain chain._.on('soul').emit({soul: Gun.roulette.call(chain), empty: true});
}); });
return gun; return gun;
} }
Chain.err = function(dud){ // WARNING: dud was depreciated. Chain.err = function(dud){ // WARNING: dud was depreciated.
@ -544,10 +563,6 @@
;(function(Util){ ;(function(Util){
Util.fns = {}; Util.fns = {};
Util.fns.is = function(fn){ return (fn instanceof Function)? true : false } Util.fns.is = function(fn){ return (fn instanceof Function)? true : false }
Util.fns.async = (function(){
var setImmediate = setImmediate || function(cb){return setTimeout(cb,0)};
return setImmediate;
})();
Util.fns.sum = function(done){ // combine with Util.obj.map for some easy parallel async operations! Util.fns.sum = function(done){ // combine with Util.obj.map for some easy parallel async operations!
var context = {task: {}, data: {}}; var context = {task: {}, data: {}};
context.end = function(e,v){ return done(e,v), done = function(){} }; context.end = function(e,v){ return done(e,v), done = function(){} };
@ -748,17 +763,10 @@
function ify(data, cb, opt){ function ify(data, cb, opt){
opt = opt || {}; opt = opt || {};
cb = cb || function(env, cb){ cb(env.at, Gun.roulette()) }; cb = cb || function(env, cb){ cb(env.at, Gun.roulette()) };
var ctx = {}, end = function(fn){ var end = function(fn){
Gun.fns.async(function wait(){ // TODO: JANKY! clean this up, possibly? ctx.end = fn || function(){};
if(ctx.err || !Gun.list.map(ctx.seen, function(at){ if(ctx.err){ return ctx.end(ctx.err, ctx), ctx.end = function(){} }
if(!at.soul){ return true } }, ctx = {};
})){
fn(ctx.err, ctx)
} else {
Gun.fns.async(wait); // TODO: BUG! JANKY!!! Make this cleaner.
}
});
}
if(!data){ return ctx.err = Gun.log('Serializer does not have correct parameters.'), end } if(!data){ return ctx.err = Gun.log('Serializer does not have correct parameters.'), end }
ctx.at = {}; ctx.at = {};
ctx.root = {}; ctx.root = {};
@ -811,6 +819,7 @@
} }
cb(ctx, function(at, soul){ cb(ctx, function(at, soul){
at.soul = at.soul || soul; at.soul = at.soul || soul;
setImmediate(function(){ unique(ctx) },0);
if(!at.back || !at.back.length){ return } if(!at.back || !at.back.length){ return }
Gun.list.map(at.back, function(rel){ Gun.list.map(at.back, function(rel){
rel[Gun._.soul] = at.soul; rel[Gun._.soul] = at.soul;
@ -818,6 +827,11 @@
}); });
}); });
} }
function unique(ctx){
if(ctx.err || !Gun.list.map(ctx.seen, function(at){
if(!at.soul){ return true }
})){ return ctx.end(ctx.err, ctx), ctx.end = function(){} }
}
function seen(ctx, at){ function seen(ctx, at){
return Gun.list.map(ctx.seen, function(has){ return Gun.list.map(ctx.seen, function(has){
if(at.obj === has.obj){ return has } if(at.obj === has.obj){ return has }
@ -830,6 +844,7 @@
} else { } else {
module.exports = Gun; module.exports = Gun;
} }
var setImmediate = setImmediate || function(cb){return setTimeout(cb,0)};
var root = this || {}; // safe for window, global, root, and 'use strict'. var root = this || {}; // safe for window, global, root, and 'use strict'.
root.console = root.console || {log: function(s){ return s }}; // safe for old browsers root.console = root.console || {log: function(s){ return s }}; // safe for old browsers
var console = {log: Gun.log = function(s){return (Gun.log.verbose && root.console.log.apply(root.console, arguments)), s}}; var console = {log: Gun.log = function(s){return (Gun.log.verbose && root.console.log.apply(root.console, arguments)), s}};
@ -917,6 +932,7 @@
}); });
Gun.obj.map(gun.__.opt.peers, function(peer, url){ Gun.obj.map(gun.__.opt.peers, function(peer, url){
request(url, nodes, function(err, reply){ request(url, nodes, function(err, reply){
console.log("PUT success?", err, reply);
if(err || !reply || (err = reply.body && reply.body.err)){ if(err || !reply || (err = reply.body && reply.body.err)){
return cb({err: Gun.log(err || "Error: Put failed on " + url) }); return cb({err: Gun.log(err || "Error: Put failed on " + url) });
} else { } else {
@ -925,7 +941,7 @@
}, {headers: tab.headers}); }, {headers: tab.headers});
cb.peers = true; cb.peers = true;
}); tab.peers(cb); }); tab.peers(cb);
Gun.obj.map(nodes, function(node, soul){ Gun.obj.map(nodes, function(node, soul){ // TODO: BUG? is this really necessary?
gun.__.on(soul).emit(node); gun.__.on(soul).emit(node);
}); });
} }
@ -938,7 +954,7 @@
request.createServer(function(req, res){ request.createServer(function(req, res){
if(!req.body){ return } if(!req.body){ return }
if(Gun.is.node(req.body) || Gun.is.graph(req.body)){ if(Gun.is.node(req.body) || Gun.is.graph(req.body)){
console.log("client server received request", req); Gun.log("client server received request", req);
Gun.union(gun, req.body); // TODO: BUG? Interesting, this won't update localStorage because .put isn't called? Gun.union(gun, req.body); // TODO: BUG? Interesting, this won't update localStorage because .put isn't called?
} }
}); });

View File

@ -380,6 +380,11 @@ html, body {
</div> <!-- END FOLD --> </div> <!-- END FOLD -->
<a href="https://github.com/amark/gun" target="_blank"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"></a> <a href="https://github.com/amark/gun" target="_blank"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"></a>
<script type="text/javascript">
window.heap=window.heap||[],heap.load=function(t,e){window.heap.appid=t,window.heap.config=e;var a=document.createElement("script");a.type="text/javascript",a.async=!0,a.src=("https:"===document.location.protocol?"https:":"http:")+"//cdn.heapanalytics.com/js/heap-"+t+".js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(a,n);for(var o=function(t){return function(){heap.push([t].concat(Array.prototype.slice.call(arguments,0)))}},p=["clearEventProperties","identify","setEventProperties","track","unsetEventProperty"],c=0;c<p.length;c++)heap[p[c]]=o(p[c])};
heap.load("2174172773");
</script>
<!-- BEGIN TUTORIAL --> <!-- BEGIN TUTORIAL -->
<div class="tutorial"> <div class="tutorial">
<script src="https://gunjs.herokuapp.com/gun.js"></script> <script src="https://gunjs.herokuapp.com/gun.js"></script>

View File

@ -127,7 +127,25 @@
var reply = {headers: {'Content-Type': tran.json}}; var reply = {headers: {'Content-Type': tran.json}};
if(!req.body){ return cb({headers: reply.headers, body: {err: "No body"}}) } if(!req.body){ return cb({headers: reply.headers, body: {err: "No body"}}) }
if(tran.put.key(req, cb)){ return } if(tran.put.key(req, cb)){ return }
console.log("tran.put", req.body);
// some NEW code that should get revised.
if(Gun.is.node(req.body) || Gun.is.graph(req.body)){
console.log("tran.put", req.body);
if(req.err = Gun.union(gun, req.body, function(err, ctx){ // TODO: BUG? Probably should give me ctx.graph
if(err){ return cb({headers: reply.headers, body: {err: err || "Union failed."}}) }
var ctx = ctx || {}; ctx.graph = {};
Gun.is.graph(req.body, function(node, soul){
ctx.graph[soul] = gun.__.graph[soul]; // TODO: BUG? Probably should be delta fields
})
gun.__.opt.hooks.put(ctx.graph, function(err, ok){
if(err){ return cb({headers: reply.headers, body: {err: err || "Failed."}}) }
cb({headers: reply.headers, body: {ok: ok || "Persisted."}});
});
}).err){ cb({headers: reply.headers, body: {err: req.err || "Union failed."}}) }
}
gun.server.on('network').emit(req);
return;
//return; //return;
// saving // saving
Gun.obj.map(req.body, function(node, soul){ // iterate over every node Gun.obj.map(req.body, function(node, soul){ // iterate over every node

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -356,40 +356,21 @@ describe('Gun', function(){
}); });
}); });
return; // TODO: COME BACK HERE? it('extraneous', function(done){
it('circular reference', function(done){ var data = {_: {'#': 'shhh', meta: {yay: 1}}, sneak: true};
data = {};
data.a = {x: 1, y: 2, z: 3}
data.b = {m: 'n', o: 'p', q: 'r', s: 't'};
data.a.kid = data.b;
data.b.parent = data.a;
Gun.ify(data)(function(err, ctx){ Gun.ify(data)(function(err, ctx){
console.log("circ ref", err, ctx, 'now see:'); expect(err).to.not.be.ok(); // extraneous metadata needs to be stored, but it can't be used for data.
ctx.seen.forEach(function(val){ console.log(val.node) });
expect(test.err).to.not.be.ok();
done(); done();
}); });
}); });
data = {_: {'#': 'shhh', meta: {yay: 1}}, sneak: true}; return; // TODO! Fix GUN to handle this!
test = Gun.ify(data);
expect(test.err).to.not.be.ok(); // metadata needs to be stored, but it can't be used for data.
data = {}; data = {};
data.sneak = false; data.sneak = false;
data.both = {inside: 'meta data'}; data.both = {inside: 'meta data'};
data._ = {'#': 'shhh', data: {yay: 1}, spin: data.both}; data._ = {'#': 'shhh', data: {yay: 1}, spin: data.both};
test = Gun.ify(data); test = Gun.ify(data);
expect(test.err.meta).to.be.ok(); // TODO: Fail: this passes, somehow? Fix ify code! expect(test.err.meta).to.be.ok(); // TODO: Fail: this passes, somehow? Fix ify code!
it('union', function(){
var graph, prime;
graph = Gun.ify({a: false, b: true, c: 0, d: 1, e: '', f: 'g', h: null}).nodes;
prime = Gun.ify({h: 9, i: 'foo', j: 'k', l: 'bar', m: 'Mark', n: 'Nadal'}).nodes;
Gun.union(graph, prime); // TODO: BUG! Where is the expect???
});
}); });
describe('Event Promise Back In Time', function(){ return; describe('Event Promise Back In Time', function(){ return;
@ -498,10 +479,207 @@ describe('Gun', function(){
}); });
}); });
describe('API', function(){ describe('Union', function(){
var gun = Gun();
//(typeof window === 'undefined') && require('../lib/file'); it('fail', function(){
var gun = Gun(); //Gun({file: 'data.json'}); var prime = {
'asdf': {
_: {'#': 'asdf', '>':{
a: 'cheating'
}},
a: 0
}
}
expect(gun.__.graph['asdf']).to.not.be.ok();
var ctx = Gun.union(gun, prime);
expect(ctx.err).to.be.ok();
});
it('basic', function(done){
var prime = {
'asdf': {
_: {'#': 'asdf', '>':{
a: Date.now()
}},
a: 0
}
}
expect(gun.__.graph['asdf']).to.not.be.ok();
var ctx = Gun.union(gun, prime, function(){
expect(gun.__.graph['asdf'].a).to.be(0);
done();
});
});
it('disjoint', function(done){
var prime = {
'asdf': {
_: {'#': 'asdf', '>':{
b: Date.now()
}},
b: 'c'
}
}
expect(gun.__.graph['asdf'].a).to.be(0);
expect(gun.__.graph['asdf'].b).to.not.be.ok();
var ctx = Gun.union(gun, prime, function(){
expect(gun.__.graph['asdf'].a).to.be(0);
expect(gun.__.graph['asdf'].b).to.be('c');
done();
});
});
it('mutate', function(done){
var prime = {
'asdf': {
_: {'#': 'asdf', '>':{
b: Date.now()
}},
b: 'd'
}
}
expect(gun.__.graph['asdf'].b).to.be('c');
var ctx = Gun.union(gun, prime, function(){
expect(gun.__.graph['asdf'].b).to.be('d');
done();
});
});
it('disjoint past', function(done){
var prime = {
'asdf': {
_: {'#': 'asdf', '>':{
x: 0 // beginning of time!
}},
x: 'hi'
}
}
expect(gun.__.graph['asdf'].x).to.not.be.ok();
var ctx = Gun.union(gun, prime, function(){
expect(gun.__.graph['asdf'].x).to.be('hi');
done();
});
});
it('past', function(done){
var prime = {
'asdf': {
_: {'#': 'asdf', '>':{
x: Date.now() - (60 * 1000) // above lower boundary, below now or upper boundary.
}},
x: 'hello'
}
}
expect(gun.__.graph['asdf'].x).to.be('hi');
var ctx = Gun.union(gun, prime, function(){
expect(gun.__.graph['asdf'].x).to.be('hello');
done();
});
});
it('future', function(done){
var prime = {
'asdf': {
_: {'#': 'asdf', '>':{
x: Date.now() + (100) // above now or upper boundary, aka future.
}},
x: 'how are you?'
}
}
expect(gun.__.graph['asdf'].x).to.be('hello');
var now = Date.now();
var ctx = Gun.union(gun, prime, function(){
expect(Date.now() - now).to.be.above(75);
expect(gun.__.graph['asdf'].x).to.be('how are you?');
done();
});
});
it('disjoint future', function(done){
var prime = {
'asdf': {
_: {'#': 'asdf', '>':{
y: Date.now() + (100) // above now or upper boundary, aka future.
}},
y: 'goodbye'
}
}
expect(gun.__.graph['asdf'].y).to.not.be.ok();
var now = Date.now();
var ctx = Gun.union(gun, prime, function(){
expect(Date.now() - now).to.be.above(75);
expect(gun.__.graph['asdf'].y).to.be('goodbye');
done();
});
});
it('disjoint future max', function(done){
var prime = {
'asdf': {
_: {'#': 'asdf', '>':{
y: Date.now() + (2), // above now or upper boundary, aka future.
z: Date.now() + (100) // above now or upper boundary, aka future.
}},
y: 'bye',
z: 'who'
}
}
expect(gun.__.graph['asdf'].y).to.be('goodbye');
expect(gun.__.graph['asdf'].z).to.not.be.ok();
var now = Date.now();
var ctx = Gun.union(gun, prime, function(){
expect(Date.now() - now).to.be.above(75);
expect(gun.__.graph['asdf'].y).to.be('bye');
expect(gun.__.graph['asdf'].z).to.be('who');
done();
});
});
it('future max', function(done){
var prime = {
'asdf': {
_: {'#': 'asdf', '>':{
w: Date.now() + (2), // above now or upper boundary, aka future.
x: Date.now() - (60 * 1000), // above now or upper boundary, aka future.
y: Date.now() + (100), // above now or upper boundary, aka future.
z: Date.now() + (50) // above now or upper boundary, aka future.
}},
w: true,
x: 'nothing',
y: 'farewell',
z: 'doctor who'
}
}
expect(gun.__.graph['asdf'].w).to.not.be.ok();
expect(gun.__.graph['asdf'].x).to.be('how are you?');
expect(gun.__.graph['asdf'].y).to.be('bye');
expect(gun.__.graph['asdf'].z).to.be('who');
var now = Date.now();
var ctx = Gun.union(gun, prime, function(){
expect(Date.now() - now).to.be.above(75);
expect(gun.__.graph['asdf'].w).to.be(true);
expect(gun.__.graph['asdf'].x).to.be('how are you?');
expect(gun.__.graph['asdf'].y).to.be('farewell');
expect(gun.__.graph['asdf'].z).to.be('doctor who');
done();
});
});
});
describe('API', function(){
var gun = Gun();
it('put', function(done){ it('put', function(done){
gun.put("hello", function(err){ gun.put("hello", function(err){
@ -748,7 +926,6 @@ describe('Gun', function(){
it('get path', function(done){ it('get path', function(done){
gun.get('hello/world').path('hello').val(function(val){ gun.get('hello/world').path('hello').val(function(val){
console.log("comfy stuff, pal", val);
expect(val).to.be('world'); expect(val).to.be('world');
done(); done();
}); });
@ -763,7 +940,6 @@ describe('Gun', function(){
it('get path put', function(done){ it('get path put', function(done){
gun.get('hello/world').path('hello').put('World').val(function(val){ gun.get('hello/world').path('hello').put('World').val(function(val){
console.log("what up dawg?", val);
expect(val).to.be('World'); expect(val).to.be('World');
done(); done();
}); });
@ -799,18 +975,18 @@ describe('Gun', function(){
}); });
*/ */
it('get not kick val', function(done){ console.log("Undo this!"); return done(); // TODO // it would be cool with GUN it('get not kick val', function(done){
gun.get("some/empty/thing").not(function(){ // that if you call not first gun.get("some/empty/thing").not(function(){ // that if you call not first
this.put({now: 'exists'}); // you can put stuff return this.put({now: 'exists'}).key("some/empty/thing"); // you can put stuff
}).val(function(val){ // and THEN still retrieve it. }).val(function(val){ // and THEN still retrieve it.
expect(val.now).to.be('exists'); expect(val.now).to.be('exists');
done(); done();
}); });
}); });
it('get not kick val when it already exists', function(done){ console.log("Undo this!"); return done(); // TODO it('get not kick val when it already exists', function(done){
gun.get("some/empty/thing").not(function(){ gun.get("some/empty/thing").not(function(){
this.put({now: 'THIS SHOULD NOT HAPPEN'}); return this.put({now: 'THIS SHOULD NOT HAPPEN'});
}).val(function(val){ }).val(function(val){
expect(val.now).to.be('exists'); expect(val.now).to.be('exists');
done(); done();
@ -819,13 +995,12 @@ describe('Gun', function(){
it('put path val sub', function(done){ it('put path val sub', function(done){
gun.put({last: {some: 'object'}}).path('last').val(function(val){ gun.put({last: {some: 'object'}}).path('last').val(function(val){
console.log("fat hat bat", val);
expect(val.some).to.be('object'); expect(val.some).to.be('object');
done(); done();
}); });
}); });
it('get put null', function(done){ console.log("Undo this!"); return done(); // TODO: BUG! WARNING: Occsionally failing, timing issue. it('get put null', function(done){
gun.put({last: {some: 'object'}}).path('last').val(function(val){ gun.put({last: {some: 'object'}}).path('last').val(function(val){
expect(val.some).to.be('object'); expect(val.some).to.be('object');
}).put(null, function(err){ }).put(null, function(err){
@ -858,13 +1033,13 @@ describe('Gun', function(){
}, 100); }, 100);
}); });
it('get not put val path val', function(done){ console.log("Undo this?"); return done(); // TODO // stickies issue it('get not put val path val', function(done){
gun.get("examples/list/foobar").not(function(){ gun.get("examples/list/foobar").not(function(){
this.put({ return this.put({
id: 'foobar', id: 'foobar',
title: 'awesome title', title: 'awesome title',
todos: {} todos: {hi: 'you'} // TODO: BUG! This should be empty?
}); }).key("examples/list/foobar");
}).val(function(data){ }).val(function(data){
expect(data.id).to.be('foobar'); expect(data.id).to.be('foobar');
}).path('todos').val(function(todos){ }).path('todos').val(function(todos){
@ -913,11 +1088,9 @@ describe('Gun', function(){
amber.pet = cat; amber.pet = cat;
cat.owner = mark; cat.owner = mark;
cat.master = amber; cat.master = amber;
gun.put(mark, function(err, ok){ gun.put(mark, function(err, ok){
expect(err).to.not.be.ok(); expect(err).to.not.be.ok();
}).val(function(val){ }).val(function(val){
console.log(val);
expect(val.age).to.be(23); expect(val.age).to.be(23);
expect(val.name).to.be("Mark Nadal"); expect(val.name).to.be("Mark Nadal");
expect(Gun.is.soul(val.wife)).to.be.ok(); expect(Gun.is.soul(val.wife)).to.be.ok();
@ -947,7 +1120,6 @@ describe('Gun', function(){
expect(mark.citizen).to.be("USA"); expect(mark.citizen).to.be("USA");
this.path('wife').val(function(Amber){ this.path('wife').val(function(Amber){
console.log('wife val', Amber);
expect(Amber.name).to.be("Amber"); expect(Amber.name).to.be("Amber");
expect(Amber.age).to.be(23); expect(Amber.age).to.be(23);
expect(Amber.citizen).to.be("USA"); expect(Amber.citizen).to.be("USA");
@ -1018,7 +1190,6 @@ describe('Gun', function(){
it('context node and field, put node', function(done){ it('context node and field, put node', function(done){
bar.path('combo').put({another: 'node'}).val(function(obj){ bar.path('combo').put({another: 'node'}).val(function(obj){
console.log("oh boy", obj);
expect(obj.another).to.be('node'); expect(obj.another).to.be('node');
bar.val(function(node){ bar.val(function(node){
expect(Gun.is.soul(node.combo)).to.be.ok(); expect(Gun.is.soul(node.combo)).to.be.ok();

127
web/notes-keys.txt Normal file
View File

@ -0,0 +1,127 @@
Alice comes online and does
`var todo = gun.get('todo')`
However she is the first peer, objectively, to be around.
Therefore, very quickly her query returns empty. So when she
`todo.put({first: "buy groceries"}).key('todo')`
the put has to generate a soul and GUN is instructed to associate 'todo' with that soul.
A few hours later, Bob comes online and does
`var todo = gun.get('todo')`
and thankfully he was connected to Alice so he gets her soul and node. So when he
`todo.put({last: "sell leftovers"}).key('todo')`
this was the expected and intended result, producing the following graph:
```{
'ASDF': {
_: {'#': 'ASDF', '>': {
first: 1,
last: 2
}},
first: "buy groceries",
last: "sell leftovers"
}
}```
For purposes of clarity, we are using states as if they were linearizable (this is not actually the case though).
Then Carl comes online and tries to
`var todo = gun.get('todo')`
But since he is not connected to Alice or Bob, gets an empty result.
Carl does nothing with it, meaning no mutation, no soul, no generation.
Then Dave comes online and does the same as everyone else:
`var todo = gun.get('todo')`
But Dave is only connected to Carl as a peer, therefore his get is empty. Like Alice, he then
`todo.put({remember: "eat food!", last: "no leftovers!"}).key('todo')`
Which unfortunately causes a new soul to be generated. Meanwhile, everybody then does the following:
`todo.on(function(val){ console.log(val) })`
Alice and Bob immediately get:
```{
_: {'#': 'ASDF', '>': {
first: 1,
last: 2
}},
first: "buy groceries",
last: "sell leftovers"
}```
But Carl and Dave immediately get:
```{
_: {'#': 'FDSA', '>': {
remember: 3,
last: 3
}},
remember: "eat food!",
last: "no leftovers!"
}```
However, a few hours later everybody gets connected. This is the graph everyone then has:
```{
'ASDF': {
_: {'#': 'ASDF', '>': {
first: 1,
last: 2
}},
first: "buy groceries",
last: "sell leftovers"
},
'FDSA': {
_: {'#': 'FDSA', '>': {
remember: 3,
last: 3
}},
remember: "eat food!",
last: "no leftovers!"
}
}```
But their `on` function triggers again, with the following `val` locally for everyone:
```{
first: "buy groceries",
remember: "eat food!",
last: "no leftovers!"
}```
GUN merges all the nodes with matching keys into a temporary in-memory object.
This way it is safe to get empty results and still put data into it,
Everyone will see a unified view of the data when they do get connected, as intended.
This solves the null, singular, and plural problems for get all together.
However, if we intentionally do not want to see a potentially conflicting unified view, any peer can:
`var todos = gun.all('todo')`
And have the discrete data via:
`todos.map(function(todo, soul){ console.log(todo) })`
Which would currently get called twice, with the distinct nodes of 'ASDF' and 'FDSA'.
The only thing that this does not address is how write operations (put/key) would effect `get` nodes.
However, I feel like finding the answer to that question will be much easier than trying to solve `get` in any other way.