mirror of
https://github.com/amark/gun.git
synced 2025-03-30 15:08:33 +00:00
commit
7e29b235e2
18
README.md
18
README.md
@ -1,17 +1,17 @@
|
||||
# gun
|
||||
# gun [](https://npmjs.org/package/gun) [](https://travis-ci.org/amark/gun) [](https://gitter.im/amark/gun?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
<a href="https://npmjs.org/package/gun"><img align="right" alt="npm downloads" src="https://img.shields.io/npm/dm/gun.svg?style=flat" /></a>
|
||||
<a href="https://travis-ci.org/amark/gun"><img align="right" alt="Build status" src="https://travis-ci.org/amark/gun.svg?branch=master" /></a>
|
||||
<a href="https://gitter.im/amark/gun?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img align="right" alt="Gitter channel" src="https://badges.gitter.im/Join%20Chat.svg" /></a>
|
||||
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](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)
|
||||
|
@ -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');
|
@ -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": {
|
||||
|
97
gun.js
97
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});
|
||||
|
@ -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.
|
||||
},
|
||||
|
@ -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);
|
||||
|
22
lib/wsp.js
22
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."}}) }
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gun",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.1",
|
||||
"description": "Graph engine",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -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': {
|
||||
|
Loading…
x
Reference in New Issue
Block a user