This commit is contained in:
Mark Nadal 2019-02-10 02:55:51 -08:00
parent de3a2f4b33
commit 85d6fc9e57
24 changed files with 543 additions and 203 deletions

46
gun.js
View File

@ -807,25 +807,9 @@
Gun.on.get = function(msg, gun){
var root = gun._, get = msg.get, soul = get[_soul], node = root.graph[soul], has = get[_has], tmp;
var next = root.next || (root.next = {}), at = next[soul];
if(obj_has(soul, '*')){ // TEMPORARY HACK FOR MARTTI, TESTING
var graph = {};
Gun.obj.map(root.graph, function(node, s){
if(Gun.text.match(s, soul)){
graph[s] = Gun.obj.copy(node);
}
});
if(!Gun.obj.empty(graph)){
root.on('in', {
'@': msg['#'],
how: '*',
put: graph,
$: gun
});
}
} // TEMPORARY HACK FOR MARTTI, TESTING
if(!node){ return root.on('get', msg) }
if(has){
if(!obj_has(node, has)){ return root.on('get', msg) }
if('string' != typeof has || !obj_has(node, has)){ return root.on('get', msg) }
node = Gun.state.to(node, has);
// If we have a key in-memory, do we really need to fetch?
// Maybe... in case the in-memory key we have is a local write
@ -966,6 +950,7 @@
at.on('in', at);
return;
}*/
if(at.lex){ msg.get = obj_to(at.lex, msg.get) }
if(get['#'] || at.soul){
get['#'] = get['#'] || at.soul;
msg['#'] || (msg['#'] = text_rand(9));
@ -988,6 +973,17 @@
get: back.get
});
if(tmp){ return }
} else
if('string' != typeof get){
var put = {}, meta = (back.put||{})._;
Gun.obj.map(back.put, function(v,k){
if(!Gun.text.match(k, get)){ return }
put[k] = v;
})
if(!Gun.obj.empty(put)){
put._ = meta;
back.on('in', {$: back.$, put: put, get: back.get})
}
}
root.ask(ack, msg);
return root.on('in', msg);
@ -1087,7 +1083,6 @@
relate(cat, msg, at, rel);
echo(cat, msg, eve);
}
var C = 0;
function relate(at, msg, from, rel){
if(!rel || node_ === at.get){ return }
@ -1203,7 +1198,7 @@
function ack(msg, ev){
var as = this.as, get = as.get || empty, at = as.$._, tmp = (msg.put||empty)[get['#']];
if(at.ack){ at.ack = (at.ack + 1) || 1; }
if(!msg.put || (get['.'] && !obj_has(tmp, at.get))){
if(!msg.put || ('string' == typeof get['.'] && !obj_has(tmp, at.get))){
if(at.put !== u){ return }
at.on('in', {
get: at.get,
@ -1259,12 +1254,18 @@
} else
if(tmp = rel.is(key)){
return this.get(tmp, cb, as);
} else
if(obj.is(key)){
gun = this;
if(tmp = ((tmp = key['#'])||empty)['='] || tmp){ gun = gun.get(tmp) }
gun._.lex = key;
return gun;
} else {
(as = this.chain())._.err = {err: Gun.log('Invalid get request!', key)}; // CLEAN UP
if(cb){ cb.call(as, as._.err) }
return as;
}
if(tmp = cat.stun){ // TODO: Refactor?
if(tmp = this._.stun){ // TODO: Refactor?
gun._.stun = gun._.stun || tmp;
}
if(cb && cb instanceof Function){
@ -1748,8 +1749,9 @@
}
function each(v,k){
if(n_ === k){ return }
var msg = this.msg, gun = msg.$, at = this.at, tmp = (gun.get(k)._);
(tmp.echo || (tmp.echo = {}))[at.id] = tmp.echo[at.id] || at;
var msg = this.msg, gun = msg.$, at = gun._, cat = this.at, tmp = at.lex;
if(tmp && !Gun.text.match(k, tmp['.'] || tmp['#'] || tmp)){ return } // TODO: Ugly hack!
((tmp = gun.get(k)._).echo || (tmp.echo = {}))[cat.id] = tmp.echo[cat.id] || cat;
}
var obj_map = Gun.obj.map, noop = function(){}, event = {stun: noop, off: noop}, n_ = Gun.node._, u;
})(USE, './map');

2
gun.min.js vendored

File diff suppressed because one or more lines are too long

18
lib/fsrm.js Normal file
View File

@ -0,0 +1,18 @@
var fs = require('fs');
var nodePath = require('path');
var dir = __dirname + '/../';
module.exports = function rm(path, full) {
path = full || nodePath.join(dir, path);
if(!fs.existsSync(path)){ return }
fs.readdirSync(path).forEach(function(file,index){
var curPath = path + "/" + file;
if(fs.lstatSync(curPath).isDirectory()) { // recurse
rm(null, curPath);
} else { // delete file
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(path);
};

View File

@ -5,6 +5,9 @@
opt = opt || {};
opt.log = opt.log || console.log;
opt.file = String(opt.file || 'radata');
var has = (Radisk.has || (Radisk.has = {}))[opt.file];
if(has){ return has }
opt.pack = opt.pack || (opt.memory? (opt.memory * 1000 * 1000) : 1399000000) * 0.3; // max_old_space_size defaults to 1400 MB.
opt.until = opt.until || opt.wait || 9;
opt.batch = opt.batch || 10 * 1000;
@ -13,6 +16,7 @@
opt.code.from = opt.code.from || '!';
function ename(t){ return encodeURIComponent(t).replace(/\*/g, '%2A') }
function atomic(v){ return u !== v && (!v || 'object' != typeof v) }
var map = Gun.obj.map;
if(!opt.store){
@ -36,20 +40,23 @@
var r = function(key, val, cb){
key = ''+key;
if(val instanceof Function){
var o = cb;
cb = val;
val = r.batch(key);
if(u !== val){
cb(u, val, o);
if(atomic(val)){ return }
// if a node is requested and some of it is cached... the other parts might not be.
return cb(u, val);
}
if(r.thrash.at){
val = r.thrash.at(key);
if(u !== val){
cb(u, val, o);
if(atomic(val)){ cb(u, val, o); return }
// if a node is requested and some of it is cached... the other parts might not be.
return cb(u, val);
}
}
return r.read(key, cb);
return r.read(key, cb, o);
}
r.batch(key, val);
if(cb){ r.batch.acks.push(cb) }
@ -73,8 +80,9 @@
r.batch = Radix();
r.batch.acks = [];
r.batch.ed = 0;
console.debug.i = 1;
r.save(batch, function(err, ok){
if(++i > 1){ return }
if(++i > 1){ opt.log('RAD ERR: Radisk has callbacked multiple times, please report this as a BUG at github.com/amark/gun/issues ! ' + i); return }
if(err){ opt.log('err', err) }
map(batch.acks, function(cb){ cb(err, ok) });
thrash.at = null;
@ -90,7 +98,7 @@
4. Read the previous file to that into memory
5. Scan through the in memory radix for all values lexically less than the limit.
6. Merge and write all of those to the in-memory file and back to disk.
7. If file to large, split. More details needed here.
7. If file too large, split. More details needed here.
*/
r.save = function(rad, cb){
var s = function Span(){};
@ -142,6 +150,7 @@
f.count = 0;
f.file = file;
f.each = function(val, key, k, pre){
//console.log("RAD:::", JSON.stringify([val, key, k, pre]));
if(u !== val){ f.count++ }
if(opt.pack <= (val||'').length){ return cb("Record too big!"), true }
var enc = Radisk.encode(pre.length) +'#'+ Radisk.encode(k) + (u === val? '' : ':'+ Radisk.encode(val)) +'\n';
@ -150,7 +159,7 @@
f.limit = Math.ceil(f.count/2);
f.count = 0;
f.sub = Radix();
Radix.map(rad, f.slice)
Radix.map(rad, f.slice);
return true;
}
f.text += enc;
@ -185,19 +194,21 @@
;(function(){
var Q = {};
r.read = function(key, cb, next){
if(RAD && !next){ // cache
r.read = function(key, cb, o){
o = o || {};
if(RAD && !o.next){ // cache
var val = RAD(key);
if(u !== val){
//if(u !== val){
//cb(u, val, o);
if(atomic(val)){ cb(u, val, o); return }
// if a node is requested and some of it is cached... the other parts might not be.
return cb(u, val);
}
//}
}
var g = function Get(){}, tmp;
g.lex = function(file){
file = (u === file)? u : decodeURIComponent(file);
if(!file || file > (next || key)){
if(next){ g.file = file }
if(!file || file > (o.next || key)){
if(o.next){ g.file = file }
if(tmp = Q[g.file]){
tmp.push({key: key, ack: cb, file: g.file});
return true;
@ -208,20 +219,25 @@
}
g.file = file;
}
g.it = function(err, disk){
g.it = function(err, disk, info){
if(g.err = err){ opt.log('err', err) }
if(disk){ RAD = g.disk = disk }
o.parsed = (o.parsed || 0) + (info.parsed||0);
o.chunks = (o.chunks || 0) + 1;
disk = Q[g.file]; delete Q[g.file];
map(disk, g.ack);
}
g.ack = function(as){
if(!as.ack){ return }
var tmp = as.key, rad = g.disk || noop, data = rad(tmp), last = rad.last;
if(data){ as.ack(g.err, data) }
else if(!as.file){ return as.ack(g.err, u) }
if(!last || last === tmp){ return as.ack(g.err, u) } // is this correct?
if(last > tmp && 0 > last.indexOf(tmp)){ return as.ack(g.err, u) }
r.read(tmp, as.ack, as.file);
if(!o.some){ o.some = (u !== data) }
if(u !== data){ as.ack(g.err, data, o) }
else if(!as.file){ !o.some && as.ack(g.err, u, o); return }
if(!last || last === tmp){ !o.some && as.ack(g.err, u, o); return }
if(last > tmp && 0 != last.indexOf(tmp)){ !o.some && as.ack(g.err, u, o); return }
if(o.some && o.parsed >= o.limit){ return }
o.next = as.file;
r.read(tmp, as.ack, o);
}
r.list(g.lex);
}
@ -236,14 +252,13 @@
Then we can work on the harder problem of being multi-process.
*/
var Q = {}, s = String.fromCharCode(31);
r.parse = function(file, cb){ var q;
r.parse = function(file, cb, raw){ var q;
if(q = Q[file]){ return q.push(cb) } q = Q[file] = [cb];
var p = function Parse(){};
var p = function Parse(){}, info = {};
p.disk = Radix();
p.read = function(err, data){ var tmp;
delete Q[file];
if((p.err = err) || (p.not = !data)){
//return cb(err, u);//map(q, p.ack);
return map(q, p.ack);
}
if(typeof data !== 'string'){
@ -257,6 +272,10 @@
if(p.err){ return map(q, p.ack) }
}
var tmp = p.split(data), pre = [], i, k, v;
if(!tmp || 0 !== tmp[1]){
p.err = "File '"+file+"' does not have root radix! ";
return map(q, p.ack);
}
while(tmp){
k = v = u;
i = tmp[1];
@ -275,6 +294,7 @@
tmp = p.split(tmp[2]);
}
//cb(err, p.disk);
info.parsed = data.length;
map(q, p.ack);
};
p.split = function(t){
@ -290,9 +310,10 @@
}
p.ack = function(cb){
if(!cb){ return }
if(p.err || p.not){ return cb(p.err, u) }
cb(u, p.disk);
if(p.err || p.not){ return cb(p.err, u, info) }
cb(u, p.disk, info);
}
if(raw){ return p.read(null, raw) }
opt.store.get(ename(file), p.read);
}
}());
@ -315,8 +336,11 @@
return cb(u, 1);
}
dir(file, true);
cb.listed = (cb.listed || 0) + 1;
r.write(f, dir, function(err, ok){
if(err){ return cb(err) }
cb.listed = (cb.listed || 0) - 1;
if(cb.listed !== 0){ return }
cb(u, 1);
}, true);
}
@ -353,6 +377,7 @@
}());
var noop = function(){}, RAD, u;
Radisk.has[opt.file] = r;
return r;
}

View File

@ -5,9 +5,9 @@
key = ''+key;
if(!t && u !== val){
radix.last = (key < radix.last)? radix.last : key;
radix.sort = null;
delete (radix.$||{})[_];
}
t = t || radix[_] || (radix[_] = {});
t = t || radix.$ || (radix.$ = {});
var i = 0, l = key.length-1, k = key[i], at, tmp;
while(!(at = t[k]) && i < l){
k += key[++i];
@ -15,9 +15,9 @@
if(!at){
if(!map(t, function(r, s){
var ii = 0, kk = '';
while(s[ii] == key[ii]){
if((s||'').length){ while(s[ii] == key[ii]){
kk += s[ii++];
}
} }
if(kk){
if(u === val){
if(ii <= l){ return }
@ -25,47 +25,52 @@
}
var __ = {};
__[s.slice(ii)] = r;
(__[key.slice(ii)] = {})[$] = val;
(t[kk] = {})[_] = __;
ii = key.slice(ii);
('' === ii)? (__[''] = val) : ((__[ii] = {})[''] = val);
t[kk] = __;
delete t[s];
return true;
}
})){
if(u === val){ return; }
(t[k] || (t[k] = {}))[$] = val;
(t[k] || (t[k] = {}))[''] = val;
}
if(u === val){
return tmp;
}
} else
if(i == l){
if(u === val){ return (u === (tmp = at[$]))? at[_] : tmp }
at[$] = val;
if(u === val){ return (u === (tmp = at['']))? at : tmp }
at[''] = val;
} else {
if(u !== val){ at.sort = null }
return radix(key.slice(++i), val, at[_] || (at[_] = {}));
if(u !== val){ delete at[_] }
return radix(key.slice(++i), val, at || (at = {}));
}
}
return radix;
};
Radix.map = function map(radix, cb, opt, pre){ pre = pre || [];
var t = radix[_] || radix, keys = radix.sort || (radix.sort = Object.keys(t).sort()), i = 0, l = keys.length;
for(;i < l; i++){ var key = keys[i], tree = t[key], tmp;
if(u !== (tmp = tree[$])){
tmp = cb(tmp, pre.join('') + key, key, pre);
var t = ('function' == typeof radix)? radix.$ || {} : radix;
if(!t){ return }
var keys = (t[_]||no).sort || (t[_] = function $(){ $.sort = Object.keys(t).sort(); return $ }()).sort;
//var keys = Object.keys(t).sort();
var i = 0, l = keys.length;
for(;i < l; i++){ var key = keys[i], tree = t[key], tmp, p;
if(!tree || '' === key || _ === key){ continue }
p = pre.slice(); p.push(key);
if(u !== (tmp = tree[''])){
tmp = cb(tmp, p.join(''), key, pre);
if(u !== tmp){ return tmp }
} else
if(opt){
cb(u, pre.join(''), key, pre);
}
if(tmp = tree[_]){
pre.push(key);
tmp = map(tree, cb, opt, pre);
//tmp = map(tmp, cb, opt, pre);
tmp = cb(u, pre.join(''), key, pre);
if(u !== tmp){ return tmp }
pre.pop();
}
pre = p;
tmp = map(tree, cb, opt, pre);
if(u !== tmp){ return tmp }
pre.pop();
}
};
@ -80,6 +85,6 @@
}
var map = Gun.obj.map, no = {}, u;
var $ = String.fromCharCode(30), _ = String.fromCharCode(29);
var _ = String.fromCharCode(24);
}());

View File

@ -3,6 +3,7 @@ var Gun = (typeof window !== "undefined")? window.Gun : require('../gun');
function Store(opt){
opt = opt || {};
opt.file = String(opt.file || 'radata');
if(Gun.TESTING){ opt.file = 'radatatest' }
var fs = require('fs'), u;
var store = function Store(){};

View File

@ -9,11 +9,19 @@
function Store(opt){
opt = opt || {};
opt.file = String(opt.file || 'radata');
if(Gun.TESTING){ opt.file = 'radatatest' }
var db = null;
opt.indexedDB = opt.indexedDB || window.indexedDB;
try{opt.indexedDB = opt.indexedDB || indexedDB}catch(e){}
try{if(!opt.indexedDB || 'file:' == location.protocol){
var store = {}, s = {}, u;
store.put = function(f, d, cb){ s[f] = d; cb(null, 1) };
store.get = function(f, cb){ cb(null, s[f] || u) };
return store;
}}catch(e){}
// Initialize indexedDB. Version 1.
var request = opt.indexedDB.open(opt.file, 1)
var request = opt.indexedDB.open(opt.file, 1);
// Create schema. onupgradeneeded is called only when DB is first created or when the DB version increases.
request.onupgradeneeded = function(event){
@ -116,6 +124,10 @@
}
};
checkFunc();
try{
//if(/^((?!chrome|android).)*safari/i.test(navigator.userAgent)){ return }
setTimeout(function(){ db && db.close(); db = null }, 1000 * 15); // reset webkit bug
}catch(e){}
};
return store;

View File

@ -1,58 +1,86 @@
var Gun = (typeof window !== "undefined")? window.Gun : require('../gun');
Gun.on('create', function(root){
this.to.next(root);
var opt = root.opt, u;
if(false === opt.radisk){ return }
var Radisk = (Gun.window && Gun.window.Radisk) || require('./radisk');
var Radix = Radisk.Radix;
this.to.next(root);
var opt = root.opt, u;
if(false === opt.radisk){ return }
var Radisk = (Gun.window && Gun.window.Radisk) || require('./radisk');
var Radix = Radisk.Radix;
opt.store = opt.store || (!Gun.window && require('./rfs')(opt));
var rad = Radisk(opt), esc = String.fromCharCode(27);
opt.store = opt.store || (!Gun.window && require('./rfs')(opt));
var rad = Radisk(opt), esc = String.fromCharCode(27);
root.on('put', function(msg){
this.to.next(msg);
var id = msg['#'], track = !msg['@'], acks = track? 0 : u; // only ack non-acks.
if(msg.rad && !track){ return } // don't save our own acks
Gun.graph.is(msg.put, null, function(val, key, node, soul){
if(track){ ++acks }
val = Radisk.encode(val, null, esc)+'>'+Radisk.encode(Gun.state.is(node, key), null, esc);
rad(soul+'.'+key, val, (track? ack : u));
});
function ack(err, ok){
acks--;
if(ack.err){ return }
if(ack.err = err){
root.on('in', {'@': id, err: err});
return;
}
if(acks){ return }
root.on('in', {'@': id, ok: 1});
}
});
root.on('get', function(msg){
this.to.next(msg);
var id = msg['#'], soul = msg.get['#'], key = msg.get['.']||'', tmp = soul+'.'+key, node;
rad(tmp, function(err, val){
if(val){
if(val && typeof val !== 'string'){
if(key){
val = u;
} else {
Radix.map(val, each)
}
}
if(!node && val){ each(val, key) }
}
root.on('in', {'@': id, put: Gun.graph.node(node), err: err? err : u, rad: Radix});
});
function each(val, key){
tmp = val.lastIndexOf('>');
var state = Radisk.decode(val.slice(tmp+1), null, esc);
val = Radisk.decode(val.slice(0,tmp), null, esc);
node = Gun.state.ify(node, key, state, val, soul);
}
});
root.on('put', function(msg){
this.to.next(msg);
var id = msg['#'] || Gun.text.random(3), track = !msg['@'], acks = track? 0 : u; // only ack non-acks.
if(msg.rad && !track){ return } // don't save our own acks
Gun.graph.is(msg.put, null, function(val, key, node, soul){
if(track){ ++acks }
//console.log('put:', soul, key, val);
val = Radisk.encode(val, null, esc)+'>'+Radisk.encode(Gun.state.is(node, key), null, esc);
//rad(soul+'.'+key, val, (track? ack : u));
//console.log("PUT!", id, JSON.stringify(soul+esc+key));
rad(soul+esc+key, val, (track? ack : u));
});
function ack(err, ok){
acks--;
if(ack.err){ return }
if(ack.err = err){
root.on('in', {'@': id, err: err});
return;
}
if(acks){ return }
//console.log("PAT!", id);
root.on('in', {'@': id, ok: 1});
}
});
root.on('get', function(msg){
this.to.next(msg);
var id = msg['#'], get = msg.get, soul = msg.get['#'], has = msg.get['.']||'', opt = {}, graph, lex, key, tmp;
if(typeof soul == 'string'){
key = soul;
} else
if(soul){
if(tmp = soul['*']){ opt.limit = 1 }
key = tmp || soul['='];
}
if(key && !opt.limit){ // a soul.has must be on a soul, and not during soul*
if(typeof has == 'string'){
key = key+esc+(opt.atom = has);
} else
if(has){
if(tmp = has['*']){ opt.limit = 1 }
if(key){ key = key+esc + (tmp || (opt.atom = has['='])) }
}
}
if((tmp = get['%']) || opt.limit){
opt.limit = (tmp <= (opt.pack || (1000 * 100)))? tmp : 1;
}
//console.log("GET!", id, JSON.stringify(key));
rad(key||'', function(err, data, o){
if(data){
if(typeof data !== 'string'){
if(opt.atom){
data = u;
} else {
Radix.map(data, each)
}
}
if(!graph && data){ each(data, '') }
}
//console.log("GOT!", id, JSON.stringify(key));
root.on('in', {'@': id, put: graph, err: err? err : u, rad: Radix});
}, opt);
function each(val, has, a,b){
if(!val){ return }
has = (key+has).split(esc);
var soul = has.slice(0,1)[0];
has = has.slice(-1)[0];
tmp = val.lastIndexOf('>');
var state = Radisk.decode(val.slice(tmp+1), null, esc);
val = Radisk.decode(val.slice(0,tmp), null, esc);
(graph = graph || {})[soul] = Gun.state.ify(graph[soul], has, state, val, soul);
}
});
});

View File

@ -11,19 +11,7 @@ var write = function(path, data){
return fs.writeFileSync(nodePath.join(dir, path), data);
}
var rm = function(path, full) {
path = full || nodePath.join(dir, path);
if(!fs.existsSync(path)){ return }
fs.readdirSync(path).forEach(function(file,index){
var curPath = path + "/" + file;
if(fs.lstatSync(curPath).isDirectory()) { // recurse
rm(null, curPath);
} else { // delete file
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(path);
};
var rm = require('./fsrm');
var mk = function(path){
path = nodePath.join(dir, path);

View File

@ -1,9 +1,9 @@
{
"name": "gun",
"version": "0.9.9999991",
"version": "0.9.9999992",
"description": "A realtime, decentralized, offline-first, graph data synchronization engine.",
"main": "index.js",
"browser": "gun.min.js",
"browser": "gun.js",
"scripts": {
"start": "node examples/http.js 8765",
"https": "HTTPS_KEY=test/https/server.key HTTPS_CERT=test/https/server.crt npm start",
@ -12,7 +12,8 @@
"testsea": "mocha test/sea.js",
"e2e": "mocha e2e/distributed.js",
"docker": "hooks/build",
"unbuild": "node lib/unbuild.js && uglifyjs gun.js -o gun.min.js -c -m"
"minify": "uglifyjs gun.js -o gun.min.js -c -m",
"unbuild": "node lib/unbuild.js"
},
"repository": {
"type": "git",

View File

@ -23,6 +23,7 @@ function output(msg){
at.on('in', at);
return;
}*/
if(at.lex){ msg.get = obj_to(at.lex, msg.get) }
if(get['#'] || at.soul){
get['#'] = get['#'] || at.soul;
msg['#'] || (msg['#'] = text_rand(9));
@ -45,6 +46,17 @@ function output(msg){
get: back.get
});
if(tmp){ return }
} else
if('string' != typeof get){
var put = {}, meta = (back.put||{})._;
Gun.obj.map(back.put, function(v,k){
if(!Gun.text.match(k, get)){ return }
put[k] = v;
})
if(!Gun.obj.empty(put)){
put._ = meta;
back.on('in', {$: back.$, put: put, get: back.get})
}
}
root.ask(ack, msg);
return root.on('in', msg);
@ -144,7 +156,6 @@ function input(msg){
relate(cat, msg, at, rel);
echo(cat, msg, eve);
}
var C = 0;
function relate(at, msg, from, rel){
if(!rel || node_ === at.get){ return }
@ -190,11 +201,11 @@ function map(data, key){ // Map over only the changes on every update.
if(!(at = next[key])){
return;
}
//if(data && data[_soul] && (tmp = Gun.val.rel.is(data)) && (tmp = (cat.root.$.get(tmp)._)) && obj_has(tmp, 'put')){
//if(data && data[_soul] && (tmp = Gun.val.link.is(data)) && (tmp = (cat.root.$.get(tmp)._)) && obj_has(tmp, 'put')){
// data = tmp.put;
//}
if(at.has){
//if(!(data && data[_soul] && Gun.val.rel.is(data) === Gun.node.soul(at.put))){
//if(!(data && data[_soul] && Gun.val.link.is(data) === Gun.node.soul(at.put))){
if(u === at.put || !Gun.val.link.is(data)){
at.put = data;
}
@ -217,7 +228,10 @@ function not(at, msg){
if(!(at.has || at.soul)){ return }
var tmp = at.map, root = at.root;
at.map = null;
if(at.has){ at.link = null }
if(at.has){
if(at.dub && at.root.stop){ at.dub = null }
at.link = null;
}
//if(!root.now || !root.now[at.id]){
if(!at.pass){
if((!msg['@']) && null === tmp){ return }
@ -257,7 +271,7 @@ function ask(at, soul){
function ack(msg, ev){
var as = this.as, get = as.get || empty, at = as.$._, tmp = (msg.put||empty)[get['#']];
if(at.ack){ at.ack = (at.ack + 1) || 1; }
if(!msg.put || (get['.'] && !obj_has(tmp, at.get))){
if(!msg.put || ('string' == typeof get['.'] && !obj_has(tmp, at.get))){
if(at.put !== u){ return }
at.on('in', {
get: at.get,
@ -276,5 +290,5 @@ function ack(msg, ev){
var empty = {}, u;
var obj = Gun.obj, obj_has = obj.has, obj_put = obj.put, obj_del = obj.del, obj_to = obj.to, obj_map = obj.map;
var text_rand = Gun.text.random;
var _soul = Gun.val.rel._, node_ = Gun.node._;
var _soul = Gun.val.link._, node_ = Gun.node._;

View File

@ -32,12 +32,18 @@ Gun.chain.get = function(key, cb, as){
} else
if(tmp = rel.is(key)){
return this.get(tmp, cb, as);
} else
if(obj.is(key)){
gun = this;
if(tmp = ((tmp = key['#'])||empty)['='] || tmp){ gun = gun.get(tmp) }
gun._.lex = key;
return gun;
} else {
(as = this.chain())._.err = {err: Gun.log('Invalid get request!', key)}; // CLEAN UP
if(cb){ cb.call(as, as._.err) }
return as;
}
if(tmp = cat.stun){ // TODO: Refactor?
if(tmp = this._.stun){ // TODO: Refactor?
gun._.stun = gun._.stun || tmp;
}
if(cb && cb instanceof Function){
@ -62,8 +68,7 @@ function cache(key, back){
}
function soul(gun, cb, opt, as){
var cat = gun._, acks = 0, tmp;
if(tmp = cat.soul){ return cb(tmp, as, cat), gun }
if(tmp = cat.link){ return cb(tmp, as, cat), gun }
if(tmp = cat.soul || cat.link || cat.dub){ return cb(tmp, as, cat), gun }
gun.get(function(msg, ev){
if(u === msg.put && (tmp = (obj_map(cat.root.opt.peers, function(v,k,t){t(k)})||[]).length) && ++acks < tmp){
return;

View File

@ -31,7 +31,7 @@ var Graph = {};
env.map = env;
}
if(env.soul){
at.rel = Val.rel.ify(env.soul);
at.link = Val.link.ify(env.soul);
}
env.shell = (as||{}).shell;
env.graph = env.graph || {};
@ -46,16 +46,16 @@ var Graph = {};
at.env = env;
at.soul = soul;
if(Node.ify(at.obj, map, at)){
at.rel = at.rel || Val.rel.ify(Node.soul(at.node));
at.link = at.link || Val.link.ify(Node.soul(at.node));
if(at.obj !== env.shell){
env.graph[Val.rel.is(at.rel)] = at.node;
env.graph[Val.link.is(at.link)] = at.node;
}
}
return at;
}
function map(v,k,n){
var at = this, env = at.env, is, tmp;
if(Node._ === k && obj_has(v,Val.rel._)){
if(Node._ === k && obj_has(v,Val.link._)){
return n._; // TODO: Bug?
}
if(!(is = valid(v,k,n, at,env))){ return }
@ -64,8 +64,8 @@ var Graph = {};
if(obj_has(v, Node._) && Node.soul(v)){ // ? for safety ?
at.node._ = obj_copy(v._);
}
at.node = Node.soul.ify(at.node, Val.rel.is(at.rel));
at.rel = at.rel || Val.rel.ify(Node.soul(at.node));
at.node = Node.soul.ify(at.node, Val.link.is(at.link));
at.link = at.link || Val.link.ify(Node.soul(at.node));
}
if(tmp = env.map){
tmp.call(env.as || {}, v,k,n, at);
@ -84,14 +84,14 @@ var Graph = {};
}
tmp = node(env, {obj: v, path: at.path.concat(k)});
if(!tmp.node){ return }
return tmp.rel; //{'#': Node.soul(tmp.node)};
return tmp.link; //{'#': Node.soul(tmp.node)};
}
function soul(id){ var at = this;
var prev = Val.link.is(at.rel), graph = at.env.graph;
at.rel = at.rel || Val.rel.ify(id);
at.rel[Val.rel._] = id;
var prev = Val.link.is(at.link), graph = at.env.graph;
at.link = at.link || Val.link.ify(id);
at.link[Val.link._] = id;
if(at.node && at.node[Node._]){
at.node[Node._][Val.rel._] = id;
at.node[Node._][Val.link._] = id;
}
if(obj_has(graph, prev)){
graph[id] = graph[prev];
@ -131,13 +131,13 @@ Graph.node = function(node){
}
function map(v,k){ var tmp, obj;
if(Node._ === k){
if(obj_empty(v, Val.rel._)){
if(obj_empty(v, Val.link._)){
return;
}
this.obj[k] = obj_copy(v);
return;
}
if(!(tmp = Val.rel.is(v))){
if(!(tmp = Val.link.is(v))){
this.obj[k] = v;
return;
}

View File

@ -28,8 +28,9 @@ function map(msg){
}
function each(v,k){
if(n_ === k){ return }
var msg = this.msg, gun = msg.$, at = this.at, tmp = (gun.get(k)._);
(tmp.echo || (tmp.echo = {}))[at.id] = tmp.echo[at.id] || at;
var msg = this.msg, gun = msg.$, at = gun._, cat = this.at, tmp = at.lex;
if(tmp && !Gun.text.match(k, tmp['.'] || tmp['#'] || tmp)){ return } // TODO: Ugly hack!
((tmp = gun.get(k)._).echo || (tmp.echo = {}))[cat.id] = tmp.echo[cat.id] || cat;
}
var obj_map = Gun.obj.map, noop = function(){}, event = {stun: noop, off: noop}, n_ = Gun.node._, u;

View File

@ -40,10 +40,11 @@ Gun.chain.put = function(data, cb, as){
if(!soul && Gun.val.is(msg.put)){
return Gun.log("The reference you are saving is a", typeof msg.put, '"'+ msg.put +'", not a node (object)!');
}
gun.put(Gun.val.rel.ify(soul), cb, as);
gun.put(Gun.val.link.ify(soul), cb, as);
}, true);
return gun;
}
if(at.has && (tmp = Gun.val.link.is(data))){ at.dub = tmp }
as.ref = as.ref || (root._ === (tmp = at.back))? gun : tmp.$;
if(as.ref._.soul && Gun.val.is(as.data) && at.get){
as.data = obj_put({}, at.get, as.data);
@ -139,7 +140,7 @@ function map(v,k,n, at){ var as = this;
function soul(id, as, msg, eve){
var as = as.as, cat = as.at; as = as.as;
var at = ((msg || {}).$ || {})._ || {};
id = at.dub = at.dub || id || Gun.node.soul(cat.obj) || Gun.node.soul(msg.put || at.put) || Gun.val.rel.is(msg.put || at.put) || (as.via.back('opt.uuid') || Gun.text.random)(); // TODO: BUG!? Do we really want the soul of the object given to us? Could that be dangerous?
id = at.dub = at.dub || id || Gun.node.soul(cat.obj) || Gun.node.soul(msg.put || at.put) || Gun.val.link.is(msg.put || at.put) || (as.via.back('opt.uuid') || Gun.text.random)(); // TODO: BUG!? Do we really want the soul of the object given to us? Could that be dangerous?
if(eve){ eve.stun = true }
if(!id){ // polyfill async uuid for SEA
at.via.back('opt.uuid')(function(err, id){ // TODO: improve perf without anonymous callback
@ -198,7 +199,7 @@ function any(soul, as, msg, eve){
if(node_ == at.get){
as.soul = (at.put||empty)['#'] || at.dub;
}
as.soul = as.soul || at.soul || at.soul || (opt.uuid || as.via.back('opt.uuid') || Gun.text.random)();
as.soul = as.soul || at.soul || at.link || (opt.uuid || as.via.back('opt.uuid') || Gun.text.random)();
}
if(!as.soul){ // polyfill async uuid for SEA
as.via.back('opt.uuid')(function(err, soul){ // TODO: improve perf without anonymous callback

View File

@ -153,25 +153,9 @@ Gun.dup = require('./dup');
Gun.on.get = function(msg, gun){
var root = gun._, get = msg.get, soul = get[_soul], node = root.graph[soul], has = get[_has], tmp;
var next = root.next || (root.next = {}), at = next[soul];
if(obj_has(soul, '*')){ // TEMPORARY HACK FOR MARTTI, TESTING
var graph = {};
Gun.obj.map(root.graph, function(node, s){
if(Gun.text.match(s, soul)){
graph[s] = Gun.obj.copy(node);
}
});
if(!Gun.obj.empty(graph)){
root.on('in', {
'@': msg['#'],
how: '*',
put: graph,
$: gun
});
}
} // TEMPORARY HACK FOR MARTTI, TESTING
if(!node){ return root.on('get', msg) }
if(has){
if(!obj_has(node, has)){ return root.on('get', msg) }
if('string' != typeof has || !obj_has(node, has)){ return root.on('get', msg) }
node = Gun.state.to(node, has);
// If we have a key in-memory, do we really need to fetch?
// Maybe... in case the in-memory key we have is a local write
@ -217,7 +201,7 @@ Gun.dup = require('./dup');
var list_is = Gun.list.is;
var text = Gun.text, text_is = text.is, text_rand = text.random;
var obj = Gun.obj, obj_is = obj.is, obj_has = obj.has, obj_to = obj.to, obj_map = obj.map, obj_copy = obj.copy;
var state_lex = Gun.state.lex, _soul = Gun.val.rel._, _has = '.', node_ = Gun.node._, rel_is = Gun.val.link.is;
var state_lex = Gun.state.lex, _soul = Gun.val.link._, _has = '.', node_ = Gun.node._, rel_is = Gun.val.link.is;
var empty = {}, u;
console.debug = function(i, s){ return (console.debug.i && i === console.debug.i && console.debug.i++) && (console.log.apply(console, arguments) || s) };

View File

@ -10,11 +10,11 @@ Val.is = function(v){ // Valid values are a subset of JSON: null, binary, number
|| num_is(v)){ // by "number" we mean integers or decimals.
return true; // simple values are valid.
}
return Val.rel.is(v) || false; // is the value a soul relation? Then it is valid and return it. If not, everything else remaining is an invalid data type. Custom extensions can be built on top of these primitives to support other types.
return Val.link.is(v) || false; // is the value a soul relation? Then it is valid and return it. If not, everything else remaining is an invalid data type. Custom extensions can be built on top of these primitives to support other types.
}
Val.link = Val.rel = {_: '#'};
;(function(){
Val.rel.is = function(v){ // this defines whether an object is a soul relation or not, they look like this: {'#': 'UUID'}
Val.link.is = function(v){ // this defines whether an object is a soul relation or not, they look like this: {'#': 'UUID'}
if(v && v[rel_] && !v._ && obj_is(v)){ // must be an object.
var o = {};
obj_map(v, map, o);
@ -33,7 +33,7 @@ Val.link = Val.rel = {_: '#'};
}
}
}());
Val.rel.ify = function(t){ return obj_put({}, rel_, t) } // convert a soul into a relation and return it.
Val.link.ify = function(t){ return obj_put({}, rel_, t) } // convert a soul into a relation and return it.
Type.obj.has._ = '.';
var rel_ = Val.link._, u;
var bi_is = Type.bi.is;

View File

@ -1,5 +1,4 @@
describe('Gun', function(){
var root;
(function(){
var env;
@ -8,6 +7,7 @@ describe('Gun', function(){
root = env.window? env.window : global;
try{ env.window && root.localStorage && root.localStorage.clear() }catch(e){}
try{ require('fs').unlinkSync('data.json') }catch(e){}
try{ require('../lib/fsrm')('radatatest') }catch(e){}
//root.Gun = root.Gun || require('../gun');
if(root.Gun){
root.Gun = root.Gun;
@ -19,6 +19,7 @@ describe('Gun', function(){
//require('../lib/file');
require('../lib/store');
require('../lib/rfs');
require('./rad/rad.js');
require('./sea/sea.js');
}
}(this));
@ -613,6 +614,7 @@ describe('Gun', function(){
});
describe('Gun Safety', function(){
/* WARNING NOTE: Internal API has significant breaking changes! */
var gun = Gun();
it('is',function(){
expect(Gun.is(gun)).to.be(true);
@ -3048,7 +3050,7 @@ describe('Gun', function(){
});
it('get put get get put reload get get then get', function(done){
this.timeout(6000);
this.timeout(9000);
var gun = Gun();
gun.get('stef').put({name:'Stef'});
@ -3081,7 +3083,7 @@ describe('Gun', function(){
if(done.c){ return } done.c = 1;
done();
});
},5000);
},1200);
});
it('get get get any parallel', function(done){
@ -3762,9 +3764,9 @@ describe('Gun', function(){
var msg = {what: 'hello world'};
//var ref = user.get('who').get('all').set(msg);
//user.get('who').get('said').set(ref);
var ref = gun.get('who').get('all').set(msg);
gun.get('who').get('said').set(ref);
gun.get('who').get('said').map().once(function(data){
var ref = gun.get('s/r/who').get('all').set(msg);
gun.get('s/r/who').get('said').set(ref);
gun.get('s/r/who').get('said').map().once(function(data){
expect(data.what).to.be.ok();
done();
})

View File

@ -23,8 +23,15 @@
<script
src="../sea.js">
</script>
<script src="./common.js"></script>
<script src="../lib/radix.js"></script>
<script src="../lib/radisk.js"></script>
<script src="../lib/store.js"></script>
<script src="../lib/rindexed.js"></script>
<script src="./rad/rad.js"></script>
<script src="./sea/sea.js"></script>
<script src="./common.js"></script>
<script>
if(location.search){
Gun.debug = true;

10
test/rad/parse.rad Normal file
View File

@ -0,0 +1,10 @@
+1#"age:"+29>+1549776205172
+1#"name:""Bob!>+1549776205172
+1#"pet:"#XAqxAKkRa6lTsfAElEjDweqt>+1549776205172
+0#"u/m
+1#"
+2#"alice:"#dlgw87rue6oVQhsvc3XFLrOu>+1549776205172
+2#"bob:"#nuTAd2Tn4S5SiDVA7nxNBbZt>+1549776205172
+1#"/p
+2#"alice:"#USw3Dp7hTD7VMBLnd8dVBR4s>+1549776205200
+2#"bob:"#1VwZRUw7vQ1hX8gspN1ZrHVj>+1549776205200

235
test/rad/rad.js Normal file
View File

@ -0,0 +1,235 @@
var root;
var Gun;
(function(){
var env;
if(typeof global !== 'undefined'){ env = global }
if(typeof window !== 'undefined'){ env = window }
root = env.window? env.window : global;
try{ env.window && root.localStorage && root.localStorage.clear() }catch(e){}
try{ require('fs').unlinkSync('data.json') }catch(e){}
try{ require('../../lib/fsrm')('radatatest') }catch(e){}
//root.Gun = root.Gun || require('../gun');
if(root.Gun){
root.Gun = root.Gun;
root.Gun.TESTING = true;
} else {
root.Gun = require('../../gun');
root.Gun.TESTING = true;
//require('../lib/file');
require('../../lib/store');
require('../../lib/rfs');
}
try{ var expect = global.expect = require("../expect") }catch(e){}
}(this));
;(function(){
Gun = root.Gun
if(Gun.window && !Gun.window.RindexedDB){ return }
var opt = {};
var Radisk = (Gun.window && Gun.window.Radisk) || require('../../lib/radisk');
opt.store = ((Gun.window && Gun.window.RindexedDB) || require('../../lib/rfs'))(opt);
opt.chunk = 1000;
var Radix = Radisk.Radix;
var rad = Radisk(opt), esc = String.fromCharCode(27);
describe('RAD', function(){
var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammamaria","Andy","Anselme","Ardeen","Armand","Ashelman","Aube","Averyl","Baker","Barger","Baten","Bee","Benia","Bernat","Bevers","Bittner","Bobbe","Bonny","Boyce","Breech","Brittaney","Bryn","Burkitt","Cadmann","Campagna","Carlee","Carver","Cavallaro","Chainey","Chaunce","Ching","Cianca","Claudina","Clyve","Colon","Cooke","Corrina","Crawley","Cullie","Dacy","Daniela","Daryn","Deedee","Denie","Devland","Dimitri","Dolphin","Dorinda","Dream","Dunham","Eachelle","Edina","Eisenstark","Elish","Elvis","Eng","Erland","Ethan","Evelyn","Fairman","Faus","Fenner","Fillander","Flip","Foskett","Fredette","Fullerton","Gamali","Gaspar","Gemina","Germana","Gilberto","Giuditta","Goer","Gotcher","Greenstein","Grosvenor","Guthrey","Haldane","Hankins","Harriette","Hayman","Heise","Hepsiba","Hewie","Hiroshi","Holtorf","Howlond","Hurless","Ieso","Ingold","Isidora","Jacoba","Janelle","Jaye","Jennee","Jillana","Johnson","Josy","Justinian","Kannan","Kast","Keeley","Kennett","Kho","Kiran","Knowles","Koser","Kroll","LaMori","Lanctot","Lasky","Laverna","Leff","Leonanie","Lewert","Lilybel","Lissak","Longerich","Lou","Ludeman","Lyman","Madai","Maia","Malvina","Marcy","Maris","Martens","Mathilda","Maye","McLain","Melamie","Meras","Micco","Millburn","Mittel","Montfort","Moth","Mutz","Nananne","Nazler","Nesta","Nicolina","Noellyn","Nuli","Ody","Olympie","Orlena","Other","Pain","Parry","Paynter","Pentheas","Pettifer","Phyllida","Plath","Posehn","Proulx","Quinlan","Raimes","Ras","Redmer","Renelle","Ricard","Rior","Rocky","Ron","Rosetta","Rubia","Ruttger","Salbu","Sandy","Saw","Scholz","Secor","September","Shanleigh","Shenan","Sholes","Sig","Sisely","Soble","Spanos","Stanwinn","Stevie","Stu","Suzanne","Tacy","Tanney","Tekla","Thackeray","Thomasin","Tilla","Tomas","Tracay","Tristis","Ty","Urana","Valdis","Vasta","Vezza","Vitoria","Wait","Warring","Weissmann","Whetstone","Williamson","Wittenburg","Wymore","Yoho","Zamir","Zimmermann"];
//console.log("HYPER TEST");var z = 10000; while(--z){ names.push(Gun.text.random(7)) }this.timeout(9000);
describe('Radix', function(){
var radix = Radix();
it('radix write read', function(done){
var all = {};
names.forEach(function(v,i){
v = v.toLowerCase();
all[v] = v;
radix(v, i)
});
expect(Gun.obj.empty(all)).to.not.be.ok();
Radix.map(radix, function(v,k){
delete all[k];
});
expect(Gun.obj.empty(all)).to.be.ok();
done();
});
it('radix write read again', function(done){
var all = {};
names.forEach(function(v,i){
v = v.toLowerCase();
all[v] = v;
//rad(v, i)
});
expect(Gun.obj.empty(all)).to.not.be.ok();
Radix.map(radix, function(v,k){
delete all[k];
});
expect(Gun.obj.empty(all)).to.be.ok();
done();
});
});
describe('Radisk', function(){
/*it('parse', function(done){
this.timeout(60000);
if(Gun.window){ return done() }
var raw = require('fs').readFileSync(__dirname + '/parse.rad').toString();
rad.parse('!', function(err, disk){
console.log("!!!!", err);
}, raw);
return;
});*/
it('write contacts', function(done){
var all = {}, to, start;
names.forEach(function(v,i){
v = v.toLowerCase();
all[v] = true;
rad(v, i, function(err, ok){
expect(err).to.not.be.ok();
delete all[v];
if(!Gun.obj.empty(all)){ return }
done();
})
})
});
it('read contacts', function(done){
var all = {}, find = 'a';
names.forEach(function(v){
v = v.toLowerCase();
if(v.indexOf(find) == 0){ all[v] = true }
});
rad(find, function(err, data){
//console.log(">>>>>>>>> KUNG FOO PANDA <<<<<<<<<<<");
//console.debug.i=1;console.log(data);
Radix.map(data, function(v,k){
delete all[find+k];
});
if(!Gun.obj.empty(all)){ return }
done();
});
});
it('read again', function(done){
var all = {}, find = 'm';
names.forEach(function(v){
v = v.toLowerCase();
if(v.indexOf(find) == 0){ all[v] = true }
});
rad(find, function(err, data){
Radix.map(data, function(v,k){
delete all[find+k];
});
if(!Gun.obj.empty(all)){ return }
done();
});
});
it('read bytes', function(done){
var all = {}, find = 'm', to;
names.forEach(function(v){
v = v.toLowerCase();
if(v.indexOf(find) == 0){ all[v] = true }
});
rad(find, function(err, data, info){
Radix.map(data, function(v,k){
delete all[find+k];
});
clearTimeout(to);
to = setTimeout(function(){
expect(Gun.obj.empty(all)).to.not.be.ok();
done();
},100);
}, {limit: 1});
});
});
var ntmp = names;
describe('RAD + GUN', function(){
var ochunk = 1000;
var gun = Gun({chunk: ochunk});
it('write contacts', function(done){
var all = {}, to, start, tmp;
names.forEach(function(v,i){
all[++i] = true;
tmp = v.toLowerCase();
gun.get('names').get(tmp).put({name: v, age: i}, function(ack){
expect(ack.err).to.not.be.ok();
delete all[i];
if(!Gun.obj.empty(all)){ return }
done();
})
})
});
it('read contacts', function(done){
var all = {}, find = 'm', to;
names.forEach(function(v){
v = v.toLowerCase();
if(v.indexOf(find) == 0){ all[v] = true }
});
gun.get('names').get({'.': {'*': find}, '%': 1000 * 100}).once().map().once(function(data, key){
expect(data.name).to.be.ok();
expect(data.age).to.be.ok();
delete all[key];
clearTimeout(to);
to = setTimeout(function(){
expect(Gun.obj.empty(all)).to.be.ok();
done();
},100);
});
});
it('read contacts again', function(done){
var all = {}, find = 'a', to;
names.forEach(function(v){
v = v.toLowerCase();
if(v.indexOf(find) == 0){ all[v] = true }
});
gun.get('names').get({'.': {'*': find}, '%': 1000 * 100}).once().map().once(function(data, key){
expect(data.name).to.be.ok();
expect(data.age).to.be.ok();
delete all[key];
clearTimeout(to);
to = setTimeout(function(){
expect(Gun.obj.empty(all)).to.be.ok();
done();
},100);
});
});
it('read contacts fresh', function(done){
var gun = Gun({chunk: ochunk});
var all = {}, find = 'b', to;
names.forEach(function(v){
v = v.toLowerCase();
if(v.indexOf(find) == 0){ all[v] = true }
});
gun.get('names').get({'.': {'*': find}, '%': 1000 * 100}).once().map().once(function(data, key){
expect(data.name).to.be.ok();
expect(data.age).to.be.ok();
delete all[key];
clearTimeout(to);
to = setTimeout(function(){
expect(Gun.obj.empty(all)).to.be.ok();
done();
},100);
});
});
});
});
}());

View File

@ -10,7 +10,7 @@ describe('Radix', function(){
rad('ablah', 'cool');
rad('node/circle.bob', 'awesome');
expect(rad('asdf.')).to.be.eql({pub: {'\u001e': 'yum'}});
expect(rad('asdf.')).to.be.eql({pub: {'': 'yum'}});
expect(rad('nv/foo.bar')).to.be(undefined);
});
});

View File

@ -7,6 +7,7 @@ var Gun;
root = env.window? env.window : global;
try{ env.window && root.localStorage && root.localStorage.clear() }catch(e){}
try{ require('fs').unlinkSync('data.json') }catch(e){}
try{ require('../../lib/fsrm')('radatatest') }catch(e){}
//root.Gun = root.Gun || require('../gun');
if(root.Gun){
root.Gun = root.Gun;
@ -361,4 +362,4 @@ describe('SEA', function(){
})
})()
}());

View File

@ -13,7 +13,7 @@
<button id="render">render</button>
<textarea id="before"></textarea>
<textarea id="after"></textarea>
<script src="../../../gun/lib/canonize.js"></script>
<script src="../../../gun/lib/normalize.js"></script>
</div>
<script>
$('#render').on('click', check);
@ -24,7 +24,7 @@
var opt = {};
opt.hierarchy = ['div', 'ol', 'ul', 'li', 'p', 'a', 'b', 'i', 'span', 's', 'sub', 'sup', 'u', 'br'];
opt.convert = {'em': 'i', 'strong': 'b', 'strike': 's', 'font': 'span'};
var b = require.htmlNormalize(a);
var b = $.normalize(a);
$('#after').val(b);
$('#out').html(b);
}