From 6de0d682b2a036fd53b02b022e6b4465ace793ab Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Thu, 28 Jan 2016 12:58:07 -0800 Subject: [PATCH 1/5] Update README.md --- README.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index eacaafe4..9cbdfb4e 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,18 @@ -# gun +# gun [![NPM downloads](https://img.shields.io/npm/dm/gun.svg?style=flat)](https://npmjs.org/package/gun) [![Build Status](https://travis-ci.org/amark/gun.svg?branch=master)](https://travis-ci.org/amark/gun) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/amark/gun?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +=== -npm downloads -Build status -Gitter channel +GUN is a realtime, distributed, offline-first, graph database engine. Lightweight and powerful, at just **~9kb** gzipped. -GUN is a realtime, distributed, offline-first, graph database engine. +## Why? -## But what makes gun **awesome**? + - **Realtime** - It may be trivial to get realtime updates with socket.io or something, but what you do not get is *state synchronization*. GUN does this for you out of the box, assuring that two users' simultaneous updates won't concurrently break each other. + - **Distributed** - GUN is peer-to-peer by design, meaning you have no centralized database server to maintain or that could crash. This lets you sleep through the night without worrying about database DevOps - we call this "NoDB". From that, you can build decentralized, federated, or centralized apps. + - **Offline-first** - GUN works even if your internet or cell reception doesn't. Users can still plug away and save data as normal, and then when the network comes back online GUN will automatically synchronize all the changes and handle any conflicts for you. + - **Graph** - Most databases force you to bend over backwards to match their storage constraints. But graphs are different, they let you have any data structure you want. Whether that be traditional tables with relations, document oriented trees, or tons of circular references. You choose. - - **Realtime** - GUN shares data across connected peers in real-time. There's no need for plugins, extensions, or anything else, just use gun as directed and enjoy realtime data sharing. - - **Distributed** - GUN is peer-to-peer by design and doesn't rely on a central data server. This results in all kinds of data deliciousness, including being able to work offline by default. Peers can be configured into more traditional centralized or federated systems, as appropriate for application needs. - - **Graph** - All data in gun is encapsulated as nodes in a graph. Graphs allow data to be related in traditional table formats, as well as tree format, and circular formats. In other words, gun lets your data needs determine your data storage structure. +## Quickstart +Try the [interactive tutorial](https://gun.js.org/think.html) in the browser (**5min** ~ average developer). Or run the NodeJS [demo example apps](#demos) (**5min** ~ average developer). ## Table of Contents - [Demos](#demos) @@ -86,4 +87,4 @@ Also see the current [Release List](https://github.com/amark/gun/releases) and [ Gitter channel -[![YouTube](https://img.shields.io/badge/You-Tube-red.svg)](https://www.youtube.com/channel/UCQAtpf-zi9Pp4__2nToOM8g) [![LinkedIn](https://img.shields.io/badge/Linked-In-blue.svg)](https://www.linkedin.com/company/gun-inc) [![Twitter Follow](https://img.shields.io/twitter/follow/databasegun.svg?style=social)](https://twitter.com/databasegun) \ No newline at end of file +[![YouTube](https://img.shields.io/badge/You-Tube-red.svg)](https://www.youtube.com/channel/UCQAtpf-zi9Pp4__2nToOM8g) [![LinkedIn](https://img.shields.io/badge/Linked-In-blue.svg)](https://www.linkedin.com/company/gun-inc) [![Twitter Follow](https://img.shields.io/twitter/follow/databasegun.svg?style=social)](https://twitter.com/databasegun) From fc51d907a538a697c6e947bc3649f25bc31d4c32 Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Thu, 28 Jan 2016 12:58:46 -0800 Subject: [PATCH 2/5] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 9cbdfb4e..f9604c8e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # gun [![NPM downloads](https://img.shields.io/npm/dm/gun.svg?style=flat)](https://npmjs.org/package/gun) [![Build Status](https://travis-ci.org/amark/gun.svg?branch=master)](https://travis-ci.org/amark/gun) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/amark/gun?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -=== GUN is a realtime, distributed, offline-first, graph database engine. Lightweight and powerful, at just **~9kb** gzipped. From 7f44bed3d9d45385c8f777056d1899eae5753c90 Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Wed, 27 Jan 2016 18:12:59 -0800 Subject: [PATCH 3/5] quick express fixes --- examples/express.js | 2 +- examples/package.json | 4 ++-- lib/file.js | 8 ++++---- lib/ws.js | 2 +- lib/wsp.js | 22 ++++++++++++---------- package.json | 2 +- 6 files changed, 21 insertions(+), 19 deletions(-) diff --git a/examples/express.js b/examples/express.js index e7b83641..ac517fb8 100644 --- a/examples/express.js +++ b/examples/express.js @@ -14,7 +14,7 @@ var gun = Gun({ } }); -gun.attach(app); +gun.wsp(app); app.use(express.static(__dirname)).listen(port); console.log('Server started on port ' + port + ' with /gun'); \ No newline at end of file diff --git a/examples/package.json b/examples/package.json index 59cbda86..89c2dc2e 100644 --- a/examples/package.json +++ b/examples/package.json @@ -2,12 +2,12 @@ "name": "examples", "main": "http.js", "description": "Example gun apps" -, "version": "0.0.2" +, "version": "0.0.3" , "engines": { "node": "~>0.10.x" } , "dependencies": { - "express": "~>4.9.0", + "express": "~>4.13.4", "gun": "~>0.3.0" } , "scripts": { diff --git a/lib/file.js b/lib/file.js index f64edb15..d16e0601 100644 --- a/lib/file.js +++ b/lib/file.js @@ -37,12 +37,12 @@ Gun.on('opt').event(function(gun, opts) { } gun.opt({wire: { - get: function get(key, cb, o){ - var node, soul = key; + get: function get(lex, cb, o){ + var node, soul = lex[Gun._.soul]; node = all.nodes[soul]; - if(!node){ return cb(null, null) } + if(!node){ return cb(null) } cb(null, node); - node = Gun.is.node.ify({_: node._}, soul); + node = Gun.is.node.soul.ify({}, soul); cb(null, node); // end. cb(null, {}); // done. }, diff --git a/lib/ws.js b/lib/ws.js index 3562590c..97c93146 100644 --- a/lib/ws.js +++ b/lib/ws.js @@ -31,7 +31,7 @@ module.exports = function(wss, server){ }); }); ws.off = function(m){ - Gun.log("ws.off", m); + //Gun.log("ws.off", m); ws.send = null; } ws.on('close', ws.off); diff --git a/lib/wsp.js b/lib/wsp.js index 94673f9f..2e32ae2b 100644 --- a/lib/wsp.js +++ b/lib/wsp.js @@ -6,9 +6,9 @@ , url = require('url'); Gun.on('opt').event(function(gun, opt){ gun.__.opt.ws = opt.ws = gun.__.opt.ws || opt.ws || {}; - function start(server, port){ - gun.__.opt.ws.server = gun.__.opt.ws.server || opt.ws.server || server; - if(server.use){ server.use(gun.wsp.server) } + function start(server, port, app){ + if(app && app.use){ app.use(gun.wsp.server) } + 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']; @@ -31,7 +31,7 @@ start(server, server.address().port); return gun; } - } else + } if(Gun.fns.is(server.get) && server.get('port')){ start(server, server.get('port')); return gun; @@ -39,7 +39,7 @@ var listen = server.listen; server.listen = function(port){ var serve = listen.apply(server, arguments); - start(serve, port); + start(serve, port, server); return serve; } return gun; @@ -126,12 +126,14 @@ key = {}; key[Gun._.soul] = req.url.query[Gun._.soul]; } - console.log("tran.get", key); - var opt = {}; + if(Gun.text.is(key)){ + key = Gun.is.rel.ify(key); + } + //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){ - //tran.sub.scribe(req.tab, graph._[Gun._.soul]); - console.log("tran.get", key, "<---", err, node); + //console.log("tran.get", key, "<---", err, node); 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)}); @@ -169,7 +171,7 @@ var reply = {headers: {'Content-Type': tran.json}}; if(!req.body){ return cb({headers: reply.headers, body: {err: "No body"}}) } gun.wsp.on('network').emit(Gun.obj.copy(req)); - console.log("tran.put", req.body); + //console.log("tran.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 if(err){ return cb({headers: reply.headers, body: {err: err || "Union failed."}}) } diff --git a/package.json b/package.json index 47560236..dfbfbe58 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gun", - "version": "0.3.0", + "version": "0.3.1", "description": "Graph engine", "main": "index.js", "scripts": { From 155f096254d08b5d7202f0b2167e52a2289053dc Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Thu, 28 Jan 2016 13:00:31 -0800 Subject: [PATCH 4/5] comments and semantics --- gun.js | 97 ++++++++++++++++++++++++-------------------------- test/common.js | 3 +- 2 files changed, 47 insertions(+), 53 deletions(-) diff --git a/gun.js b/gun.js index ceed1a55..570673f2 100644 --- a/gun.js +++ b/gun.js @@ -211,17 +211,13 @@ return Gun.is.rel(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. } - Gun.is.val.as = function(v){ // check if it is a valid value and return the value if so, - return Gun.is.val(v)? v : null; // else return null. - } - Gun.is.rel = function(v){ // this defines whether an object is a soul relation or not, they look like this: {'#': 'UUID'} if(Gun.obj.is(v)){ // must be an object. var id; - Gun.obj.map(v, function(soul, field){ // map over the object... + Gun.obj.map(v, function(s, f){ // map over the object... if(id){ return id = false } // if ID is already defined AND we're still looping through the object, it is considered invalid. - if(field == Gun._.soul && Gun.text.is(soul)){ // the field should be '#' and have a text value. - id = soul; // we found the soul! + if(f == Gun._.soul && Gun.text.is(s)){ // the field should be '#' and have a text value. + id = s; // we found the soul! } else { return id = false; // if there exists anything else on the object that isn't the soul, then it is considered invalid. } @@ -233,70 +229,69 @@ return false; // the value was not a valid soul relation. } - Gun.is.rel.ify = function(s, rel){ return Gun.obj.as(rel = {}, Gun._.soul, s), rel } + 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.node = function(node, cb, t){ // checks to see if an object is a valid node. - var soul; - if(!Gun.obj.is(node)){ return false } // must be an object. - if(soul = Gun.is.node.soul(node)){ // must have a soul on it. - return !Gun.obj.map(node, function(val, field){ // we invert this because the way we check for this is via a negation. - if(field == Gun._.meta){ return } // skip over the metadata. - if(!Gun.is.val(val)){ return true } // it is true that this is an invalid node. - if(cb){ cb.call(t, val, field, node) } // optionally callback each field/value. + 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. + if(s = Gun.is.node.soul(n)){ // must have a soul on it. + return !Gun.obj.map(n, function(v, f){ // we invert this because the way we check for this is via a negation. + if(f == Gun._.meta){ return } // skip over the metadata. + if(!Gun.is.val(v)){ return true } // it is true that this is an invalid node. + if(cb){ cb.call(t, v, f, n) } // optionally callback each field/value. }); } return false; // nope! This was not a valid node. } - Gun.is.node.ify = function(vertex, soul, state, t){ - vertex = Gun.is.node.soul.ify(vertex, soul, t); - Gun.obj.map(vertex, function(val, field){ - if(Gun._.meta === field){ return } - Gun.is.node.state.ify([vertex], field, val, state = state || Gun.time.now()); + Gun.is.node.ify = function(n, s, o){ // convert a shallow object into a node. + o = Gun.bi.is(o)? {force: o} : o || {}; // detect options. + n = Gun.is.node.soul.ify(n, s, o.force); // put a soul on it. + Gun.obj.map(n, function(v, f){ // iterate over each field/value. + if(Gun._.meta === f){ return } // ignore meta. + Gun.is.node.state.ify([n], f, v, o.state = o.state || Gun.time.now()); // and set the state for this field and value on this node. }); - return vertex; + return n; // This will only be a valid node if the object wasn't already deep! } Gun.is.node.soul = function(n, s){ return (n && n._ && n._[s || Gun._.soul]) || false } // convenience function to check to see if there is a soul on a node and return it. - Gun.is.node.soul.ify = function(n, s, t){ - n = n || {}; - n._ = n._ || {}; - n._[Gun._.soul] = t? s : n._[Gun._.soul] || s || Gun.text.random(); + Gun.is.node.soul.ify = function(n, s, o){ // put a soul on an object. + n = n || {}; // make sure it exists. + n._ = n._ || {}; // make sure meta exists. + n._[Gun._.soul] = o? s : n._[Gun._.soul] || s || Gun.text.random(); // if it already has a soul then use that instead - unless you force the soul you want with an option. return n; } - Gun.is.node.state = function(n, f){ return (f && n && n._ && n._[Gun._.state] && n._[Gun._.state][f]) || false } + Gun.is.node.state = function(n, f){ return (f && n && n._ && n._[Gun._.state] && Gun.num.is(n._[Gun._.state][f]))? n._[Gun._.state][f] : false } // convenience function to get the state on a field on a node and return it. - Gun.is.node.state.ify = function(l, f, v, s){ - var u, l = l.reverse(), d = l[0]; - Gun.list.map(l, function(n, i){ - n = n || {}; - if(u !== v && Gun.is.val(v)){ n[f] = v } - n._ = n._ || {}; - n = n._[Gun._.state] = n._[Gun._.state] || {}; - if(i = d._[Gun._.state][f]){ n[f] = i } - if(Gun.num.is(s)){ n[f] = s } + Gun.is.node.state.ify = function(l, f, v, state){ // put a field's state and value on some nodes. + l = Gun.list.is(l)? l : [l]; // handle a list of nodes or just one node. + var l = l.reverse(), d = l[0]; // we might want to inherit the state from the last node in the list. + Gun.list.map(l, function(n, i){ // iterate over each node. + n = n || {}; // make sure it exists. + if(Gun.is.val(v)){ n[f] = v } // if we have a value, then put it. + n._ = n._ || {}; // make sure meta exists. + n = n._[Gun._.state] = n._[Gun._.state] || {}; // make sure HAM state exists. + if(i = d._[Gun._.state][f]){ n[f] = i } // inherit the state! + if(Gun.num.is(state)){ n[f] = state } // or manually set the state. }); } - Gun.is.graph = function(graph, cb, fn, t){ // checks to see if an object is a valid graph. + Gun.is.graph = function(g, cb, fn, t){ // checks to see if an object is a valid graph. var exist = false; - if(!Gun.obj.is(graph)){ return false } // must be an object. - return !Gun.obj.map(graph, function(node, soul){ // we invert this because the way we check for this is via a negation. - if(!node || soul !== Gun.is.node.soul(node) || !Gun.is.node(node, fn)){ return true } // it is true that this is an invalid graph. - (cb || function(){}).call(t, node, soul, function(fn){ // optional callback for each node. - if(fn){ Gun.is.node(node, fn, t) } // where we then have an optional callback for each field/value. + if(!Gun.obj.is(g)){ return false } // must be an object. + return !Gun.obj.map(g, function(n, s){ // we invert this because the way we check for this is via a negation. + if(!n || s !== Gun.is.node.soul(n) || !Gun.is.node(n, fn)){ return true } // it is true that this is an invalid graph. + (cb || function(){}).call(t, n, s, function(fn){ // optional callback for each node. + if(fn){ Gun.is.node(n, fn, t) } // where we then have an optional callback for each field/value. }); exist = true; }) && exist; // makes sure it wasn't an empty object. } - Gun.is.graph.ify = function(node){ - var soul; - if(soul = Gun.is.node.soul(node)){ - var graph = {}; graph[soul] = node; - return graph; + Gun.is.graph.ify = function(n){ var s; // wrap a node into a graph. + if(s = Gun.is.node.soul(n)){ // grab the soul from the node, if it is a node. + return Gun.obj.put({}, s, n); // then create and return a graph which has a node on the matching soul property. } } @@ -346,7 +341,7 @@ if(!prime){ ctx.err = {err: Gun.log("No data to merge!") } } if(ctx.soul = Gun.is.node.soul(prime)){ prime = Gun.is.graph.ify(prime) } if(!Gun.is.graph(prime, null, function(val, field, node){ var meta; - if(!(meta = (node||{})[Gun._.meta]) || !(meta = meta[Gun._.state]) || !Gun.num.is(meta[field])){ + if(!Gun.num.is(Gun.is.node.state(node, field))){ return ctx.err = {err: Gun.log("No state on '" + field + "'!") } } }) || ctx.err){ return ctx.err = ctx.err || {err: Gun.log("Invalid graph!", prime)}, ctx } @@ -590,7 +585,7 @@ Gun.union(chain, Gun.is.node.soul.ify({}, soul)); // fire off an end node if there hasn't already been one, to comply with the wire spec. }}).err){ return cb.call(chain, err), chain._.at('err').emit(err) } // now actually union the serialized data, emit error if any occur. if(Gun.fns.is(end.wire = chain.__.opt.wire.put)){ - function wcb(err, ok, info){ + var wcb = function(err, ok, info){ if(err){ return Gun.log(err.err || err), cb.call(chain, err), chain._.at('err').emit(err) } return cb.call(chain, err, ok); } @@ -736,7 +731,7 @@ if(at.soul === key || at.key === key){ return } if(cb.hash[at.hash = at.hash || Gun.on.at.hash(at)]){ return } cb.hash[at.hash] = true; ctx.obj = (1 === Gun.is.node.soul(ctx.node, 'key'))? Gun.obj.copy(ctx.node) : Gun.obj.put({}, at.soul, Gun.is.rel.ify(at.soul)); - Gun.obj.as((ctx.put = Gun.is.node.ify(ctx.obj, key, null, true))._, 'key', 1); + Gun.obj.as((ctx.put = Gun.is.node.ify(ctx.obj, key, true))._, 'key', 1); gun.__.gun.put(ctx.put, function(err, ok){cb.call(this, err, ok)}, {chain: opt.chain, key: true, init: true}); } if(opt.soul){ @@ -960,7 +955,7 @@ if(at.soul){ if(ctx.by.node){ return } var soul = Gun.text.random(); - gun.__.gun.put(Gun.is.node.soul.ify({}, soul, {init: true})); + gun.__.gun.put(Gun.is.node.soul.ify({}, soul), null, {init: true}); gun.__.gun.key(at.soul, null, soul); } }, {raw: true}); diff --git a/test/common.js b/test/common.js index b61b2b60..035447c6 100644 --- a/test/common.js +++ b/test/common.js @@ -788,14 +788,13 @@ describe('Gun', function(){ x: 'hi' } } - expect(gun.__.graph['asdf'].x).to.not.be.ok(); var ctx = Gun.union(gun, prime, function(){ expect(gun.__.graph['asdf'].x).to.be('hi'); done(); }); }); - + it('past', function(done){ var prime = { 'asdf': { From a4924d85d0f0fa0149dc792b93838629b76a80e7 Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Thu, 28 Jan 2016 14:22:14 -0800 Subject: [PATCH 5/5] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f9604c8e..af561150 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # gun [![NPM downloads](https://img.shields.io/npm/dm/gun.svg?style=flat)](https://npmjs.org/package/gun) [![Build Status](https://travis-ci.org/amark/gun.svg?branch=master)](https://travis-ci.org/amark/gun) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/amark/gun?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -GUN is a realtime, distributed, offline-first, graph database engine. Lightweight and powerful, at just **~9kb** gzipped. +GUN is a realtime, distributed, offline-first, graph database engine. Lightweight and powerful, at just **~9KB** gzipped. ## Why? @@ -11,7 +11,7 @@ GUN is a realtime, distributed, offline-first, graph database engine. Lightweigh ## Quickstart -Try the [interactive tutorial](https://gun.js.org/think.html) in the browser (**5min** ~ average developer). Or run the NodeJS [demo example apps](#demos) (**5min** ~ average developer). +Try the [interactive tutorial](http://gun.js.org/think.html) in the browser (**5min** ~ average developer). Or run the NodeJS [demo example apps](#demos) (**5min** ~ average developer). ## Table of Contents - [Demos](#demos)