├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── _README.md ├── cmd.js ├── config.js ├── db.js ├── deploy └── tacodb-service-manifest.xml ├── docs ├── commands.md ├── faq.md ├── features.md ├── getting-started.md └── internal.md ├── examples ├── README.md ├── build.sh ├── changes │ ├── README.md │ ├── index.js │ └── package.json ├── db.js ├── db2.js ├── http │ ├── README.md │ ├── db.js │ ├── index.js │ └── package.json ├── package.json └── ws │ ├── README.md │ ├── client.js │ ├── index.html │ ├── package.json │ └── server.js ├── favicon.ico ├── intro.md ├── master-db.js ├── package.json ├── server.js ├── site ├── _index.htmt ├── about.md ├── favicon.ico ├── img │ ├── background2.jpg │ ├── favicon.ico │ ├── favicon.png │ ├── footer1.png │ ├── line.png │ ├── logo.png │ └── sombrero.png ├── index.html ├── install.md └── style │ └── style.css └── test └── test-update-db.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | node_modules/* 3 | npm-debug.log 4 | .tacodb 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.6 4 | - 0.8 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Dominic Tarr 2 | 3 | Permission is hereby granted, free of charge, 4 | to any person obtaining a copy of this software and 5 | associated documentation files (the "Software"), to 6 | deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom 10 | the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 20 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tacodb 2 | 3 | reusable leveldb server 4 | 5 | ## Synopsis 6 | 7 | tacodb wraps leveldbs in a server such that it would be possible 8 | to create a hosted service with it, that can be connected to via 9 | http and WebSockets. 10 | 11 | You database is configured by writing a .js file, which is then 12 | bundled (a la browserify) and sent to the tacodb server via http. 13 | 14 | ## Getting Started 15 | 16 | start by installing `tacodb` 17 | 18 | [more examples](https://github.com/dominictarr/tacodb/blob/master/examples/README.md) 19 | 20 | ``` js 21 | npm install -g tacodb 22 | ``` 23 | 24 | Then, create an customization file 25 | 26 | ``` js 27 | //db.js 28 | //creat a static http style interface to leveldb. 29 | var static = require('level-static') 30 | module.exports = function (db) { 31 | db.on('http_connection', static(db)) 32 | } 33 | ``` 34 | 35 | start the server locally. 36 | 37 | ``` js 38 | tacodb local db.js --port 8080 39 | ``` 40 | 41 | ``` js 42 | echo hello | curl -sSNT . -X PUT http://localhost:8080/greeting 43 | curl http://localhost:8080/greeting 44 | ``` 45 | 46 | ## connecting to tacodb/level via http 47 | 48 | Create a database customization file... 49 | This will expose an http interface, 50 | that can store files inside leveldb. 51 | 52 | ### simple http access 53 | 54 | ``` js 55 | //examples/http/index.js 56 | 57 | var static = require('level-static') 58 | 59 | module.exports = function (db) { 60 | db.on('http_connection', static(db)) 61 | } 62 | 63 | ``` 64 | 65 | ``` 66 | >tacodb local static.js --name static 67 | listening on 8000 68 | ``` 69 | 70 | This starts a tacodb server running around your `db.js` file. 71 | 72 | ``` sh 73 | #http PUT hi = HELLO 74 | echo 'HELLO!' | curl -sSNT . localhost:8000/hi 75 | #http GET hi 76 | curl localhost:8000/hi 77 | HELLO! 78 | ``` 79 | 80 | 81 | ## Add real time logging 82 | 83 | `level-static` extend the simple http example, 84 | so that we can track changes as they occur. 85 | 86 | ### changes http with changes feed. 87 | 88 | ``` js 89 | //examples/changes/index.js 90 | 91 | var static = require('level-static') 92 | var through = require('through') 93 | var route = require('tiny-route') 94 | var live = require('level-live-stream') 95 | var stack = require('stack') 96 | 97 | module.exports = function (db) { 98 | //level-static creates a http handling middleware around leveldb. 99 | db.on('http_connection', stack( 100 | route.get('/_changes', function (req, res) { 101 | live(db) 102 | .pipe(through(function (data) { 103 | this.queue(JSON.stringify(data) + '\n') 104 | })) 105 | .pipe(res) 106 | req.resume() 107 | }), 108 | static(db) 109 | )) 110 | } 111 | 112 | ``` 113 | 114 | ### start the server... 115 | 116 | ``` sh 117 | tacodb local ./index.js --name changes 118 | ``` 119 | 120 | Then, connect and stream changes like this: 121 | 122 | ``` sh 123 | curl localhost:8000/_changes 124 | ``` 125 | 126 | Then in another terminal: 127 | 128 | ``` sh 129 | echo 'Hi!' | curl -sSNT . localhost:8000/hi 130 | echo 'whats up?' | curl -sSNT . localhost:8000/wazzup 131 | echo 'Good Bye!' | curl -sSNT . localhost:8000/bye 132 | ``` 133 | 134 | 135 | ## connect to tacodb/level over websockets 136 | 137 | Create a streaming connection with websockets! 138 | 139 | ### server with websockets & multilevel: 140 | 141 | ``` js 142 | //examples/ws/server.js 143 | 144 | var multilevel = require('multilevel') 145 | var fs = require('fs') 146 | var index 147 | try { 148 | index = fs.readFileSync(__dirname + '/index.html','utf8') 149 | } catch (err) { 150 | console.error("run `npm run build` to generate the html file") 151 | throw err 152 | } 153 | module.exports = function (db) { 154 | db.on('http_connection', function (req, res) { 155 | req.resume() 156 | res.end(index) 157 | }) 158 | db.on('connection', function (stream) { 159 | console.log('connect') 160 | stream.pipe(multilevel.server(db)).pipe(stream) 161 | }) 162 | } 163 | 164 | ``` 165 | 166 | ### brower/node client with websockets 167 | 168 | ``` js 169 | //examples/ws/client.js 170 | 171 | 172 | var multilevel = require('multilevel') 173 | var reconnect = require('reconnect/sock') 174 | 175 | //This client works from both the browser and in node! 176 | //WebSockets everywhere! 177 | 178 | //use `npm run build` to generate the index.html file. 179 | 180 | var node = process.title != 'browser' 181 | 182 | var log = (node ? console.log : 183 | function () { 184 | var data = [].slice.call(arguments).map(function (e) { 185 | return JSON.stringify(e, null, 2) 186 | }).join(' ') 187 | var pre = document.createElement('pre') 188 | pre.innerText = data 189 | document.body.appendChild(pre) 190 | }) 191 | 192 | reconnect(function(stream) { 193 | log('connected!') 194 | var db = multilevel.client() 195 | stream.pipe(db).pipe(stream) 196 | 197 | setInterval(function () { 198 | db.put('hi', new Date(), function (err) { 199 | if(err) return console.error(err) 200 | db.get('hi', function (err, value) { 201 | if(err) return console.error(err) 202 | log('GET', 'hi', value) 203 | }) 204 | }) 205 | }, 1000) 206 | 207 | }).connect(node ? 'http://localhost:8000/ws/ws' : '/ws/ws') 208 | //^ on the browser, this assumes you are on the same host as window.location... 209 | 210 | ``` 211 | 212 | this client can be used from both the browser and node.js! 213 | 214 | ### running the server & client 215 | 216 | start the server 217 | ``` sh 218 | tacodb local server.js --name ws 219 | ``` 220 | 221 | connect from node: 222 | 223 | ``` sh 224 | node client.js 225 | ``` 226 | 227 | or from the [browser](http://localhost:8000/) 228 | 229 | 230 | 231 | ## License 232 | 233 | MIT 234 | -------------------------------------------------------------------------------- /_README.md: -------------------------------------------------------------------------------- 1 | # tacodb 2 | 3 | reusable leveldb server 4 | 5 | ## Synopsis 6 | 7 | tacodb wraps leveldbs in a server such that it would be possible 8 | to create a hosted service with it, that can be connected to via 9 | http and WebSockets. 10 | 11 | You database is configured by writing a .js file, which is then 12 | bundled (a la browserify) and sent to the tacodb server via http. 13 | 14 | {{{!cat ./docs/getting-started.md}}} 15 | {{{!cat ./examples/README.md}}} 16 | ## License 17 | 18 | MIT 19 | -------------------------------------------------------------------------------- /cmd.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | var http = require('http') 4 | var path = require('path') 5 | var cp = require('child_process') 6 | 7 | var shoe = require('shoe') 8 | var levelup = require('level') 9 | var request = require('request') 10 | var split = require('split') 11 | var through = require('through') 12 | 13 | var bundle = require('securify/bundle') 14 | var config = require('./config') 15 | 16 | var commands = { 17 | bundle: function (config, cb) { 18 | bundle(config._[0]) 19 | .pipe( 20 | config.o ? fs.createWriteStream(config.o) 21 | : process.stdout 22 | ) 23 | }, 24 | update: function (config, cb) { 25 | bundle( 26 | config.main || config._[0] || './index.js' 27 | , function (err, bundle) { 28 | if(err) return cb(err) 29 | request.put( 30 | config.master + '/data/' + config.name 31 | , function (err, res, body) { 32 | if(err) cb(err) 33 | else if(res.statusCode >= 300) 34 | cb(new Error(body || res.statusCode)) 35 | else 36 | cb() 37 | }).end(bundle) 38 | }) 39 | }, 40 | local: function (config, cb) { 41 | if(!config.name) { 42 | console.error('must provide --name NAME') 43 | process.exit(1) 44 | } 45 | var main = config.main || config._[0] || './index.js' 46 | var dir = path.join(config.root, config.name) 47 | var db = levelup(dir)//, {encoding: 'json'}) 48 | 49 | 50 | var setup = require(path.resolve(main)) 51 | setup(db) 52 | shoe(function (stream) { 53 | db.emit('connection', stream) 54 | }).install( 55 | http.createServer(function (req, res) { 56 | db.emit('http_connection', req, res) 57 | }).listen(config.port, function () { 58 | console.error('tacodb listening on:', config.port) 59 | }), '/ws/' + config.name 60 | ) 61 | }, 62 | start: function (config, cb) { 63 | require('./server')(config, function (err) { 64 | if(err) return cb(err) 65 | console.error('tacodb listening on:', config.port) 66 | cb() 67 | }) 68 | }, 69 | config: function (config, cb) { 70 | console.log(JSON.stringify(config, null, 2)) 71 | cb() 72 | }, 73 | log: function (config, cb) { 74 | 75 | request({ 76 | uri: config.master + '/log/' + config.name, 77 | qs: { 78 | min: config.since || config.min, 79 | max: config.until || config.max, 80 | tail: config.tail 81 | } 82 | }) 83 | .pipe(split(null, null, JSON.parse)) 84 | .pipe(through(console.log)) 85 | 86 | cb() 87 | }, 88 | //install tacodb plugin. 89 | install: function (config, cb) { 90 | var args = config._.slice() 91 | console.error('installing tacodb extension:', args.join(' ')) 92 | args.unshift('install') 93 | cp.spawn('npm', args, {stdio: 'inherit', cwd: __dirname}) 94 | .on('exit', cb) 95 | } 96 | } 97 | 98 | var command = config._.shift() 99 | 100 | function exec (cmd, cb) { 101 | if(!cmd) return cb() 102 | console.error('>', cmd) 103 | cp.exec(cmd, cb) 104 | } 105 | 106 | function run (command, cb) { 107 | exec(config.on && config.on['pre-' + command], function (err) { 108 | if(err) return cb(err) 109 | commands[command](config, function (err) { 110 | if(err) return cb(err) 111 | exec(config.on && config.on['post-' + command], function (err) { 112 | cb(err) 113 | }) 114 | }) 115 | }) 116 | } 117 | 118 | run(command, function (err) { 119 | if(err) throw err 120 | }) 121 | 122 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | var mkdirp = require('mkdirp') 2 | var path = require('path') 3 | var os = require('os') 4 | var config = module.exports = require('rc')('taco', { 5 | path: path.join(os.tmpdir(), 'taco-master'), 6 | root: path.join(os.tmpdir(), 'tacos'), 7 | port: 8000, 8 | master: 'http://localhost:8000' 9 | }) 10 | 11 | mkdirp.sync(config.root) 12 | 13 | -------------------------------------------------------------------------------- /db.js: -------------------------------------------------------------------------------- 1 | var levelup = require('level') 2 | var config = require('./config') 3 | var securify = require('securify') 4 | var bundle = require('securify/bundle') 5 | var path = require('path') 6 | var EventEmitter 7 | = require('events').EventEmitter 8 | 9 | // (update closing update* closed updated)* 10 | // update can be a new bundle, or stop. 11 | // a db must always asynchronously close before 12 | // a new bundle is activated. 13 | 14 | // how should I test this? 15 | // make a simple app, and update it. 16 | module.exports = 17 | function Db(id) { 18 | var db, _cb 19 | var state = 'ready' 20 | var bundle, current 21 | 22 | var domain 23 | 24 | var emitter = new EventEmitter() 25 | 26 | function close (cb) { 27 | var n = 1 28 | 29 | db.on('closed', function () { 30 | if(--n) return 31 | domain.dispose() 32 | cb() 33 | }) 34 | 35 | db.on('error', function (err) { 36 | if(--n) return 37 | domain.dispose() 38 | cb(err) 39 | }) 40 | 41 | db.close() 42 | } 43 | 44 | var events = ['console_log', 'console_error', 'error'] 45 | var db_events = ['ready', 'closed'] 46 | 47 | function reemit (source, events) { 48 | var removers = [] 49 | 50 | events.forEach(function (event) { 51 | function onEvent (value) { 52 | emitter.emit('log', event, value) 53 | } 54 | source.on(event, onEvent) 55 | removers.push(function () { 56 | source.removeListener(event, onEvent) 57 | }) 58 | }) 59 | return function () { 60 | removers.forEach(function (e) { e() }) 61 | } 62 | } 63 | 64 | function start (bundle, cb) { 65 | //EVENT: updated 66 | state = 'running' 67 | db = levelup(path.join(config.root, id)) 68 | domain = securify(bundle)(db) 69 | 70 | db.once('closed', reemit(domain, events)) 71 | db.once('closed', reemit(db, db_events)) 72 | 73 | emitter.db = db 74 | emitter.domain = domain 75 | cb && cb(null, db, domain) 76 | } 77 | 78 | emitter.update = function (_bundle, cb) { 79 | 80 | //EVENT: update 81 | bundle = _bundle 82 | if(state == 'ready') { 83 | start(_bundle, cb) 84 | } 85 | else if (state == 'running') { 86 | //EVENT: closing 87 | state = 'closing' 88 | close(function (err) { 89 | if(err) return cb(err) 90 | state = 'ready' 91 | start(bundle, cb) 92 | }) 93 | } 94 | } 95 | 96 | return emitter 97 | } 98 | -------------------------------------------------------------------------------- /deploy/tacodb-service-manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 32 | 33 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /docs/commands.md: -------------------------------------------------------------------------------- 1 | ## Commands 2 | 3 | All command line options may also be set in a 4 | `.tacorc` file. [rc](https://github.com/dominictarr/rc) is used 5 | for configuration. 6 | 7 | All of the options in the following examples are the default options. 8 | 9 | ### tacodb start 10 | 11 | Start a tacodb server. 12 | use `tacodb update` command to create databases in it. 13 | 14 | ``` 15 | tacodb local --port 8000 \ 16 | --root /tmp/tacos \ 17 | --path /tmp/taco-master 18 | ``` 19 | 20 | ### tacodb local 21 | 22 | Start a database instance locally. Useful for testing. 23 | 24 | ``` js 25 | tacodb local --port 8000 \ 26 | --root /tmp/tacos \ 27 | --name foo 28 | ``` 29 | 30 | ### tacodb update 31 | 32 | Update a database customization. The entry file is bundled, 33 | and then sent to the `tacodb` server via http. 34 | 35 | ``` js 36 | tacodb update --main entry.js \ 37 | --master http://localhost:8000 \ 38 | --name foo 39 | ``` 40 | 41 | ### tacodb config 42 | 43 | Dump tacodb config from current directory. 44 | (will load config searching for local `.tacorc`) 45 | and print all config as JSON. 46 | 47 | ### tacodb logs 48 | 49 | 50 | 51 | ``` js 52 | tacodb logs --name foo --tail --since now 53 | ``` 54 | 55 | ## SmartOS Deploy 56 | 57 | Running the following actions will enable tacodb as a service on SmartOS 58 | The default is port 8000 as defined in the ```exec_method``` tag. 59 | N.B Test on Standard64 1.0.7 with a downloaded install of node.js 0.10.10 60 | 61 | ``` 62 | > cd deploy 63 | > svccfg import tacodb-service-manifest.xml 64 | > svcadm enable tacodb-service 65 | ``` 66 | 67 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | 2 | ## what is tacodb based on? 3 | 4 | tacodb is based on leveldb, an open source database library, created by google. 5 | 6 | ## where else is leveldb used? 7 | 8 | leveldb was originally developed to be used in google-chrome to implement 9 | [indexedDb](https://developer.mozilla.org/en-US/docs/IndexedDB), it's 10 | also become popular as a back end for a number of database projects. 11 | [riak](http://basho.com/) and [HyperDex](http://hyperdex.org/) both use 12 | a fork of leveldb. 13 | 14 | ## installation 15 | 16 | ``` 17 | npm install -g tacodb 18 | ``` 19 | 20 | ## Why not just use mongo, couch or redis? 21 | 22 | tacodb is a responsive database, for responsive applications. 23 | 24 | instead of quering the database - you effectively tell the 25 | database what you are interested in, and then the database 26 | tells you when that changes. 27 | 28 | If you are building an application about the real-time interactions of humans, 29 | then tacodb/level is perfect. 30 | 31 | ## why implement a database in node.js? 32 | 33 | node.js is a performant framework for implementing servers, 34 | it combines this tight performant core with a vibrant ecosystem of modules 35 | installed via `npm`, it's package manager. 36 | 37 | javascript allows the community to rapidly iterate on new features, 38 | and also dramatically lower the barrier to entry to work on database stuff. 39 | 40 | ## how do i X? 41 | 42 | The core feature set of tacodb/level is very simple, 43 | if you want something complex, the first thing you do is 44 | install a plugin. many are available! most node.js level plugins are 45 | named `level-SOMETHING`, thus the node.js ecosystem is known as "level-*" 46 | 47 | [search level-* modules](https://npmjs.org/search?q=level-&page=0) 48 | 49 | ## how can I get involved with level-* 50 | 51 | join the irc room, ##leveldb on irc.freenode.net. 52 | 53 | 54 | -------------------------------------------------------------------------------- /docs/features.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 |
Feature\Databasetacodb/levelmongocouchredisMySql
key-value yes yes yes yes no
Range Queries yes yes pouch yes yes 27 |
Update Notifications? yes no _changes feed pub/sub triggers
Internal Format binary/configurable BSON JSON binary/text typed
Runs on Web? yes no yes no no
Runs on Mobile? on phonegap & mobile browser no pouch no no
Schema/Validation via plugin no yes no yes
RESTful/Http Interface via plugin no yes no no
Streaming/WebSockets yes no no no no
Graph/Recursive Queries via plugin no no no no
Full Text Index via plugin yes no no yes
Pluggable? Yes! no no no no
master-slave replication via plugin yes no yes yes
master-master replication via plugin no yes no yes
125 | 126 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | ## Getting Started 2 | 3 | start by installing `tacodb` 4 | 5 | ``` js 6 | npm install -g tacodb 7 | ``` 8 | 9 | Then, create an customization file 10 | 11 | ``` js 12 | //db.js 13 | //creat a static http style interface to leveldb. 14 | var static = require('level-static') 15 | module.exports = function (db) { 16 | db.on('http_connection', static(db)) 17 | } 18 | ``` 19 | 20 | start the server locally. 21 | 22 | ``` js 23 | tacodb local db.js --port 8000 24 | ``` 25 | 26 | ``` js 27 | echo hello | curl -sSNT . -X PUT http://localhost:8000/greeting 28 | curl http://localhost:8000/greeting 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/internal.md: -------------------------------------------------------------------------------- 1 | ## API 2 | 3 | expose your app with `module.exports` 4 | 5 | ``` js 6 | module.exports = function (db) { 7 | db.on('connection', function (ws) { 8 | //handle websocket stream 9 | }) 10 | db.on('http_connection', function (req, res) { 11 | //handle http request 12 | }) 13 | } 14 | ``` 15 | 16 | `db` is a [levelup](https://github.com/rvagg/node-levelup) instance. 17 | 18 | ### on("connection", function (stream){...}) 19 | 20 | emitted on a websocket connection to tacodb. 21 | 22 | ### on("http_connection", function (req, res) {...}) 23 | 24 | emitted on a http connection to tacodb. 25 | 26 | ## Routes 27 | 28 | When `tacodb` is run globally, with `tacodb start` 29 | each service is prefixed with /http/NAME/ 30 | 31 | When `tacodb` is run locally, with `tacodb local file --name foo` 32 | then routes are unprefixed. 33 | 34 | 35 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | ## connecting to tacodb/level via http 2 | 3 | Create a database customization file... 4 | This will expose an http interface, 5 | that can store files inside leveldb. 6 | 7 | ### simple http access 8 | 9 | ``` js 10 | //examples/http/index.js 11 | 12 | var static = require('level-static') 13 | 14 | module.exports = function (db) { 15 | db.on('http_connection', static(db)) 16 | } 17 | 18 | ``` 19 | 20 | ``` 21 | >tacodb local static.js --name static 22 | listening on 8000 23 | ``` 24 | 25 | This starts a tacodb server running around your `db.js` file. 26 | 27 | ``` sh 28 | #http PUT hi = HELLO 29 | echo 'HELLO!' | curl -sSNT . localhost:8000/hi 30 | #http GET hi 31 | curl localhost:8000/hi 32 | HELLO! 33 | ``` 34 | 35 | 36 | ## Add real time logging 37 | 38 | `level-static` extend the simple http example, 39 | so that we can track changes as they occur. 40 | 41 | ### changes http with changes feed. 42 | 43 | ``` js 44 | //examples/changes/index.js 45 | 46 | var static = require('level-static') 47 | var through = require('through') 48 | var route = require('tiny-route') 49 | var live = require('level-live-stream') 50 | var stack = require('stack') 51 | 52 | module.exports = function (db) { 53 | //level-static creates a http handling middleware around leveldb. 54 | db.on('http_connection', stack( 55 | route.get('/_changes', function (req, res) { 56 | live(db) 57 | .pipe(through(function (data) { 58 | this.queue(JSON.stringify(data) + '\n') 59 | })) 60 | .pipe(res) 61 | req.resume() 62 | }), 63 | static(db) 64 | )) 65 | } 66 | 67 | ``` 68 | 69 | ### start the server... 70 | 71 | ``` sh 72 | tacodb local ./index.js --name changes 73 | ``` 74 | 75 | Then, connect and stream changes like this: 76 | 77 | ``` sh 78 | curl localhost:8000/_changes 79 | ``` 80 | 81 | Then in another terminal: 82 | 83 | ``` sh 84 | echo 'Hi!' | curl -sSNT . localhost:8000/hi 85 | echo 'whats up?' | curl -sSNT . localhost:8000/wazzup 86 | echo 'Good Bye!' | curl -sSNT . localhost:8000/bye 87 | ``` 88 | 89 | 90 | ## connect to tacodb/level over websockets 91 | 92 | Create a streaming connection with websockets! 93 | 94 | ### server with websockets & multilevel: 95 | 96 | ``` js 97 | //examples/ws/server.js 98 | 99 | var multilevel = require('multilevel') 100 | var fs = require('fs') 101 | var index 102 | try { 103 | index = fs.readFileSync(__dirname + '/index.html','utf8') 104 | } catch (err) { 105 | console.error("run `npm run build` to generate the html file") 106 | throw err 107 | } 108 | module.exports = function (db) { 109 | db.on('http_connection', function (req, res) { 110 | req.resume() 111 | res.end(index) 112 | }) 113 | db.on('connection', function (stream) { 114 | console.log('connect') 115 | stream.pipe(multilevel.server(db)).pipe(stream) 116 | }) 117 | } 118 | 119 | ``` 120 | 121 | ### browser/node client with websockets 122 | 123 | ``` js 124 | //examples/ws/client.js 125 | 126 | 127 | var multilevel = require('multilevel') 128 | var reconnect = require('reconnect/sock') 129 | 130 | //This client works from both the browser and in node! 131 | //WebSockets everywhere! 132 | 133 | //use `npm run build` to generate the index.html file. 134 | 135 | var node = process.title != 'browser' 136 | 137 | var log = (node ? console.log : 138 | function () { 139 | var data = [].slice.call(arguments).map(function (e) { 140 | return JSON.stringify(e, null, 2) 141 | }).join(' ') 142 | var pre = document.createElement('pre') 143 | pre.innerText = data 144 | document.body.appendChild(pre) 145 | }) 146 | 147 | reconnect(function(stream) { 148 | log('connected!') 149 | var db = multilevel.client() 150 | stream.pipe(db).pipe(stream) 151 | 152 | setInterval(function () { 153 | db.put('hi', new Date(), function (err) { 154 | if(err) return console.error(err) 155 | db.get('hi', function (err, value) { 156 | if(err) return console.error(err) 157 | log('GET', 'hi', value) 158 | }) 159 | }) 160 | }, 1000) 161 | 162 | }).connect(node ? 'http://localhost:8000/ws/ws' : '/ws/ws') 163 | //^ on the browser, this assumes you are on the same host as window.location... 164 | 165 | ``` 166 | 167 | this client can be used from both the browser and node.js! 168 | 169 | ### running the server & client 170 | 171 | start the server 172 | ``` sh 173 | tacodb local server.js --name ws 174 | ``` 175 | 176 | connect from node: 177 | 178 | ``` sh 179 | node client.js 180 | ``` 181 | 182 | or from the [browser](http://localhost:8000/) 183 | 184 | 185 | -------------------------------------------------------------------------------- /examples/build.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | { 4 | for r in http changes ws; do 5 | pushd $r > /dev/null 6 | test -f README.md && carpenter README.md 7 | popd > /dev/null 8 | done 9 | } > README.md 10 | -------------------------------------------------------------------------------- /examples/changes/README.md: -------------------------------------------------------------------------------- 1 | ## Add real time logging 2 | 3 | `level-static` extend the simple http example, 4 | so that we can track changes as they occur. 5 | 6 | ### changes http with changes feed. 7 | 8 | ``` js 9 | //examples/changes/index.js 10 | 11 | {{{!cat index.js}}} 12 | ``` 13 | 14 | ### start the server... 15 | 16 | ``` sh 17 | tacodb local ./index.js --name changes 18 | ``` 19 | 20 | Then, connect and stream changes like this: 21 | 22 | ``` sh 23 | curl localhost:8000/_changes 24 | ``` 25 | 26 | Then in another terminal: 27 | 28 | ``` sh 29 | echo 'Hi!' | curl -sSNT . localhost:8000/hi 30 | echo 'whats up?' | curl -sSNT . localhost:8000/wazzup 31 | echo 'Good Bye!' | curl -sSNT . localhost:8000/bye 32 | ``` 33 | 34 | 35 | -------------------------------------------------------------------------------- /examples/changes/index.js: -------------------------------------------------------------------------------- 1 | var static = require('level-static') 2 | var through = require('through') 3 | var route = require('tiny-route') 4 | var live = require('level-live-stream') 5 | var stack = require('stack') 6 | 7 | module.exports = function (db) { 8 | //level-static creates a http handling middleware around leveldb. 9 | db.on('http_connection', stack( 10 | route.get('/_changes', function (req, res) { 11 | live(db) 12 | .pipe(through(function (data) { 13 | this.queue(JSON.stringify(data) + '\n') 14 | })) 15 | .pipe(res) 16 | req.resume() 17 | }), 18 | static(db) 19 | )) 20 | } 21 | -------------------------------------------------------------------------------- /examples/changes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http", 3 | "version": "0.0.0", 4 | "main": "index.js", 5 | "dependencies": { 6 | "level-static": "~1.0.7", 7 | "stack": "~0.1.0", 8 | "through": "~2.3.4", 9 | "level-live-stream": "~1.4.4", 10 | "tiny-route": "~1.1.0" 11 | }, 12 | "license": "MIT" 13 | } 14 | -------------------------------------------------------------------------------- /examples/db.js: -------------------------------------------------------------------------------- 1 | var sublevel = require('level-sublevel') 2 | var MapReduce = require('map-reduce') 3 | var multilevel = require('multilevel') 4 | var static = require('level-static') 5 | 6 | return module.exports = function (db) { 7 | 8 | db.options.valueEncoding = 'json' 9 | 10 | sublevel(db) 11 | console.log('STARTED') 12 | 13 | db.on('connection', function (stream) { 14 | console.error('CONNECTION', stream) 15 | }) 16 | 17 | db.on('http_connection', static(db.sublevel('static'))) 18 | 19 | MapReduce(db, 'map', function (key, value, emit) { 20 | emit(typeof value, 1) 21 | }, function (acc, item) { 22 | return Number(acc || 0) + Number(acc || 1) 23 | }) 24 | } 25 | 26 | -------------------------------------------------------------------------------- /examples/db2.js: -------------------------------------------------------------------------------- 1 | var sublevel = require('level-sublevel') 2 | var MapReduce = require('map-reduce') 3 | var multilevel = require('multilevel') 4 | 5 | return module.exports = function (db) { 6 | 7 | db.options.valueEncoding = 'json' 8 | 9 | //map-reduce depends on sublevel support being added to the database. 10 | sublevel(db) 11 | 12 | MapReduce(db, 'map', function (key, value, emit) { 13 | emit(typeof value, 1) 14 | }, function (acc, item) { 15 | return Number(acc || 0) + Number(acc || 1) 16 | }) 17 | 18 | db.on('connect', function (stream) { 19 | stream.pipe(multilevel.server(db)).pipe(stream) 20 | }) 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /examples/http/README.md: -------------------------------------------------------------------------------- 1 | ## connecting to tacodb/level via http 2 | 3 | Create a database customization file... 4 | This will expose an http interface, 5 | that can store files inside leveldb. 6 | 7 | ### simple http access 8 | 9 | ``` js 10 | //examples/http/index.js 11 | 12 | {{{!cat index.js}}} 13 | ``` 14 | 15 | ``` 16 | >tacodb local static.js --name static 17 | listening on 8000 18 | ``` 19 | 20 | This starts a tacodb server running around your `db.js` file. 21 | 22 | ``` sh 23 | #http PUT hi = HELLO 24 | echo 'HELLO!' | curl -sSNT . localhost:8000/hi 25 | #http GET hi 26 | curl localhost:8000/hi 27 | HELLO! 28 | ``` 29 | 30 | 31 | -------------------------------------------------------------------------------- /examples/http/db.js: -------------------------------------------------------------------------------- 1 | var sublevel = require('level-sublevel') 2 | var MapReduce = require('map-reduce') 3 | var multilevel = require('multilevel') 4 | var static = require('level-static') 5 | 6 | return module.exports = function (db) { 7 | 8 | db.options.valueEncoding = 'json' 9 | 10 | sublevel(db) 11 | console.log('STARTED') 12 | 13 | db.on('connection', function (stream) { 14 | console.error('CONNECTION', stream) 15 | }) 16 | 17 | db.on('http_connection', static(db.sublevel('static'))) 18 | 19 | MapReduce(db, 'map', function (key, value, emit) { 20 | emit(typeof value, 1) 21 | }, function (acc, item) { 22 | return Number(acc || 0) + Number(acc || 1) 23 | }) 24 | } 25 | 26 | -------------------------------------------------------------------------------- /examples/http/index.js: -------------------------------------------------------------------------------- 1 | var static = require('level-static') 2 | 3 | module.exports = function (db) { 4 | db.on('http_connection', static(db)) 5 | } 6 | -------------------------------------------------------------------------------- /examples/http/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http", 3 | "version": "0.0.0", 4 | "main": "index.js", 5 | "dependencies": { 6 | "level-static": "~1.0.7" 7 | }, 8 | "license": "MIT" 9 | } 10 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "map-reduce": "~3.2.3", 6 | "level-static": "~1.0.7", 7 | "multilevel": "~4.0.2", 8 | "stack": "~0.1.0", 9 | "through": "~2.3.4", 10 | "level-live-stream": "~1.4.3" 11 | }, 12 | "devDependencies": {}, 13 | "author": "Dominic Tarr (http://dominictarr.com)", 14 | "license": "MIT" 15 | } 16 | -------------------------------------------------------------------------------- /examples/ws/README.md: -------------------------------------------------------------------------------- 1 | ## connect to tacodb/level over websockets 2 | 3 | Create a streaming connection with websockets! 4 | 5 | ### server with websockets & multilevel: 6 | 7 | ``` js 8 | //examples/ws/server.js 9 | 10 | {{{!cat ./server.js}}} 11 | ``` 12 | 13 | ### brower/node client with websockets 14 | 15 | ``` js 16 | //examples/ws/client.js 17 | 18 | {{{!cat ./client.js}}} 19 | ``` 20 | 21 | this client can be used from both the browser and node.js! 22 | 23 | ### running the server & client 24 | 25 | start the server 26 | ``` sh 27 | tacodb local server.js --name ws 28 | ``` 29 | 30 | connect from node: 31 | 32 | ``` sh 33 | node client.js 34 | ``` 35 | 36 | or from the [browser](http://localhost:8000/) 37 | 38 | 39 | -------------------------------------------------------------------------------- /examples/ws/client.js: -------------------------------------------------------------------------------- 1 | 2 | var multilevel = require('multilevel') 3 | var reconnect = require('reconnect/sock') 4 | 5 | //This client works from both the browser and in node! 6 | //WebSockets everywhere! 7 | 8 | //use `npm run build` to generate the index.html file. 9 | 10 | var node = process.title != 'browser' 11 | 12 | var log = (node ? console.log : 13 | function () { 14 | var data = [].slice.call(arguments).map(function (e) { 15 | return JSON.stringify(e, null, 2) 16 | }).join(' ') 17 | var pre = document.createElement('pre') 18 | pre.innerText = data 19 | document.body.appendChild(pre) 20 | }) 21 | 22 | reconnect(function(stream) { 23 | log('connected!') 24 | var db = multilevel.client() 25 | stream.pipe(db).pipe(stream) 26 | 27 | setInterval(function () { 28 | db.put('hi', new Date(), function (err) { 29 | if(err) return console.error(err) 30 | db.get('hi', function (err, value) { 31 | if(err) return console.error(err) 32 | log('GET', 'hi', value) 33 | }) 34 | }) 35 | }, 1000) 36 | 37 | }).connect(node ? 'http://localhost:8000/ws/ws' : '/ws/ws') 38 | //^ on the browser, this assumes you are on the same host as window.location... 39 | -------------------------------------------------------------------------------- /examples/ws/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ws", 3 | "version": "0.0.0", 4 | "description": "connect to tacodb via websockets", 5 | "main": "client.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "browserify client.js --debug | indexhtmlify > index.html" 9 | }, 10 | "repository": "", 11 | "author": "", 12 | "license": "BSD", 13 | "devDependencies": { 14 | "indexhtmlify": "~1.0.0", 15 | "browserify": "~2.18.1" 16 | }, 17 | "dependencies": { 18 | "reconnect": "~1.4" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/ws/server.js: -------------------------------------------------------------------------------- 1 | var multilevel = require('multilevel') 2 | var fs = require('fs') 3 | var index 4 | try { 5 | index = fs.readFileSync(__dirname + '/index.html','utf8') 6 | } catch (err) { 7 | console.error("run `npm run build` to generate the html file") 8 | throw err 9 | } 10 | module.exports = function (db) { 11 | db.on('http_connection', function (req, res) { 12 | req.resume() 13 | res.end(index) 14 | }) 15 | db.on('connection', function (stream) { 16 | console.log('connect') 17 | stream.pipe(multilevel.server(db)).pipe(stream) 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dominictarr/tacodb/7512cd84893a070b2df170f3fe903ea25f309a3e/favicon.ico -------------------------------------------------------------------------------- /intro.md: -------------------------------------------------------------------------------- 1 | 2 | # TacoDb 3 | 4 | ## install client 5 | 6 | ``` 7 | npm install -g tacodb 8 | echo '{ "master": "http://taco-db.com:8000" }' > ~/.tacorc 9 | ``` 10 | 11 | ## write app 12 | 13 | Write a tacodb "app". 14 | 15 | ``` js 16 | //index.js 17 | var static = require('level-static') 18 | 19 | module.exports = function (db) { 20 | db.on('http_connection', static(db) 21 | } 22 | ``` 23 | 24 | ## deploy it 25 | 26 | ``` 27 | tacodb update index.js --name foo 28 | 29 | echo WORLD | curl -sSnT . -X PUT http://tacodb.org/http/foo/hello 30 | curl http://tacodb.org/http/foo/hello 31 | ``` 32 | -------------------------------------------------------------------------------- /master-db.js: -------------------------------------------------------------------------------- 1 | 2 | var levelup = require('level') 3 | var sublevel = require('level-sublevel') 4 | var config = require('./config') 5 | var securify = require('securify') 6 | 7 | var DB = require('./db') 8 | 9 | var db = sublevel(levelup(config.path)) 10 | var dbs = {} 11 | 12 | module.exports = db 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tacodb", 3 | "description": "leveldb inside a service", 4 | "version": "1.0.16", 5 | "homepage": "https://github.com/dominictarr/tacodb", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/dominictarr/tacodb.git" 9 | }, 10 | "dependencies": { 11 | "shoe": "0.0.10", 12 | "stack": "~0.1.0", 13 | "level-static": "~1.0.8", 14 | "stream-to-pull-stream": "1.2", 15 | "request": "~2.21.0", 16 | "securify": ">=1.1.8 <2", 17 | "mkdirp": "~0.3.5", 18 | "rc": "~0.3.0", 19 | "level": ">=0.8 <1", 20 | "level-sublevel": ">=4.6 <5", 21 | "pull-level": ">=1.1 <2", 22 | "level-live-stream": "~1.4.3", 23 | "through": "~2.3.4", 24 | "date.js": "~0.1.1", 25 | "split": "~0.2.5", 26 | "monotonic-timestamp": "0.0.8", 27 | "tiny-route": "~2.0.0", 28 | "pull-stream": "~2.18.2", 29 | "pull-glob": "~1.0.0", 30 | "ecstatic": "~0.4.5" 31 | }, 32 | "bin": "./cmd.js", 33 | "devDependencies": { 34 | "tape": "~1.0.2", 35 | "pull-level": "~1.1.3" 36 | }, 37 | "scripts": { 38 | "test": "set -e; for t in test/*.js; do node $t; done" 39 | }, 40 | "author": "Dominic Tarr (http://dominictarr.com)", 41 | "license": "MIT" 42 | } 43 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var http = require('http') 2 | var url = require('url') 3 | var qs = require('querystring') 4 | var fs = require('fs') 5 | var path = require('path') 6 | 7 | var shoe = require('shoe') 8 | var stack = require('stack') 9 | var timestamp = require('monotonic-timestamp') 10 | 11 | var levelup = require('level') 12 | var sublevel = require('level-sublevel') 13 | var static = require('level-static') 14 | 15 | var pull = require('pull-stream') 16 | var toPull = require('stream-to-pull-stream') 17 | var pl = require('pull-level') 18 | var LiveStream = require('level-live-stream') 19 | var through = require('through') 20 | var route = require('tiny-route') 21 | var ecstatic = require('ecstatic') 22 | 23 | var master = require('./master-db') 24 | var createDb = require('./db') 25 | 26 | var prefix = '/ws/([\\w-\\d]+)' 27 | var rxWs = new RegExp('^'+prefix) 28 | var rxHttp = /^\/http\/([\w-\d]+)/ 29 | 30 | var dbs = {} 31 | var servers = {} 32 | 33 | var intro = fs.readFileSync(path.join(__dirname, 'intro.md')) 34 | var end = http.OutgoingMessage.prototype.end 35 | http.OutgoingMessage.prototype.end = function (data, enc) { 36 | if(data) { 37 | console.error('end:', data.constructor.name, data.length) 38 | } 39 | end.call(this, data, enc) 40 | } 41 | 42 | function applyPrefix(url, handler) { 43 | return function (req, res, next) { 44 | if(url !== req.url.substring(0, url.length)) 45 | return next() 46 | req.url = req.url.substring(url.length) 47 | if(!req.url) req.url = '/' 48 | handler(req, res, next) 49 | } 50 | } 51 | 52 | function getId(rx, string) { 53 | var id 54 | var m = rx.exec(string) 55 | if(m) id = m[1] 56 | return id && dbs[id] ? id : null 57 | } 58 | 59 | var bundles = master.sublevel('bundles') 60 | var logs = master.sublevel('logs') 61 | var logStream = logs.createWriteStream() 62 | 63 | function log(req, res, next) { 64 | console.error( 65 | req.method, req.url, Date.now() 66 | ) 67 | next() 68 | } 69 | 70 | function tail(name, opts) { 71 | opts.min = name + '\x00' + (opts.min || opts.since || Date.now()) 72 | 73 | for(var k in opts) 74 | if(opts[k] === 'false') opts[k] = false 75 | 76 | opts.max = name + '\x00\xff' 77 | 78 | return LiveStream(logs, opts) 79 | .on('data', function (data) { 80 | try { data.value = JSON.parse(data.value) } catch (_) {} 81 | }) 82 | } 83 | 84 | module.exports = function (config, cb) { 85 | var server 86 | shoe(function (stream) { 87 | var id = getId(rxWs, stream.pathname) 88 | if(!id || !dbs[id].db.listeners('connection').length) { 89 | //abort this stream 90 | stream.end() 91 | } else { 92 | //connect to the db... 93 | dbs[id].db.emit('connection', stream) 94 | } 95 | }).install( 96 | server = http.createServer(stack( 97 | //if POST /master/USER 98 | // update this database. 99 | // ... hmm, this is a value. 100 | // when the value updates, 101 | // update server. 102 | //... but we need an up and a down update... 103 | //which will be async... 104 | log, 105 | function (req, res, next) { 106 | var u = url.parse(req.url) 107 | req.opts = qs.parse(u.query) 108 | req.pathname = u.pathname 109 | next() 110 | }, 111 | applyPrefix('/log', function (req, res, next) { 112 | var m = /\/([\w-\d]+)/.exec(req.url) 113 | var name = m && m[1] 114 | if(!dbs[name]) 115 | return next(new Error('no database:'+name)) 116 | req.resume() 117 | 118 | tail(name, req.opts) 119 | .pipe(through(function (data) { 120 | this.queue(JSON.stringify(data) + '\n') 121 | })).pipe(res) 122 | }), 123 | applyPrefix('/data', static(bundles)), //TODO: add auth! 124 | route(rxHttp, function (req, res, next) { 125 | var id = req.params[0] 126 | 127 | if(!req.url) { 128 | req.resume() 129 | res.writeHead(302, {Location: '/http/' + id + '/'}) 130 | return res.end() 131 | } 132 | 133 | if(!id || !dbs[id]) 134 | return next(new Error('no service at '+id)) 135 | 136 | if(!dbs[id].db.listeners('http_connection').length) //404 137 | return next(new Error('no handler for "http_connection"')) 138 | 139 | dbs[id].db.emit('http_connection', req, res) 140 | }), 141 | ecstatic(__dirname + '/site'), 142 | function (error, req, res, next) { 143 | res.writeHead(error.status || 404) 144 | res.end(JSON.stringify({ 145 | error: true, 146 | message: err.message, 147 | code: err.code 148 | }, null, 2)) 149 | } 150 | )).listen(config.port, function () { 151 | if(cb) cb(null, server) 152 | }) 153 | , prefix) 154 | 155 | // instead of creating a new db on each update 156 | // just tail the database, and update dbs that way! 157 | // this essentially just syncronizes the state of the dbs 158 | // with the state of the dbs. 159 | // 160 | // SO Simple! 161 | 162 | pl.read(bundles, {tail: true}) 163 | .pipe(pull.drain(function (data) { 164 | var key = data.key 165 | var bundle = data.value 166 | 167 | if(bundle) { 168 | var db = dbs[key] 169 | 170 | if(!db) { 171 | db = dbs[key] = createDb(key) 172 | db.on('log', function (event, value) { 173 | if(config.log) console.error(event, value) 174 | logStream.write({ 175 | key: key + '\x00' + timestamp(), 176 | value: JSON.stringify({event: event, value: value}) 177 | }) 178 | }) 179 | } 180 | 181 | db.update(bundle) 182 | } else { 183 | var db = dbs[key] 184 | if(db) db.close() 185 | } 186 | })) 187 | 188 | return server 189 | } 190 | 191 | if(!module.parent) { 192 | var config = require('./config') 193 | module.exports(config, function (_, server) { 194 | console.log('listening on', config.port) 195 | }) 196 | } 197 | 198 | -------------------------------------------------------------------------------- /site/_index.htmt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 |
    13 |
  1. about
  2. 14 |
  3. FAQ
  4. 15 |
  5. features
  6. 16 |
  7. examples
  8. 17 |
  9. installation
  10. 18 |
19 | 20 |
21 | {{{!himark ./about.md}}} 22 |
23 | 24 |
25 | {{{!himark ../docs/faq.md}}} 26 |
27 | 28 |
29 | {{{!himark ../docs/features.md}}} 30 |
31 | 32 |
33 | {{{!himark ../examples/README.md}}} 34 |
35 | 36 |
37 | {{{!himark ./install.md}}} 38 |
39 | 40 | 41 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /site/about.md: -------------------------------------------------------------------------------- 1 | 2 | ## About tacodb 3 | 4 | tacodb is a reusable server that wraps leveldb, 5 | tacodb/level represents a radical new approach to databases: 6 | 7 | * A responsive database for responsive applications, core features strongly suite realtime. 8 | * You build your own database by assembling community developed plugins, installed via [npm](https://npmjs.org). 9 | * Since each feature is a plugin, new features are rapidly developed and integrated. 10 | * since all features are implemented it javascript - level can be used in the _web browser_, 11 | with [level.js](https://github.com/maxogden/level.js) 12 | 13 | 14 | -------------------------------------------------------------------------------- /site/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dominictarr/tacodb/7512cd84893a070b2df170f3fe903ea25f309a3e/site/favicon.ico -------------------------------------------------------------------------------- /site/img/background2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dominictarr/tacodb/7512cd84893a070b2df170f3fe903ea25f309a3e/site/img/background2.jpg -------------------------------------------------------------------------------- /site/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dominictarr/tacodb/7512cd84893a070b2df170f3fe903ea25f309a3e/site/img/favicon.ico -------------------------------------------------------------------------------- /site/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dominictarr/tacodb/7512cd84893a070b2df170f3fe903ea25f309a3e/site/img/favicon.png -------------------------------------------------------------------------------- /site/img/footer1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dominictarr/tacodb/7512cd84893a070b2df170f3fe903ea25f309a3e/site/img/footer1.png -------------------------------------------------------------------------------- /site/img/line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dominictarr/tacodb/7512cd84893a070b2df170f3fe903ea25f309a3e/site/img/line.png -------------------------------------------------------------------------------- /site/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dominictarr/tacodb/7512cd84893a070b2df170f3fe903ea25f309a3e/site/img/logo.png -------------------------------------------------------------------------------- /site/img/sombrero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dominictarr/tacodb/7512cd84893a070b2df170f3fe903ea25f309a3e/site/img/sombrero.png -------------------------------------------------------------------------------- /site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 284 | 288 | 289 | 290 | 291 | 292 | 300 | 301 |
302 |
303 | 304 |
305 |

about tacodb

306 |

tacodb is a reusable server that wraps leveldb, 307 | tacodb/level represents a radical new approach to databases:

308 |
    309 |
  • − A responsive database for responsive applications, core features strongly suite realtime.
  • 310 |
  • − You build your own database by assembling community developed plugins, installed via npm.
  • 311 |
  • − Since each feature is a plugin, new features are rapidly developed and integrated.
  • 312 |
  • − Since all features are implemented it javascript - level can be used in the web browser, 313 | with level.js
  • 314 |
315 | 316 | 317 |
318 | 319 |
320 |

what is tacodb based on?

321 |

tacodb is based on leveldb, an open source database library, created by google.

322 |

where else is leveldb used?

323 |

leveldb was originally developed to be used in google-chrome to implement 324 | indexedDb, it's 325 | also become popular as a back end for a number of database projects. 326 | riak and HyperDex both use 327 | a fork of leveldb.

328 |

installation

329 |
npm install -g tacodb
330 |

Why not just use mongo, couch or redis?

331 |

tacodb is a responsive database, for responsive applications.

332 |

instead of quering the database - you effectively tell the 333 | database what you are interested in, and then the database 334 | tells you when that changes.

335 |

If you are building an application about the real-time interactions of humans, 336 | then tacodb/level is perfect.

337 |

why implement a database in node.js?

338 |

node.js is a performant framework for implementing servers, 339 | it combines this tight performant core with a vibrant ecosystem of modules 340 | installed via npm, it's package manager.

341 |

javascript allows the community to rapidly iterate on new features, 342 | and also dramatically lower the barrier to entry to work on database stuff.

343 |

how do i X?

344 |

The core feature set of tacodb/level is very simple, 345 | if you want something complex, the first thing you do is 346 | install a plugin. many are available! most node.js level plugins are 347 | named level-SOMETHING, thus the node.js ecosystem is known as "level-*"

348 |

search level-* modules

349 |

how can I get involved with level-*

350 |

join the irc room, ##leveldb on irc.freenode.net.

351 | 352 |
353 | 354 |
355 |

features

356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 |
Feature\DatabaseTacodb/LevelMongoCouchRedisMySql
key-value yes yes yes yes no
Range Queries yes yes pouch yes yes
Update Notifications? yes no _changes feed pub/sub triggers
Internal Format binary/configurable BSON JSON binary/text typed
Runs on Web? yes no yes no no
Runs on Mobile? on phonegap & mobile browser no pouch no no
Schema/Validation via plugin no yes no yes
RESTful/Http Interface via plugin no yes no no
Streaming/WebSockets yes no no no no
Graph/Recursive Queries via plugin no no no no
Full Text Index via plugin yes no no yes
Pluggable? Yes! no no no no
master-slave replication via plugin yes no yes yes
master-master replication via plugin no yes no yes
480 | 481 | 482 | 483 |
484 | 485 |
486 |

connecting to tacodb/level via http

487 |

Create a database customization file... 488 | This will expose an http interface, 489 | that can store files inside leveldb.

490 |

simple http access

491 |
//examples/http/index.js
492 | 
493 | var static = require('level-static')
494 | 
495 | module.exports = function (db) {
496 |   db.on('http_connection', static(db))
497 | }
498 |
>tacodb local static.js --name static
499 | listening on 8000
500 |

This starts a tacodb server running around your db.js file.

501 |
#http PUT hi = HELLO
502 | echo 'HELLO!' | curl -sSNT . localhost:8000/hi
503 | #http GET hi
504 | curl localhost:8000/hi
505 | HELLO!
506 |

Add real time logging

507 |

level-static extend the simple http example, 508 | so that we can track changes as they occur.

509 |

changes http with changes feed.

510 |
//examples/changes/index.js
511 | 
512 | var static   = require('level-static')
513 | var through  = require('through')
514 | var route    = require('tiny-route')
515 | var live     = require('level-live-stream')
516 | var stack    = require('stack')
517 | 
518 | module.exports = function (db) {
519 |   //level-static creates a http handling middleware around leveldb.
520 |   db.on('http_connection', stack(
521 |     route.get('/_changes', function (req, res) {
522 |       live(db)
523 |         .pipe(through(function (data) {
524 |           this.queue(JSON.stringify(data) + '\n')
525 |         }))
526 |         .pipe(res)
527 |       req.resume()
528 |     }),
529 |     static(db)
530 |   ))
531 | }
532 |

start the server...

533 |
tacodb local ./index.js --name changes
534 |

Then, connect and stream changes like this:

535 |
curl localhost:8000/_changes
536 |

Then in another terminal:

537 |
echo 'Hi!' | curl -sSNT . localhost:8000/hi
538 | echo 'whats up?' | curl -sSNT . localhost:8000/wazzup
539 | echo 'Good Bye!' | curl -sSNT . localhost:8000/bye
540 |

connect to tacodb/level over websockets

541 |

Create a streaming connection with websockets!

542 |

server with websockets & multilevel:

543 |
//examples/ws/server.js
544 | 
545 | var multilevel = require('multilevel')
546 | var fs         = require('fs')
547 | var index
548 | try {
549 |   index = fs.readFileSync(__dirname + '/index.html','utf8')
550 | } catch (err) {
551 |   console.error("run `npm run build` to generate the html file")
552 |   throw err
553 | }
554 | module.exports = function (db) {
555 |   db.on('http_connection', function (req, res) {
556 |     req.resume()
557 |     res.end(index)
558 |   })
559 |   db.on('connection', function (stream) {
560 |     console.log('connect')
561 |     stream.pipe(multilevel.server(db)).pipe(stream)
562 |   })
563 | }
564 |

brower/node client with websockets

565 |
//examples/ws/client.js
566 | 
567 | 
568 | var multilevel = require('multilevel')
569 | var reconnect  = require('reconnect/sock')
570 | 
571 | //This client works from both the browser and in node!
572 | //WebSockets everywhere!
573 | 
574 | //use `npm run build` to generate the index.html file.
575 | 
576 | var node = process.title != 'browser'
577 | 
578 | var log = (node ? console.log :
579 |   function () {
580 |     var data = [].slice.call(arguments).map(function (e) {
581 |       return JSON.stringify(e, null, 2)
582 |     }).join(' ')
583 |     var pre = document.createElement('pre')
584 |     pre.innerText = data
585 |     document.body.appendChild(pre)
586 |   })
587 | 
588 | reconnect(function(stream) {
589 |   log('connected!')
590 |   var db = multilevel.client()
591 |   stream.pipe(db).pipe(stream)
592 | 
593 |   setInterval(function () {
594 |     db.put('hi', new Date(), function (err) {
595 |       if(err) return console.error(err)
596 |       db.get('hi', function (err, value) {
597 |         if(err) return console.error(err)
598 |         log('GET', 'hi', value)
599 |       })
600 |     })
601 |   }, 1000)
602 | 
603 | }).connect(node ? 'http://localhost:8000/ws/ws' : '/ws/ws')
604 | //^ on the browser, this assumes you are on the same host as window.location...
605 |

this client can be used from both the browser and node.js!

606 |

running the server & client

607 |

start the server

608 |
tacodb local server.js --name ws
609 |

connect from node:

610 |
node client.js
611 |

or from the browser

612 | 613 | 614 |
615 | 616 |
617 |

installation

618 |
npm install tacodb -g
619 |

That's all! Code is on github

620 | 621 | 622 |
623 | 624 | 625 | 626 | 649 | 650 | 651 | -------------------------------------------------------------------------------- /site/install.md: -------------------------------------------------------------------------------- 1 | 2 | ## `npm install tacodb -g` 3 | 4 | that's all! 5 | 6 | code is on [github](https://github.com/dominictarr/tacodb) 7 | -------------------------------------------------------------------------------- /site/style/style.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /* CSS Document */ 3 | 4 | 5 | h1 { 6 | font-family: 'Pacifico', cursive; 7 | color: #7cab6f; 8 | font-weight: normal; 9 | } 10 | 11 | h2 { 12 | font-family: 'Pacifico', cursive; 13 | color: #7cab6f; 14 | font-size: 36px; 15 | font-weight: normal; 16 | } 17 | 18 | h3 { 19 | font-family: 'Titillium Web', sans-serif; 20 | font-weight:700; 21 | color: #e4703e; 22 | font-size:22px; 23 | } 24 | 25 | pre { 26 | font-family: 'Titillium Web', sans-serif; 27 | } 28 | 29 | code { 30 | font-family: Arial, Helvetica, sans-serif; 31 | } 32 | 33 | p { 34 | font-family: 'Titillium Web', sans-serif; 35 | font-weight:600; 36 | color: #9f6640; 37 | font-size:20px; 38 | } 39 | 40 | a { 41 | font-family: 'Titillium Web', sans-serif; 42 | font-weight: 600; 43 | color: #9f6640; 44 | } 45 | 46 | body { 47 | background-color: #f6fff7; 48 | background-image:url(../img/background2.jpg); 49 | background-repeat: no-repeat; 50 | background-position: top center; 51 | margin: 0 auto; 52 | font-family: 'Titillium Web', sans-serif; 53 | color: #7b6648; 54 | font-size:18px; 55 | font-weight: 400; 56 | margin-bottom: 0px; 57 | padding-bottom: 0px; 58 | } 59 | 60 | 61 | 62 | #about ul { 63 | list-style-type: none; 64 | margin-left: 0; 65 | padding-left: 0; 66 | margin-top: 0px; 67 | } 68 | 69 | #about ul li { 70 | padding-top:3px; 71 | padding-bottom: 3px; 72 | font-size: 18px; 73 | } 74 | 75 | #sombrero { 76 | width: 960px; 77 | margin: auto; 78 | margin-top: 0px; 79 | margin-bottom: 0px; 80 | background-image:url(../img/sombrero.png); 81 | background-position: bottom right; 82 | background-repeat: no-repeat; 83 | height:200px; 84 | } 85 | 86 | .features { 87 | margin-top: 40px; 88 | } 89 | 90 | 91 | 92 | 93 | .tab { 94 | margin: auto; 95 | width: 960px; 96 | margin-top:-145px; 97 | min-height:500px; 98 | } 99 | 100 | .boxwidth { 101 | width:400px; 102 | padding-top:20px; 103 | padding-bottom: 20px; 104 | font-size: 20px; 105 | padding-left:10px; 106 | padding-right: 10px; 107 | } 108 | 109 | table { 110 | width: 960px; 111 | margin-top:40px; 112 | } 113 | 114 | th { 115 | text-align: left; 116 | border-bottom: 1px solid #bcbcbc; 117 | } 118 | 119 | td { 120 | text-align: left; 121 | border-bottom: 1px solid #bcbcbc; 122 | } 123 | 124 | 125 | .footer { 126 | background-image:url(../img/footer1.png); 127 | background-position: bottom center; 128 | background-repeat:no-repeat; 129 | height:223px; 130 | margin-bottom:0px; 131 | padding-bottom: 0px; 132 | border-bottom: 5px solid #775c4c; 133 | } 134 | 135 | ol.nav { 136 | width: 960px; 137 | margin: auto; 138 | list-style-type:none; 139 | margin-top:30px; 140 | height:112px; 141 | background-image:url(../img/line.png); 142 | background-repeat:no-repeat; 143 | background-position:bottom; 144 | } 145 | 146 | .nav li { 147 | display: inline; 148 | text-decoration: none; 149 | } 150 | 151 | .nav li a { 152 | text-transform: uppercase; 153 | text-decoration: none; 154 | font-size: 17px; 155 | color: #9f6640; 156 | padding-left:26px; 157 | padding-right: 34px; 158 | font-weight: 400; 159 | line-height:40px; 160 | } 161 | 162 | .nav li a.logoalignment { 163 | padding-left: 0px; 164 | padding-right: 0px; 165 | margin-bottom: -20px; 166 | width:200px; 167 | height:100px; 168 | padding-bottom: 0px; 169 | border: 0; 170 | } 171 | 172 | a img { 173 | border: 0; 174 | } 175 | 176 | .nav li a:hover { 177 | color: #e4703e; 178 | background-color:transparent; 179 | } 180 | 181 | .nav li a:active { 182 | color: #e4703e; 183 | background-color:transparent; 184 | } 185 | 186 | 187 | .active { 188 | color: #e4703e; 189 | } 190 | 191 | -------------------------------------------------------------------------------- /test/test-update-db.js: -------------------------------------------------------------------------------- 1 | 2 | var createDb = require('../db') 3 | 4 | var boxedDb = createDb('RANDOM'+~~(Math.random()*1000000)) 5 | var fs = require('fs') 6 | var bundle = require('securify/bundle') 7 | 8 | var pull = require('pull-stream') 9 | var pl = require('pull-level') 10 | 11 | var test = require('tape') 12 | 13 | process.on('uncaughtException', function (e) { 14 | console.error(e.stack) 15 | }) 16 | 17 | test('update', function (t) { 18 | 19 | bundle(__dirname+'/../examples/db.js', function (err, b) { 20 | bundle(__dirname+'/../examples/db2.js', function (err, b2) { 21 | console.log('BUNDLED') 22 | boxedDb.update(b, function (err, db, domain) { 23 | console.log('UPDATED') 24 | pull.values([ 25 | {key: 'a', value: 'apple'}, 26 | {key: 'b', value: 'banana'}, 27 | {key: 'c', value: 'cherry'}, 28 | {key: 'd', value: 'durian'}, 29 | {key: 'e', value: 'elder-berry'} 30 | ]).pipe(pl.write(db, function (err) { 31 | 32 | db.get('e', function (err, value) { 33 | boxedDb.update(b2, function (err, _db, domain) { 34 | 35 | pl.read(_db) 36 | .pipe(pull.reduce(function (a, e) { 37 | a[e.key] = e.value 38 | return a 39 | }, {}, function (err, all) { 40 | console.log(all) 41 | t.deepEqual(all, { a: 'apple', 42 | b: 'banana', 43 | c: 'cherry', 44 | d: 'durian', 45 | e: 'elder-berry' 46 | }) 47 | t.end() 48 | })) 49 | }) 50 | }) 51 | })) 52 | }) 53 | }) 54 | }) 55 | }) 56 | --------------------------------------------------------------------------------