├── .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 |
52 |
53 | node.js tacodb service
54 |
55 |
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 | Feature\Database
4 |
5 | tacodb/level
6 | mongo
7 | couch
8 | redis
9 | MySql
10 |
11 |
12 | key-value
13 |
14 | yes
15 | yes
16 | yes
17 | yes
18 | no
19 |
20 |
21 | Range Queries
22 | yes
23 | yes
24 | pouch
25 | yes
26 | yes
27 |
28 |
29 | Update Notifications?
30 | yes
31 | no
32 | _changes feed
33 | pub/sub
34 | triggers
35 |
36 |
37 | Internal Format
38 | binary/configurable
39 | BSON
40 | JSON
41 | binary/text
42 | typed
43 |
44 |
45 | Runs on Web?
46 | yes
47 | no
48 | yes
49 | no
50 | no
51 |
52 |
53 | Runs on Mobile?
54 | on phonegap & mobile browser
55 | no
56 | pouch
57 | no
58 | no
59 |
60 |
61 | Schema/Validation
62 | via plugin
63 | no
64 | yes
65 | no
66 | yes
67 |
68 |
69 | RESTful/Http Interface
70 | via plugin
71 | no
72 | yes
73 | no
74 | no
75 |
76 |
77 | Streaming/WebSockets
78 | yes
79 | no
80 | no
81 | no
82 | no
83 |
84 |
85 | Graph/Recursive Queries
86 | via plugin
87 | no
88 | no
89 | no
90 | no
91 |
92 |
93 | Full Text Index
94 | via plugin
95 | yes
96 | no
97 | no
98 | yes
99 |
100 |
101 | Pluggable?
102 | Yes!
103 | no
104 | no
105 | no
106 | no
107 |
108 |
109 | master-slave replication
110 | via plugin
111 | yes
112 | no
113 | yes
114 | yes
115 |
116 |
117 | master-master replication
118 | via plugin
119 | no
120 | yes
121 | no
122 | yes
123 |
124 |
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 | about
14 | FAQ
15 | features
16 | examples
17 | installation
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 |
293 | about
294 | FAQ
295 | features
296 |
297 | examples
298 | installation
299 |
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 | Feature\Database
359 |
360 | Tacodb/Level
361 | Mongo
362 | Couch
363 | Redis
364 | MySql
365 |
366 |
367 | key-value
368 |
369 | yes
370 | yes
371 | yes
372 | yes
373 | no
374 |
375 |
376 | Range Queries
377 | yes
378 | yes
379 | pouch
380 | yes
381 | yes
382 |
383 |
384 | Update Notifications?
385 | yes
386 | no
387 | _changes feed
388 | pub/sub
389 | triggers
390 |
391 |
392 | Internal Format
393 | binary/configurable
394 | BSON
395 | JSON
396 | binary/text
397 | typed
398 |
399 |
400 | Runs on Web?
401 | yes
402 | no
403 | yes
404 | no
405 | no
406 |
407 |
408 | Runs on Mobile?
409 | on phonegap & mobile browser
410 | no
411 | pouch
412 | no
413 | no
414 |
415 |
416 | Schema/Validation
417 | via plugin
418 | no
419 | yes
420 | no
421 | yes
422 |
423 |
424 | RESTful/Http Interface
425 | via plugin
426 | no
427 | yes
428 | no
429 | no
430 |
431 |
432 | Streaming/WebSockets
433 | yes
434 | no
435 | no
436 | no
437 | no
438 |
439 |
440 | Graph/Recursive Queries
441 | via plugin
442 | no
443 | no
444 | no
445 | no
446 |
447 |
448 | Full Text Index
449 | via plugin
450 | yes
451 | no
452 | no
453 | yes
454 |
455 |
456 | Pluggable?
457 | Yes!
458 | no
459 | no
460 | no
461 | no
462 |
463 |
464 | master-slave replication
465 | via plugin
466 | yes
467 | no
468 | yes
469 | yes
470 |
471 |
472 | master-master replication
473 | via plugin
474 | no
475 | yes
476 | no
477 | yes
478 |
479 |
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 |
--------------------------------------------------------------------------------