Merge pull request #549 from amark/debug

Debug
This commit is contained in:
Mark Nadal 2018-05-23 23:18:11 -07:00 committed by GitHub
commit 6fd70c3e6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 349 additions and 46 deletions

126
gun.js
View File

@ -169,7 +169,7 @@
// On event emitter generic javascript utility.
module.exports = function onto(tag, arg, as){
if(!tag){ return {to: onto} }
var tag = (this.tag || (this.tag = {}))[tag] ||
var u, tag = (this.tag || (this.tag = {}))[tag] ||
(this.tag[tag] = {tag: tag, to: onto._ = {
next: function(arg){ var tmp;
if((tmp = this.to)){
@ -200,7 +200,7 @@
(be.back = tag.last || tag).to = be;
return tag.last = be;
}
(tag = tag.to).next(arg);
if((tag = tag.to) && u !== arg){ tag.next(arg) }
return tag;
};
})(USE, './onto');
@ -685,7 +685,9 @@
var gun = at.gun.opt(at.opt);
if(!at.once){
at.on('in', root, at);
at.on('out', root, at);
at.on('out', root, obj_to(at, {out: root}));
Gun.on('create', at);
at.on('create', at);
}
at.once = 1;
return gun;
@ -695,7 +697,13 @@
var ev = this, at = ev.as, gun = at.gun, dup, tmp;
//if(!msg.gun){ msg.gun = at.gun }
if(!(tmp = msg['#'])){ tmp = msg['#'] = text_rand(9) }
if((dup = at.dup).check(tmp)){ return }
if((dup = at.dup).check(tmp)){
if(at.out === msg.out){
msg.out = u;
ev.to.next(msg);
}
return;
}
dup.track(tmp);
//msg = obj_to(msg);//, {gun: at.gun}); // can we delete this now?
if(!at.ask(msg['@'], msg)){
@ -708,7 +716,11 @@
//at.on('put', put(msg));
}
}
at.on('out', msg);
ev.to.next(msg);
if(!at.out){
msg.out = root;
at.on('out', msg);
}
}
}());
@ -748,8 +760,11 @@
function merge(node, soul){
var ctx = this, cat = ctx.gun._, at = (cat.next || empty)[soul];
if(!at){
ctx.souls[soul] = false;
return
if(!(cat.opt||empty).super){
ctx.souls[soul] = false;
return;
}
at = (ctx.gun.get(soul)._);
}
var msg = ctx.map[soul] = {
put: node,
@ -936,6 +951,7 @@
function output(msg){
var put, get, at = this.as, back = at.back, root = at.root;
if(!msg.I){ msg.I = at.gun }
if(!msg.gun){ msg.gun = at.gun }
this.to.next(msg);
if(get = msg.get){
@ -1366,6 +1382,7 @@
as.res = as.res || function(cb){ if(cb){ cb() } };
as.res(function(){
var cat = (as.gun.back(-1)._), ask = cat.ask(function(ack){
cat.root.on('ack', ack);
this.off(); // One response is good enough for us currently. Later we may want to adjust this.
if(!as.ack){ return }
as.ack(ack, this);
@ -1742,7 +1759,72 @@
If you update anything here, consider updating the other adapters as well.
*/
Gun.on('opt', function(root){
Gun.on('create', function(root){
// This code is used to queue offline writes for resync.
// See the next 'opt' code below for actual saving of data.
var ev = this.to, opt = root.opt;
if(root.once){ return ev.next(root) }
if(false === opt.localStorage){ return ev.next(root) }
opt.file = opt.file || 'gun/';
var gap = Gun.obj.ify(store.getItem('gap/'+opt.file)) || {};
var empty = Gun.obj.empty, id, to, go;
// add re-sync command.
if(!empty(gap)){
root.on('localStorage', function(disk){
this.off();
var send = {}
Gun.obj.map(gap, function(node, soul){
Gun.obj.map(node, function(val, key){
send[soul] = Gun.state.to(disk[soul], key, send[soul]);
});
});
setTimeout(function(){
root.on('out', {put: send, '#': root.ask(ack), I: root.gun});
},10);
});
}
root.on('out', function(msg){
if(msg.lS){ return }
if(msg.I && msg.put && !msg['@'] && !empty(opt.peers)){
id = msg['#'];
Gun.graph.is(msg.put, null, map);
if(!to){ to = setTimeout(flush, opt.wait || 1) }
}
this.to.next(msg);
});
root.on('ack', ack);
function ack(ack){ // TODO: This is experimental, not sure if we should keep this type of event hook.
if(ack.err || !ack.ok){ return }
var id = ack['@'];
setTimeout(function(){
Gun.obj.map(gap, function(node, soul){
Gun.obj.map(node, function(val, key){
if(id !== val){ return }
delete node[key];
});
if(empty(node)){
delete gap[soul];
}
});
flush();
}, opt.wait || 1);
};
ev.next(root);
var map = function(val, key, node, soul){
(gap[soul] || (gap[soul] = {}))[key] = id;
}
var flush = function(){
clearTimeout(to);
to = false;
try{store.setItem('gap/'+opt.file, JSON.stringify(gap));
}catch(e){ Gun.log(err = e || "localStorage failure") }
}
});
Gun.on('create', function(root){
this.to.next(root);
var opt = root.opt;
if(root.once){ return }
@ -1750,6 +1832,8 @@
opt.file = opt.file || opt.prefix || 'gun/'; // support old option name.
var graph = root.graph, acks = {}, count = 0, to;
var disk = Gun.obj.ify(store.getItem(opt.file)) || {};
var lS = function(){}, u;
root.on('localStorage', disk); // NON-STANDARD EVENT!
root.on('put', function(at){
this.to.next(at);
@ -1763,12 +1847,12 @@
to = setTimeout(flush, opt.wait || 1);
});
root.on('get', function(at){
this.to.next(at);
var lex = at.get, soul, data, u;
root.on('get', function(msg){
this.to.next(msg);
var lex = msg.get, soul, data, u;
//setTimeout(function(){
if(!lex || !(soul = lex['#'])){ return }
//if(0 >= at.cap){ return }
//if(0 >= msg.cap){ return }
var has = lex['.'];
data = disk[soul] || u;
if(data && has){
@ -1777,7 +1861,7 @@
if(!data && !Gun.obj.empty(opt.peers)){ // if data not found, don't ack if there are peers.
return; // Hmm, what if we have peers but we are disconnected?
}
root.on('in', {'@': at['#'], put: Gun.graph.node(data), how: 'lS'});
root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.I});
//},1);
});
@ -1813,7 +1897,9 @@
var mesh = function(){};
mesh.out = function(msg){ var tmp;
//console.log("count:", msg['#'], msg);
if(this.to){ this.to.next(msg) }
//if(mesh.last != msg['#']){ return mesh.last = msg['#'], this.to.next(msg) }
if((tmp = msg['@'])
&& (tmp = ctx.dup.s[tmp])
&& (tmp = tmp.it)
@ -1886,7 +1972,8 @@
return; // TODO: this still needs to be tested in the browser!
}
}
if((tmp = msh.to) && (tmp[peer.url] || tmp[peer.id])){ return } // TODO: still needs to be tested
if((tmp = msh.to) && (tmp[peer.url] || tmp[peer.id])){ return } // TODO: still needs to be tested
//console.log('out', JSON.parse(raw));
if(peer.batch){
peer.batch.push(raw);
return;
@ -1898,14 +1985,16 @@
peer.batch = null;
if(!tmp.length){ return }
send(JSON.stringify(tmp), peer);
}, ctx.opt.wait || 1);
}, ctx.opt.gap || ctx.opt.wait || 1);
send(raw, peer);
}
function send(raw, peer){
var wire = peer.wire;
try{
if(wire.send){
if(wire.readyState === wire.OPEN){
//console.log("send:", raw);
wire.send(raw);
} else {
(peer.queue = peer.queue || []).push(raw);
@ -2019,7 +2108,10 @@
opt.WebSocket = websocket;
var mesh = opt.mesh = opt.mesh || Gun.Mesh(root);
root.on('out', mesh.out);
root.on('create', function(at){
this.to.next(at);
root.on('out', mesh.out);
});
opt.wire = opt.wire || open;
function open(peer){
@ -2041,7 +2133,9 @@
mesh.hi(peer);
}
wire.onmessage = function(msg){
//console.log('in', JSON.parse(msg.data || msg));
if(!msg){ return }
env.inLength = (env.inLength || 0) + (msg.data || msg).length; // TEMPORARY, NON-STANDARD, FOR DEBUG
mesh.hear(msg.data || msg, peer);
};
return wire;

2
gun.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,13 +1,73 @@
;(function(){
if('debug' !== process.env.GUN_ENV){ return }
var db = {length: 0, hash: {}};
console.log("start :)");
global.DEBUG = 1;
setInterval(function(){
var print = '', tmp;
var mem = process.memoryUsage();
var used = mem.rss / 1024 / 1024;
used = used.toFixed(1);
print += used +' MB rss. ';
var used = mem.heapTotal / 1024 / 1024;
used = used.toFixed(1);
print += used +' MB hT. ';
var used = mem.heapUsed / 1024 / 1024;
used = used.toFixed(1);
console.log(used, 'MB');
}, 1000);
print += used +' MB hU. ';
if(db.root){
db.concurrency = Object.keys(db.peers||{}).length;
print += db.concurrency +' peers. ';
db.nodes = Object.keys(db.root.graph||{}).length;
print += db.nodes + ' nodes. ';
if(db.count){ print += db.count + ' msgs. '}
if(tmp = db.root.msgsLength){
tmp = (tmp / 1024 / 1024).toFixed(2);
print += tmp + ' length MB. ';
}
if(db.last){ print += '\n' + JSON.stringify(db.last, null, 2) }
if(db.hash){
print += '\nSome 100 Fast Hash Counts: \n' + JSON.stringify(db.hash, null, 2);
var l = Object.keys(db.hash), i = l.length;
if(i > 100){
i = i - 100;
Gun.list.map(l, function(k){
if(--i <= 0){ return }
delete db.hash[k];
});
}
}
}
db.print = print;
print = print.split('\n')[0];
console.log(print);
}, 2500);
var Gun = require('../gun');
Gun.on('opt', function(root){
this.to.next(root);
if(root.once){ return }
console.log(">>>>>>>>>", root);
root.debug = db;
db.root = root;
db.peers = root.opt.peers;
db.count = 0;
root.on('in', function(msg){
this.to.next(msg);
if(!msg.NTS){ db.last = msg }
db.count++;
var tmp = msg['##'];
if(tmp && msg.put){
if(!db.hash[tmp]){ db.hash[tmp] = [0, ''] }
db.hash[tmp][0] = (db.hash[tmp][0] || 0) + 1;
var preview = Object.keys(msg.put||{});
db.hash[tmp][1] = preview.toString(', ').slice(0,500) + ' ...';
}
});
})
}());

46
lib/evict.js Normal file
View File

@ -0,0 +1,46 @@
;(function(){
var Gun = (typeof window !== 'undefined')? window.Gun : require('../gun');
var ev = {}, empty = {}, u;
Gun.on('opt', function(root){
this.to.next(root);
if(root.once){ return }
if(typeof process == 'undefined'){ return }
var util = process.memoryUsage;
if(!util){ return }
ev.max = parseFloat(root.opt.memory || process.env.WEB_MEMORY || 512) * 0.8;
setInterval(check, 1000);
function check(){
var used = ev.used = util().rss / 1024 / 1024;
if(used < ev.max){ return }
setTimeout(GC, 1);
}
function GC(){
var souls = Object.keys(root.graph||empty);
var toss = Math.ceil(souls.length * 0.01);
//var start = Gun.state(), i = toss;
Gun.list.map(souls, function(soul){
if(--toss < 0){ return }
root.gun.get(soul).off();
});
//console.log("evicted", i, 'nodes in', ((Gun.state() - start)/1000).toFixed(2), 'sec.');
}
/*
root.on('in', function(msg){
this.to.next(msg);
if(msg.get){
return;
}
Gun.graph.is(msg, function(node, soul){
var meta = (root.next||empty)[soul];
if(!meta){ return }
Gun.node.is(node, function(data, key){
});
});
});
*/
});
}());

View File

@ -6,9 +6,9 @@ function Radisk(opt){
opt = opt || {};
opt.file = String(opt.file || 'radata');
opt.thrash = opt.thrash || opt.wait || 1;
opt.until = opt.until || opt.wait || 1;
opt.batch = opt.batch || 10 * 1000;
opt.size = opt.size || (1024 * 1024 * 10); // 10MB
opt.chunk = opt.chunk || (1024 * 1024 * 10); // 10MB
opt.code = opt.code || {};
opt.code.from = opt.code.from || '!';
@ -51,7 +51,7 @@ function Radisk(opt){
if(cb){ r.batch.acks.push(cb) }
if(++r.batch.ed >= opt.batch){ return r.thrash() } // (2)
clearTimeout(r.batch.to); // (1)
r.batch.to = setTimeout(r.thrash, opt.thrash || 1);
r.batch.to = setTimeout(r.thrash, opt.until || 1);
}
r.batch = Radix();
@ -137,7 +137,7 @@ function Radisk(opt){
f.each = function(val, key, k, pre){
f.count++;
var enc = Radisk.encode(pre.length) +'#'+ Radisk.encode(k) + (u === val? '' : '='+ Radisk.encode(val)) +'\n';
if(opt.size < f.text.length + enc.length){
if(opt.chunk < f.text.length + enc.length){
f.text = '';
f.limit = Math.ceil(f.count/2);
f.count = 0;
@ -204,7 +204,7 @@ function Radisk(opt){
}
g.ack = function(as){
if(!as.ack){ return }
as.ack(g.err, RAD(as.key));
as.ack(g.err, (RAD || noop)(as.key));
}
opt.store.list(g.lex);
}
@ -257,7 +257,7 @@ function Radisk(opt){
opt.store.get(file, p.read);
}
var q = {}, RAD, u;
var q = {}, noop = function(){}, RAD, u;
return r;
}

View File

@ -10,8 +10,8 @@ Gun.on('opt', function(ctx){
if(ctx.once){ return }
if(!process.env.AWS_S3_BUCKET){ return }
opt.batch = opt.batch || (1000 * 10);
opt.thrash = opt.thrash || (1000 * 15);
opt.size = opt.size || (1024 * 1024 * 10); // 10MB
opt.until = opt.until || (1000 * 15);
opt.chunk = opt.chunk || (1024 * 1024 * 10); // 10MB
try{AWS = require('aws-sdk');
}catch(e){

View File

@ -1,6 +1,13 @@
;(function(){
var Gun = require('../gun');
var Gun = require('../gun'), u;
Gun.serve = require('./serve');
process.env.GUN_ENV = process.env.GUN_ENV || 'debug';
Gun.on('opt', function(root){
this.to.next(root);
if(root.once){ return }
if(u !== root.opt.super){ return }
root.opt.super = true;
})
require('../nts');
require('./store');
require('./rs3');
@ -9,6 +16,7 @@
require('./verify');
require('./file');
require('./bye');
require('./evict');
if('debug' === process.env.GUN_ENV){ require('./debug') }
module.exports = Gun;
}());

View File

@ -10,7 +10,7 @@ Gun.on('opt', function(ctx){
if(ctx.once){ return }
if(false !== opt.localStorage && !process.env.AWS_S3_BUCKET){ return } // TODO: Remove this after migration.
if(false === opt.radisk){ return }
console.log("BUG WARNING: Radix Storage Engine (RSE) has a known rare edge case, if data gets split between file chunks, a GET may only return the first chunk!!!");
console.log("BUG WARNING: Radix Storage Engine (RAD) has a known rare edge case, if data gets split between file chunks, a GET may only return the first chunk!!!");
opt.store = opt.store || Store(opt);
var rad = Radisk(opt);

View File

@ -77,6 +77,7 @@ Gun.on('opt', function(ctx){
ctx.on('hi', peer);
wire.on('message', function(msg){
//console.log("MESSAGE", msg);
ctx.msgsLength = (ctx.msgsLength || 0) + (msg.data || msg).length; // TEMPORARY, NON-STANDARD, FOR DEBUG
opt.mesh.hear(msg.data || msg, peer);
});
wire.on('close', function(){

View File

@ -1,6 +1,6 @@
{
"name": "gun",
"version": "0.9.996",
"version": "0.9.997",
"description": "A realtime, decentralized, offline-first, graph data synchronization engine.",
"main": "index.js",
"browser": "gun.min.js",

View File

@ -10,7 +10,72 @@ var store = root.localStorage || {setItem: noop, removeItem: noop, getItem: noop
If you update anything here, consider updating the other adapters as well.
*/
Gun.on('opt', function(root){
Gun.on('create', function(root){
// This code is used to queue offline writes for resync.
// See the next 'opt' code below for actual saving of data.
var ev = this.to, opt = root.opt;
if(root.once){ return ev.next(root) }
if(false === opt.localStorage){ return ev.next(root) }
opt.file = opt.file || 'gun/';
var gap = Gun.obj.ify(store.getItem('gap/'+opt.file)) || {};
var empty = Gun.obj.empty, id, to, go;
// add re-sync command.
if(!empty(gap)){
root.on('localStorage', function(disk){
this.off();
var send = {}
Gun.obj.map(gap, function(node, soul){
Gun.obj.map(node, function(val, key){
send[soul] = Gun.state.to(disk[soul], key, send[soul]);
});
});
setTimeout(function(){
root.on('out', {put: send, '#': root.ask(ack), I: root.gun});
},10);
});
}
root.on('out', function(msg){
if(msg.lS){ return }
if(msg.I && msg.put && !msg['@'] && !empty(opt.peers)){
id = msg['#'];
Gun.graph.is(msg.put, null, map);
if(!to){ to = setTimeout(flush, opt.wait || 1) }
}
this.to.next(msg);
});
root.on('ack', ack);
function ack(ack){ // TODO: This is experimental, not sure if we should keep this type of event hook.
if(ack.err || !ack.ok){ return }
var id = ack['@'];
setTimeout(function(){
Gun.obj.map(gap, function(node, soul){
Gun.obj.map(node, function(val, key){
if(id !== val){ return }
delete node[key];
});
if(empty(node)){
delete gap[soul];
}
});
flush();
}, opt.wait || 1);
};
ev.next(root);
var map = function(val, key, node, soul){
(gap[soul] || (gap[soul] = {}))[key] = id;
}
var flush = function(){
clearTimeout(to);
to = false;
try{store.setItem('gap/'+opt.file, JSON.stringify(gap));
}catch(e){ Gun.log(err = e || "localStorage failure") }
}
});
Gun.on('create', function(root){
this.to.next(root);
var opt = root.opt;
if(root.once){ return }
@ -18,6 +83,8 @@ Gun.on('opt', function(root){
opt.file = opt.file || opt.prefix || 'gun/'; // support old option name.
var graph = root.graph, acks = {}, count = 0, to;
var disk = Gun.obj.ify(store.getItem(opt.file)) || {};
var lS = function(){}, u;
root.on('localStorage', disk); // NON-STANDARD EVENT!
root.on('put', function(at){
this.to.next(at);
@ -31,12 +98,12 @@ Gun.on('opt', function(root){
to = setTimeout(flush, opt.wait || 1);
});
root.on('get', function(at){
this.to.next(at);
var lex = at.get, soul, data, u;
root.on('get', function(msg){
this.to.next(msg);
var lex = msg.get, soul, data, u;
//setTimeout(function(){
if(!lex || !(soul = lex['#'])){ return }
//if(0 >= at.cap){ return }
//if(0 >= msg.cap){ return }
var has = lex['.'];
data = disk[soul] || u;
if(data && has){
@ -45,7 +112,7 @@ Gun.on('opt', function(root){
if(!data && !Gun.obj.empty(opt.peers)){ // if data not found, don't ack if there are peers.
return; // Hmm, what if we have peers but we are disconnected?
}
root.on('in', {'@': at['#'], put: Gun.graph.node(data), how: 'lS'});
root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.I});
//},1);
});

View File

@ -5,7 +5,9 @@ function Mesh(ctx){
var mesh = function(){};
mesh.out = function(msg){ var tmp;
//console.log("count:", msg['#'], msg);
if(this.to){ this.to.next(msg) }
//if(mesh.last != msg['#']){ return mesh.last = msg['#'], this.to.next(msg) }
if((tmp = msg['@'])
&& (tmp = ctx.dup.s[tmp])
&& (tmp = tmp.it)
@ -78,7 +80,8 @@ function Mesh(ctx){
return; // TODO: this still needs to be tested in the browser!
}
}
if((tmp = msh.to) && (tmp[peer.url] || tmp[peer.id])){ return } // TODO: still needs to be tested
if((tmp = msh.to) && (tmp[peer.url] || tmp[peer.id])){ return } // TODO: still needs to be tested
//console.log('out', JSON.parse(raw));
if(peer.batch){
peer.batch.push(raw);
return;
@ -90,14 +93,16 @@ function Mesh(ctx){
peer.batch = null;
if(!tmp.length){ return }
send(JSON.stringify(tmp), peer);
}, ctx.opt.wait || 1);
}, ctx.opt.gap || ctx.opt.wait || 1);
send(raw, peer);
}
function send(raw, peer){
var wire = peer.wire;
try{
if(wire.send){
if(wire.readyState === wire.OPEN){
//console.log("send:", raw);
wire.send(raw);
} else {
(peer.queue = peer.queue || []).push(raw);

View File

@ -18,7 +18,10 @@ Gun.on('opt', function(root){
opt.WebSocket = websocket;
var mesh = opt.mesh = opt.mesh || Gun.Mesh(root);
root.on('out', mesh.out);
root.on('create', function(at){
this.to.next(at);
root.on('out', mesh.out);
});
opt.wire = opt.wire || open;
function open(peer){
@ -40,7 +43,9 @@ Gun.on('opt', function(root){
mesh.hi(peer);
}
wire.onmessage = function(msg){
//console.log('in', JSON.parse(msg.data || msg));
if(!msg){ return }
env.inLength = (env.inLength || 0) + (msg.data || msg).length; // TEMPORARY, NON-STANDARD, FOR DEBUG
mesh.hear(msg.data || msg, peer);
};
return wire;

View File

@ -16,6 +16,7 @@ Gun.chain.chain = function(sub){
function output(msg){
var put, get, at = this.as, back = at.back, root = at.root;
if(!msg.I){ msg.I = at.gun }
if(!msg.gun){ msg.gun = at.gun }
this.to.next(msg);
if(get = msg.get){

View File

@ -2,7 +2,7 @@
// On event emitter generic javascript utility.
module.exports = function onto(tag, arg, as){
if(!tag){ return {to: onto} }
var tag = (this.tag || (this.tag = {}))[tag] ||
var u, tag = (this.tag || (this.tag = {}))[tag] ||
(this.tag[tag] = {tag: tag, to: onto._ = {
next: function(arg){ var tmp;
if((tmp = this.to)){
@ -33,7 +33,7 @@ module.exports = function onto(tag, arg, as){
(be.back = tag.last || tag).to = be;
return tag.last = be;
}
(tag = tag.to).next(arg);
if((tag = tag.to) && u !== arg){ tag.next(arg) }
return tag;
};

View File

@ -93,6 +93,7 @@ function batch(){ var as = this;
as.res = as.res || function(cb){ if(cb){ cb() } };
as.res(function(){
var cat = (as.gun.back(-1)._), ask = cat.ask(function(ack){
cat.root.on('ack', ack);
this.off(); // One response is good enough for us currently. Later we may want to adjust this.
if(!as.ack){ return }
as.ack(ack, this);

View File

@ -34,7 +34,9 @@ Gun.dup = require('./dup');
var gun = at.gun.opt(at.opt);
if(!at.once){
at.on('in', root, at);
at.on('out', root, at);
at.on('out', root, obj_to(at, {out: root}));
Gun.on('create', at);
at.on('create', at);
}
at.once = 1;
return gun;
@ -44,7 +46,13 @@ Gun.dup = require('./dup');
var ev = this, at = ev.as, gun = at.gun, dup, tmp;
//if(!msg.gun){ msg.gun = at.gun }
if(!(tmp = msg['#'])){ tmp = msg['#'] = text_rand(9) }
if((dup = at.dup).check(tmp)){ return }
if((dup = at.dup).check(tmp)){
if(at.out === msg.out){
msg.out = u;
ev.to.next(msg);
}
return;
}
dup.track(tmp);
//msg = obj_to(msg);//, {gun: at.gun}); // can we delete this now?
if(!at.ask(msg['@'], msg)){
@ -57,7 +65,11 @@ Gun.dup = require('./dup');
//at.on('put', put(msg));
}
}
at.on('out', msg);
ev.to.next(msg);
if(!at.out){
msg.out = root;
at.on('out', msg);
}
}
}());
@ -97,8 +109,11 @@ Gun.dup = require('./dup');
function merge(node, soul){
var ctx = this, cat = ctx.gun._, at = (cat.next || empty)[soul];
if(!at){
ctx.souls[soul] = false;
return
if(!(cat.opt||empty).super){
ctx.souls[soul] = false;
return;
}
at = (ctx.gun.get(soul)._);
}
var msg = ctx.map[soul] = {
put: node,