├── .gitignore ├── .travis.yml ├── README.md ├── bin └── multilevel-http.js ├── index.js ├── lib ├── client.js └── server.js ├── package.json └── test ├── client.test.js └── server.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test/*.db -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 4 | - "0.10" 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # multilevel-http 2 | 3 | *Access a leveldb instance from multiple processes via HTTP.* 4 | 5 | A limitation of LevelDB is that only one process is allowed access to the underlying data. **multilevel-http** exports a LevelDB instance over http. 6 | 7 | [![Build Status](https://travis-ci.org/juliangruber/multilevel-http.png)](https://travis-ci.org/juliangruber/multilevel-http) 8 | 9 | ## Installation 10 | 11 | ```bash 12 | npm install multilevel-http 13 | ``` 14 | 15 | ## API 16 | 17 | Server: 18 | 19 | ```js 20 | var multilevel = require('multilevel-http') 21 | // db = levelup instance or path to db 22 | var server = multilevel.server(db, options) 23 | server.listen(5000) 24 | ``` 25 | 26 | Client: 27 | 28 | ```js 29 | var multilevel = require('multilevel-http') 30 | var db = multilevel.client('http://localhost:5000/') 31 | // now you have the complete levelUP api! 32 | // ...except for events - for those consider multilevel and level-live-stream 33 | ``` 34 | 35 | ## CLI 36 | 37 | ```bash 38 | $ sudo npm install -g multilevel-http 39 | $ multilevel-http -p 5000 path/to.db 40 | ``` 41 | 42 | ## HTTP API 43 | 44 | ### Params 45 | 46 | Use get-params to pass options to LevelDB, like `?encoding=json` 47 | 48 | ### GET /meta 49 | 50 | Get meta information about the DB. 51 | 52 | ```js 53 | // GET /meta 54 | { 55 | "compression" : false, 56 | "cacheSize" : 8 * 1024 * 1024, 57 | "encoding" : 'utf8', 58 | "keyEncoding" : 'utf8', 59 | "valueEncoding" : 'utf8', 60 | "path" : path 61 | } 62 | ``` 63 | 64 | ### GET /data/:key 65 | 66 | Get the value stored at `:key`. 67 | 68 | ```js 69 | // GET /data/foo 70 | bar 71 | ``` 72 | 73 | ### POST /data/:key 74 | 75 | Store data at `:key`. 76 | 77 | ```js 78 | // POST /data/foo bar 79 | "ok" 80 | ``` 81 | 82 | ### DEL /data/:key 83 | 84 | Delete data stored at `:key`. 85 | 86 | ```js 87 | // DEL /data/foo 88 | "ok" 89 | ``` 90 | 91 | ### PUT /data 92 | 93 | Store many values batched. 94 | 95 | ```js 96 | /* PUT /data [ 97 | { key : 'bar', value : 'baz' }, 98 | { key : 'foo', value : 'bar' } 99 | ] */ 100 | ``` 101 | 102 | ### POST /data 103 | 104 | Do many operations batched. 105 | 106 | ```js 107 | /* PUT /data [ 108 | { type : 'put', key : 'bar', value : 'baz' }, 109 | { type : 'del', key : 'foo' } 110 | ] */ 111 | ``` 112 | 113 | ### GET /approximateSize/:from..:to 114 | 115 | Get an approximation of disk space used to store the data in the given range. 116 | 117 | ```js 118 | // GET /approximateSize/a..z 119 | 123 120 | ``` 121 | 122 | ### GET /data 123 | 124 | Get all the data. 125 | 126 | ```js 127 | // GET /data/12 128 | [ 129 | { key : 'bar', value : 'baz' }, 130 | { key : 'foo', value : 'bar' }, 131 | /* ... */ 132 | ] 133 | ``` 134 | 135 | ### GET /range/:from..:to 136 | 137 | Get all data in the given range. 138 | 139 | ```js 140 | // GET /range/a..c 141 | [ { key : 'bar', value : 'baz' } ] 142 | ``` 143 | 144 | ### GET /keys 145 | 146 | Get all the keys. 147 | 148 | ```js 149 | // GET /keys 150 | [ 'bar', 'foo' ] 151 | ``` 152 | 153 | ### GET /keys/:from..:to 154 | 155 | Get all the keys in the given range. 156 | 157 | ```js 158 | // GET /keys/a..c 159 | [ 'bar' ] 160 | ``` 161 | 162 | ### GET /values 163 | 164 | Get all the values. 165 | 166 | ```js 167 | // GET /values 168 | [ 'baz', 'bar' ] 169 | ``` 170 | 171 | ### GET /values/:from..:to 172 | 173 | Get all the values in the given range. 174 | 175 | ```js 176 | // GET /values/a..c 177 | [ 'baz' ] 178 | ``` 179 | 180 | ## Server API 181 | 182 | ```js 183 | // server 184 | var multilevel = require('multilevel-http')('my.db') 185 | multilevel.listen(3000) 186 | ``` 187 | 188 | ### multilevel.server(path|db, meta) 189 | 190 | ### server#listen(port) 191 | 192 | Start serving on the given port. 193 | 194 | ### server#{db,meta} 195 | 196 | The stored db and meta data exposed. 197 | 198 | ## License 199 | 200 | (MIT) 201 | 202 | Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> 203 | 204 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 205 | 206 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 207 | 208 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 209 | -------------------------------------------------------------------------------- /bin/multilevel-http.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var multilevel = require('..') 4 | var optimist = require('optimist') 5 | var express = require('express') 6 | 7 | var argv = optimist 8 | .usage('$0 [OPTIONS] path-to-db') 9 | 10 | .describe('port', 'The port to listen on') 11 | .default('port', 3000) 12 | .alias('port', 'p') 13 | 14 | .describe('help', 'Print usage instructions') 15 | .alias('h', 'help') 16 | 17 | .argv 18 | 19 | 20 | if (argv.help || argv._.length != 1) return optimist.showHelp() 21 | 22 | multilevel.server(argv._[0]).listen(argv.port, function () { 23 | console.log('multilevel-http listening on port ' + argv.port) 24 | }) -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * multilevel-http 3 | */ 4 | 5 | module.exports = { 6 | server : require('./lib/server'), 7 | client : require('./lib/client') 8 | } -------------------------------------------------------------------------------- /lib/client.js: -------------------------------------------------------------------------------- 1 | var request = require('request') 2 | var JSONStream = require('JSONStream') 3 | var through = require('through') 4 | var duplex = require('duplexer') 5 | 6 | module.exports = db 7 | 8 | function db (addr) { 9 | if (!(this instanceof db)) return new db(addr) 10 | if (!addr.match(/http/)) addr = 'http://' + addr 11 | if (addr[addr.length - 1] != '/') addr += '/' 12 | 13 | this.addr = addr 14 | } 15 | 16 | function getOpts (opts, cb) { 17 | return typeof opts != 'function' 18 | ? opts 19 | : {} 20 | } 21 | 22 | function getCallback (opts, cb) { 23 | if (cb) return cb 24 | if (typeof opts == 'function') return opts 25 | return function () { /* noop */ } 26 | } 27 | 28 | db.prototype.put = function (key, value, opts, cb) { 29 | cb = getCallback(opts, cb) 30 | opts = getOpts(opts) 31 | if (opts.encoding == 'json' || opts.valueEncoding == 'json') { 32 | value = JSON.stringify(value) 33 | delete opts.encoding 34 | delete opts.valueEncoding 35 | } 36 | request.post({ 37 | uri : this.addr + 'data/' + key, 38 | qs : opts, 39 | body : value 40 | }, function (err, res) { 41 | cb(err) 42 | }) 43 | } 44 | 45 | db.prototype.get = function (key, opts, cb) { 46 | cb = getCallback(opts, cb) 47 | opts = getOpts(opts) 48 | var isJSON = opts.encoding == 'json' || opts.valueEncoding == 'json' 49 | var isBinary = opts.encoding == 'binary' || opts.valueEncoding == 'binary' 50 | delete opts.encoding 51 | 52 | request(this.addr + 'data/' + key, function (err, res, body) { 53 | if (err) return cb(err) 54 | if (res.statusCode != 200) return cb(body) 55 | if (isJSON) body = JSON.parse(body) 56 | if (isBinary) body = new Buffer(body) 57 | cb(null, body) 58 | }) 59 | } 60 | 61 | db.prototype.del = function (key, opts, cb) { 62 | cb = getCallback(opts, cb) 63 | opts = getOpts(opts) 64 | 65 | request.del(this.addr + 'data/' + key, function (err, res, body) { 66 | cb(err, body) 67 | }) 68 | } 69 | 70 | db.prototype.batch = function (ops, opts, cb) { 71 | cb = getCallback(opts, cb) 72 | opts = getOpts(opts) 73 | 74 | request.post({ 75 | uri : this.addr + 'data', 76 | json : ops 77 | }, cb) 78 | } 79 | 80 | db.prototype.approximateSize = function (from, to, cb) { 81 | request(this.addr + 'approximateSize/' + from + '..' + to, function (err, res, body) { 82 | if (err) return cb(err) 83 | if (res.statusCode != 200) return cb(body) 84 | if (cb) cb(null, Number(body)) 85 | }) 86 | } 87 | 88 | db.prototype.readStream = function (opts) { 89 | return request({ 90 | uri : this.addr + 'data', 91 | qs : opts || {} 92 | }) 93 | .pipe(JSONStream.parse()) 94 | .pipe(through(function (arr) { 95 | // turn it into an object emitting stream 96 | arr.forEach(this.emit.bind(this, 'data')) 97 | })) 98 | } 99 | 100 | db.prototype.writeStream = function (opts) { 101 | var parser = JSONStream.stringify() 102 | var req = request.put({ 103 | uri : this.addr + 'data', 104 | qs : opts || {} 105 | }) 106 | parser.pipe(req) 107 | return duplex(parser, req) 108 | } 109 | 110 | db.prototype.keyStream = function (opts) { 111 | if (!opts) opts = {} 112 | opts.keys = true 113 | opts.values = false 114 | return this.readStream(opts) 115 | } 116 | 117 | db.prototype.valueStream = function (opts) { 118 | if (!opts) opts = {} 119 | opts.keys = false 120 | opts.values = true 121 | return this.readStream(opts) 122 | } -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | var JSONStream = require('JSONStream') 3 | var through = require('through') 4 | var levelup = require('levelup') 5 | 6 | function getOpts (opts) { 7 | if (opts.limit) opts.limit = Number(opts.limit) 8 | return opts 9 | } 10 | 11 | module.exports = function (db, meta) { 12 | if (typeof db == 'string') db = levelup(db) 13 | 14 | var app = express() 15 | 16 | app.get('/', function (req, res) { 17 | res.redirect('meta') 18 | }) 19 | 20 | app.get('/meta', function (req, res) { 21 | res.json(meta) 22 | }) 23 | 24 | app.get('/data/:key', function (req, res, next) { 25 | db.get(req.params['key'], req.query, function (err, value) { 26 | if (err && err.name == 'NotFoundError') res.status(404) 27 | if (err) return next(err) 28 | res.send((typeof value === 'number' ? '' + value : value)) 29 | }) 30 | }) 31 | 32 | app.post('/data/:key', function (req, res, next) { 33 | var chunks = [] 34 | req.on('data', function (chunk) { 35 | chunks.push(chunk) 36 | }) 37 | req.on('end', function () { 38 | var body = chunks.join('') 39 | if (req.query.encoding == 'json' || req.query.valueEncoding == 'json') { 40 | body = JSON.parse(body) 41 | } 42 | db.put(req.params['key'], body, req.query, function (err) { 43 | if (err) return next(err) 44 | res.send('ok') 45 | }) 46 | }) 47 | }) 48 | 49 | app.del('/data/:key', function (req, res, next) { 50 | db.del(req.params['key'], req.query, function (err) { 51 | if (err) return next(err) 52 | res.send('ok') 53 | }) 54 | }) 55 | 56 | app.get('/approximateSize/:from..:to', function (req, res, next) { 57 | db.approximateSize(req.params['from'], req.params['to'], function (err, size) { 58 | if (err) return next(err) 59 | res.end(size+'') 60 | }) 61 | }) 62 | 63 | app.get('/data', function (req, res) { 64 | var opts = getOpts(req.query) 65 | 66 | res.type('json') 67 | db.readStream(opts) 68 | .pipe(JSONStream.stringify()) 69 | .pipe(res) 70 | }) 71 | 72 | app.get('/range/:from..:to', function (req, res) { 73 | var opts = getOpts(req.query) 74 | opts.start = req.params['from'] 75 | opts.end = req.params['to'] 76 | 77 | res.type('json') 78 | db.readStream(opts) 79 | .pipe(JSONStream.stringify()) 80 | .pipe(res) 81 | }) 82 | 83 | app.get('/keys/:from..:to', function (req, res) { 84 | var opts = getOpts(req.query) 85 | opts.start = req.params['from'] 86 | opts.end = req.params['to'] 87 | 88 | res.type('json') 89 | db.keyStream(opts) 90 | .pipe(JSONStream.stringify()) 91 | .pipe(res) 92 | }) 93 | 94 | app.get('/keys', function (req, res) { 95 | var opts = getOpts(req.query) 96 | 97 | res.type('json') 98 | db.keyStream(opts) 99 | .pipe(JSONStream.stringify()) 100 | .pipe(res) 101 | }) 102 | 103 | app.get('/values/:from..:to', function (req, res) { 104 | var opts = getOpts(req.query) 105 | opts.start = req.params['from'] 106 | opts.end = req.params['to'] 107 | 108 | res.type('json') 109 | db.valueStream(opts) 110 | .pipe(JSONStream.stringify()) 111 | .pipe(res) 112 | }) 113 | 114 | app.get('/values', function (req, res) { 115 | var opts = getOpts(req.query) 116 | 117 | res.type('json') 118 | db.valueStream(opts) 119 | .pipe(JSONStream.stringify()) 120 | .pipe(res) 121 | }) 122 | 123 | app.put('/data', function (req, res) { 124 | var ws = db.writeStream(req.query) 125 | ws.on('close', res.end.bind(res, 'ok')) 126 | 127 | req 128 | .pipe(JSONStream.parse()) 129 | .pipe(through(function (data) { 130 | Array.isArray(data) 131 | ? data.forEach(this.emit.bind(this, 'data')) 132 | : this.emit('data', data) 133 | })) 134 | .pipe(ws) 135 | }) 136 | 137 | app.post('/data', function (req, res, next) { 138 | var ops = [] 139 | 140 | req 141 | .pipe(JSONStream.parse()) 142 | .pipe(through(function (data) { 143 | Array.isArray(data) 144 | ? data.forEach(function (d) { ops.push(d) }) 145 | : ops.push(data) 146 | })) 147 | .on('end', function () { 148 | db.batch(ops, req.query, function (err) { 149 | if (err) return next(err) 150 | res.send('ok') 151 | }) 152 | }) 153 | }) 154 | 155 | app.db = db 156 | app.meta = meta 157 | 158 | return app 159 | } 160 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multilevel-http", 3 | "version": "0.0.2", 4 | "description": "Access a leveldb instance from multiple processes via HTTP", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "NODE_ENV=test mocha" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/juliangruber/multilevel-http.git" 12 | }, 13 | "keywords": [ 14 | "leveldb", 15 | "levelup", 16 | "process", 17 | "rpc", 18 | "http" 19 | ], 20 | "author": "Julian Gruber ", 21 | "license": "MIT", 22 | "readmeFilename": "README.md", 23 | "dependencies": { 24 | "express": "~3.1.0", 25 | "JSONStream": "~0.4.3", 26 | "optimist": "~0.3.5", 27 | "request": "~2.12.0", 28 | "through": "~2.1.0", 29 | "duplexer": "~0.0.2", 30 | "levelup": "~0.6.1" 31 | }, 32 | "devDependencies": { 33 | "should": "~1.2.1", 34 | "mocha": "~1.8.1", 35 | "supertest": "~0.5.1", 36 | "fs.extra": "~1.2.0" 37 | }, 38 | "bin": { 39 | "multilevel-http": "./bin/multilevel-http.js" 40 | }, 41 | "engines": { 42 | "node": ">=0.8" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/client.test.js: -------------------------------------------------------------------------------- 1 | var multilevel = require('..') 2 | var server = multilevel.server(__dirname + '/client.test.db') 3 | var should = require('should') 4 | var fs = require('fs.extra') 5 | 6 | server.listen(5001) 7 | 8 | beforeEach(function (done) { 9 | server.db.close() 10 | fs.rmrf(__dirname + '/client.test.db', function () { 11 | server.db.open() 12 | done() 13 | }) 14 | }) 15 | 16 | var db = multilevel.client('http://localhost:5001/') 17 | 18 | describe('client', function () { 19 | describe('db#put(key, value)', function () { 20 | it('should store text', function (done) { 21 | db.put('foo', 'bar', function (err) { 22 | if (err) return done(err) 23 | 24 | db.get('foo', function (err, value) { 25 | if (err) return done(err) 26 | 27 | should.exist(value) 28 | value.should.equal('bar') 29 | done() 30 | }) 31 | }) 32 | }) 33 | 34 | it('should store json', function (done) { 35 | db.put('foo', { some : 'json' }, { encoding : 'json' }, function (err) { 36 | if (err) return done(err) 37 | db.get('foo', { encoding : 'json' }, function (err, value) { 38 | if (err) return done(err) 39 | value.should.eql({ some : 'json' }) 40 | done() 41 | }) 42 | }) 43 | }) 44 | 45 | it('should store binary', function (done) { 46 | db.put('foo', new Buffer([0, 1]), { encoding : 'binary' }, function (err) { 47 | if (err) return done(err) 48 | db.get('foo', { encoding : 'binary' }, function (err, value) { 49 | if (err) return done(err) 50 | value.toString().should.equal("\u0000\u0001") 51 | done() 52 | }) 53 | }) 54 | }) 55 | }) 56 | 57 | describe('db#get(key)', function () { 58 | it('should get', function (done) { 59 | db.put('foo', 'bar', function (err) { 60 | if (err) return done(err) 61 | 62 | db.get('foo', function (err, value) { 63 | if (err) return done(err) 64 | 65 | should.exist(value) 66 | value.should.equal('bar') 67 | done() 68 | }) 69 | }) 70 | }) 71 | }) 72 | 73 | describe('db#del(key)', function () { 74 | it('should delete', function (done) { 75 | db.put('foo', 'bar', function (err) { 76 | if (err) return done(err) 77 | 78 | db.del('foo', function (err) { 79 | if (err) return done(err) 80 | 81 | db.get('foo', function (err, value) { 82 | should.exist(err) 83 | should.not.exist(value) 84 | done() 85 | }) 86 | }) 87 | }) 88 | }) 89 | }) 90 | 91 | describe('db#batch(ops, cb)', function () { 92 | it('should create', function (done) { 93 | db.batch([ 94 | { type : 'put', key : 'key', value : 'value' } 95 | ], function (err) { 96 | if (err) return done(err) 97 | 98 | db.get('key', function (err, value) { 99 | if (err) return done(err) 100 | 101 | should.exist(value) 102 | value.should.equal('value') 103 | done() 104 | }) 105 | }) 106 | }) 107 | }) 108 | 109 | describe('db#approximateSize(from, to, cb)', function () { 110 | it('should get a size', function (done) { 111 | db.approximateSize('a', 'z', function (err, size) { 112 | if (err) return done(err) 113 | should.exist(size) 114 | size.should.be.a('number') 115 | done() 116 | }) 117 | }) 118 | }) 119 | 120 | describe('db#readStream()', function () { 121 | it('should read', function (done) { 122 | db.put('foo', 'bar', function (err) { 123 | if (err) return done(err) 124 | var count = 0 125 | 126 | db.readStream() 127 | .on('data', function (data) { 128 | count++ 129 | should.exist(data) 130 | data.should.eql({ key : 'foo', value : 'bar' }) 131 | }) 132 | .on('error', done) 133 | .on('end', function (data) { 134 | count.should.equal(1) 135 | done() 136 | }) 137 | }) 138 | }) 139 | }) 140 | 141 | describe('db#writeStream()', function () { 142 | it('should save', function (done) { 143 | var ws = db.writeStream() 144 | 145 | ws.on('end', function () { 146 | db.get('key', function (err, value) { 147 | if (err) return done(err) 148 | should.exist(value) 149 | value.should.equal('value') 150 | done() 151 | }) 152 | }) 153 | 154 | ws.write({ key : 'key', value : 'value' }) 155 | ws.end() 156 | }) 157 | }) 158 | }) -------------------------------------------------------------------------------- /test/server.test.js: -------------------------------------------------------------------------------- 1 | var should = require('should') 2 | var request = require('supertest') 3 | var levelup = require('levelup') 4 | var fs = require('fs.extra') 5 | var multilevel = require('..') 6 | 7 | var app 8 | 9 | beforeEach(function (done) { 10 | if (app && app.db) app.db.close() 11 | fs.rmrf(__dirname + '/server.test.db', function () { 12 | done() 13 | }) 14 | }) 15 | 16 | beforeEach(function () { 17 | app = multilevel.server(__dirname + '/server.test.db', { some : 'meta' }) 18 | }) 19 | 20 | beforeEach(function (done) { 21 | app.db.put('foo', 'bar', done) 22 | }) 23 | 24 | beforeEach(function (done) { 25 | app.db.put('bar', 'foo', done) 26 | }) 27 | 28 | describe('http', function () { 29 | describe('GET /meta', function () { 30 | it('should send meta', function (done) { 31 | request(app) 32 | .get('/meta') 33 | .expect('Content-Type', /json/) 34 | .expect(200) 35 | .end(function (err, res) { 36 | should.not.exist(err) 37 | should.exist(res.body) 38 | should.exist(res.body.some) 39 | res.body.some.should.equal('meta') 40 | 41 | done() 42 | }) 43 | }) 44 | }) 45 | 46 | describe('GET /data/:key', function () { 47 | it('should get text', function (done) { 48 | request(app) 49 | .get('/data/foo') 50 | .expect('bar', done) 51 | }) 52 | 53 | it('should get json', function (done) { 54 | app.db.put('json', { some : 'json' }, { encoding : 'json' }, function (err) { 55 | if (err) return done(err) 56 | 57 | request(app) 58 | .get('/data/json?encoding=json') 59 | .expect(200) 60 | .expect({ some : 'json' }, done) 61 | }) 62 | }) 63 | 64 | it('should respond with 404', function (done) { 65 | request(app) 66 | .get('/data/baz') 67 | .expect(404) 68 | .expect(/not found/, done) 69 | }) 70 | }) 71 | 72 | describe('POST /data/:key', function () { 73 | it('should save text', function (done) { 74 | request(app) 75 | .post('/data/foo') 76 | .send('changed') 77 | .end(function (err) { 78 | if (err) return done(err) 79 | request(app).get('/data/foo').expect('changed').end(done) 80 | }) 81 | }) 82 | 83 | it('should save json', function (done) { 84 | request(app) 85 | .post('/data/json?encoding=json') 86 | .send({ some : 'json' }) 87 | .end(function (err) { 88 | if (err) return done(err) 89 | 90 | app.db.get('json', { encoding : 'json' }, function (err, value) { 91 | if (err) return done(err) 92 | should.exist(value) 93 | value.should.eql({ some : 'json' }) 94 | done() 95 | }) 96 | }) 97 | }) 98 | }) 99 | 100 | describe('DEL /data/:key', function () { 101 | it('should delete', function (done) { 102 | request(app) 103 | .del('/data/foo') 104 | .expect(200) 105 | .expect('ok') 106 | .end(function (err) { 107 | request(app).get('/foo').expect(404).end(done) 108 | }) 109 | }) 110 | }) 111 | 112 | describe('GET /approximateSize/:from..:to', function () { 113 | it('should get a size', function (done) { 114 | request(app) 115 | .get('/approximateSize/a..z') 116 | .expect(200) 117 | .expect('0', done) 118 | }) 119 | }) 120 | 121 | describe('GET /data', function () { 122 | it('should get all', function (done) { 123 | request(app) 124 | .get('/data') 125 | .expect(200) 126 | .end(function (err, res) { 127 | if (err) return done(err) 128 | res.body.should.be.an.instanceOf(Array) 129 | res.body.should.have.length(2) 130 | done() 131 | }) 132 | }) 133 | 134 | it('should limit', function (done) { 135 | request(app) 136 | .get('/data?limit=1') 137 | .expect(200) 138 | .end(function (err, res) { 139 | if (err) return done(err) 140 | res.body.should.be.an.instanceOf(Array) 141 | res.body.should.have.length(1) 142 | done() 143 | }) 144 | }) 145 | }) 146 | 147 | describe('GET /range/:from..:to', function () { 148 | it('should get data', function (done) { 149 | request(app) 150 | .get('/range/a..z') 151 | .expect(200) 152 | .end(function (err, res) { 153 | if (err) return done(err) 154 | res.body.should.be.an.instanceOf(Array) 155 | res.body.should.have.length(2) 156 | done() 157 | }) 158 | }) 159 | 160 | it('should limit', function (done) { 161 | request(app) 162 | .get('/range/a..z?limit=1') 163 | .expect(200) 164 | .end(function (err, res) { 165 | if (err) return done(err) 166 | res.body.should.be.an.instanceOf(Array) 167 | res.body.should.have.length(1) 168 | done() 169 | }) 170 | }) 171 | }) 172 | 173 | describe('GET /values/(:from..:to)', function () { 174 | it('should get values', function (done) { 175 | request(app) 176 | .get('/values') 177 | .expect(200) 178 | .end(function (err, res) { 179 | if (err) return done(err) 180 | res.body.should.be.an.instanceOf(Array) 181 | res.body.should.have.length(2) 182 | done() 183 | }) 184 | }) 185 | 186 | it('should limit', function (done) { 187 | request(app) 188 | .get('/values?limit=1') 189 | .expect(200) 190 | .end(function (err, res) { 191 | if (err) return done(err) 192 | res.body.should.be.an.instanceOf(Array) 193 | res.body.should.have.length(1) 194 | done() 195 | }) 196 | }) 197 | 198 | it('should get a range', function (done) { 199 | request(app) 200 | .get('/values/0..z?limit=1') 201 | .expect(200) 202 | .end(function (err, res) { 203 | if (err) return done(err) 204 | res.body.should.be.an.instanceOf(Array) 205 | res.body.should.have.length(1) 206 | done() 207 | }) 208 | }) 209 | }) 210 | 211 | describe('/keys/(:from..:to)', function () { 212 | it('should get keys', function (done) { 213 | request(app) 214 | .get('/keys') 215 | .expect(200) 216 | .end(function (err, res) { 217 | if (err) return done(err) 218 | res.body.should.be.an.instanceOf(Array) 219 | res.body.should.have.length(2) 220 | done() 221 | }) 222 | }) 223 | 224 | it('should limit', function (done) { 225 | request(app) 226 | .get('/keys?limit=1') 227 | .expect(200) 228 | .end(function (err, res) { 229 | if (err) return done(err) 230 | res.body.should.be.an.instanceOf(Array) 231 | res.body.should.have.length(1) 232 | done() 233 | }) 234 | }) 235 | 236 | it('should get a range', function (done) { 237 | request(app) 238 | .get('/keys/0..z?limit=1') 239 | .expect(200) 240 | .end(function (err, res) { 241 | if (err) return done(err) 242 | res.body.should.be.an.instanceOf(Array) 243 | res.body.should.have.length(1) 244 | done() 245 | }) 246 | }) 247 | }) 248 | 249 | describe('PUT /data', function () { 250 | it('should save', function (done) { 251 | request(app) 252 | .put('/data') 253 | .send({ key : 'key', value : 'value'}) 254 | .expect(200) 255 | .expect('ok') 256 | .end(function (err) { 257 | if (err) return done(err) 258 | request(app).get('/data/key').expect('value').end(done) 259 | }) 260 | }) 261 | }) 262 | 263 | describe('POST /data', function () { 264 | it('should save', function (done) { 265 | request(app) 266 | .post('/data') 267 | .send({ type : 'put', key : 'key', value : 'value' }) 268 | .expect(200) 269 | .expect('ok') 270 | .end(function (err) { 271 | if (err) return done(err) 272 | setTimeout(function () { 273 | request(app).get('/data/key').expect('value').end(done) 274 | }, 10) 275 | }) 276 | }) 277 | }) 278 | }) --------------------------------------------------------------------------------