Merge branch 'develop' of https://github.com/amark/gun into develop

This commit is contained in:
Jesse Gibson 2016-08-08 17:40:36 -06:00
commit 4a0fb994be
11 changed files with 218 additions and 78 deletions

View File

@ -12,8 +12,8 @@
GUN is a realtime, distributed, offline-first, graph database engine. Lightweight and powerful, at just **~9KB** gzipped.
[![1 minute demo of fault tolerance](http://img.youtube.com/vi/-i-11T5ZI9o/0.jpg)](https://youtu.be/-i-11T5ZI9o)
<a href="https://youtu.be/-i-11T5ZI9o" title="1 minute demo of fault tolerance"><img src="http://img.youtube.com/vi/-i-11T5ZI9o/0.jpg" width="425px"></a>
<a href="https://youtu.be/-FN_J3etdvY" title="1 minute demo of fault tolerance"><img src="http://img.youtube.com/vi/-FN_J3etdvY/0.jpg" width="425px"></a>
## Why?
@ -89,7 +89,7 @@ Designed with ♥ by Mark Nadal, the gun team, and many amazing contributors. L
Thanks to the following people who have contributed to GUN, via code, issues, or conversation (this list has quickly become tremendously behind! We'll probably turn this into a dedicated wiki page so you can add yourself):
[agborkowski](https://github.com/agborkowski); [alexlafroscia](https://github.com/alexlafroscia); [anubiann00b](https://github.com/anubiann00b); [bromagosa](https://github.com/bromagosa); [coolaj86](https://github.com/coolaj86); [d-oliveros](https://github.com/d-oliveros), [danscan](https://github.com/danscan); **[forrestjt](https://github.com/forrestjt) ([file.js](https://github.com/amark/gun/blob/master/lib/file.js))**; [gedw99](https://github.com/gedw99); [HelloCodeMing](https://github.com/HelloCodeMing); **[JosePedroDias](https://github.com/josepedrodias) ([graph visualizer](http://acor.sl.pt:9966))**; **[jveres](https://github.com/jveres) ([todoMVC](https://github.com/jveres/todomvc) [live demo](http://todos.loqali.com/))**; [ndarilek](https://github.com/ndarilek); [onetom](https://github.com/onetom); [phpnode](https://github.com/phpnode); [PsychoLlama](https://github.com/PsychoLlama); **[RangerMauve](https://github.com/RangerMauve) ([schema](https://github.com/gundb/gun-schema))**; [riston](https://github.com/riston); [rootsical](https://github.com/rootsical); [rrrene](https://github.com/rrrene); [sbeleidy](https://github.com/sbeleidy) [ssr1ram](https://github.com/ssr1ram); [Xe](https://github.com/Xe); [zot](https://github.com/zot);
[agborkowski](https://github.com/agborkowski); [alexlafroscia](https://github.com/alexlafroscia); [anubiann00b](https://github.com/anubiann00b); [bromagosa](https://github.com/bromagosa); [coolaj86](https://github.com/coolaj86); [d-oliveros](https://github.com/d-oliveros), [danscan](https://github.com/danscan); **[forrestjt](https://github.com/forrestjt) ([file.js](https://github.com/amark/gun/blob/master/lib/file.js))**; [gedw99](https://github.com/gedw99); [HelloCodeMing](https://github.com/HelloCodeMing); **[JosePedroDias](https://github.com/josepedrodias) ([graph visualizer](http://acor.sl.pt:9966))**; **[jveres](https://github.com/jveres) ([todoMVC](https://github.com/jveres/todomvc))**; [ndarilek](https://github.com/ndarilek); [onetom](https://github.com/onetom); [phpnode](https://github.com/phpnode); [PsychoLlama](https://github.com/PsychoLlama); **[RangerMauve](https://github.com/RangerMauve) ([schema](https://github.com/gundb/gun-schema))**; [riston](https://github.com/riston); [rootsical](https://github.com/rootsical); [rrrene](https://github.com/rrrene); [sbeleidy](https://github.com/sbeleidy) [ssr1ram](https://github.com/ssr1ram); **[Stefdv](https://github.com/stefdv) ([Polymer/web components](http://stefdv.github.io/gun-collection/components/gun-collection/))**; [Xe](https://github.com/Xe); [zot](https://github.com/zot);
[ayurmedia](https://github.com/ayurmedia);
This list of contributors was manually compiled and alphabetically sorted. If we missed you, please submit an issue so we can get you added!

30
examples/express-auth.js Normal file
View File

@ -0,0 +1,30 @@
console.log("If modules not found, run `npm install` in /example folder!"); // git subtree push -P examples heroku master // OR // git subtree split -P examples master && git push heroku [['HASH']]:master --force
var port = process.env.OPENSHIFT_NODEJS_PORT || process.env.VCAP_APP_PORT || process.env.PORT || process.argv[2] || 80;
var express = require('express');
var app = express();
var Gun = require('gun');
var gun = Gun({
file: 'data.json',
s3: {
key: '', // AWS Access Key
secret: '', // AWS Secret Token
bucket: '' // The bucket you want to save into
}
});
gun.wsp(app/*, function(req, res, next){
console.log("auth!", req, req.body['#']);
if('get' === req.method){
if('example/todo/data' === req.body['#']){
next(req, res);
}
}
if('put' === req.method){
res({body: {err: "Permission denied!"}});
}
}*/);
app.use(express.static(__dirname)).listen(port);
console.log('Server started on port ' + port + ' with /gun');

82
gun.js
View File

@ -127,9 +127,17 @@
}
Type.time = {};
Type.time.is = function(t){ return t? t instanceof Date : (+new Date().getTime()) }
Type.time.now = function(t){
return ((t=t||Type.time.is()) > (Type.time.now.last || -Infinity)? (Type.time.now.last = t) : Type.time.now(t + 1)) + (Type.time.now.drift || 0); // TODO: BUG? Should this go on the inside?
};
Type.time.now = (function(){
var time = Type.time.is, last = -Infinity, n = 0, d = 1000;
return function(){
var t = time();
if(last < t){
n = 0;
return last = t;
}
return last = t + ((n += 1) / d);
}
}());
}(Util));
;(function(exports){ // On event emitter generic javascript utility.
function On(){};
@ -530,13 +538,15 @@
var gun = this, root = (gun.__ && gun.__.gun)? gun.__.gun : (gun._ = gun.__ = {gun: gun}).gun.chain(); // if root does not exist, then create a root chain.
root.__.by = root.__.by || function(f){ return gun.__.by[f] = gun.__.by[f] || {} };
root.__.graph = root.__.graph || {};
root.__.opt = root.__.opt || {};
root.__.opt = root.__.opt || {peers: {}};
root.__.opt.wire = root.__.opt.wire || {};
if(Gun.text.is(opt)){ opt = {peers: opt} }
if(Gun.list.is(opt)){ opt = {peers: opt} }
if(Gun.text.is(opt.peers)){ opt.peers = [opt.peers] }
if(Gun.list.is(opt.peers)){ opt.peers = Gun.obj.map(opt.peers, function(n,f,m){ m(n,{}) }) }
root.__.opt.peers = opt.peers || gun.__.opt.peers || {};
Gun.obj.map(opt.peers, function(v, f){
root.__.opt.peers[f] = v;
});
Gun.obj.map(opt.wire, function(h, f){
if(!Gun.fns.is(h)){ return }
root.__.opt.wire[f] = h;
@ -908,6 +918,11 @@
}
function each(val, field){
//if(!Gun.is.rel(val)){ path.call(this.gun, null, val, field);return;}
if(opt.node){
if(!Gun.is.rel(val)){
return;
}
}
cb.hash[this.soul + field] = cb.hash[this.soul + field] || this.gun.path(field, path, {chain: chain, via: 'map'}); // TODO: path should reuse itself! We shouldn't have to do it ourselves.
// TODO:
// 1. Ability to turn off an event. // automatically happens within path since reusing is manual?
@ -1132,7 +1147,7 @@
}(Gun));
var root = this || {}; // safe for window, global, root, and 'use strict'.
if(root.window){ (root = window).Gun = Gun }
if(typeof window !== "undefined"){ (root = window).Gun = Gun }
if(typeof module !== "undefined" && module.exports){ module.exports = Gun }
if(typeof global !== "undefined"){ root = global; }
root.console = root.console || {log: function(s){ return s }}; // safe for old browsers
@ -1163,7 +1178,8 @@
opt = opt || {};
var tab = gun.tab = gun.tab || {};
tab.store = tab.store || Tab.store;
tab.request = tab.request || request;
tab.request = tab.request || Gun.request;
if(!tab.request){ throw new Error("Default GUN driver could not find default network abstraction.") }
tab.request.s = tab.request.s || {};
tab.headers = opt.headers || {};
tab.headers['gun-sid'] = tab.headers['gun-sid'] || Gun.text.random(); // stream id
@ -1173,7 +1189,8 @@
var soul = lex[Gun._.soul];
if(!soul){ return }
cb = cb || function(){};
(opt.headers = Gun.obj.copy(tab.headers)).id = tab.msg();
var ropt = {};
(ropt.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.
@ -1184,11 +1201,11 @@
});
}(soul, cb));
if(!(cb.local = opt.local)){
tab.request.s[opt.headers.id] = tab.error(cb, "Error: Get failed!", function(reply){
tab.request.s[ropt.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, lex, tab.request.s[opt.headers.id], opt);
tab.request(url, lex, tab.request.s[ropt.headers.id], ropt);
cb.peers = true;
});
var node = gun.__.graph[soul];
@ -1198,18 +1215,18 @@
} 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();
var ropt = {};
(ropt.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], function(err){if(err){ cb({err: err}) }});
});
if(!(cb.local = opt.local)){
tab.request.s[opt.headers.id] = tab.error(cb, "Error: Put failed!");
tab.request.s[ropt.headers.id] = tab.error(cb, "Error: Put failed!");
Gun.obj.map(opt.peers || gun.__.opt.peers, function(peer, url){
tab.request(url, graph, tab.request.s[opt.headers.id], opt);
tab.request(url, graph, tab.request.s[ropt.headers.id], ropt);
cb.peers = true;
});
} tab.peers(cb);
@ -1287,16 +1304,25 @@
gun.__.opt.wire.get = gun.__.opt.wire.get || tab.get;
gun.__.opt.wire.put = gun.__.opt.wire.put || tab.put;
gun.__.opt.wire.key = gun.__.opt.wire.key || tab.key;
Tab.request = tab.request;
Gun.Tab = Tab;
});
}.bind(this || module)({}));
;(function(Tab){
var request = (function(){
function r(base, body, cb, opt){
opt = opt || (base.length? {base: base} : base);
opt.base = opt.base || base;
opt.body = opt.body || body;
function r(base, body, cb, opt){ opt = opt || {};
var o = base.length? {base: base} : {};
o.base = opt.base || base;
o.body = opt.body || body;
o.headers = opt.headers;
o.url = opt.url;
cb = cb || function(){};
if(!opt.base){ return }
r.transport(opt, cb);
if(!o.base){ return }
r.transport(o, cb);
}
r.createServer = function(fn){ r.createServer.s.push(fn) }
r.createServer.ing = function(req, cb){
@ -1311,7 +1337,7 @@
r.jsonp(opt, cb);
}
r.ws = function(opt, cb){
var ws, WS = window.WebSocket || window.mozWebSocket || window.webkitWebSocket;
var ws, WS = r.WebSocket || window.WebSocket || window.mozWebSocket || window.webkitWebSocket;
if(!WS){ return }
if(ws = r.ws.peers[opt.base]){
if(!ws.readyState){ return setTimeout(function(){ r.ws(opt, cb) },10), true }
@ -1328,9 +1354,10 @@
return true;
}
if(ws === false){ return }
ws = r.ws.peers[opt.base] = new WS(opt.base.replace('http','ws'));
try{ws = r.ws.peers[opt.base] = new WS(opt.base.replace('http','ws'));
}catch(e){}
ws.onopen = function(o){ r.back = 2; r.ws(opt, cb) };
ws.onclose = window.onbeforeunload = function(c){
ws.onclose = function(c){
if(!c){ return }
if(ws && ws.close instanceof Function){ ws.close() }
if(1006 === c.code){ // websockets cannot be used
@ -1343,6 +1370,7 @@
r.ws(opt, function(){}); // opt here is a race condition, is it not? Does this matter?
}, r.back *= r.backoff);
};
if(typeof window !== "undefined"){ window.onbeforeunload = ws.onclose; }
ws.onmessage = function(m){
if(!m || !m.data){ return }
var res;
@ -1351,15 +1379,17 @@
if(!res){ return }
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(res){ r(opt.base, null, null, res)}) } // emit extra events.
};
ws.onerror = function(e){ Gun.log(e); };
ws.onerror = function(e){ console.log(e); };
return true;
}
r.ws.peers = {};
r.ws.cbs = {};
r.jsonp = function(opt, cb){
if(typeof window === "undefined"){
return cb("JSONP is currently browser only.");
}
//Gun.log("jsonp send", opt);
r.jsonp.ify(opt, function(url){
//Gun.log(url);
@ -1442,4 +1472,6 @@
}
return r;
}());
if(typeof window !== "undefined"){ Gun.request = request }
if(typeof module !== "undefined" && module.exports){ module.exports.request = request }
}.bind(this || module)({}));

View File

@ -40,12 +40,14 @@ Gun.on('opt').event(function(gun, opts) {
gun.opt({wire: {
get: function get(lex, cb, o){
var node, soul = lex[Gun._.soul];
setImmediate(function(){
node = all.nodes[soul];
if(!node){ return cb(null) }
cb(null, node);
node = Gun.is.node.soul.ify({}, soul);
cb(null, node); // end.
cb(null, {}); // done.
});
},
put: function(graph, cb, o){
for (key in gun.__.graph) all.nodes[key]=gun.__.graph[key]||graph[key];

View File

@ -2,7 +2,7 @@
console.log("Hello wonderful person! :) I'm mark@gunDB.io, message me for help or with hatemail. I want to hear from you! <3");
var Gun = require('../gun');
require('./s3');
require('./wsp');
require('./file');
require('./wsp');
module.exports = Gun;
}());

View File

@ -1,5 +1,5 @@
;(function(wsp){
var Gun = require('../gun')
var Gun = require('../gun')
, ws = require('ws').Server
, http = require('./http')
, url = require('url');
@ -10,10 +10,10 @@
server = gun.__.opt.ws.server = gun.__.opt.ws.server || opt.ws.server || server;
require('./ws')(gun.wsp.ws = gun.wsp.ws || new ws(gun.__.opt.ws), function(req, res){
var ws = this;
req.headers['gun-sid'] = ws.sid = ws.sid? ws.sid : req.headers['gun-sid'];
req.headers['gun-sid'] = ws.sid = (ws.sid? ws.sid : req.headers['gun-sid']);
ws.sub = ws.sub || gun.wsp.on('network').event(function(msg){
if(!ws || !ws.send || !ws._socket || !ws._socket.writable){ return this.off() }
if(!msg || (msg.headers && msg.headers['gun-sid'] === ws.sid)){ return }
if(!msg || (ws.sid && msg.headers && msg.headers['gun-sid'] === ws.sid)){ return }
if(msg && msg.headers){ delete msg.headers['ws-rid'] }
// TODO: BUG? ^ What if other peers want to ack? Do they use the ws-rid or a gun declared id?
try{ws.send(Gun.text.ify(msg));
@ -23,7 +23,8 @@
});
gun.__.opt.ws.port = gun.__.opt.ws.port || opt.ws.port || port || 80;
}
var wsp = gun.wsp = gun.wsp || function(server){
var wsp = gun.wsp = gun.wsp || function(server, auth){
gun.wsp.auth = auth;
if(!server){ return gun }
if(Gun.fns.is(server.address)){
if(server.address()){
@ -112,15 +113,24 @@
// 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, 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';
function flow(req, res){
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'}});
}
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 && !Gun.is.lex(req.body))? 'put' : 'get';
if(gun.wsp.auth){ return gun.wsp.auth(req, function(reply){
if(!reply.headers){ reply.headers = {} }
if(!reply.headers['Content-Type']){ reply.headers['Content-Type'] = tran.json }
if(!reply.rid){ reply.headers.rid = req.headers.id }
if(!reply.id){ reply.headers.id = gun.wsp.msg() }
res(reply);
}, flow) }
else { return flow(req, res) }
}
tran.get = function(req, cb){
var key = req.url.key
@ -140,7 +150,7 @@
//Gun.log("GET!", req);
key = req.body;
//Gun.log("tran.get", key);
var opt = {key: false};
var opt = {key: false, local: true};
//gun.get(key, function(err, node){
(gun.__.opt.wire.get||function(key, cb){cb(null,null)})(key, function(err, node){
//Gun.log("tran.get", key, "<---", err, node);
@ -193,7 +203,7 @@
if(err){ return cb({headers: reply.headers, body: {err: err || "Failed."}}) } // TODO: err should already be an error object?
cb({headers: reply.headers, body: {ok: ok || "Persisted."}});
//Gun.log("tran.put <------------------------", ok);
});
}, {local: true});
}).err){ cb({headers: reply.headers, body: {err: req.err || "Union failed."}}) }
} else {
cb({headers: reply.headers, body: {err: "Not a valid graph!"}});
@ -209,22 +219,55 @@
wsp(opt.server);
}
setTimeout(function(){ // hack it in :( for now, since 0.4.x will allow us to have multiple drivers.
var proxy = gun.__.opt.wire;
var driver = {
put: function(graph, cb, opt){
proxy.put(graph, cb, opt);
var msg = {
headers: {'Content-Type': 'application/json', id: gun.wsp.msg()},
body: graph
};
gun.wsp.on('network').emit(msg);
},
get: proxy.get
if(gun.wsp.driver){ return }
var driver = gun.wsp.driver = {};
var noop = function(){};
var get = gun.__.opt.wire.get || noop;
var put = gun.__.opt.wire.put || noop;
var driver = {
put: function(graph, cb, opt){
put(graph, cb, opt);
opt = opt || {};
if(opt.local){ return }
var id = gun.wsp.msg();
gun.wsp.on('network').emit({ // sent to dynamic peers!
headers: {'Content-Type': 'application/json', id: id},
body: graph
});
var ropt = {headers:{}, WebSocket: WebSocket};
ropt.headers.id = id;
Gun.obj.map(opt.peers || gun.__.opt.peers, function(peer, url){
Gun.request(url, graph, function(err, reply){
reply.body = reply.body || reply.chunk || reply.end || reply.write;
if(err || !reply || (err = reply.body && reply.body.err)){
return cb({err: Gun.log(err || "Put failed.") });
}
cb(null, reply.body);
}, ropt);
});
},
get: function(lex, cb, opt){
get(lex, cb, opt);
opt = opt || {};
if(opt.local){ return }
if(!Gun.request){ return console.log("Server could not find default network abstraction.") }
var ropt = {headers:{}};
ropt.headers.id = gun.wsp.msg();
Gun.obj.map(opt.peers || gun.__.opt.peers, function(peer, url){
Gun.request(url, lex, function(err, reply){
reply.body = reply.body || reply.chunk || reply.end || reply.write;
if(err || !reply || (err = reply.body && reply.body.err)){
return cb({err: Gun.log(err || "Get failed.") });
}
cb(null, reply.body);
}, ropt);
});
}
gun.__.opt.wire = driver;
gun.opt({wire: driver}, true);
},1);
}
var WebSocket = require('ws');
Gun.request.WebSocket = WebSocket;
Gun.request.createServer(gun.wsp.wire);
gun.__.opt.wire = driver;
gun.opt({wire: driver}, true);
});
}({}));

View File

@ -1,6 +1,6 @@
{
"name": "gun",
"version": "0.3.92",
"version": "0.3.991",
"description": "Graph engine",
"main": "index.js",
"scripts": {

View File

@ -3799,7 +3799,7 @@ describe('Gun', function(){
});
it("get context", function(done){ // TODO: HUH?????? This was randomly causing errors?
var gun = Gun();
var gun = Gun();
var ref = gun.get('ctx/lol').get('ctx/foo').put({hello: 'world'});
gun.get('ctx/lol').val(function(implicit){
done.fail = true;
@ -3975,29 +3975,31 @@ describe('Gun', function(){
it("Don't put on parents", function(done){ // TODO: ADD TO 0.5 BRANCH! // Another Stefdv find.
var test = gun.get('test');
test.path('try.this.at.lvl4').put({msg:'hoi'})
test.val(function(node,b){
delete node._;
expect(Gun.obj.empty(node, 'try')).to.be.ok();
node = Gun.obj.copy(gun.__.graph[Gun.is.rel(node.try)]);
test.path('try.this.at.lvl4').put({msg:'hoi'});
setTimeout(function(){ // TODO: Is this cheating??
test.val(function(node,b){
delete node._;
expect(Gun.obj.empty(node, 'try')).to.be.ok();
node = Gun.obj.copy(gun.__.graph[Gun.is.rel(node.try)]);
delete node._;
expect(Gun.obj.empty(node, 'this')).to.be.ok();
node = Gun.obj.copy(gun.__.graph[Gun.is.rel(node.this)]);
delete node._;
expect(Gun.obj.empty(node, 'this')).to.be.ok();
node = Gun.obj.copy(gun.__.graph[Gun.is.rel(node.this)]);
delete node._;
expect(Gun.obj.empty(node, 'at')).to.be.ok();
node = Gun.obj.copy(gun.__.graph[Gun.is.rel(node.at)]);
delete node._;
expect(Gun.obj.empty(node, 'at')).to.be.ok();
node = Gun.obj.copy(gun.__.graph[Gun.is.rel(node.at)]);
delete node._;
expect(Gun.obj.empty(node, 'lvl4')).to.be.ok();
node = Gun.obj.copy(gun.__.graph[Gun.is.rel(node.lvl4)]);
delete node._;
expect(Gun.obj.empty(node, 'lvl4')).to.be.ok();
node = Gun.obj.copy(gun.__.graph[Gun.is.rel(node.lvl4)]);
delete node._;
expect(Gun.obj.empty(node, 'msg')).to.be.ok();
expect(node.msg).to.be('hoi');
done();
});
delete node._;
expect(Gun.obj.empty(node, 'msg')).to.be.ok();
expect(node.msg).to.be('hoi');
done();
});
},100);
});
});

15
test/server/http.js Normal file
View File

@ -0,0 +1,15 @@
//client.js writes data up to a listening hub.js, which relays to a server.js that reads the data.
var http = require('http');
var Gun = require('../../index');
var gun = Gun({
file: 'http.json'
});
var server = http.createServer(function(req, res){});
gun.wsp(server);
server.listen(8080);
console.log('Server started on port ' + 8080 + ' with /gun');

View File

@ -0,0 +1,9 @@
var Gun = require('../../index');
var location = {host:"localhost"};
var gun = Gun( { file: 'read.json', peers: ['http://' + location.host + ':8080/gun'] });
gun.get( 'data' ).path('stuff').map(function(val,field){ console.log( field, "=", val ); } );
console.log( "done... wait forever?" );

View File

@ -0,0 +1,7 @@
var Gun = require('../../index');
var location = {host:"localhost"};
var gun = Gun( { file: 'write.json', peers: ['http://' + location.host + ':8080/gun'] });
gun.get( 'data' ).path('stuff').put({a: {data: 1}, b: {data: 2}});