mirror of
https://github.com/amark/gun.git
synced 2025-03-30 15:08:33 +00:00
commit
3bcfccc1a9
@ -1,5 +1,11 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 0.3.4
|
||||
|
||||
- Breaking Change! `list.set(item)` returns the item's chain now, not the list chain.
|
||||
- Client and Server GUN servers are now more up to spec, trimmed excess HTTP/REST header data.
|
||||
- Gun.is.lex added.
|
||||
|
||||
## 0.3.3
|
||||
|
||||
- You can now link nodes natively, `gun.get('mark').path('owner').put(gun.get('cat'))`!
|
||||
|
109
gun.js
109
gun.js
@ -27,7 +27,7 @@
|
||||
}
|
||||
Type.text.match = function(t, o){ var r = false;
|
||||
t = t || '';
|
||||
o = o || {}; // {'~', '=', '*', '<', '>', '+', '-', '?', '!'} // ignore uppercase, exactly equal, anything after, lexically larger, lexically lesser, added in, subtacted from, questionable fuzzy match, and ends with.
|
||||
o = Gun.text.is(o)? {'=': o} : o || {}; // {'~', '=', '*', '<', '>', '+', '-', '?', '!'} // ignore uppercase, exactly equal, anything after, lexically larger, lexically lesser, added in, subtacted from, questionable fuzzy match, and ends with.
|
||||
if(Type.obj.has(o,'~')){ t = t.toLowerCase() }
|
||||
if(Type.obj.has(o,'=')){ return t === o['='] }
|
||||
if(Type.obj.has(o,'*')){ if(t.slice(0, o['*'].length) === o['*']){ r = true; t = t.slice(o['*'].length) } else { return false }}
|
||||
@ -205,6 +205,10 @@
|
||||
,soul: '#' // a soul is a UUID of a node but it always points to the "latest" data known.
|
||||
,field: '.' // a field is a property on a node which points to a value.
|
||||
,state: '>' // other than the soul, we store HAM metadata.
|
||||
,'#':'soul'
|
||||
,'.':'field'
|
||||
,'=':'value'
|
||||
,'>':'state'
|
||||
}
|
||||
|
||||
Gun.is = function(gun){ return (gun instanceof Gun)? true : false } // check to see if it is a GUN instance.
|
||||
@ -239,6 +243,14 @@
|
||||
}
|
||||
|
||||
Gun.is.rel.ify = function(s){ var r = {}; return Gun.obj.put(r, Gun._.soul, s), r } // convert a soul into a relation and return it.
|
||||
|
||||
Gun.is.lex = function(l){ var r = true;
|
||||
if(!Gun.obj.is(l)){ return false }
|
||||
Gun.obj.map(l, function(v,f){
|
||||
if(!Gun.obj.has(Gun._,f) || !(Gun.text.is(v) || Gun.obj.is(v))){ return r = false }
|
||||
}); // TODO: What if the lex cursor has a document on the match, that shouldn't be allowed!
|
||||
return r;
|
||||
}
|
||||
|
||||
Gun.is.node = function(n, cb, t){ var s; // checks to see if an object is a valid node.
|
||||
if(!Gun.obj.is(n)){ return false } // must be an object.
|
||||
@ -678,6 +690,7 @@
|
||||
return true;
|
||||
}
|
||||
function stream(err, data, info){
|
||||
//console.log("wire.get <--", err, data);
|
||||
Gun.on('wire.get').emit(ctx.by.chain, ctx, err, data, info);
|
||||
if(err){
|
||||
Gun.log(err.err || err);
|
||||
@ -962,15 +975,17 @@
|
||||
}
|
||||
|
||||
Gun.chain.set = function(item, cb, opt){
|
||||
var gun = this, ctx = {};
|
||||
if(!Gun.is(item)){ return cb.call(gun, {err: Gun.log('Set only supports node references currently!')}), gun }
|
||||
item.val(function(node){
|
||||
var gun = this, ctx = {}, chain;
|
||||
if(!Gun.is(item)){ return cb.call(gun, {err: Gun.log('Set only supports node references currently!')}), gun } // TODO: Bug? Should we return not gun on error?
|
||||
(ctx.chain = item.chain()).back = gun;
|
||||
ctx.chain._ = item._;
|
||||
item.val(function(node){ // TODO: BUG! Return proxy chain with back = list.
|
||||
if(ctx.done){ return } ctx.done = true;
|
||||
var put = {}, soul = Gun.is.node.soul(node);
|
||||
if(!soul){ return cb.call(gun, {err: Gun.log('Only a node can be linked! Not "' + node + '"!')}) }
|
||||
gun.put(Gun.obj.put(put, soul, Gun.is.rel.ify(soul)), cb, opt);
|
||||
})
|
||||
return gun;
|
||||
});
|
||||
return ctx.chain;
|
||||
}
|
||||
|
||||
Gun.chain.init = function(cb, opt){
|
||||
@ -1125,7 +1140,7 @@
|
||||
;(function(exports){
|
||||
function s(){}
|
||||
s.put = function(key, val){ return store.setItem(key, Gun.text.ify(val)) }
|
||||
s.get = function(key, cb){ setTimeout(function(){ return cb(null, Gun.obj.ify(store.getItem(key) || null)) },1)}
|
||||
s.get = function(key, cb){ /*setTimeout(function(){*/ return cb(null, Gun.obj.ify(store.getItem(key) || null)) /*},1)*/}
|
||||
s.del = function(key){ return store.removeItem(key) }
|
||||
var store = this.localStorage || {setItem: function(){}, removeItem: function(){}, getItem: function(){}};
|
||||
exports.store = s;
|
||||
@ -1136,6 +1151,7 @@
|
||||
var tab = gun.tab = gun.tab || {};
|
||||
tab.store = tab.store || Tab.store;
|
||||
tab.request = tab.request || request;
|
||||
tab.request.s = tab.request.s || {};
|
||||
tab.headers = opt.headers || {};
|
||||
tab.headers['gun-sid'] = tab.headers['gun-sid'] || Gun.text.random(); // stream id
|
||||
tab.prefix = tab.prefix || opt.prefix || 'gun/';
|
||||
@ -1144,11 +1160,7 @@
|
||||
var soul = lex[Gun._.soul];
|
||||
if(!soul){ return }
|
||||
cb = cb || function(){};
|
||||
cb.GET = true;
|
||||
(opt = opt || {}).url = opt.url || {};
|
||||
opt.headers = Gun.obj.copy(tab.headers);
|
||||
opt.url.pathname = '/' + soul;
|
||||
//Gun.log("tab get --->", lex);
|
||||
(opt.headers = Gun.obj.copy(tab.headers)).id = tab.msg();
|
||||
(function local(soul, cb){
|
||||
tab.store.get(tab.prefix + soul, function(err, data){
|
||||
if(!data){ return } // let the peers handle no data.
|
||||
@ -1159,30 +1171,32 @@
|
||||
});
|
||||
}(soul, cb));
|
||||
if(!(cb.local = opt.local)){
|
||||
tab.request.s[opt.headers.id] = tab.error(cb, "Error: Get failed!", function(reply){
|
||||
setTimeout(function(){ tab.put(Gun.is.graph.ify(reply.body), function(){}, {local: true, peers: {}}) },1); // and flush the in memory nodes of this graph to localStorage after we've had a chance to union on it.
|
||||
});
|
||||
Gun.obj.map(opt.peers || gun.__.opt.peers, function(peer, url){ var p = {};
|
||||
tab.request(url, null, tab.error(cb, "Error: Get failed through " + url, function(reply){
|
||||
if(!p.node && cb.node){ // if we have local data
|
||||
//Gun.log("tab get <---", lex);
|
||||
tab.put(Gun.is.graph.ify(p.node = cb.node), function(e,r){ // then sync it if we haven't already
|
||||
//Gun.log("Stateless handshake sync:", e, r);
|
||||
}, {peers: tab.peers(url)}); // to the peer. // TODO: This forces local to flush again, not necessary.
|
||||
}
|
||||
setTimeout(function(){ tab.put(reply.body, function(){}, {local: true}) },1); // and flush the in memory nodes of this graph to localStorage after we've had a chance to union on it.
|
||||
}), opt);
|
||||
tab.request(url, lex, tab.request.s[opt.headers.id], opt);
|
||||
cb.peers = true;
|
||||
});
|
||||
var node = gun.__.graph[soul];
|
||||
if(node){
|
||||
tab.put(Gun.is.graph.ify(node));
|
||||
}
|
||||
} tab.peers(cb);
|
||||
}
|
||||
tab.put = tab.put || function(graph, cb, opt){
|
||||
//console.log("SAVE", graph);
|
||||
cb = cb || function(){};
|
||||
opt = opt || {};
|
||||
(opt.headers = Gun.obj.copy(tab.headers)).id = tab.msg();
|
||||
Gun.is.graph(graph, function(node, soul){
|
||||
if(!gun.__.graph[soul]){ return }
|
||||
tab.store.put(tab.prefix + soul, gun.__.graph[soul]);
|
||||
});
|
||||
if(!(cb.local = opt.local)){
|
||||
tab.request.s[opt.headers.id] = tab.error(cb, "Error: Put failed!");
|
||||
Gun.obj.map(opt.peers || gun.__.opt.peers, function(peer, url){
|
||||
tab.request(url, graph, tab.error(cb, "Error: Put failed on " + url), {headers: tab.headers});
|
||||
tab.request(url, graph, tab.request.s[opt.headers.id], opt);
|
||||
cb.peers = true;
|
||||
});
|
||||
} tab.peers(cb);
|
||||
@ -1204,22 +1218,46 @@
|
||||
if(!(cb.graph || cb.node)){ cb(null) }
|
||||
},1)}
|
||||
}
|
||||
tab.msg = tab.msg || function(id){
|
||||
if(!id){
|
||||
return tab.msg.debounce[id = Gun.text.random(9)] = Gun.time.is(), id;
|
||||
}
|
||||
clearTimeout(tab.msg.clear);
|
||||
tab.msg.clear = setTimeout(function(){
|
||||
var now = Gun.time.is();
|
||||
Gun.obj.map(tab.msg.debounce, function(t,id){
|
||||
if(now - t < 1000 * 60 * 5){ return }
|
||||
Gun.obj.del(tab.msg.debounce, id);
|
||||
});
|
||||
},500);
|
||||
if(id = tab.msg.debounce[id]){
|
||||
return tab.msg.debounce[id] = Gun.time.is(), id;
|
||||
}
|
||||
};
|
||||
tab.msg.debounce = tab.msg.debounce || {};
|
||||
tab.server = tab.server || function(req, res){
|
||||
if(!req || !res || !req.url || !req.method){ return }
|
||||
req.url = req.url.href? req.url : document.createElement('a');
|
||||
req.url.href = req.url.href || req.url;
|
||||
req.url.key = (req.url.pathname||'').replace(tab.server.regex,'').replace(/^\//i,'') || '';
|
||||
req.method = req.body? 'put' : 'get';
|
||||
if('get' == req.method){ return tab.server.get(req, res) }
|
||||
if('put' == req.method || 'post' == req.method){ return tab.server.put(req, res) }
|
||||
if(!req || !res || !req.body || !req.headers || !req.headers.id){ return }
|
||||
if(tab.request.s[req.headers.rid]){ return tab.request.s[req.headers.rid](null, req) }
|
||||
if(tab.msg(req.headers.id)){ return }
|
||||
// TODO: Re-emit message to other peers if we have any non-overlaping ones.
|
||||
if(req.headers.rid){ return } // no need to process
|
||||
if(Gun.is.lex(req.body)){ return tab.server.get(req, res) }
|
||||
else { return tab.server.put(req, res) }
|
||||
}
|
||||
tab.server.json = 'application/json';
|
||||
tab.server.regex = gun.__.opt.route = gun.__.opt.route || opt.route || /^\/gun/i;
|
||||
tab.server.get = function(){}
|
||||
tab.server.get = function(req, cb){
|
||||
var soul = req.body[Gun._.soul], node;
|
||||
if(!(node = gun.__.graph[soul])){ return }
|
||||
var reply = {headers: {'Content-Type': tab.server.json, rid: req.headers.id, id: tab.msg()}};
|
||||
cb({headers: reply.headers, body: node});
|
||||
}
|
||||
tab.server.put = function(req, cb){
|
||||
var reply = {headers: {'Content-Type': tab.server.json}};
|
||||
var reply = {headers: {'Content-Type': tab.server.json, rid: req.headers.id, id: tab.msg()}}, keep;
|
||||
if(!req.body){ return cb({headers: reply.headers, body: {err: "No body"}}) }
|
||||
// TODO: Re-emit message to other peers if we have any non-overlaping ones.
|
||||
if(!Gun.obj.is(req.body, function(node, soul){
|
||||
if(gun.__.graph[soul]){ return true }
|
||||
})){ return }
|
||||
if(req.err = Gun.union(gun, req.body, function(err, ctx){
|
||||
if(err){ return cb({headers: reply.headers, body: {err: err || "Union failed."}}) }
|
||||
var ctx = ctx || {}; ctx.graph = {};
|
||||
@ -1227,7 +1265,7 @@
|
||||
gun.__.opt.wire.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."}});
|
||||
}, {local: true});
|
||||
}, {local: true, peers: {}});
|
||||
}).err){ cb({headers: reply.headers, body: {err: req.err || "Union failed."}}) }
|
||||
}
|
||||
Gun.obj.map(gun.__.opt.peers, function(){ // only create server if peers and do it once by returning immediately.
|
||||
@ -1243,6 +1281,7 @@
|
||||
opt = opt || (base.length? {base: base} : base);
|
||||
opt.base = opt.base || base;
|
||||
opt.body = opt.body || body;
|
||||
cb = cb || function(){};
|
||||
if(!opt.base){ return }
|
||||
r.transport(opt, cb);
|
||||
}
|
||||
@ -1300,7 +1339,7 @@
|
||||
res.headers = res.headers || {};
|
||||
if(res.headers['ws-rid']){ return (r.ws.cbs[res.headers['ws-rid']]||function(){})(null, res) }
|
||||
//Gun.log("We have a pushed message!", res);
|
||||
if(res.body){ r.createServer.ing(res, function(){}) } // emit extra events.
|
||||
if(res.body){ r.createServer.ing(res, function(res){ r(opt.base, null, null, res)}) } // emit extra events.
|
||||
};
|
||||
ws.onerror = function(e){ Gun.log(e); };
|
||||
return true;
|
||||
@ -1345,7 +1384,7 @@
|
||||
while(reply.body && reply.body.length && reply.body.shift){ // we're assuming an array rather than chunk encoding. :(
|
||||
var res = reply.body.shift();
|
||||
//Gun.log("-- go go go", res);
|
||||
if(res && res.body){ r.createServer.ing(res, function(){}) } // emit extra events.
|
||||
if(res && res.body){ r.createServer.ing(res, function(){ r(opt.base, null, null, res) }) } // emit extra events.
|
||||
}
|
||||
});
|
||||
}, res.headers.poll);
|
||||
|
@ -12,14 +12,14 @@ module.exports = function(wss, server){
|
||||
msg = Gun.obj.ify(msg);
|
||||
msg.url = msg.url || {};
|
||||
msg.url.pathname = (req.url.pathname||'') + (msg.url.pathname||'');
|
||||
Gun.obj.map(req.url, function(val, i){
|
||||
/*Gun.obj.map(req.url, function(val, i){
|
||||
msg.url[i] = msg.url[i] || val; // reattach url
|
||||
});
|
||||
});*/
|
||||
msg.method = msg.method || req.method;
|
||||
msg.headers = msg.headers || {};
|
||||
Gun.obj.map(req.headers, function(val, i){
|
||||
/*Gun.obj.map(req.headers, function(val, i){
|
||||
msg.headers[i] = msg.headers[i] || val; // reattach headers
|
||||
});
|
||||
});*/
|
||||
server.call(ws, msg, function(reply){
|
||||
if(!ws || !ws.send || !ws._socket || !ws._socket.writable){ return }
|
||||
reply = reply || {};
|
||||
|
51
lib/wsp.js
51
lib/wsp.js
@ -92,20 +92,40 @@
|
||||
if((gun.__.opt.maxSockets = opt.maxSockets || gun.__.opt.maxSockets) !== false){
|
||||
require('https').globalAgent.maxSockets = require('http').globalAgent.maxSockets = gun.__.opt.maxSockets || Infinity;
|
||||
}
|
||||
gun.wsp.msg = gun.wsp.msg || function(id){
|
||||
if(!id){
|
||||
return gun.wsp.msg.debounce[id = Gun.text.random(9)] = Gun.time.is(), id;
|
||||
}
|
||||
clearTimeout(gun.wsp.msg.clear);
|
||||
gun.wsp.msg.clear = setTimeout(function(){
|
||||
var now = Gun.time.is();
|
||||
Gun.obj.map(gun.wsp.msg.debounce, function(t,id){
|
||||
if((now - t) < (1000 * 60 * 5)){ return }
|
||||
Gun.obj.del(gun.wsp.msg.debounce, id);
|
||||
});
|
||||
},500);
|
||||
if(id = gun.wsp.msg.debounce[id]){
|
||||
return gun.wsp.msg.debounce[id] = Gun.time.is(), id;
|
||||
}
|
||||
};
|
||||
gun.wsp.msg.debounce = gun.wsp.msg.debounce || {};
|
||||
gun.wsp.wire = gun.wsp.wire || (function(){
|
||||
// all streams, technically PATCH but implemented as PUT or POST, are forwarded to other trusted peers
|
||||
// except for the ones that are listed in the message as having already been sending to.
|
||||
// all states, implemented with GET, are replied to the source that asked for it.
|
||||
function tran(req, cb){
|
||||
req.method = req.body? 'put' : 'get'; // put or get is based on whether there is a body or not
|
||||
req.url.key = req.url.pathname.replace(gun.wsp.regex,'').replace(/^\//i,'') || '';
|
||||
if('get' == req.method){ return tran.get(req, cb) }
|
||||
if('put' == req.method || 'post' == req.method){ return tran.put(req, cb) }
|
||||
function tran(req, res){
|
||||
if(!req || !res || !req.body || !req.headers || !req.headers.id){ return }
|
||||
if(gun.wsp.msg(req.headers.id)){ return }
|
||||
req.method = req.body? 'put' : 'get';
|
||||
gun.wsp.on('network').emit(Gun.obj.copy(req));
|
||||
if(req.headers.rid){ return } // no need to process.
|
||||
if(Gun.is.lex(req.body)){ return tran.get(req, res) }
|
||||
else { return tran.put(req, res) }
|
||||
cb({body: {hello: 'world'}});
|
||||
}
|
||||
tran.get = function(req, cb){
|
||||
var key = req.url.key
|
||||
, reply = {headers: {'Content-Type': tran.json}};
|
||||
, reply = {headers: {'Content-Type': tran.json, rid: req.headers.id, id: gun.wsp.msg()}};
|
||||
//console.log(req);
|
||||
// NTS HACK! SHOULD BE ITS OWN ISOLATED MODULE! //
|
||||
if(req && req.url && req.url.pathname && req.url.pathname.indexOf('gun.nts') >= 0){
|
||||
@ -118,22 +138,14 @@
|
||||
cb({headers: reply.headers, body: (err? (err.err? err : {err: err || "Unknown error."}) : list || null ) })
|
||||
});
|
||||
}
|
||||
// END ALL HACK! //
|
||||
if(!key){
|
||||
if(!Gun.obj.has(req.url.query, Gun._.soul)){
|
||||
return cb({headers: reply.headers, body: {err: "No key or soul to get."}});
|
||||
}
|
||||
key = {};
|
||||
key[Gun._.soul] = req.url.query[Gun._.soul];
|
||||
}
|
||||
if(Gun.text.is(key)){
|
||||
key = Gun.is.rel.ify(key);
|
||||
}
|
||||
//console.log("tran.get", key);
|
||||
console.log("GET!", req);
|
||||
key = req.body;
|
||||
console.log("tran.get", key);
|
||||
var opt = {key: false};
|
||||
//gun.get(key, function(err, node){
|
||||
(gun.__.opt.wire.get||function(key, cb){cb(null,null)})(key, function(err, node){
|
||||
//console.log("tran.get", key, "<---", err, node);
|
||||
reply.headers.id = gun.wsp.msg();
|
||||
if(err || !node){
|
||||
if(opt.on && opt.on.off){ opt.on.off() }
|
||||
return cb({headers: reply.headers, body: (err? (err.err? err : {err: err || "Unknown error."}) : null)});
|
||||
@ -168,9 +180,8 @@
|
||||
tran.put = function(req, cb){
|
||||
// NOTE: It is highly recommended you do your own PUT/POSTs through your own API that then saves to gun manually.
|
||||
// This will give you much more fine-grain control over security, transactions, and what not.
|
||||
var reply = {headers: {'Content-Type': tran.json}};
|
||||
var reply = {headers: {'Content-Type': tran.json, rid: req.headers.id, id: gun.wsp.msg()}};
|
||||
if(!req.body){ return cb({headers: reply.headers, body: {err: "No body"}}) }
|
||||
gun.wsp.on('network').emit(Gun.obj.copy(req));
|
||||
//console.log("\n\ntran.put ----------------->", req.body);
|
||||
if(Gun.is.graph(req.body)){
|
||||
if(req.err = Gun.union(gun, req.body, function(err, ctx){ // TODO: BUG? Probably should give me ctx.graph
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gun",
|
||||
"version": "0.3.3",
|
||||
"version": "0.3.4",
|
||||
"description": "Graph engine",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -435,7 +435,7 @@ describe('Gun', function(){
|
||||
expect(Gun.is.val({a:1})).to.be(false);
|
||||
expect(Gun.is.val(function(){})).to.be(false);
|
||||
});
|
||||
it('is soul',function(){
|
||||
it('is rel',function(){
|
||||
expect(Gun.is.rel({'#':'somesoulidhere'})).to.be('somesoulidhere');
|
||||
expect(Gun.is.rel({'#':'somethingelsehere'})).to.be('somethingelsehere');
|
||||
expect(Gun.is.rel({'#':'somesoulidhere', and: 'nope'})).to.be(false);
|
||||
@ -454,6 +454,17 @@ describe('Gun', function(){
|
||||
expect(Gun.is.rel({a:1})).to.be(false);
|
||||
expect(Gun.is.rel(function(){})).to.be(false);
|
||||
});
|
||||
it('is lex',function(){
|
||||
expect(Gun.is.lex({'#': 'soul'})).to.be(true);
|
||||
expect(Gun.is.lex({'.': 'field'})).to.be(true);
|
||||
expect(Gun.is.lex({'=': 'value'})).to.be(true);
|
||||
expect(Gun.is.lex({'>': 'state'})).to.be(true);
|
||||
expect(Gun.is.lex({'#': {'=': 'soul'}})).to.be(true);
|
||||
expect(Gun.is.lex({'#': {'=': 'soul'}, '.': []})).to.be(false);
|
||||
expect(Gun.is.lex({'#': {'=': 'soul'}, 'asdf': 'oye'})).to.be(false);
|
||||
expect(Gun.is.lex()).to.be(false);
|
||||
expect(Gun.is.lex('')).to.be(false);
|
||||
});
|
||||
it('is node',function(){
|
||||
expect(Gun.is.node({_:{'#':'somesoulidhere'}})).to.be(true);
|
||||
expect(Gun.is.node({_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}})).to.be(true);
|
||||
@ -3736,14 +3747,18 @@ describe('Gun', function(){
|
||||
var carl = gun.put({name: 'carl', birth: Math.random()}).key('person/carl');
|
||||
var dave = gun.put({name: 'dave', birth: Math.random()}).key('person/dave');
|
||||
|
||||
users.set(alice).set(bob).set(carl).set(dave);
|
||||
users.set(alice);
|
||||
users.set(bob);
|
||||
users.set(carl);
|
||||
users.set(dave);
|
||||
|
||||
alice.path('friends').set(bob).set(carl);
|
||||
alice.path('friends').set(bob).back.set(carl);
|
||||
bob.path('friends').set(alice);
|
||||
dave.path('friends').set(alice).set(carl);
|
||||
dave.path('friends').set(alice).back.set(carl);
|
||||
|
||||
var team = gun.get('team/lions').put({name: "Lions"});
|
||||
team.path('members').set(alice).set(bob);
|
||||
team.path('members').set(alice);
|
||||
team.path('members').set(bob);
|
||||
|
||||
alice.path('team').put(team);
|
||||
bob.path('team').put(team);
|
||||
|
@ -1,6 +1,6 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Gun Tests</title>
|
||||
<title>GUN Tests</title>
|
||||
<link rel="stylesheet" href="./mocha.css"/>
|
||||
<script src="./json2.js"></script>
|
||||
<script src="./mocha.js"></script>
|
||||
@ -14,7 +14,8 @@
|
||||
</head>
|
||||
</head>
|
||||
<body onload="mocha.run()">
|
||||
Gun Tests
|
||||
<h1>GUN Tests</h1>
|
||||
Warning! You cannot run these tests in the background, this tab must be in active focus.
|
||||
<div id="debug"></div>
|
||||
<div id="mocha"></div>
|
||||
</body>
|
||||
|
Loading…
x
Reference in New Issue
Block a user