diff --git a/README.md b/README.md index 43cd8855..303bba87 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ -gun [![Build Status](https://travis-ci.org/amark/gun.svg?branch=master)](https://travis-ci.org/amark/gun) +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, decentralized, embedded, graph database engine. ## Getting Started +For the browser, try out this [tutorial](https://dl.dropboxusercontent.com/u/4374976/gun/web/think.html). This README is for GUN servers. + If you do not have [node](http://nodejs.org/) or [npm](https://www.npmjs.com/), read [this](https://github.com/amark/gun/blob/master/examples/install.sh) first. Then in your terminal, run: @@ -35,42 +37,21 @@ These are the default persistence layers, they are modular and can be replaced o Using S3 is recommended for deployment, and using a file is recommended for local development. -Now you can save your first object, and create a reference to it. +## Demos -```javascript -gun.set({ hello: 'world' }).key('my/first/data'); -``` - -Altogether, try it with the node hello world web server which will reply with your data. - -```javascript -var Gun = require('gun'); -var gun = Gun({ file: 'data.json' }); -gun.set({ hello: 'world' }).key('my/first/data'); - -var http = require('http'); -http.createServer(function(req, res){ - gun.load('my/first/data', function(err, data){ - res.writeHead(200, {'Content-Type': 'application/json'}); - res.end(JSON.stringify(data)); - }); -}).listen(1337, '127.0.0.1'); -console.log('Server running at http://127.0.0.1:1337/'); -``` - -Fire up your browser and hit that URL - you'll see your data, plus some gun specific metadata. - -## Examples - -Try out some online [examples](http://gunjs.herokuapp.com/) or run them yourself with the following command: +The examples included in this repo are online [here](http://gunjs.herokuapp.com/), you can run them locally by: ```bash -git clone http://github.com/amark/gun -cd gun/examples && npm install -node express.js 8080 +sudo npm install gun +cd node_modules/gun +node examples/http.js 8080 ``` -Then visit [http://localhost:8080](http://localhost:8080) in your browser. +Then visit [http://localhost:8080](http://localhost:8080) in your browser. If that did not work it is probably because npm installed to a global directory, to fix this try `mkdir node_modules` in your desired directory and re-run the above commands. + +## WARNINGS + +Version 0.2.0 breaks **everything** from 0.1.x, see [#54](/../../issues/54) to migrate (`.all` is not implemented yet). GUN is not stable, and therefore should not be trusted in a production environment. ## API @@ -87,20 +68,20 @@ In gun, it can be helpful to think of everything as field/value pairs. For examp "email": "mark@gunDB.io" } ``` -Now, we want to save this object to a key called `usernames/marknadal`. We can do that like this: +Now, we want to save this object to a key called `'usernames/marknadal'`. We can do that like this: ```javascript -gun.set({ +gun.put({ username: "marknadal", name: "Mark Nadal", email: "mark@gunDB.io" }).key('usernames/marknadal'); ``` -We can also pass `set` a callback that can be used to handle errors: +We can also pass `put` a callback that can be used to handle errors: ```javascript -gun.set({ +gun.put({ username: "marknadal", name: "Mark Nadal", email: "mark@gunDB.io" @@ -114,33 +95,40 @@ gun.set({ Once we have some data stored in gun, we need a way to get them out again. Retrieving the data that we just stored would look like this: ```javascript -gun.load('usernames/marknadal').get(function(user){ +gun.get('usernames/marknadal').val(function(user){ console.log(user.name); // Prints `Mark Nadal` to the console }); ``` -Basically, this tells gun to check `usernames/marknadal`, and then return the object it finds associated with it. For more information, including how to save relational or document based data, [check out the wiki](https://github.com/amark/gun/wiki). +Basically, this tells gun to check `'usernames/marknadal'`, and then return the object it finds associated with it. For more information, including how to save relational or document based data, [check out the wiki](https://github.com/amark/gun/wiki). --- -## YOU -We're just getting started, so join us! Being lonely is never any fun, especially when programming. -I want to help you, because my goal is for GUN to be the easiest database ever. -That means if you ever get stuck on something for longer than 5 minutes, -you should talk to me so I can help you solve it. -Your input will then help me improve gun. -We are also really open to contributions! GUN is easy to extend and customize: +## YOU +Being lonely is never any fun, especially when programming. +Our goal is for GUN to be the easiest database ever, +which means if you ever get stuck on something for longer than 5 minutes, +let us know so we can help you. Your input is invaluable, +as it enables us where to refine GUN. So drop us a line in the [![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)! Or join the [mail list](https://groups.google.com/forum/#!forum/g-u-n). + +Thanks to the following people who have contributed to GUN, via code, issues, or conversation: + +[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), [gedw99](https://github.com/gedw99), [HelloCodeMing](https://github.com/HelloCodeMing), [JosePedroDias](https://github.com/josepedrodias), [onetom](https://github.com/onetom), [ndarilek](https://github.com/ndarilek), [phpnode](https://github.com/phpnode), [riston](https://github.com/riston), [rootsical](https://github.com/rootsical), [rrrene](https://github.com/rrrene), [ssr1ram](https://github.com/ssr1ram), [Xe](https://github.com/Xe), [zot](https://github.com/zot) + +This list of contributors was manually compiled, alphabetically sorted. If we missed you, please submit an issue so we can get you added! + +## Contribute + +Extending GUN or writing modules for it is as simple as: `Gun.on('opt').event(function(gun, opt){ /* Your module here! */ })` -It is also important to us that your database is not a magical black box. -So often our questions get dismissed with "its complicated hard low level stuff, let the experts handle it." -And we do not think that attitude will generate any progress for people. -Instead, we want to make everyone an expert by actually getting really good at explaining the concepts. -So join our community, in the quest of learning cool things and helping yourself and others build awesome technology. +We also want our database to be comprehensible, not some magical black box. +So often database questions get dismissed with "its complicated hard low level stuff, let the experts handle it". +That attitude prevents progress, instead we welcome teaching people and listening to new perspectives. +Join along side us in a quest to learn cool things and help others build awesome technology! - - [![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) (all chats relating to GUN and development should be here! IRC style) - - Google Group: https://groups.google.com/forum/#!forum/g-u-n (for slower threaded discussions) +We need help on the following roadmap. ## Ahead - ~~Realtime push to the browser~~ @@ -151,40 +139,11 @@ So join our community, in the quest of learning cool things and helping yourself - Test more - Bug fixes - Data Structures: - - ~~Groups~~ - - Linked Lists - - Collections (hybrid: linked-groups/paginated-lists) + - ~~Sets~~ (Table/Collections, Unordered Lists) - CRDTs - OT - Query: - SQL - MongoDB Query Documents - Neo4j Cypher - - Gremlin Query Language - - -## Contributors - -Thanks to the following people who have contributed to GUN: - - [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) - - [gedw99](https://github.com/gedw99) - - [HelloCodeMing](https://github.com/HelloCodeMing) - - [JosePedroDias](https://github.com/josepedrodias) - - [onetom](https://github.com/onetom) - - [ndarilek](https://github.com/ndarilek) - - [phpnode](https://github.com/phpnode) - - [riston](https://github.com/riston) - - [rootsical](https://github.com/rootsical) - - [rrrene](https://github.com/rrrene) - - [ssr1ram](https://github.com/ssr1ram) - - [Xe](https://github.com/Xe) - - [zot](https://github.com/zot) - -This list of contributors was manually compiled; if you have contributed in code, issues, or conversation and your GitHub username is not listed, please submit an issue so we can get you added! + - Gremlin Query Language \ No newline at end of file diff --git a/examples/todo/index.html b/examples/todo/index.html index ad1fe935..92dd811c 100644 --- a/examples/todo/index.html +++ b/examples/todo/index.html @@ -11,20 +11,18 @@ function ready(){ var $ = document.querySelector.bind(document); var gun = Gun(location.origin + '/gun').get('example/todo/data'); - gun.not(function(){ - return this.put({hello: "world!"}).key('example/todo/data'); - }).on(function renderToDo(val){ + gun.on(function renderToDo(val){ var todoHTML = ''; - for(key in val) { - if(!val[key] || key == '_') continue; - todoHTML += '
  • ' + (val[key]||'').toString().replace(/\X
  • '; + for(field in val) { + if(!val[field] || field == '_') continue; + todoHTML += '
  • ' + + (val[field]||'').toString().replace(/\X
  • '; } $("#todos").innerHTML = todoHTML; }); $("#addToDo").onsubmit = function(){ - var id = randomId(); - gun.path(id).put(($("#todoItem").value||'').toString().replace(/\ 100){ + // split object into many objects that have a fixed size + // iterate over each object + // cb({headers: reply.headers, chunk: {object} ); + } + });*/ + return cb({headers: reply.headers, chunk: graph }); // keep streaming }); } tran.put = function(req, cb){ @@ -130,7 +142,7 @@ if(tran.put.key(req, cb)){ return } // some NEW code that should get revised. if(Gun.is.node(req.body) || Gun.is.graph(req.body)){ - console.log("tran.put", req.body); + //console.log("tran.put", 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."}}) } var ctx = ctx || {}; ctx.graph = {}; diff --git a/test/chain.js b/test/chain.js deleted file mode 100644 index 4bc62a5f..00000000 --- a/test/chain.js +++ /dev/null @@ -1,165 +0,0 @@ -var expect = global.expect = require("./expect"); - -var Gun = Gun || require('../gun'); -Gun.log.squelch = true; - -describe('All', function(){ - var gun = Gun(), g = function(){ - return Gun({hooks: {get: ctx.get}}); - }, ctx = {}; - - /* - ctx.hook(key, function(err, data){ // multiple times potentially - //console.log("chain.get from load", err, data); - if(err){ return cb.call(gun, err, data) } - if(!data){ return cb.call(gun, null, null), gun._.at('null').emit() } - if(ctx.soul = Gun.is.soul.on(data)){ - gun._.at('soul').emit({soul: ctx.soul}); - } else { return cb.call(gun, {err: Gun.log('No soul on data!') }, data) } - if(err = Gun.union(gun, data).err){ return cb.call(gun, err) } - cb.call(gun, null, data); - gun._.at('node').emit({soul: ctx.soul}); - }, opt); - */ - - it('prep hook', function(done){ - var peer = Gun(), ref; - ctx.get = function(key, cb){ - var c = 0; - cb = cb || function(){}; - if('big' !== key){ return cb(null, null) } - setTimeout(function badNetwork(){ - c += 1; - var data = {_: {'#': Gun.is.soul.on(ref), '>': {}}}; - if(!ref['f' + c]){ - return cb(null, data); - } - data._[Gun._.HAM]['f' + c] = ref._[Gun._.HAM]['f' + c]; - data['f' + c] = ref['f' + c]; - cb(null, data); - setTimeout(badNetwork, 5); - },5); - } - ctx.get.fake = {}; - for(var i = 1; i < 6; i++){ - ctx.get.fake['f'+i] = i; - } - var big = peer.put(ctx.get.fake).val(function(val){ - ref = val; - ctx.get('big', function(err, data){ - var next = Gun.obj.map(data, function(val, field){ - if(Gun._.meta === field){ return } - return true; - }); - //console.log(data); - if(!next){ done() } - }); - gun.opt({hooks: {get: ctx.get}}); - }); - }); - - it('map chain', function(done){ - var set = gun.put({a: {here: 'you'}, b: {go: 'dear'}, c: {sir: '!'} }); - set.map().val(function(obj, field){ - if(obj.here){ - done.a = obj.here; - expect(obj.here).to.be('you'); - } - if(obj.go){ - done.b = obj.go; - expect(obj.go).to.be('dear'); - } - if(obj.sir){ - done.c = obj.sir; - expect(obj.sir).to.be('!'); - } - if(done.a && done.b && done.c){ - done(); - } - }); - }); - - it('map chain path', function(done){ - var set = gun.put({ - a: {name: "Mark", - pet: {coat: "tabby", name: "Hobbes"} - }, b: {name: "Alice", - pet: {coat: "calico", name: "Cali"} - }, c: {name: "Bob", - pet: {coat: "tux", name: "Casper"} - } - }); - set.map().path('pet').val(function(obj, field){ - if(obj.name === 'Hobbes'){ - done.hobbes = obj.name; - expect(obj.name).to.be('Hobbes'); - expect(obj.coat).to.be('tabby'); - } - if(obj.name === 'Cali'){ - done.cali = obj.name; - expect(obj.name).to.be('Cali'); - expect(obj.coat).to.be('calico'); - } - if(obj.name === 'Casper'){ - done.casper = obj.name; - expect(obj.name).to.be('Casper'); - expect(obj.coat).to.be('tux'); - } - if(done.hobbes && done.cali && done.casper){ - done(); - } - }); - }); - - it('get big on', function(done){ - var c = 0; - g().get('big').on(function(val){ - delete val._; - c += 1; - if(c === 1){ - expect(val).to.eql({f1: 1}); - } - if(c === 5){ - expect(val).to.eql({f1: 1, f2: 2, f3: 3, f4: 4, f5: 5}); - done(); - } - }); - }); - - it('get big on delta', function(done){ - var c = 0; - g().get('big').on(function(val){ - delete val._; - c += 1; - if(c === 1){ - expect(val).to.eql({f1: 1}); - } - if(c === 5){ - expect(val).to.eql({f5: 5}); - done(); - } - }, true); - }); - - it('get val', function(done){ - g().get('big').val(function(obj){ - delete obj._; - expect(obj.f1).to.be(1); - expect(obj.f5).to.be(5); - done(); - }); - }); - - it('get big map val', function(done){ - g().get('big').map().val(function(val, field){ - delete val._; - if('f1' === field){ - expect(val).to.be(1); - } - if('f5' === field){ - expect(val).to.be(5); - done(); - } - }); - }); -}); \ No newline at end of file diff --git a/test/common.js b/test/common.js index a043c8c0..284e5acf 100644 --- a/test/common.js +++ b/test/common.js @@ -1630,15 +1630,14 @@ describe('Gun', function(){ gun.put({a: 1, z: -1}).key('pseudo'); gun.put({b: 2, z: 0}).key('pseudo'); - Gun.log.verbose = true; gun.get('pseudo').val(function(val){ expect(val.a).to.be(1); expect(val.b).to.be(2); expect(val.z).to.be(0); - //done(); + done(); }); }); - return; + it('get pseudo merge on', function(done){ var gun = Gun(); @@ -1646,18 +1645,16 @@ describe('Gun', function(){ gun.put({b: 2, z: 0}).key('pseudon'); gun.get('pseudon').on(function(val){ - console.log("HOW MANY pseudon TIMES??", val); if(done.val){ return } // TODO: Maybe prevent repeat ons where there is no diff? done.val = val; expect(val.a).to.be(1); expect(val.b).to.be(2); expect(val.z).to.be(0); - //done(); + done(); }); }); - return; + it('get pseudo merge across peers', function(done){ - alert(1); Gun.on('opt').event(function(gun, o){ if(connect){ return } gun.__.opt.hooks = {get: function(key, cb, opt){ @@ -1718,5 +1715,164 @@ describe('Gun', function(){ },10); },10); }); + }); + + describe('Streams', function(){ + var gun = Gun(), g = function(){ + return Gun({hooks: {get: ctx.get}}); + }, ctx = {gen: 5, extra: 45, network: 2}; + + it('prep hook', function(done){ + this.timeout(ctx.gen * ctx.extra); + var peer = Gun(), ref; + ctx.get = function(key, cb){ + var c = 0; + cb = cb || function(){}; + if('big' !== key){ return cb(null, null) } + setTimeout(function badNetwork(){ + c += 1; + var soul = Gun.is.soul.on(ref); + var graph = {}; + var data = graph[soul] = {_: {'#': soul, '>': {}}}; + if(!ref['f' + c]){ + return cb(null, graph), cb(null, {}); + } + data._[Gun._.HAM]['f' + c] = ref._[Gun._.HAM]['f' + c]; + data['f' + c] = ref['f' + c]; + cb(null, graph); + setTimeout(badNetwork, ctx.network); + },ctx.network); + } + ctx.get.fake = {}; + for(var i = 1; i < (ctx.gen) + 1; i++){ + ctx.get.fake['f'+i] = i; + ctx.length = i; + } + var big = peer.put(ctx.get.fake).val(function(val){ + ref = val; + ctx.get('big', function(err, graph){ + if(Gun.obj.empty(graph)){ done() } + }); + gun.opt({hooks: {get: ctx.get}}); + }); + }); + + it('map chain', function(done){ + var set = gun.put({a: {here: 'you'}, b: {go: 'dear'}, c: {sir: '!'} }); + set.map().val(function(obj, field){ + if(obj.here){ + done.a = obj.here; + expect(obj.here).to.be('you'); + } + if(obj.go){ + done.b = obj.go; + expect(obj.go).to.be('dear'); + } + if(obj.sir){ + done.c = obj.sir; + expect(obj.sir).to.be('!'); + } + if(done.a && done.b && done.c){ + done(); + } + }); + }); + + it('map chain path', function(done){ + var set = gun.put({ + a: {name: "Mark", + pet: {coat: "tabby", name: "Hobbes"} + }, b: {name: "Alice", + pet: {coat: "calico", name: "Cali"} + }, c: {name: "Bob", + pet: {coat: "tux", name: "Casper"} + } + }); + set.map().path('pet').val(function(obj, field){ + if(obj.name === 'Hobbes'){ + done.hobbes = obj.name; + expect(obj.name).to.be('Hobbes'); + expect(obj.coat).to.be('tabby'); + } + if(obj.name === 'Cali'){ + done.cali = obj.name; + expect(obj.name).to.be('Cali'); + expect(obj.coat).to.be('calico'); + } + if(obj.name === 'Casper'){ + done.casper = obj.name; + expect(obj.name).to.be('Casper'); + expect(obj.coat).to.be('tux'); + } + if(done.hobbes && done.cali && done.casper){ + done(); + } + }); + }); + + it('get big on', function(done){ + this.timeout(ctx.gen * ctx.extra); + var test = {c: 0, last: 0}; + g().get('big').on(function(val){ + if(test.done){ return console.log("hey yo! you got duplication on your ons!"); } + delete val._; + if(val['f' + (test.last + 1)]){ + test.c += 1; + test.last += 1; + } + var obj = {}; + for(var i = 1; i < test.c + 1; i++){ + obj['f'+i] = i; + } + expect(val).to.eql(obj); + if(test.c === ctx.length){ + test.done = true; + done(); + } + }); + }); + + it('get big on delta', function(done){ + this.timeout(ctx.gen * ctx.extra); + var test = {c: 0, seen: {}}; + g().get('big').on(function(val){ + delete val._; + if(test.seen['f' + test.c]){ return } + test.seen['f' + test.c] = true; + test.c += 1; + var obj = {}; + obj['f' + test.c] = test.c; + expect(val).to.eql(obj); + if(test.c === ctx.length){ + done(); + } + }, true); + }); + + it('get val', function(done){ + this.timeout(ctx.gen * ctx.extra); + g().get('big').val(function(obj){ + delete obj._; + expect(obj.f1).to.be(1); + expect(obj['f' + ctx.length]).to.be(ctx.length); + expect(obj).to.be.eql(ctx.get.fake); + done(); + }); + }); + + it('get big map val', function(done){ + this.timeout(ctx.gen * ctx.extra); + var test = {c: 0, seen: {}}; + g().get('big').map().val(function(val, field){ + if(test.seen[field]){ return } + test.seen[field] = true; + delete val._; + expect(field).to.be('f' + (test.c += 1)); + expect(val).to.be(test.c); + if(test.c === ctx.length){ + done(); + } + }); + }); }); }); \ No newline at end of file