Before adding queue to on

This commit is contained in:
Mark Nadal 2016-06-08 15:07:04 -07:00
parent ac761bf674
commit 752fe41a19
10 changed files with 343 additions and 79 deletions

159
gun.js
View File

@ -176,7 +176,8 @@
function noop(){};
function Event(tag, arg, at, as, skip){
var ctx = this, ons = ctx.ons || (ctx.ons = {}), on = ons[tag] || (ons[tag] = {s: []}), act, mem;
/*if(!arg && 1 === arguments.length){
//typeof console !== 'undefined' && console.debug(102, 'on', tag, arg, 'ons', ctx, ons);
/*if(!arg && 1 === arguments.length){ // Performance drops significantly even though `arguments.length` should be okay to use.
return on.s;
}*/
if(arg instanceof Function){
@ -188,7 +189,7 @@
return;
}*/
}
return;
return act;
}
if(emit){ emit(tag, arg, on, ctx) }
on.arg = arg;
@ -226,9 +227,9 @@
}
}
on.s = still;
if(0 === still.length){
delete ons[tag];
}
//if(0 === still.length){ // TODO: BUG! If we clean up the events themselves when no longer needed by deleting this code, it causes some tests to fail.
// delete ons[tag];
//}
}
if(!gap && at && at instanceof Function){
at.call(as, arg);
@ -237,7 +238,9 @@
}
exports.on = Event;
}(Util, function add(tag, act, on, ctx){ // Gun specific extensions
var mem = on.mem, at;
var mem = on.mem;
typeof console !== 'undefined' && console.debug(9, 'ON', tag, mem);
typeof console !== 'undefined' && console.debug(10, 'SO! The problem is that subsequent event listeners trigger lazy again, but the should queue for the results of the one already trying to finish or else you will get wrong results.');
if(mem){
if(mem instanceof Array){
act.fn.apply(act.at, mem.concat(act));
@ -246,11 +249,10 @@
}
return;
}
at = act.at? act.at.gun? act.at : ctx : ctx;
if(!at.lex || !at.lex.soul){ return } // TODO: What about lex cursors?
console.debug(16, 'on', tag, mem);
console.debug(3, 'on', tag, mem);
Gun.get(at);
if(!ctx.lazy){ return }
var at = act.at? act.at.gun? act.at : ctx : ctx;
if(!at.gun){ return }
ctx.lazy(at, tag);
//if(on.mem){ add(tag, act, on, ctx) } // for synchronous async actions.
}, function(tag, arg, on, at){
on.mem = arg;
@ -606,8 +608,7 @@
is_graph(at.graph, map, null, at);
});
function map(node, soul){
console.debug(12, 'put to get', soul);
console.debug(9, 'put to get', soul);
//Gun.get.got.call(this, null, node);
Gun.get.got.call(this.gun.__.gun.get(soul)._, null, node);
}
}());
@ -626,20 +627,21 @@
stream: cb
}
}
console.debug(4, 'GET', at);
console.debug(5, 'GET', at.lex);
Gun.on('get', at); // TODO: What is the cleanest way to reply if there is no responses, without assuming drivers do reply or not?
return at.gun;
}
Gun.get.got = got;
function got(err, node){
function got(err, node){ var at = this;
if(err){ Gun.log(err) }
console.debug(105, 'GOT', err, node);
console.debug(5, 'GOT', err, node);
Gun.on('stream', Gun.obj.to(this, {err: err, change: node}), stream);
if(!at.stream){ var soul;
if(!node && !at.lex.soul){ return }
at = at.gun.__.gun.get(is_node_soul(node) || at.lex.soul)._;
}
Gun.on('stream', Gun.obj.to(at, {err: err, change: node}), stream);
}
function stream(at){
if(!at.stream){ console.log("WARNING! No at.get", at); }
console.debug(6, 'STREAM', at);
if(!at.stream){ console.log("WARNING! No at.stream", at); }
at.stream(at.err, at.change);
}
}());
@ -649,6 +651,7 @@
if(opt.force){ return }
var lex = at.lex, gun = at.gun, graph = gun.__.graph, node = graph[lex.soul];
if(opt.memory || node){ return at.cb(null, node), ev.stun() }
console.debug(6, 'not in memory');
});
Gun.on('stream', function(at, ev){ var node;
@ -667,8 +670,8 @@
if(get && get[soul] && !get[soul]._.node){
get[soul]._.node = __.graph[soul];
}
});
*/
});*/
Gun.on('stream', function(at){
var lex = at.lex, soul = lex.soul, field = lex.field;
var gun = at.gun, graph = gun.__.graph, node = graph[soul], u;
@ -840,12 +843,13 @@
put.any = cb;
put.data = data;
put.state = (opt.state || opts.state)();
console.debug(3, 'put put', data);
at.on('chain', link, at); // TODO: ONE?
return gun;
};
function link(cat, ev){ ev.off(); // TODO: BUG!
console.log(8, 'putting', cat);
var at = this, put = at.put, data, cb;
console.debug(7, 'putting', at, cat.gun === at.gun, cat);
if(cat.err){ return }
if(!cat.node && (put.opt.init || cat.gun.__.opt.init)){ return }
// TODO: BUG! `at` doesn't have correct backwards data!
@ -858,9 +862,9 @@
ev.stun();
//put.resume = ev.stun(put.resume);
Gun.ify(data, end, {
node: function(env, cb){ var eat = env.at;
node: function(env, cb){ var eat = env.at, tmp;
if(1 === eat.path.length && cat.node){
eat.soul = is_rel(cat.node[eat.path[0]]); // TODO: BUG! Need to handle paths that aren't loaded yet.
eat.soul = (tmp = is_rel(at.value))? tmp : is_rel(cat.node[eat.path[0]]); // TODO: BUG! Need to handle paths that aren't loaded yet.
}
cb(env, eat);
}, value: function(env){ var eat = env.at;
@ -892,11 +896,9 @@
Gun.on('normalize', Gun.obj.to(at, {err: err, graph: env.graph, env: env}), wire);
}
function wire(at){
console.debug(8, 'pat', at);
Gun.put(Gun.obj.to(at, {cb: ack}));
}
function ack(err, ok){ var at = this, cb;
console.debug(14, 'ack', err, ok);
if((cb = at.put.any) && cb instanceof Function){
cb.call(at.gun, err, ok);
}
@ -925,16 +927,23 @@
}
Gun.on('chain', function(cat, e){
if(!is_node_soul(cat.node, 'key')){ return }
var resume = e.stun(1), node = cat.node, pseudo = cat.gun._.pseudo || (cat.gun._.pseudo = cat.gun._.node = is_node_ify({}, is_node_soul(node)));
var resume = e.stun(1), node = cat.change, field = cat.lex.field, pseudo = cat.gun._.pseudo || (cat.gun._.pseudo = cat.gun._.node = is_node_ify({}, is_node_soul(cat.node)));
pseudo._.key = 'pseudo';
cat.seen = cat.seen || {}; // TODO: There is a better way.
'hello/world' === is_node_soul(node) && console.log("scan", node);
is_node(node, function(n, f){ // TODO: PERF! BAD! Filter out items we've already seen.
if(cat.seen[f]){ return } cat.seen[f] = true; // TODO: There is a better way.
cat.gun.get(Gun.obj.to(cat.lex, {soul: f}), on);
function on(err, node){
//cat.gun.__.gun.get(Gun.obj.to(cat.lex, {soul: f}), on);
if(field){
cat.gun.__.gun.get(f).path(field, on);
} else {
cat.gun.__.gun.get(f, on);
}
function on(err, node, field, at){
if(!node){ return }
HAM_node(pseudo, node);
cat.node = pseudo;
cat.change = at.change;
resume();
}
});
@ -966,17 +975,10 @@
if(!opt || !opt.path){ var back = this.__.gun; } // TODO: CHANGING API! Remove this line!
var gun, back = back || this;
var get = back._.get || (back._.get = {}), tmp;
console.debug(2, 'get', lex);
if(typeof lex === 'string'){
if(!(gun = get[lex])){
gun = cache(get, lex, back);
console.debug(15, 'get', lex, get, 'and the', gun);
console.debug(2, 'get', lex);
if((tmp = gun._.lex).field){
/*if(!back._.ons.chain || !back._.ons.chain.s.length){ // TODO: CLEAN UP! // TODO: ONE?
back._.on('chain', link, gun._);
}*/
back._.on('field:' + tmp.field, field, gun._); // TODO: ONE?
}
}
} else
if(!lex && 0 != lex){ // TODO: BUG!?
@ -990,7 +992,7 @@
if(tmp = lex.soul){
if(lex.field){
gun = back.chain();
gun._.stream = cb;
//gun._.stream = cb;
gun._.lex = lex;
Gun.get(gun._);
return gun;
@ -1000,59 +1002,82 @@
}
} else
if(tmp = lex[_soul]){
if(lex[_field]){
return back.get({soul: tmp, field: lex[_field]}, cb, opt);
}
if(!(gun = get[tmp])){
gun = cache(get, tmp, back);
}
if(tmp = lex[_field]){
return gun.path(tmp, cb, opt);
}
}
console.debug(7, 'get', lex);
if(cb && cb instanceof Function){
gun._.on('any', pop(cb), gun); // TODO: Perf! Batch! // TODO: API CHANGE? Having to use pop is annoying. Should we support NodeJS style callbacks anymore?
//gun._.on('any', pop(cb), gun); // TODO: Perf! Batch! // TODO: API CHANGE? Having to use pop is annoying. Should we support NodeJS style callbacks anymore?
console.debug(8, 'getting');
gun._.on('any', cb, gun); // TODO: Perf! Batch! // TODO: API CHANGE? Having to use pop is annoying. Should we support NodeJS style callbacks anymore?
}
return gun;
}
function cache(get, key, back){
var gun = get[key] = back.chain(), at = gun._;
at.stream = stream;
at.lazy = lazy;
if(!back.back){
at.stream = stream;
at.lex.soul = key;
} else {
var lex = at.lex, flex = back._.lex;
var lex = at.lex, cat = back._, flex = cat.lex;
lex.field = key;
if(!flex.field && flex.soul){
lex.soul = flex.soul;
}
back._.on('field:' + key, field, at);
}
return gun;
}
function stream(err, node){
console.debug(10, 'stream', err, node);
Gun.on('chain', this);
//Gun.on('chain', this, link, this);
}
Gun.on('chain', link);
function link(cat, ev){ var at = cat.gun._, u;
var err = cat.err, node = cat.node, cex = cat.lex, lex = at.lex, field = lex.field, rel, val;
console.debug(13, 'link', at, cat);
console.debug(11, 'link', at, cat);
if(lex !== cex && lex.field && cex.field){
if(obj_has(node, cex.field) && (rel = is_rel(val = node[cex.field]))){
return Gun.get(Gun.obj.to(at, {lex: {soul: rel, field: lex.field}}));
function lazy(at){ var cat = this;
if(at.path && at.path.wait){ return } // means we're being lazy again.
console.debug(4, 'lazy', at);
var lex = at.lex;
if(!lex.soul){
if(!(lex.soul = is_rel(cat.value))){
return; // TODO: BUG! Handle async case.
}
}
console.debug(17, 'link', at, cat);
at.on('any', [err, (field && node)? node[field] : node, field, cat]); // TODO: Revisit!
Gun.get(at);
};
function stream(err, node){
//Gun.on('chain', this);
Gun.on('chain', this, link, this);
}
//Gun.on('chain', link);
function link(cat, ev){ var at = this, u;
//at.value = cat.value = cat.node;
var err = cat.err, node = cat.node, cex = cat.lex, lex = at.lex, field = lex.field, rel, val;
if(at.path && is_node_soul(node) === at.path.rel){ field = u }
at.on('any', [err, (field && node)? node[field] : node, lex.field, cat]) // TODO: Revisit!
if(err){ at.on('err', err) }
if(node){ at.on('ok', [(field && node)? node[field] : node, field, cat]) } // TODO: Revisit!
is_node(at.change, map, {cat: cat, at: at});
if(node){ at.on('ok', [(field && node)? node[field] : node, lex.field, cat]) } // TODO: Revisit!
at.on('chain', cat);
if(at.path && field){ return }
is_node(cat.change, map, {cat: cat, at: at}) || obj_map(at.get, map, {cat: cat, at: at}); // TODO: Cleaner way? Necessary to pass error/nots down the chain for implicit puts.
}
function map(val, field){
this.cat.on('field:' + field, this.cat);
this.at.on('field:' + field, this.cat);
}
function field(cat, ev){ var at = this;
at.on('chain', Gun.obj.to(at, {err: cat.err, change: cat.change}));
var node = cat.node, lex, field, rel;
if(!at.path){ at.path = {} }
if(!node){ return link.call(at, cat, ev) } // TODO: Errors and nots?
(lex = at.lex).soul = is_node_soul(node);
if(at.value === node[field = lex.field] && obj_has(at, 'value')){ return }
if(at.path.ev){ at.path.ev.off() }
at.value = node[field];
if(rel = at.path.rel = is_rel(at.value)){
at.path.wait = true;
at.path.ev = at.gun.__.gun.get(rel)._.on('chain', link, at);
at.path.wait = false;
return;
}
link.call(at, cat, ev);
}
}());
Gun.chain.path = function(field, cb, opt){
@ -1160,10 +1185,12 @@
;(function(){
function get(err, data, at){
if(!data && !Gun.obj.empty(at.opt.peers)){ return } // let the peers handle no data.
console.log("ouch");
at.cb(err, data); // node
}
Gun.on('get', function(at){
var opt = at.opt, lex = at.lex;
console.debug(7, 'get ASYNC');
Tab.store.get((opt.prefix || '') + lex.soul, get, at);
});
}());
@ -1303,10 +1330,10 @@
;(function(exports){ var u;
function s(){}
s.put = function(key, val, cb){ try{ store.setItem(key, Gun.text.ify(val));if(cb)cb(null) }catch(e){if(cb)cb(e)} }
s.get = function(key, cb, t){ //setTimeout(function(){
s.get = function(key, cb, t){ setTimeout(function(){
try{ cb(null, Gun.obj.ify(store.getItem(key) || null), t);
}catch(e){ cb(e,u,t)}
}//,1) }
},1) }
s.del = function(key){ return store.removeItem(key) }
var store = window.localStorage || {setItem: function(){}, removeItem: function(){}, getItem: function(){}};
exports.store = s;

View File

@ -49,7 +49,7 @@
},
"devDependencies": {
"mocha": "~>1.9.0",
"panic-server": "~>0.2.4",
"panic-server": "~>0.3.0",
"selenium-webdriver": "~>2.53.2"
}
}

View File

@ -1352,9 +1352,11 @@ describe('Gun', function(){
});
});
it('put node with soul get soul', function(done){
it.only('put node with soul get soul', function(done){
Gun.log.debug=1;console.log("--------------------------");
gun.put({_: {'#': 'foo'}, hello: 'world'})
.get({'#': 'foo'}, function(err, node){
console.log("huh?", err, node);
expect(err).to.not.be.ok();
expect(Gun.is.node.soul(node)).to.be('foo');
expect(node.hello).to.be('world');
@ -1583,15 +1585,14 @@ describe('Gun', function(){
it('put node path path', function(done){
var gun = Gun();
Gun.log.debug=1;console.log("----------------------------------");
gun.put({hello: {little: 'world'}}).path('hello').path('little', function(err, val, field){
var g = gun.put({hello: {little: 'world'}}).path('hello').path('little', function(err, val, field){
if(done.end){ return } // it is okay for path's callback to be called multiple times.
expect(err).to.not.be.ok();
expect(field).to.be('little');
expect(val).to.be('world');
done(); done.end = true;
});
});return;
});
it('put node path rel', function(done){
gun.put({foo: {bar: 'lol'}}).path('foo', function(err, val, field){
@ -1617,8 +1618,9 @@ describe('Gun', function(){
var gun = Gun();
gun.put({_:{'#': 'soul/field'}, hi: 'lol', foo: 'bar'});//.key('key/field');
gun.get({'#': 'soul/field', '.': 'hi'}, function(err, val){
expect(val.hi).to.be('lol');
expect(Gun.obj.has(val,'foo')).to.not.be.ok();
//expect(val.hi).to.be('lol'); // TODO: REVISE API?
expect(val).to.be('lol');
//expect(Gun.obj.has(val,'foo')).to.not.be.ok();
done();
})
});
@ -1767,7 +1769,7 @@ describe('Gun', function(){
expect(val).to.be('are');
expect(field).to.be('you');
done();
})
});
});
it('get node path put object merge isolated', function(done){
@ -1778,6 +1780,8 @@ describe('Gun', function(){
var puthi = get.put({hi: 'you'});
puthi.on(function(node){
if(done.hi){ return }
//console.log(1, node);
expect(node.hello).to.be('key');
expect(node.hi).to.be('you');
done.hi = 1;
});
@ -1787,8 +1791,11 @@ describe('Gun', function(){
path2._.id = 'path2';
var putyay = path2.put({yay: "value"});
putyay.on(function(node, field){
if(done.yay){ return }
//console.log(2, field, node);
expect(field).to.be('hi');
expect(node.yay).to.be('value');
done.yay = true;
});
setTimeout(function(){
var get3 = gun.get('hello/key/iso');
@ -1796,9 +1803,10 @@ describe('Gun', function(){
path3._.id = 'path3';
var puthappy = path3.put({happy: "faces"});
puthappy.on(function(node, field){
//console.log(3, field, node);
expect(field).to.be('hi');
expect(node.yay).to.be('value');
expect(node.happy).to.be('faces');
expect(node.yay).to.be('value');
setTimeout(function(){
done();
},200);
@ -1931,6 +1939,7 @@ describe('Gun', function(){
});
it('get put path', function(done){
Gun.log.debug=1;console.log("-------------------------");
gun.get('hello/world').put({hello: 'Mark'}).path('hello').val(function(val, field){
console.log("WAT?", field, val);
expect(val).to.be('Mark');

View File

@ -1,3 +1,5 @@
require('./holy/grail');
describe('PANIC!', function(){
this.timeout(1000 * 100);
@ -35,7 +37,7 @@ describe('PANIC!', function(){
function min(n, done, list){
list = list || clients;
function ready() {
if (list.len() >= n) {
if (list.length >= n) {
done();
list.removeListener('add', ready);
return true;
@ -46,7 +48,7 @@ describe('PANIC!', function(){
}
}
function gunify(ctx, done){
function gunify(done, ctx){
var s = document.createElement('script');
s.src = 'gun.js';
s.onload = done;
@ -69,7 +71,7 @@ describe('PANIC!', function(){
var sync = gun.get('sync');
sync.put({hello: 'world'})
}).then(function(){
return bob.run(function(ctx, done){
return bob.run(function(done, ctx){
var sync = gun.get('sync');
sync.on(function(val){
if(val.hello === 'world'){

177
test/holy/grail.js Normal file
View File

@ -0,0 +1,177 @@
/*
This file should be one
folder deeper than every
other file. It requires
mocha to run.
*/
var panic = require('panic-server');
var spawn = require('child_process').spawn;
var path = require('path');
var http = require('http');
var fs = require('fs');
var ports = require('./ports');
var wd = require('selenium-webdriver');
function open(url) {
var driver = new wd.Builder()
.forBrowser('firefox')
.build();
driver.get(url);
return driver;
}
open('http://localhost:' + ports.panic + '/index.html');
open('http://localhost:' + ports.panic + '/index.html');
var staticServer = new http.Server(function (req, res) {
if (req.url === '/') {
req.url = '/index.html';
}
var file = path.join(__dirname, req.url);
try {
var page = fs.readFileSync(file, 'utf8');
res.end(page);
} catch (e) {
// don't care
}
});
var server = panic.clients.filter('Node.js').pluck(1);
var browsers = panic.clients.excluding(server);
var alice = browsers.pluck(1);
var bob = browsers.excluding(alice).pluck(1);
var serverPath = path.join(__dirname, 'gun-server.js');
// start the server on :8080
spawn('node', [serverPath]);
function waitFor (num, list) {
return new Promise(function (res) {
function ready() {
if (list.length < num) {
return;
}
res();
list.removeListener('add', ready);
return true;
}
if (!ready()) {
list.on('add', ready);
}
});
}
before(function () {
this.timeout(1500000);
// start the panic server
panic.server(staticServer).listen(ports.panic);
return waitFor(2, browsers)
.then(function () {
return waitFor(1, server);
});
});
var scope = {
uniqueKey: Math.random().toString(16).slice(2),
file: path.join(process.cwd(), 'delete-me.json'),
'@scope': true
}
describe('The holy grail', function () {
it('should allow full recovery', function () {
this.timeout(1500000);
return browsers.run(function () {
localStorage.clear();
})
.then(function () {
return browsers.run(function () {
window.ref = gun.get(uniqueKey).put({
text: 'ignore'
});
}, scope)
})
.then(function () {
return alice.run(function (done) {
// alice saves some data
//ref.path('text').put('Initial text', done); // TODO: USE THIS LINE INSTEAD!
ref.path('text').put('Initial text');
setTimeout(done, 50);
});
})
.then(function () {
return bob.run(function (done) {
var ctx = this;
ref.path('text').on(function (data) {
if (data === 'ignore') {
return;
}
// bob sees alice's data
if (data !== 'Initial text') {
ctx.fail('Wrong data showed up: ' + JSON.stringify(data));
}
done();
});
});
})
.then(function () {
return server.run(function () {
var fs = require('fs');
// destroy the data
fs.unlinkSync(file);
// crash the server
process.exit(0);
}, scope);
})
.then(function () {
return alice.run(function (done) {
ref.path('text').put('A conflicting update');
setTimeout(done, 50);
});
})
.then(function () {
return bob.run(function () {
ref.path('text').put('B conflicting update');
});
})
.then(function () {
spawn('node', [serverPath]);
return waitFor(1, server);
})
.then(function () {
return browsers.run(function (done) {
var ctx = this;
ref.path('text').on(function (value) {
if (value === 'B conflicting update') {
done();
}
});
});
});
});
});
after(function () {
if (server.length) {
return server.run(function () {
process.exit(0);
});
}
});

16
test/holy/gun-server.js Normal file
View File

@ -0,0 +1,16 @@
var panic = require('panic-client');
var ports = require('./ports');
var Gun = require('gun');
var gun = new Gun({
file: 'delete-me.json'
});
var http = require('http');
var server = new http.Server(gun.wsp.server);
gun.wsp(server);
server.listen(ports.gun);
panic.server('http://localhost:' + ports.panic);

27
test/holy/index.html Normal file
View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Holy grail demo</title>
</head>
<body>
<script src='panic.js'></script>
<script src='http://localhost:8080/gun.js'></script>
<script>
(function () {
var req = new XMLHttpRequest();
req.overrideMimeType('application/json');
req.open('GET', 'ports.json');
req.addEventListener('load', function () {
var ports = JSON.parse(req.responseText);
var server = 'http://localhost:' + ports.gun + '/gun';
window.gun = new Gun(server);
panic.server('http://localhost:' + ports.panic);
});
req.send();
}());
</script>
</body>
</html>

4
test/holy/ports.json Normal file
View File

@ -0,0 +1,4 @@
{
"panic": 3000,
"gun": 8080
}

View File

@ -1978,11 +1978,13 @@
var hewo = {hello: "world"};
window.puti = window.puti || 0;
window.geti = window.geti || 0;
/*
localStorage.clear();
gun.get('users').put({1: {where: {lat: Math.random(), lng: Math.random(), i: 1}}});
//var ok = function(a,b){ console.log('wat', a,b) }
//Gun.log.debug=1;console.log("------------------");
var val = gun.get('users').path(1).path('where').val(ok);
*/
});
//localStorage.clear();
/*

View File

@ -27,7 +27,7 @@
<button id="abort">Abort</button>
<button id="share">Share</button>
<a id="share-result"></a>
<script src="./mocha/json2.js"></script>
<script src="../json2.js"></script>
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script src="benchmark.js"></script>
<script src="ptsd.js"></script>