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>
<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>
</body>
</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>
// by Forrest Tait! Edited by Mark Nadal.
function ready(){
Gun.log.verbose = true;
var $ = document.querySelector.bind(document);
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 = '';
for(key in val) {
if(!val[key] || key == '_') continue;

250
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.
if(field == Gun._.meta){ return } // skip this.
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;
@ -70,8 +70,8 @@
}) && exist;
}
// Gun.ify // the serializer is too long for right here, it has been relocated towards the bottom.
Gun.union = function(gun, prime){
var ctx = {};
Gun.union = function(gun, prime, cb){
var ctx = {count: 0, cb: function(){ cb = cb? cb() && null : null }};
ctx.graph = gun.__.graph;
if(!ctx.graph){ ctx.err = {err: Gun.log("No graph!") } }
if(!prime){ ctx.err = {err: Gun.log("No data to merge!") } }
@ -80,16 +80,22 @@
ctx.tmp[ctx.soul] = prime;
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 }
(function union(graph, prime){
Gun.obj.map(prime, function(node, soul){
soul = Gun.is.soul.on(node);
if(!soul){ return }
var vertex = graph[soul];
if(!vertex){ // disjoint
if(!vertex){ // disjoint // TODO: Maybe not correct? BUG, probably.
gun.__.on(soul).emit(graph[soul] = node);
return;
}
ctx.count += 1;
Gun.HAM(vertex, node, function(){}, function(vertex, field, value){
if(!vertex){ return }
var change = {};
@ -101,27 +107,27 @@
//context.nodes[change._[Gun._.soul]] = change;
//context('change').fire(change);
gun.__.on(Gun.is.soul.on(change)).emit(change);
}, function(){
}, function(){})(function(){
if(!(ctx.count -= 1)){ ctx.cb() }
});
});
})(ctx.graph, prime);
if(!ctx.count){ ctx.cb() }
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){
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;
ctx.drift = Gun.time.is();
ctx.incoming.value = Gun.is.soul(incoming) || incoming;
ctx.current.value = Gun.is.soul(vertex[field]) || vertex[field]
ctx.incoming.state = ((delta._||{})[Gun._.HAM]||{})[field] || 0;
ctx.current.state = ((vertex._||{})[Gun._.HAM]||{})[field] || 0;
ctx.current.value = Gun.is.soul(vertex[field]) || vertex[field];
ctx.incoming.state = Gun.num.is(ctx.tmp = ((delta._||{})[Gun._.HAM]||{})[field])? ctx.tmp : -Infinity;
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);
//console.log("HAM:", ctx);
//root.console.log("HAM:", ctx);
if(state.err){
root.console.log(".!HYPOTHETICAL AMNESIA MACHINE ERR!.", state.err); // this error should never happen.
return;
@ -131,13 +137,15 @@
return;
}
if(state.incoming){
each.call(state, vertex, field, incoming);
now.call(state, vertex, field, incoming);
return;
}
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(){
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"};
}
}
Gun.when = function(gun, cb){ // how much memory will this consume?
var setImmediate = setImmediate || function(cb){return setTimeout(cb,0)};
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);
});
return function(fn){
upper.last = fn || function(){};
if(!upper.wait){ upper.last() }
}
}
Gun.roulette = function(l, c){
var gun = Gun.is(this)? this : {};
@ -221,13 +224,31 @@
});
if(!stun){ Gun.on('opt').emit(gun, opt) }
return gun;
}
}
Gun.chain.chain = function(from){
var gun = Gun(null);
from = from || this;
gun.back = 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;
}
Chain.get = function(key, cb, opt){ // get opens up a reference to a node and loads it.
@ -238,46 +259,46 @@
cb = cb || function(){};
opt = opt || {};
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
cb.call(gun, null, Gun.obj.copy(ctx.node));
gun._.status('node').emit({soul: ctx.soul});
} else { load(key) } // not in memory
} else
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(true === ctx.node){
Gun.fns.async(foo);
setTimeout(foo,0);
} else {
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 { 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){
if(Gun.fns.is(ctx.hook = gun.__.opt.hooks.get)){
ctx.hook(key, function(err, data){ // multiple times potentially
//console.log("chain.get from load", 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)){
open(ctx.soul);
gun._.status('soul').emit({soul: ctx.soul});
} 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) }
cb.call(gun, null, data);
gun._.status('node').emit({soul: ctx.soul});
}, opt);
} else {
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.
gun._.status('null').emit();
}
}
return gun;
@ -288,14 +309,12 @@
cb = cb || function(){};
opt = opt || {};
gun.__.keys[key] = true;
Gun.when(gun, function(soul){
Gun.fns.async(function wait(node){ // TODO: UGLY!!! JANKY!!!
node = gun.__.graph[soul];
if(true === node){ return Gun.fns.async(wait) }
gun.__.keys[key] = node;
gun._.status('soul').event(function($){ // TODO: once per soul in graph. (?)
gun._.status('node').once(function($){
gun.__.keys[key] = gun.__.graph[$.soul];
});
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);
}, opt);
} else {
@ -307,6 +326,7 @@
}
Chain.all = function(key, cb){
var gun = this.chain();
return gun; // TODO: BUG! We need to create all!
cb = cb || function(){};
gun.shot.next(function(next){
Gun.obj.map(gun.__.keys, function(node, key){ // TODO: BUG!! Need to handle souls too!
@ -349,21 +369,16 @@
var gun = this.chain(), ctx = {};
cb = cb || function(){};
path = (Gun.text.ify(path) || '').split('.');
//gun.back._.on('soul').event(function trace(soul){ // TODO: Check for field as well and merge?
Gun.when(gun.back, function trace(soul){ // TODO: Check for field as well and merge?
var node = gun.__.graph[soul], field = node && 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
// TODO: Hmmm once also? figure it out later.
gun.back._.status('node').event(function trace($){ // TODO: Check for field as well and merge?
var node = gun.__.graph[$.soul], field = Gun.text.ify(path.shift()), val;
if(path.length){
if(Gun.is.soul(val = node[field])){
//root.console.log('path RECURSION', field);
gun.get(val, function(err, data){
if(err){ return cb.call(gun, err, data) }
if(!data){ return cb.call(gun, null) }
trace(Gun.is.soul.on(data));
trace({soul: Gun.is.soul.on(data)});
});
} else {
cb.call(gun, null);
@ -371,14 +386,20 @@
} else
if(!Gun.obj.has(node, field)){ // TODO: THIS MAY NOT BE CORRECT BEHAVIOR!!!!
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
if(Gun.is.soul(val = node[field])){
gun.get(val, cb);
gun._.on('soul').emit(Gun.is.soul(val), null, soul, field);
gun.get(val, function(err, data){
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 {
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){
var gun = this, ctx = {};
cb = cb || function(){};
cb = cb || root.console.log.bind(root.console);
Gun.when(gun, function(soul, field){
Gun.fns.async(function wait(node){ // TODO: UGLY!!! JANKY!!!
node = gun.__.graph[soul];
if(!node || true === node){ return Gun.fns.async(wait) }
cb.call(gun, field? node[field] : Gun.obj.copy(node));
});
gun._.status('node').event(function($){ // TODO: once per soul on graph. (?)
var node = gun.__.graph[$.soul];
cb.call(gun, $.field? node[$.field] : Gun.obj.copy(node)); // TODO: at terminating
});
return gun;
@ -403,10 +421,12 @@
var gun = this, ctx = {};
cb = cb || function(){};
Gun.when(gun, function(soul, field){
gun.__.on(soul).event(function(delta){
var node = gun.__.graph[soul];
cb.call(gun, Gun.obj.copy(node), field);
// TODO: below is also probably going to be on node.
gun.val(cb)._.status('soul').event(function($){ // TODO: once per soul on graph. (?)
// TODO: Don't use val :(, but trigger callback now as well.
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.
*/
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(){};
opt = opt || {};
if(!gun.back.back){
gun = gun.chain();
Gun.fns.async(function(){
flag = true;
gun.back._.on('soul').emit(Gun.is.soul.on(val) || Gun.roulette.call(gun));
});
gun.back._.status('soul').emit({soul: Gun.is.soul.on(val) || Gun.roulette.call(gun), empty: true});
}
Gun.when(gun.back, function(soul, field, from, at){
//console.log("chain.put", field, val, "on", soul, 'or', from, at);
var ctx = {}, obj = val;
gun.back._.status('soul').event(function($){ // TODO: maybe once per soul?
var ctx = {}, obj = val, $ = Gun.obj.copy($);
console.log("chain.put", val, $);
if(Gun.is.value(obj)){
if(from && at){
soul = from;
field = at;
if($.from && $.at){
$.soul = $.from;
$.field = $.at;
} // no else!
if(!field){
if(!$.field){
return cb.call(gun, {err: Gun.log("No field exists for " + (typeof obj) + "!")});
} else
if(gun.__.graph[soul]){
if(gun.__.graph[$.soul]){
ctx.tmp = {};
ctx.tmp[ctx.field = field] = obj;
ctx.tmp[ctx.field = $.field] = obj;
obj = ctx.tmp;
} else {
return cb.call(gun, {err: Gun.log("No node exists to put " + (typeof obj) + " in!")});
}
}
if(Gun.obj.is(obj)){
if(field && !ctx.field){
if($.field && !ctx.field){
ctx.tmp = {};
ctx.tmp[ctx.field = field] = obj;
ctx.tmp[ctx.field = $.field] = obj;
obj = ctx.tmp;
}
Gun.ify(obj, function(env, cb){
@ -468,10 +485,10 @@
}
if(!Gun.is.soul.on(at.node)){
if(obj === at.obj){
env.graph[at.node._[Gun._.soul] = soul] = at.node;
cb(at, soul);
env.graph[at.node._[Gun._.soul] = $.soul] = at.node;
cb(at, $.soul);
} 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){
var soul = Gun.is.soul.on(data) || Gun.roulette.call(gun);
env.graph[at.node._[Gun._.soul] = soul] = at.node;
@ -485,11 +502,12 @@
if(!at.field){ return }
at.node._[Gun._.HAM][at.field] = drift;
})(function(err, ify){
console.log("chain.put PUT", ify.graph);
console.log("chain.put PUT <----", ify.graph);
if(err || ify.err){ return cb.call(gun, err || ify.err) }
if(err = Gun.union(gun, ify.graph).err){ return cb.call(gun, err) }
if(from = Gun.is.soul(ify.root[field])){ soul = from; field = null }
gun._.on('soul').emit(soul, field);
if($.from = Gun.is.soul(ify.root[$.field])){ $.soul = $.from; $.field = null }
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)){
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) }
@ -524,16 +542,17 @@
var ahead = gun.chain(); return ahead;
return gun;
}
Chain.not = function(not){
var gun = this;
not = not || function(){};
gun.shot.next(function(next){
if(gun._.node){ // if it does indeed exist
return next(); // yet fire off the chain
}
not.call(gun); // call not
next(); // fire off the chain
Chain.not = function(cb){
var gun = this, ctx = {};
cb = cb || function(){};
gun._.status('null').once(function(){
var chain = gun.chain(), next = cb.call(chain);
next._.status('soul').event(function($){ gun._.on('soul').emit($) });
next._.status('node').event(function($){ gun._.on('node').emit($) });
chain._.on('soul').emit({soul: Gun.roulette.call(chain), empty: true});
});
return gun;
}
Chain.err = function(dud){ // WARNING: dud was depreciated.
@ -544,10 +563,6 @@
;(function(Util){
Util.fns = {};
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!
var context = {task: {}, data: {}};
context.end = function(e,v){ return done(e,v), done = function(){} };
@ -748,17 +763,10 @@
function ify(data, cb, opt){
opt = opt || {};
cb = cb || function(env, cb){ cb(env.at, Gun.roulette()) };
var ctx = {}, end = function(fn){
Gun.fns.async(function wait(){ // TODO: JANKY! clean this up, possibly?
if(ctx.err || !Gun.list.map(ctx.seen, function(at){
if(!at.soul){ return true }
})){
fn(ctx.err, ctx)
} else {
Gun.fns.async(wait); // TODO: BUG! JANKY!!! Make this cleaner.
}
});
}
var end = function(fn){
ctx.end = fn || function(){};
if(ctx.err){ return ctx.end(ctx.err, ctx), ctx.end = function(){} }
}, ctx = {};
if(!data){ return ctx.err = Gun.log('Serializer does not have correct parameters.'), end }
ctx.at = {};
ctx.root = {};
@ -811,6 +819,7 @@
}
cb(ctx, function(at, soul){
at.soul = at.soul || soul;
setImmediate(function(){ unique(ctx) },0);
if(!at.back || !at.back.length){ return }
Gun.list.map(at.back, function(rel){
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){
return Gun.list.map(ctx.seen, function(has){
if(at.obj === has.obj){ return has }
@ -830,6 +844,7 @@
} else {
module.exports = Gun;
}
var setImmediate = setImmediate || function(cb){return setTimeout(cb,0)};
var root = this || {}; // safe for window, global, root, and 'use strict'.
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}};
@ -917,6 +932,7 @@
});
Gun.obj.map(gun.__.opt.peers, function(peer, url){
request(url, nodes, function(err, reply){
console.log("PUT success?", err, reply);
if(err || !reply || (err = reply.body && reply.body.err)){
return cb({err: Gun.log(err || "Error: Put failed on " + url) });
} else {
@ -925,7 +941,7 @@
}, {headers: tab.headers});
cb.peers = true;
}); 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);
});
}
@ -938,7 +954,7 @@
request.createServer(function(req, res){
if(!req.body){ return }
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?
}
});

View File

@ -380,6 +380,11 @@ html, body {
</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>
<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 -->
<div class="tutorial">
<script src="https://gunjs.herokuapp.com/gun.js"></script>

View File

@ -127,7 +127,25 @@
var reply = {headers: {'Content-Type': tran.json}};
if(!req.body){ return cb({headers: reply.headers, body: {err: "No body"}}) }
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;
// saving
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,42 +356,23 @@ describe('Gun', function(){
});
});
return; // TODO: COME BACK HERE?
it('circular reference', function(done){
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;
it('extraneous', function(done){
var data = {_: {'#': 'shhh', meta: {yay: 1}}, sneak: true};
Gun.ify(data)(function(err, ctx){
console.log("circ ref", err, ctx, 'now see:');
ctx.seen.forEach(function(val){ console.log(val.node) });
expect(test.err).to.not.be.ok();
expect(err).to.not.be.ok(); // extraneous metadata needs to be stored, but it can't be used for data.
done();
});
});
data = {_: {'#': 'shhh', meta: {yay: 1}}, sneak: true};
test = Gun.ify(data);
expect(test.err).to.not.be.ok(); // metadata needs to be stored, but it can't be used for data.
return; // TODO! Fix GUN to handle this!
data = {};
data.sneak = false;
data.both = {inside: 'meta data'};
data._ = {'#': 'shhh', data: {yay: 1}, spin: data.both};
test = Gun.ify(data);
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;
/*
var ref = gun.put({field: 'value'}).key('field/value').get('field/value', function(){
@ -498,10 +479,207 @@ describe('Gun', function(){
});
});
describe('API', function(){
describe('Union', function(){
var gun = Gun();
it('fail', function(){
var prime = {
'asdf': {
_: {'#': 'asdf', '>':{
a: 'cheating'
}},
a: 0
}
}
//(typeof window === 'undefined') && require('../lib/file');
var gun = Gun(); //Gun({file: 'data.json'});
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){
gun.put("hello", function(err){
@ -748,7 +926,6 @@ describe('Gun', function(){
it('get path', function(done){
gun.get('hello/world').path('hello').val(function(val){
console.log("comfy stuff, pal", val);
expect(val).to.be('world');
done();
});
@ -763,7 +940,6 @@ describe('Gun', function(){
it('get path put', function(done){
gun.get('hello/world').path('hello').put('World').val(function(val){
console.log("what up dawg?", val);
expect(val).to.be('World');
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
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.
expect(val.now).to.be('exists');
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(){
this.put({now: 'THIS SHOULD NOT HAPPEN'});
return this.put({now: 'THIS SHOULD NOT HAPPEN'});
}).val(function(val){
expect(val.now).to.be('exists');
done();
@ -819,13 +995,12 @@ describe('Gun', function(){
it('put path val sub', function(done){
gun.put({last: {some: 'object'}}).path('last').val(function(val){
console.log("fat hat bat", val);
expect(val.some).to.be('object');
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){
expect(val.some).to.be('object');
}).put(null, function(err){
@ -858,13 +1033,13 @@ describe('Gun', function(){
}, 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(){
this.put({
id: 'foobar',
title: 'awesome title',
todos: {}
});
return this.put({
id: 'foobar',
title: 'awesome title',
todos: {hi: 'you'} // TODO: BUG! This should be empty?
}).key("examples/list/foobar");
}).val(function(data){
expect(data.id).to.be('foobar');
}).path('todos').val(function(todos){
@ -872,7 +1047,7 @@ describe('Gun', function(){
done();
});
});
it('put circular ref', function(done){
var data = {};
data[0] = "DATA!";
@ -913,11 +1088,9 @@ describe('Gun', function(){
amber.pet = cat;
cat.owner = mark;
cat.master = amber;
gun.put(mark, function(err, ok){
expect(err).to.not.be.ok();
}).val(function(val){
console.log(val);
expect(val.age).to.be(23);
expect(val.name).to.be("Mark Nadal");
expect(Gun.is.soul(val.wife)).to.be.ok();
@ -947,7 +1120,6 @@ describe('Gun', function(){
expect(mark.citizen).to.be("USA");
this.path('wife').val(function(Amber){
console.log('wife val', Amber);
expect(Amber.name).to.be("Amber");
expect(Amber.age).to.be(23);
expect(Amber.citizen).to.be("USA");
@ -1018,7 +1190,6 @@ describe('Gun', function(){
it('context node and field, put node', function(done){
bar.path('combo').put({another: 'node'}).val(function(obj){
console.log("oh boy", obj);
expect(obj.another).to.be('node');
bar.val(function(node){
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.