├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── _test ├── batch.js ├── hook.js ├── limit.js ├── mixed-value-encodings-per-action-del.js ├── mixed-value-encodings-per-sub-del.js ├── mixed-value-encodings-writeStream-per-sub.js ├── mixed-value-encodings-writeStream-per-write.js ├── posthook-ranges.js ├── prehook-batch.js └── prehook-insert.js ├── batch.js ├── bytewise.js ├── codec ├── bytewise.js ├── index.js └── legacy.js ├── examples ├── queue.js └── trigger.js ├── hooks.js ├── index.js ├── legacy.js ├── nut.js ├── package.json ├── pull.js ├── range.js ├── read-stream.js ├── shell.js ├── test-runner.js └── test ├── close.js ├── codec.js ├── errors.js ├── falsey-value.js ├── hooks.js ├── inherit-encodings.js ├── is-open.js ├── key-stream-issues.js ├── key-value-stream.js ├── keyvalue.js ├── legacy-apis.js ├── legacy-start-end.js ├── level.js ├── mixed-value-encodings-per-action.js ├── mixed-value-encodings-per-sub.js ├── mock.js ├── nested-prehooks.js ├── range.js ├── reverse-order.js ├── reverse.js ├── streams-sublevel-key-value.js ├── streams.js ├── sublevels.js └── version.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | node_modules/* 3 | npm_debug.log 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.12" 4 | - "iojs" 5 | - "4.0" 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Dominic Tarr 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # level-sublevel 2 | 3 | Separate sections of levelup, with hooks! 4 | 5 | [![build status](https://secure.travis-ci.org/dominictarr/level-sublevel.png)](http://travis-ci.org/dominictarr/level-sublevel) 6 | 7 | [![testling badge](https://ci.testling.com/dominictarr/level-sublevel.png)](https://ci.testling.com/dominictarr/level-sublevel) 8 | 9 | This module allows you to create separate sections of a 10 | [levelup](https://github.com/rvagg/node-levelup) database, 11 | kinda like tables in an sql database, but evented, and ranged, 12 | for real-time changing data. 13 | 14 | ## level-sublevel@6 **BREAKING CHANGES** 15 | 16 | The long awaited `level-sublevel` rewrite is out! 17 | You are hereby warned this is a _significant breaking_ change. 18 | So it's good to use it with a new project, 19 | The user api is _mostly_ the same as before, 20 | but the way that keys are _encoded_ has changed, and _this means 21 | you cannot run 6 on a database you created with 5_. 22 | 23 | Also, `createWriteStream` has been removed, in anticipation of [this 24 | change](https://github.com/rvagg/node-levelup/pull/207) use something 25 | like [level-write-stream](https://github.com/Raynos/level-write-stream) 26 | 27 | ### Legacy Mode 28 | 29 | Using leveldb with legacy mode is the simplest way to get the new sublevel 30 | on top of a database that used old sublevel. Simply require sublevel like this: 31 | 32 | ``` js 33 | var level = require('level') 34 | // V *** require legacy.js *** 35 | var sublevel = require('level-sublevel/legacy') 36 | var db = sublevel(level(path)) 37 | 38 | ``` 39 | 40 | ### Migration Tool 41 | 42 | @calvinmetcalf has created a migration tool: 43 | [sublevel-migrate](https://github.com/calvinmetcalf/sublevel-migrate) 44 | 45 | This can be used to copy an old level-sublevel into the new format. 46 | 47 | ## Stability 48 | 49 | Unstable: Expect patches and features, possible api changes. 50 | 51 | This module is working well, but may change in the future as its use is further explored. 52 | 53 | ## Example 54 | 55 | ``` js 56 | var LevelUp = require('levelup') 57 | var Sublevel = require('level-sublevel') 58 | 59 | var db = Sublevel(LevelUp('/tmp/sublevel-example')) 60 | var sub = db.sublevel('stuff') 61 | 62 | //put a key into the main levelup 63 | db.put(key, value, function () {}) 64 | 65 | //put a key into the sub-section! 66 | sub.put(key2, value, function () {}) 67 | ``` 68 | 69 | Sublevel prefixes each subsection so that it will not collide 70 | with the outer db when saving or reading! 71 | 72 | ## Hooks 73 | 74 | Hooks are specially built into Sublevel so that you can 75 | do all sorts of clever stuff, like generating views or 76 | logs when records are inserted! 77 | 78 | Records added via hooks will be atomically inserted with the triggering change. 79 | 80 | ### Hooks Example 81 | 82 | Whenever a record is inserted, 83 | save an index to it by the time it was inserted. 84 | 85 | ``` js 86 | var sub = db.sublevel('SEQ') 87 | 88 | db.pre(function (ch, add) { 89 | add({ 90 | key: ''+Date.now(), 91 | value: ch.key, 92 | type: 'put', 93 | // NOTE: pass the destination db to add the value to that subsection! 94 | prefix: sub 95 | }) 96 | }) 97 | 98 | db.put('key', 'VALUE', function (err) { 99 | // read all the records inserted by the hook! 100 | sub.createReadStream().on('data', console.log) 101 | }) 102 | ``` 103 | 104 | Notice that the `prefix` property to `add()` is set to `sub`, which tells the hook to save the new record in the `sub` section. 105 | 106 | ## Batches 107 | 108 | In `sublevel` batches also support a `prefix: subdb` property, 109 | if set, this row will be inserted into that database section, 110 | instead of the current section, similar to the `pre` hook above. 111 | 112 | ``` js 113 | var sub1 = db.sublevel('SUB_1') 114 | var sub2 = db.sublevel('SUM_2') 115 | 116 | sub.batch([ 117 | {key: 'key', value: 'Value', type: 'put'}, 118 | {key: 'key', value: 'Value', type: 'put', prefix: sub2}, 119 | ], function (err) {...}) 120 | ``` 121 | 122 | ## License 123 | 124 | MIT 125 | 126 | -------------------------------------------------------------------------------- /_test/batch.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var level = require('level-test')() 3 | 4 | function all (db, cb) { 5 | var obj = {}, fin = false 6 | 7 | function done (err) { 8 | if(fin) return 9 | fin = true 10 | cb(err, obj) 11 | } 12 | 13 | db.createReadStream({end: '\xff\xff'}) 14 | .on('data', function (ch) { 15 | obj[ch.key] = ch.value 16 | }) 17 | .on('end', done) 18 | .on('error', done) 19 | } 20 | 21 | var sublevel = require('../') 22 | function sl(name) { 23 | return sublevel(level(name), {sep: "~"}) 24 | } 25 | 26 | 27 | test('sublevel - batch', function (t) { 28 | 29 | var base = sl('test-sublevel') 30 | 31 | var a = base.sublevel('A') 32 | var b = base.sublevel('B') 33 | 34 | 35 | var sum = 0 36 | 37 | a.batch([ 38 | {key: 'a', value: 1, type: 'put'}, 39 | {key: 'b', value: 2, type: 'put'}, 40 | {key: 'c', value: 3, type: 'put'}, 41 | {key: 'd', value: 4, type: 'put'}, 42 | {key: 'e', value: 5, type: 'put'}, 43 | ], function (err) { 44 | all(a, function (err, obj) { 45 | t.notOk(err) 46 | var keys = Object.keys(obj).join('') 47 | for(var k in obj) { 48 | sum += Number(obj[k]) 49 | } 50 | t.equal(keys, 'abcde') 51 | t.equal(sum, 15) 52 | t.end() 53 | }) 54 | }) 55 | 56 | }) 57 | 58 | test('sublevel - prefixed batches', function (t) { 59 | 60 | var base = sl('test-sublevel2') 61 | 62 | var a = base.sublevel('A') 63 | var b = base.sublevel('B') 64 | 65 | base.batch([ 66 | {key: 'a', value: 1, type: 'put'}, 67 | {key: 'b', value: 2, type: 'put', prefix: b}, 68 | {key: 'c', value: 3, type: 'put'}, 69 | {key: 'd', value: 4, type: 'put', prefix: a}, 70 | {key: 'e', value: 5, type: 'put', prefix: base}, 71 | ], function (err) { 72 | all(base, function (_, obj) { 73 | t.deepEqual(obj, { 74 | 'a': '1', 75 | 'c': '3', 76 | 'e': '5', 77 | '~A~d': '4', 78 | '~B~b': '2' 79 | }) 80 | console.log(obj) 81 | t.end() 82 | }) 83 | }) 84 | }) 85 | 86 | test('sublevel - prefixed batches on subsection', function (t) { 87 | 88 | var base = sl('test-sublevel3') 89 | 90 | var a = base.sublevel('A') 91 | var b = base.sublevel('B') 92 | 93 | a.batch([ 94 | {key: 'a', value: 1, type: 'put', prefix: base}, 95 | {key: 'b', value: 2, type: 'put', prefix: b}, 96 | {key: 'c', value: 3, type: 'put', prefix: base}, 97 | {key: 'd', value: 4, type: 'put'}, 98 | {key: 'e', value: 5, type: 'put', prefix: base}, 99 | ], function (err) { 100 | all(base, function (_, obj) { 101 | t.deepEqual(obj, { 102 | 'a': '1', 103 | 'c': '3', 104 | 'e': '5', 105 | '~A~d': '4', 106 | '~B~b': '2' 107 | }) 108 | t.end() 109 | }) 110 | }) 111 | }) 112 | 113 | 114 | test('sublevel - prefixed batches on subsection - strings', function (t) { 115 | 116 | var base = sl('test-sublevel4') 117 | 118 | var a = base.sublevel('A') 119 | var b = base.sublevel('B') 120 | var b_c = b.sublevel('C') 121 | 122 | base.batch([ 123 | {key: 'a', value: 1, type: 'put'}, 124 | {key: 'b', value: 2, type: 'put', prefix: b.prefix()}, 125 | {key: 'c', value: 3, type: 'put'}, 126 | {key: 'd', value: 4, type: 'put', prefix: a.prefix()}, 127 | {key: 'e', value: 5, type: 'put'}, 128 | {key: 'f', value: 6, type: 'put', prefix: b_c.prefix()}, 129 | ], function (err) { 130 | all(base, function (_, obj) { 131 | t.deepEqual(obj, { 132 | 'a': '1', 133 | 'c': '3', 134 | 'e': '5', 135 | '~A~d': '4', 136 | '~B~b': '2', 137 | '~B~~C~f': '6' 138 | }) 139 | console.log(obj) 140 | t.end() 141 | }) 142 | }) 143 | }) 144 | 145 | test('sublevel - batch - chained', function (t) { 146 | 147 | var base = sl('test-sublevel5') 148 | 149 | var a = base.sublevel('A') 150 | var b = base.sublevel('B') 151 | 152 | 153 | var sum = 0 154 | 155 | a.batch() 156 | .put('a', 1) 157 | .put('b', 2) 158 | .put('c', 3) 159 | .put('d', 4) 160 | .put('e', 5) 161 | .write(function (err) { 162 | all(a, function (err, obj) { 163 | t.notOk(err) 164 | var keys = Object.keys(obj).join('') 165 | for(var k in obj) { 166 | sum += Number(obj[k]) 167 | } 168 | t.equal(keys, 'abcde') 169 | t.equal(sum, 15) 170 | t.end() 171 | }) 172 | }) 173 | 174 | }) 175 | 176 | test('sublevel - prefixed batches - chained', function (t) { 177 | 178 | var base = sl('test-sublevel6') 179 | 180 | var a = base.sublevel('A') 181 | var b = base.sublevel('B') 182 | 183 | base.batch() 184 | .put('a', 1) 185 | .put('b', 2, {prefix: b}) 186 | .put('c', 3) 187 | .put('d', 4, {prefix: a}) 188 | .put('e', 5, {prefix: base}) 189 | .write(function (err) { 190 | all(base, function (_, obj) { 191 | t.deepEqual(obj, { 192 | 'a': '1', 193 | 'c': '3', 194 | 'e': '5', 195 | '~A~d': '4', 196 | '~B~b': '2' 197 | }) 198 | console.log(obj) 199 | t.end() 200 | }) 201 | }) 202 | }) 203 | 204 | test('sublevel - prefixed batches on subsection - chained', function (t) { 205 | 206 | var base = sl('test-sublevel7') 207 | 208 | var a = base.sublevel('A') 209 | var b = base.sublevel('B') 210 | 211 | a.batch() 212 | .put('a', 1, {prefix: base}) 213 | .put('b', 2, {prefix: b}) 214 | .put('c', 3, {prefix: base}) 215 | .put('d', 4) 216 | .put('e', 5, {prefix: base}) 217 | .write(function (err) { 218 | all(base, function (_, obj) { 219 | t.deepEqual(obj, { 220 | 'a': '1', 221 | 'c': '3', 222 | 'e': '5', 223 | '~A~d': '4', 224 | '~B~b': '2' 225 | }) 226 | t.end() 227 | }) 228 | }) 229 | }) 230 | 231 | 232 | test('sublevel - prefixed batches on subsection - strings - chained', function (t) { 233 | 234 | var base = sl('test-sublevel8') 235 | 236 | var a = base.sublevel('A') 237 | var b = base.sublevel('B') 238 | var b_c = b.sublevel('C') 239 | 240 | base.batch() 241 | .put('a', 1) 242 | .put('b', 2, {prefix: b.prefix()}) 243 | .put('c', 3) 244 | .put('d', 4, {prefix: a.prefix()}) 245 | .put('e', 5) 246 | .put('f', 6, {prefix: b_c.prefix()}) 247 | .write(function (err) { 248 | all(base, function (_, obj) { 249 | t.deepEqual(obj, { 250 | 'a': '1', 251 | 'c': '3', 252 | 'e': '5', 253 | '~A~d': '4', 254 | '~B~b': '2', 255 | '~B~~C~f': '6' 256 | }) 257 | console.log(obj) 258 | t.end() 259 | }) 260 | }) 261 | }) 262 | 263 | test('sublevel - delete - chained', function(t) { 264 | var base = sl('test-sublevel9') 265 | 266 | var a = base.sublevel('A') 267 | var b = base.sublevel('B') 268 | 269 | var sum = 0 270 | 271 | a.batch() 272 | .put('a', 1) 273 | .put('b', 2) 274 | .put('c', 3) 275 | .put('d', 4) 276 | .put('e', 5) 277 | .write() 278 | 279 | a.batch() 280 | .del('c') 281 | .del('e') 282 | .write(function (err) { 283 | all(a, function (err, obj) { 284 | t.notOk(err) 285 | var keys = Object.keys(obj).join('') 286 | for(var k in obj) { 287 | sum += Number(obj[k]) 288 | } 289 | t.equal(keys, 'abd') 290 | t.equal(sum, 7) 291 | t.end() 292 | }) 293 | }) 294 | }) 295 | -------------------------------------------------------------------------------- /_test/hook.js: -------------------------------------------------------------------------------- 1 | var level = require('level-test')() 2 | var Sublevel = require('../') 3 | 4 | function sl (name) { 5 | return Sublevel(level(name), {sep: '~'}) 6 | } 7 | 8 | require('tape')('sublevel', function (t) { 9 | 10 | var base = sl('test-sublevel') 11 | 12 | var a = base.sublevel('A') 13 | var b = base.sublevel('SEQ') 14 | 15 | var i = 0 16 | 17 | function all(db, cb) { 18 | var o = {} 19 | db.createReadStream({end: '\xff\xff'}).on('data', function (data) { 20 | o[data.key.toString()] = data.value.toString() 21 | }) 22 | .on('end', function () { 23 | cb(null, o) 24 | }) 25 | .on('error', cb) 26 | } 27 | 28 | a.pre(function (ch, add) { 29 | console.log(ch) 30 | add({key: i++, value: ch.key, type: 'put'}, b) 31 | }) 32 | 33 | var n = 3, _a, _b, _c 34 | 35 | a.put('a', _a ='AAA_'+Math.random(), next) 36 | a.put('b', _b = 'BBB_'+Math.random(), next) 37 | a.put('c', _c = 'CCC_'+Math.random(), next) 38 | 39 | function next () { 40 | if(--n) return 41 | 42 | all(base, function (err, obj) { 43 | console.log(obj) 44 | t.deepEqual(obj, 45 | { '~A~a': _a, 46 | '~A~b': _b, 47 | '~A~c': _c, 48 | '~SEQ~0': 'a', 49 | '~SEQ~1': 'b', 50 | '~SEQ~2': 'c' }) 51 | t.end() 52 | }) 53 | } 54 | 55 | }) 56 | -------------------------------------------------------------------------------- /_test/limit.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | 3 | function all (db, range, cb) { 4 | var o = {} 5 | db.createReadStream(range) 6 | .on('data', function (data) { 7 | o[data.key] = data.value 8 | }) 9 | .on('end', function () { 10 | cb(null, o) 11 | }) 12 | } 13 | 14 | function makeTest(db, name) { 15 | 16 | test(name, function (t) { 17 | 18 | t.plan(19) 19 | 20 | var docs = { 21 | a: 'apple', 22 | b: 'banana', 23 | c: 'cherry', 24 | d: 'durian', 25 | e: 'elder-berry' 26 | } 27 | 28 | function limit(a, b) { 29 | t.deepEqual(a, b) 30 | t.equal(JSON.stringify(a), JSON.stringify(b)) 31 | } 32 | 33 | db.batch(Object.keys(docs).map(function (key) { 34 | console.log(key, docs[key]) 35 | return {key: key, value: docs[key], type: 'put'} 36 | }), function (err) { 37 | t.notOk(err) 38 | 39 | all(db, {limit: -1}, function (err, all) { 40 | limit(all, docs) 41 | }) 42 | 43 | all(db, {limit: 2, min: 'a~'}, function (err, all) { 44 | limit(all, { 45 | b: 'banana', 46 | c: 'cherry' 47 | }) 48 | }) 49 | 50 | all(db, {limit: 3, min: 'b'}, function (err, all) { 51 | limit(all, { 52 | b: 'banana', 53 | c: 'cherry', 54 | d: 'durian' 55 | }) 56 | }) 57 | 58 | 59 | all(db, {limit: 2, min: 'a~', reverse: true}, function (err, all) { 60 | limit(all, { 61 | e: 'elder-berry', 62 | d: 'durian' 63 | }) 64 | }) 65 | 66 | all(db, {limit: 1, min: 'c~', reverse: true}, function (err, all) { 67 | console.log(all) 68 | limit(all, { 69 | e: 'elder-berry' 70 | }) 71 | }) 72 | 73 | all(db, {limit: 1, min: 'c~', max: 'd~'}, function (err, all) { 74 | console.log(all) 75 | limit(all, { 76 | d: 'durian', 77 | }) 78 | }) 79 | 80 | all(db, {limit: 3, min: 'a~'}, function (err, all) { 81 | limit(all, { 82 | b: 'banana', 83 | c: 'cherry', 84 | d: 'durian' 85 | }) 86 | }) 87 | 88 | all(db, {limit: 1, min: 'c~'}, function (err, all) { 89 | console.log('d, e', all) 90 | limit(all, { 91 | d: 'durian' 92 | }) 93 | }) 94 | 95 | all(db, {limit: 2, min: 'c~', max: 'd~', reverse: true}, function (err, all) { 96 | console.log(all) 97 | limit(all, { 98 | d: 'durian', 99 | }) 100 | }) 101 | }) 102 | }) 103 | } 104 | 105 | 106 | var levelup = require('level-test')() 107 | 108 | var base = require('../')(levelup('test-sublevel-limit')) 109 | 110 | var A = base.sublevel('A') 111 | makeTest(base, 'simple') 112 | 113 | makeTest(A, 'sublevel') 114 | 115 | makeTest(base, 'simple, again') 116 | 117 | var A_B = A.sublevel('B') 118 | makeTest(A_B, 'sublevel2') 119 | 120 | makeTest(A, 'sublevel, again') 121 | 122 | makeTest(base, 'simple, again 2') 123 | 124 | -------------------------------------------------------------------------------- /_test/mixed-value-encodings-per-action-del.js: -------------------------------------------------------------------------------- 1 | var levelup = require('level-test')() 2 | var base = require('../')(levelup('test-mixed-value-encodings-per-del')) 3 | 4 | var test = require('tape') 5 | 6 | test('subsections support mixed encodings per del', function (t) { 7 | t.plan(6) 8 | 9 | var foo = base.sublevel('foo') 10 | var bar = base.sublevel('bar') 11 | 12 | foo.put('foo1', 'foo1-value', { valueEncoding: 'utf8' }, function () { 13 | bar.put('bar1', { obj: 'ect' }, { valueEncoding: 'json' }, function () { 14 | 15 | foo.get('foo1', { valueEncoding: 'utf8' }, function (err, value) { 16 | t.notOk(err, 'getting string value by key has no error') 17 | t.equal(value, 'foo1-value', 'and returns value for that key') 18 | 19 | foo.del('foo1', { valueEncoding: 'utf8' }, function (err, value) { 20 | foo.get('foo1', { valueEncoding: 'utf8' }, function (err, value) { 21 | t.equal(err.name, 'NotFoundError', 'properly deletes utf8 encoded value') 22 | }) 23 | }) 24 | }) 25 | 26 | bar.get('bar1', { valueEncoding: 'json' }, function (err, value) { 27 | t.notOk(err, 'getting object value by key has no error') 28 | t.equal(value.obj, 'ect', 'and returns value for that key') 29 | 30 | bar.del('bar1', { valueEncoding: 'json' }, function (err, value) { 31 | bar.get('bar1', { valueEncoding: 'json' }, function (err, value) { 32 | t.equal(err.name, 'NotFoundError', 'properly deletes json encoded value') 33 | }) 34 | }) 35 | }) 36 | 37 | }) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /_test/mixed-value-encodings-per-sub-del.js: -------------------------------------------------------------------------------- 1 | var levelup = require('level-test')() 2 | var base = require('../')(levelup('test-mixed-value-encodings-per-sub-del')) 3 | 4 | var test = require('tape') 5 | 6 | test('subsections support mixed encodings per sub with del', function (t) { 7 | t.plan(6) 8 | 9 | var foo = base.sublevel('foo', { valueEncoding: 'utf8' }) 10 | var bar = base.sublevel('bar', { valueEncoding: 'json' }) 11 | 12 | foo.put('foo1', 'foo1-value', function () { 13 | bar.put('bar1', { obj: 'ect' }, function () { 14 | 15 | foo.get('foo1', function (err, value) { 16 | t.notOk(err, 'getting string value by key has no error') 17 | t.equal(value, 'foo1-value', 'and returns value for that key') 18 | 19 | foo.del('foo1', function (err, value) { 20 | foo.get('foo1', { valueEncoding: 'utf8' }, function (err, value) { 21 | t.equal(err.name, 'NotFoundError', 'properly deletes utf8 encoded value') 22 | }) 23 | }) 24 | }) 25 | 26 | bar.get('bar1', function (err, value) { 27 | t.notOk(err, 'getting object value by key has no error') 28 | t.equal(value.obj, 'ect', 'and returns value for that key') 29 | 30 | bar.del('bar1', function (err, value) { 31 | bar.get('bar1', { valueEncoding: 'json' }, function (err, value) { 32 | t.equal(err.name, 'NotFoundError', 'properly deletes json encoded value') 33 | }) 34 | }) 35 | }) 36 | 37 | }) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /_test/mixed-value-encodings-writeStream-per-sub.js: -------------------------------------------------------------------------------- 1 | var levelup = require('level-test')() 2 | var base = require('../')(levelup('test-mixed-value-encodings-writeStream-sub')) 3 | 4 | var test = require('tape') 5 | 6 | test('subsections support mixed encodings per sub for write stream', function (t) { 7 | t.plan(6) 8 | 9 | var foo = base.sublevel('foo', { valueEncoding: 'utf8' }) 10 | var foos = foo.createWriteStream() 11 | 12 | foos.write({ key: 'foo1', value: 'foo1-value' }) 13 | foos.end() 14 | foos.on('close', function (err) { 15 | t.notOk(err, 'writing utf8 encoded stream has no error') 16 | 17 | foo.get('foo1', { valueEncoding: 'utf8' }, function (err, value) { 18 | t.notOk(err, 'getting string value by key has no error') 19 | t.equal(value, 'foo1-value', 'and returns value for that key') 20 | }) 21 | }) 22 | 23 | var bar = base.sublevel('bar', { valueEncoding: 'json' }) 24 | var bars = bar.createWriteStream() 25 | 26 | bars.write({ key: 'bar1', value: { obj: 'ect' } }) 27 | bars.end() 28 | bars.on('close', function (err) { 29 | t.notOk(err, 'writing utf8 encoded stream has no error') 30 | 31 | bar.get('bar1', { valueEncoding: 'json' }, function (err, value) { 32 | t.notOk(err, 'getting object value by key has no error') 33 | t.equal(value.obj, 'ect', 'and returns value for that key') 34 | }) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /_test/mixed-value-encodings-writeStream-per-write.js: -------------------------------------------------------------------------------- 1 | var levelup = require('level-test')() 2 | var base = require('../')(levelup('test-mixed-value-encodings-writeStream-write')) 3 | 4 | var test = require('tape') 5 | 6 | test('subsections support mixed encodings per write for write stream', function (t) { 7 | t.plan(6) 8 | 9 | var foo = base.sublevel('foo') 10 | var foos = foo.createWriteStream() 11 | 12 | foos.write({ key: 'foo1', value: 'foo1-value' }, { valueEncoding: 'utf8' }) 13 | foos.end() 14 | foos.on('close', function (err) { 15 | t.notOk(err, 'writing utf8 encoded stream has no error') 16 | 17 | foo.get('foo1', { valueEncoding: 'utf8' }, function (err, value) { 18 | t.notOk(err, 'getting string value by key has no error') 19 | t.equal(value, 'foo1-value', 'and returns value for that key') 20 | }) 21 | }) 22 | 23 | var bar = base.sublevel('bar') 24 | var bars = bar.createWriteStream() 25 | 26 | bars.write({ key: 'bar1', value: { obj: 'ect' }, valueEncoding: 'json' }) 27 | bars.end() 28 | bars.on('close', function (err) { 29 | t.notOk(err, 'writing utf8 encoded stream has no error') 30 | 31 | bar.get('bar1', { valueEncoding: 'json' }, function (err, value) { 32 | t.notOk(err, 'getting object value by key has no error') 33 | t.equal(value.obj, 'ect', 'and returns value for that key') 34 | }) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /_test/posthook-ranges.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var level = require('level-test')() 3 | var SubLevel = require('../') 4 | 5 | test('sublevel - batch', function (t) { 6 | 7 | var base = SubLevel(level('test-sublevel')) 8 | 9 | var lc = [], uc = [] 10 | 11 | base.post(/^[a-z]/, function (data) { 12 | lc.push(data.key) 13 | }) 14 | 15 | base.post(/^[A-Z]/, function (data) { 16 | uc.push(data.key) 17 | }) 18 | var n = 4 19 | 20 | base.put('thing', Math.random(), next) 21 | base.put('Thing', Math.random(), next) 22 | base.put('lalala', Math.random(), next) 23 | base.put('WHATEVER', Math.random(), next) 24 | 25 | function next () { 26 | if(--n) return 27 | t.deepEqual(lc.sort(), ['lalala', 'thing']) 28 | t.deepEqual(uc.sort(), ['Thing', 'WHATEVER']) 29 | t.end() 30 | } 31 | }) 32 | 33 | 34 | test('sublevel - post hook rang on sublevel', function (t) { 35 | 36 | var db = SubLevel(level('test-sublevel2')) 37 | var base = db.sublevel('stuff') 38 | 39 | var lc = [], uc = [] 40 | 41 | base.post(/^[a-z]/, function (data) { 42 | console.log('POST', data) 43 | lc.push(data.key) 44 | }) 45 | 46 | base.post(/^[A-Z]/, function (data) { 47 | uc.push(data.key) 48 | }) 49 | var n = 4 50 | 51 | base.put('thing', Math.random(), next) 52 | base.put('Thing', Math.random(), next) 53 | base.put('lalala', Math.random(), next) 54 | base.put('WHATEVER', Math.random(), next) 55 | 56 | function next () { 57 | if(--n) return 58 | t.deepEqual(lc.sort(), ['lalala', 'thing']) 59 | t.deepEqual(uc.sort(), ['Thing', 'WHATEVER']) 60 | t.end() 61 | } 62 | }) 63 | 64 | -------------------------------------------------------------------------------- /_test/prehook-batch.js: -------------------------------------------------------------------------------- 1 | var level = require('level-test')() 2 | var sublevel = require('../') 3 | var test = require('tape') 4 | 5 | function find(ary, test) { 6 | for(var i = 0; i < ary.length; i++) { 7 | if(test(ary[i], i, ary)) return ary[i] 8 | } 9 | } 10 | 11 | test('prehook can introspect whole batch', function (t) { 12 | 13 | var db = sublevel(level('introspect')) 14 | var logDb = db.sublevel('log') 15 | 16 | var didHaveLog = 0, didNotHaveLog = 0 17 | 18 | var prefix = logDb.prefix() 19 | 20 | db.pre(function (op, add, batch) { 21 | if(find(batch, function (_op) { 22 | return op.key == _op.value && _op.key.indexOf(prefix) === 0 23 | })) 24 | didHaveLog ++ 25 | else { 26 | add({key: Date.now(), value: op.key, type: 'put', prefix: logDb}) 27 | didNotHaveLog ++ 28 | } 29 | }) 30 | 31 | db.batch([ 32 | {key: 'foo', value: new Date(), type: 'put'}, 33 | {key: Date.now(), value: 'foo', type: 'put', prefix: logDb}, 34 | ], function (err) { 35 | if(err) console.error(err.stack) 36 | t.notOk(err, 'save did not error') 37 | t.ok(didHaveLog) 38 | t.end() 39 | }) 40 | 41 | }) 42 | 43 | test('prehook can introspect whole batch - when sublevel', function (t) { 44 | 45 | var db = sublevel(level('introspect2')).sublevel('main') 46 | var logDb = db.sublevel('log') 47 | 48 | var didHaveLog = 0, didNotHaveLog = 0 49 | var prefix = logDb.prefix() 50 | 51 | db.pre(function (op, add, batch) { 52 | if(find(batch, function (_op) { 53 | return op.key == _op.value && _op.key.indexOf(prefix) === 0 54 | })) 55 | didHaveLog ++ 56 | else { 57 | add({key: Date.now(), value: op.key, type: 'put', prefix: logDb}) 58 | didNotHaveLog ++ 59 | } 60 | }) 61 | 62 | db.batch([ 63 | {key: 'foo', value: new Date(), type: 'put'}, 64 | {key: Date.now(), value: 'foo', type: 'put', prefix: logDb}, 65 | ], function (err) { 66 | if(err) console.error(err.stack) 67 | t.notOk(err, 'save did not error') 68 | t.ok(didHaveLog) 69 | t.end() 70 | }) 71 | 72 | }) 73 | -------------------------------------------------------------------------------- /_test/prehook-insert.js: -------------------------------------------------------------------------------- 1 | var Sublevel = require('../') 2 | var level = require('level-test')() 3 | 4 | var tape = require('tape') 5 | 6 | tape('insert in prehook', function (t) { 7 | 8 | var base = Sublevel(level('test-sublevel')) 9 | 10 | Sublevel(base, { sep: '~' }) 11 | 12 | var a = base.sublevel('A') 13 | var b = base.sublevel('B') 14 | 15 | var as = {} 16 | var aas = {} 17 | 18 | a.pre(function (op, add) { 19 | as[op.key] = op.value 20 | console.log('A :', op) 21 | add({ 22 | key: op.key, value: op.value, 23 | type: 'put', prefix: b.prefix() 24 | }) 25 | }) 26 | 27 | var val = 'random_' + Math.random() 28 | a.put('foo', val, function () { 29 | 30 | b.get('foo', function (err, _val) { 31 | t.equal(_val, val) 32 | t.end() 33 | }) 34 | }) 35 | 36 | }) 37 | 38 | tape('insert in prehook 2', function (t) { 39 | 40 | var base = Sublevel(level('test-sublevel2')) 41 | 42 | Sublevel(base, '~') 43 | 44 | var a = base.sublevel('A') 45 | var b = base.sublevel('B') 46 | 47 | var as = {} 48 | var aas = {} 49 | 50 | a.pre(function (op, add) { 51 | as[op.key] = op.value 52 | console.log('A :', op) 53 | add({ 54 | key: op.key, value: op.value, 55 | type: 'put', prefix: b 56 | }) 57 | }) 58 | 59 | var val = 'random_' + Math.random() 60 | a.put('foo', val, function () { 61 | 62 | b.get('foo', function (err, _val) { 63 | t.equal(_val, val) 64 | t.end() 65 | }) 66 | }) 67 | 68 | }) 69 | 70 | 71 | tape('insert in prehook - encodings', function (t) { 72 | 73 | var base = Sublevel(level('test-sublevel3', {valueEncoding: 'json'})) 74 | 75 | Sublevel(base, '~') 76 | 77 | var b = base.sublevel('B', {valueEncoding: 'utf8'}) 78 | 79 | var as = {} 80 | // var aas = {} 81 | 82 | base.pre(function (op, add) { 83 | as[op.key] = op.value 84 | console.log('A :', op) 85 | add({ 86 | key: op.key, value: JSON.stringify({value: op.value}), 87 | type: 'put', prefix: b//, valueEncoding: 'utf8' 88 | }) 89 | }) 90 | 91 | var val = {'random': + Math.random()} 92 | base.put('foo', val, function (err) { 93 | if(err) throw err 94 | b.get('foo', function (err, _val) { 95 | console.log('GET', _val, val) 96 | t.deepEqual(JSON.parse(_val), {value: val}) 97 | t.end() 98 | }) 99 | }) 100 | 101 | }) 102 | 103 | 104 | -------------------------------------------------------------------------------- /batch.js: -------------------------------------------------------------------------------- 1 | function addOperation (type, key, value, options) { 2 | var operation = { 3 | type: type, 4 | key: key, 5 | value: value, 6 | options: options 7 | } 8 | 9 | if (options && options.prefix) { 10 | operation.prefix = options.prefix 11 | delete options.prefix 12 | } 13 | 14 | this._operations.push(operation) 15 | 16 | return this 17 | } 18 | 19 | function Batch(sdb) { 20 | this._operations = [] 21 | this._sdb = sdb 22 | 23 | this.put = addOperation.bind(this, 'put') 24 | this.del = addOperation.bind(this, 'del') 25 | } 26 | 27 | var B = Batch.prototype 28 | 29 | 30 | B.clear = function () { 31 | this._operations = [] 32 | } 33 | 34 | B.write = function (cb) { 35 | this._sdb.batch(this._operations, cb) 36 | } 37 | 38 | module.exports = Batch 39 | -------------------------------------------------------------------------------- /bytewise.js: -------------------------------------------------------------------------------- 1 | var nut = require('./nut') 2 | var shell = require('./shell') //the shell surrounds the nut 3 | var Codec = require('level-codec') 4 | var merge = require('xtend') 5 | var compare = require('typewiselite') 6 | var ReadStream = require('./read-stream') 7 | 8 | var precodec = require('./codec/bytewise') 9 | 10 | function id (e) { 11 | return e 12 | } 13 | 14 | module.exports = function (db, opts) { 15 | 16 | opts = merge(db.options, { 17 | keyEncoding: { 18 | encode: id, 19 | decode: id, 20 | buffer: true 21 | } 22 | }, opts) 23 | 24 | return shell ( 25 | nut ( db, precodec, new Codec, compare ), 26 | [], ReadStream, opts 27 | ) 28 | } 29 | 30 | 31 | -------------------------------------------------------------------------------- /codec/bytewise.js: -------------------------------------------------------------------------------- 1 | var bytewise = require('bytewise') 2 | 3 | module.exports = { 4 | encode: bytewise.encode, 5 | decode: bytewise.decode, 6 | lowerBound: null, 7 | upperBound: undefined, 8 | buffer: true 9 | } 10 | -------------------------------------------------------------------------------- /codec/index.js: -------------------------------------------------------------------------------- 1 | 2 | //define the key ordering for level-sublevelq 3 | 4 | //var join = '\x01', separate = '\x00' 5 | var join = '#', separate = '!' 6 | 7 | exports.encode = function (e) { 8 | return separate + e[0].join(join) + separate + e[1] 9 | } 10 | 11 | exports.decode = function (s) { 12 | var i = s.indexOf(separate, 1) 13 | return [s.substring(1, i).split(join).filter(Boolean), s.substring(++i)] 14 | } 15 | 16 | exports.buffer = false 17 | 18 | exports.lowerBound = '\x00' 19 | exports.upperBound = '\uffff' 20 | 21 | -------------------------------------------------------------------------------- /codec/legacy.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | encode: function (e) { 3 | var s = ''; 4 | var prefix = e[0].slice() 5 | while(prefix.length) { 6 | s += '\xff' + prefix.shift().toString() + '\xff' 7 | } 8 | return s + (e[1] || '').toString() 9 | }, 10 | decode: function (e) { 11 | var k = e.toString().split('\xff').filter(Boolean) 12 | var j = k.pop() 13 | return [k, j] 14 | }, 15 | lowerBound: '\x00', 16 | upperBound: '\xff' 17 | } 18 | 19 | -------------------------------------------------------------------------------- /examples/queue.js: -------------------------------------------------------------------------------- 1 | var shasum = require('shasum') 2 | 3 | //if a job starts, and another is queued before the current job ends, 4 | //delay it, so that the job is only triggered once. 5 | 6 | 7 | module.exports = function (input, jobs, map, work) { 8 | if(!work) work = map, map = function (data) { return data.key } 9 | //create a subsection for the jobs, 10 | //if you don't pass in a separate db, 11 | //create a section inside the input 12 | var pending = {}, running = {} 13 | 14 | if('string' === typeof jobs) 15 | jobs = input.sublevel(jobs) 16 | 17 | var retry = [] 18 | 19 | function doJob (data) { 20 | //don't process deletes! 21 | if(!data.value) return 22 | var hash = shasum(data.value) 23 | 24 | if(!running[hash]) 25 | running[hash] = true 26 | else return 27 | 28 | var done = false 29 | 30 | work(data.value, function (err) { 31 | if(done) return 32 | done = true 33 | if(err) { 34 | running[hash] 35 | return setTimeout(function () { 36 | doJob(data) 37 | }, 500) 38 | } 39 | 40 | jobs.del(data.key, function (err) { 41 | if(err) return retry.push(data) 42 | delete running[hash] 43 | if(pending[hash]) { 44 | delete pending[hash] 45 | doJob(data) 46 | } 47 | }) 48 | }) 49 | } 50 | 51 | input.pre(function (ch, add) { 52 | var key = map(ch) 53 | var hash = shasum(key) 54 | console.log('KEY', key) 55 | if(!pending[hash]) 56 | add({key: Date.now(), value: key, type: 'put'}, jobs) 57 | else 58 | pending[hash] = (0 || pending[hash]) + 1 59 | }) 60 | 61 | jobs.createReadStream().on('data', doJob) 62 | jobs.post(doJob) 63 | 64 | return jobs 65 | } 66 | 67 | -------------------------------------------------------------------------------- /examples/trigger.js: -------------------------------------------------------------------------------- 1 | 2 | var db = require('../')(require('levelup')('/tmp/whatever')) 3 | var copy = require('../')(require('levelup')('/tmp/whatever2')) 4 | 5 | var makeQueue = require('./queue') 6 | 7 | makeQueue(db, 'jobs', function (key, done) { 8 | console.log("JOB KEY", key) 9 | db.get(key, function (err, value) { 10 | console.log(key, value, err) 11 | value && copy.put(key, value, done) || done() 12 | }) 13 | }) 14 | 15 | db.put('hello' + Date.now(), 'value_' + new Date(), function () { 16 | 17 | setTimeout(function () { 18 | 19 | copy 20 | .createReadStream() 21 | .on('data', console.log) 22 | 23 | }, 1000) 24 | 25 | }) 26 | 27 | -------------------------------------------------------------------------------- /hooks.js: -------------------------------------------------------------------------------- 1 | var inRange = require('./range') 2 | 3 | module.exports = function (compare) { 4 | var hooks = [] 5 | 6 | return { 7 | add: function (range, hook) { 8 | var m = {range: range, hook: hook} 9 | hooks.push(m) 10 | //call this to remove 11 | return function () { 12 | var i = hooks.indexOf(m) 13 | if(~i) return hooks.splice(i, 1) 14 | } 15 | 16 | }, 17 | 18 | //remove all listeners within a range. 19 | //this will be used to close a sublevel. 20 | removeAll: function (range) { 21 | throw new Error('not implemented') 22 | }, 23 | 24 | trigger: function (key, args) { 25 | for(var i = 0; i < hooks.length; i++) { 26 | var test = hooks[i] 27 | if(inRange(test.range, key, compare)) 28 | test.hook.apply(this, args) 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | var nut = require('./nut') 5 | var shell = require('./shell') //the shell surrounds the nut 6 | var precodec = require('./codec') 7 | var Codec = require('level-codec') 8 | var merge = require('xtend') 9 | 10 | var ReadStream = require('./read-stream') 11 | 12 | var sublevel = function (db, opts) { 13 | opts = merge(db.options, opts) 14 | return shell ( nut ( db, precodec, new Codec ), [], ReadStream, opts) 15 | } 16 | 17 | module.exports = function (db, opts) { 18 | if (typeof db.sublevel === 'function' && typeof db.clone === 'function') return db.clone(opts) 19 | return sublevel(db, opts) 20 | } 21 | -------------------------------------------------------------------------------- /legacy.js: -------------------------------------------------------------------------------- 1 | var nut = require('./nut') 2 | var shell = require('./shell') //the shell surrounds the nut 3 | var Codec = require('level-codec') 4 | var merge = require('xtend') 5 | 6 | var ReadStream = require('./read-stream') 7 | 8 | var precodec = require('./codec/legacy') 9 | 10 | module.exports = function (db, opts) { 11 | 12 | opts = merge(db.options, opts) 13 | 14 | return shell ( nut ( db, precodec, new Codec ), [], ReadStream, db.options) 15 | 16 | } 17 | 18 | -------------------------------------------------------------------------------- /nut.js: -------------------------------------------------------------------------------- 1 | var hooks = require('./hooks') 2 | var ltgt = require('ltgt') 3 | 4 | function isFunction (f) { 5 | return 'function' === typeof f 6 | } 7 | 8 | function getPrefix (db) { 9 | if(db == null) return db 10 | if(isFunction(db.prefix)) return db.prefix() 11 | return db 12 | } 13 | 14 | function has(obj, name) { 15 | return Object.hasOwnProperty.call(obj, name) 16 | } 17 | 18 | function clone (_obj) { 19 | var obj = {} 20 | for(var k in _obj) 21 | obj[k] = _obj[k] 22 | return obj 23 | } 24 | 25 | module.exports = function (db, precodec, codec, compare) { 26 | var prehooks = hooks(compare) 27 | var posthooks = hooks(compare) 28 | var waiting = [], ready = false 29 | 30 | function encodePrefix(prefix, key, opts1, opts2) { 31 | return precodec.encode([ prefix, codec.encodeKey(key, opts1, opts2 ) ]) 32 | } 33 | 34 | function decodePrefix(data) { 35 | return precodec.decode(data) 36 | } 37 | 38 | function addEncodings(op, prefix) { 39 | if(prefix && prefix.options) { 40 | op.keyEncoding = 41 | op.keyEncoding || prefix.options.keyEncoding 42 | op.valueEncoding = 43 | op.valueEncoding || prefix.options.valueEncoding 44 | } 45 | return op 46 | } 47 | 48 | function start () { 49 | ready = true 50 | while(waiting.length) 51 | waiting.shift()() 52 | } 53 | 54 | if(isFunction(db.isOpen)) { 55 | if(db.isOpen()) 56 | ready = true 57 | else 58 | db.open(start) 59 | } else { 60 | db.open(start) 61 | } 62 | 63 | return { 64 | location: db.location, 65 | apply: function (ops, opts, cb) { 66 | //apply prehooks here. 67 | for(var i = 0; i < ops.length; i++) { 68 | var op = ops[i] 69 | 70 | function add(op) { 71 | if(op === false) return delete ops[i] 72 | ops.push(op) 73 | } 74 | 75 | addEncodings(op, op.prefix) 76 | op.prefix = getPrefix(op.prefix) 77 | prehooks.trigger([op.prefix, op.key], [op, add, ops]) 78 | } 79 | 80 | opts = opts || {} 81 | 82 | if('object' !== typeof opts) throw new Error('opts must be object, was:'+ opts) 83 | 84 | if('function' === typeof opts) cb = opts, opts = {} 85 | 86 | if(ops.length) 87 | (db.db || db).batch( 88 | ops.map(function (op) { 89 | return { 90 | key: encodePrefix(op.prefix, op.key, opts, op), 91 | value: 92 | op.type !== 'del' 93 | ? codec.encodeValue( 94 | op.value, 95 | opts, 96 | op 97 | ) 98 | : undefined, 99 | type: 100 | op.type || (op.value === undefined ? 'del' : 'put') 101 | } 102 | }), 103 | opts, 104 | function (err) { 105 | if(err) return cb(err) 106 | ops.forEach(function (op) { 107 | posthooks.trigger([op.prefix, op.key], [op]) 108 | }) 109 | cb() 110 | } 111 | ) 112 | else 113 | cb() 114 | }, 115 | get: function (key, prefix, opts, cb) { 116 | opts.asBuffer = codec.valueAsBuffer(opts) 117 | return (db.db || db).get( 118 | encodePrefix(prefix, key, opts), 119 | opts, 120 | function (err, value) { 121 | if(err) cb(err) 122 | else cb(null, codec.decodeValue(value, opts)) 123 | } 124 | ) 125 | }, 126 | pre: prehooks.add, 127 | post: posthooks.add, 128 | createDecoder: function (opts) { 129 | if(opts.keys !== false && opts.values !== false) 130 | return function (key, value) { 131 | return { 132 | key: codec.decodeKey(precodec.decode(key)[1], opts), 133 | value: codec.decodeValue(value, opts) 134 | } 135 | } 136 | if(opts.values !== false) 137 | return function (_, value) { 138 | return codec.decodeValue(value, opts) 139 | } 140 | if(opts.keys !== false) 141 | return function (key) { 142 | return codec.decodeKey(precodec.decode(key)[1], opts) 143 | } 144 | return function () {} 145 | }, 146 | isOpen: function isOpen() { 147 | if (db.db && isFunction(db.db.isOpen)) 148 | return db.db.isOpen() 149 | 150 | return db.isOpen() 151 | }, 152 | isClosed: function isClosed() { 153 | if (db.db && isFunction(db.db.isClosed)) 154 | return db.db.isClosed() 155 | 156 | return db.isClosed() 157 | }, 158 | close: function close (cb) { 159 | return db.close(cb) 160 | }, 161 | iterator: function (_opts, cb) { 162 | var opts = clone(_opts || {}) 163 | var prefix = _opts.prefix || [] 164 | 165 | function encodeKey(key) { 166 | return encodePrefix(prefix, key, opts, {}) 167 | } 168 | 169 | ltgt.toLtgt(_opts, opts, encodeKey, precodec.lowerBound, precodec.upperBound) 170 | 171 | // if these legacy values are in the options, remove them 172 | 173 | opts.prefix = null 174 | 175 | //************************************************ 176 | //hard coded defaults, for now... 177 | //TODO: pull defaults and encoding out of levelup. 178 | opts.keyAsBuffer = opts.valueAsBuffer = false 179 | //************************************************ 180 | 181 | 182 | //this is vital, otherwise limit: undefined will 183 | //create an empty stream. 184 | if ('number' !== typeof opts.limit) 185 | opts.limit = -1 186 | 187 | opts.keyAsBuffer = precodec.buffer 188 | opts.valueAsBuffer = codec.valueAsBuffer(opts) 189 | 190 | function wrapIterator (iterator) { 191 | return { 192 | next: function (cb) { 193 | return iterator.next(cb) 194 | }, 195 | end: function (cb) { 196 | iterator.end(cb) 197 | } 198 | } 199 | } 200 | 201 | if(ready) 202 | return wrapIterator((db.db || db).iterator(opts)) 203 | else 204 | waiting.push(function () { 205 | cb(null, wrapIterator((db.db || db).iterator(opts))) 206 | }) 207 | 208 | } 209 | } 210 | 211 | } 212 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "level-sublevel", 3 | "description": "partition levelup databases", 4 | "version": "6.6.4", 5 | "homepage": "https://github.com/dominictarr/level-sublevel", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/dominictarr/level-sublevel.git" 9 | }, 10 | "dependencies": { 11 | "bytewise": "~1.1.0", 12 | "level-codec": "^9.0.0", 13 | "level-errors": "^2.0.0", 14 | "level-iterator-stream": "^2.0.3", 15 | "ltgt": "~2.1.1", 16 | "pull-defer": "^0.2.2", 17 | "pull-level": "^2.0.3", 18 | "pull-stream": "^3.6.8", 19 | "typewiselite": "~1.0.0", 20 | "xtend": "~4.0.0" 21 | }, 22 | "devDependencies": { 23 | "level": "^3.0.1", 24 | "level-test": "^3.0.0", 25 | "monotonic-timestamp": "0.0.8", 26 | "rimraf": "~2.1.4", 27 | "shasum": "0.0.2", 28 | "stream-to-pull-stream": "~1.2.0", 29 | "tape": "^4.9.1", 30 | "through": "~2.3.4" 31 | }, 32 | "scripts": { 33 | "test": "node test-runner.js" 34 | }, 35 | "author": "Dominic Tarr (http://dominictarr.com)", 36 | "license": "MIT", 37 | "stability": "unstable", 38 | "testling": { 39 | "files": "test/*.js", 40 | "browsers": [ 41 | "ie/8..latest", 42 | "firefox/17..latest", 43 | "firefox/nightly", 44 | "chrome/22..latest", 45 | "chrome/canary", 46 | "opera/12..latest", 47 | "opera/next", 48 | "safari/5.1..latest", 49 | "ipad/6.0..latest", 50 | "iphone/6.0..latest", 51 | "android-browser/4.2..latest" 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /pull.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var pull = require('pull-stream') 4 | var defer = require('pull-defer/source') 5 | // Currently this uses pull streams, 6 | // and not levelup's readstream, but in theory 7 | // I should be able pretty much just drop that in. 8 | 9 | module.exports = function pullReadStream (options, makeData) { 10 | var stream = defer() 11 | 12 | stream.setIterator = function (iterator) { 13 | stream.resolve(function (end, cb) { 14 | if(!end) iterator.next(function (err, key, value) { 15 | if(err) return cb(err) 16 | if(key === undefined || value === undefined) 17 | return cb(true) 18 | cb(null, makeData(key, value)) 19 | }) 20 | else 21 | iterator.end(cb) 22 | }) 23 | } 24 | 25 | return stream 26 | } 27 | -------------------------------------------------------------------------------- /range.js: -------------------------------------------------------------------------------- 1 | var ltgt = require('ltgt') 2 | 3 | //compare two array items 4 | function isArrayLike (a) { 5 | return Array.isArray(a) || Buffer.isBuffer(a) 6 | } 7 | 8 | function isPrimitive (a) { 9 | return 'string' === typeof a || 'number' === typeof a 10 | } 11 | 12 | function has(o, k) { 13 | return Object.hasOwnProperty.call(o, k) 14 | } 15 | 16 | function compare (a, b) { 17 | if(isArrayLike(a) && isArrayLike(b)) { 18 | var l = Math.min(a.length, b.length) 19 | for(var i = 0; i < l; i++) { 20 | var c = compare(a[i], b[i]) 21 | if(c) return c 22 | } 23 | return a.length - b.length 24 | } 25 | if(isPrimitive(a) && isPrimitive(b)) 26 | return a < b ? -1 : a > b ? 1 : 0 27 | 28 | throw new Error('items not comparable:' 29 | + JSON.stringify(a) + ' ' + JSON.stringify(b)) 30 | } 31 | 32 | //this assumes that the prefix is of the form: 33 | // [Array, string] 34 | 35 | function prefix (a, b) { 36 | if(a.length > b.length) return false 37 | var l = a.length - 1 38 | var lastA = a[l] 39 | var lastB = b[l] 40 | 41 | if(typeof lastA !== typeof lastB) 42 | return false 43 | 44 | if('string' == typeof lastA 45 | && 0 != lastB.indexOf(lastA)) 46 | return false 47 | 48 | //handle cas where there is no key prefix 49 | //(a hook on an entire sublevel) 50 | if(a.length == 1 && isArrayLike(lastA)) l ++ 51 | 52 | while(l--) { 53 | if(compare(a[l], b[l])) return false 54 | } 55 | return true 56 | } 57 | 58 | exports = module.exports = function (range, key, _compare) { 59 | _compare = _compare || compare 60 | //handle prefix specially, 61 | //check that everything up to the last item is equal 62 | //then check the last item starts with 63 | if(isArrayLike(range)) return prefix(range, key) 64 | 65 | return ltgt.contains(range, key, _compare) 66 | } 67 | 68 | function addPrefix(prefix, range) { 69 | var o = ltgt.toLtgt(range, null, function (key) { 70 | return [prefix, key] 71 | }) 72 | 73 | //if there where no ranges, then then just use a prefix. 74 | if(!has(o, 'gte') && !has(o, 'lte')) return [prefix] 75 | 76 | return o 77 | } 78 | 79 | exports.compare = compare 80 | exports.prefix = prefix 81 | exports.addPrefix = addPrefix 82 | -------------------------------------------------------------------------------- /read-stream.js: -------------------------------------------------------------------------------- 1 | var IteratorStream = require('level-iterator-stream'); 2 | var inherits = require('util').inherits; 3 | var EncodingError = require('level-errors').EncodingError; 4 | 5 | function wrapIterator(it, options, makeData) { 6 | return { 7 | next: function (callback) { 8 | it.next(function(err, key, value) { 9 | if (err) { 10 | return callback(err); 11 | } 12 | if (key === undefined && value === undefined) { 13 | return callback(err, key, value); 14 | } 15 | var data; 16 | try { 17 | data = makeData(key, value); 18 | } catch (err) { 19 | return callback(new EncodingError(err)); 20 | } 21 | if (options.keys !== false && options.values === false) { 22 | return callback(err, data, value); 23 | } 24 | if (options.keys === false && options.values !== false) { 25 | return callback(err, key, data); 26 | } 27 | return callback(err, data.key, data.value); 28 | }); 29 | }, 30 | end: function end(callback) { 31 | return it.end(callback); 32 | } 33 | } 34 | } 35 | 36 | function ReadStream (options, makeData) { 37 | if (!(this instanceof ReadStream)) { 38 | return new ReadStream(options, makeData); 39 | } 40 | 41 | IteratorStream.call(this, null, options); 42 | 43 | this._waiting = false; 44 | this._makeData = makeData; 45 | } 46 | 47 | inherits(ReadStream, IteratorStream) 48 | 49 | ReadStream.prototype.setIterator = function (it) { 50 | this._iterator = wrapIterator(it, this._options, this._makeData); 51 | if (this.destroyed) { 52 | this._iterator.end(function () {}); 53 | return; 54 | } 55 | if (this._waiting) { 56 | this._waiting = false 57 | this._read(); 58 | return; 59 | } 60 | }; 61 | 62 | ReadStream.prototype._read = function () { 63 | if (!this._iterator) { 64 | this._waiting = true; 65 | return; 66 | } 67 | IteratorStream.prototype._read.call(this); 68 | } 69 | 70 | module.exports = ReadStream 71 | 72 | 73 | -------------------------------------------------------------------------------- /shell.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter 2 | var addpre = require('./range').addPrefix 3 | 4 | var errors = require('level-errors') 5 | 6 | function isFunction (f) { 7 | return 'function' === typeof f 8 | } 9 | 10 | function isString (s) { 11 | return 'string' === typeof s 12 | } 13 | 14 | function isObject (o) { 15 | return o && 'object' === typeof o 16 | } 17 | 18 | var version = require('./package.json').version 19 | 20 | var sublevel = module.exports = function (nut, prefix, createStream, options) { 21 | var emitter = new EventEmitter() 22 | emitter.sublevels = {} 23 | emitter.options = options 24 | 25 | emitter.version = version 26 | 27 | emitter.methods = {} 28 | prefix = prefix || [] 29 | 30 | function errback (err) { if (err) emitter.emit('error', err) } 31 | 32 | createStream = createStream || function (e) { return e } 33 | 34 | function mergeOpts(opts) { 35 | var o = {} 36 | if(options) 37 | for(var k in options) 38 | if(options[k] != undefined)o[k] = options[k] 39 | if(opts) 40 | for(var k in opts) 41 | if(opts[k] != undefined) o[k] = opts[k] 42 | return o 43 | } 44 | 45 | emitter.put = function (key, value, opts, cb) { 46 | if('function' === typeof opts) cb = opts, opts = {} 47 | if(!cb) cb = errback 48 | 49 | nut.apply([{ 50 | key: key, value: value, 51 | prefix: prefix.slice(), type: 'put' 52 | }], mergeOpts(opts), function (err) { 53 | if(!err) { emitter.emit('put', key, value); cb(null) } 54 | if(err) return cb(err) 55 | }) 56 | } 57 | 58 | emitter.prefix = function () { 59 | return prefix.slice() 60 | } 61 | 62 | emitter.del = function (key, opts, cb) { 63 | if('function' === typeof opts) cb = opts, opts = {} 64 | if(!cb) cb = errback 65 | 66 | nut.apply([{ 67 | key: key, 68 | prefix: prefix.slice(), type: 'del' 69 | }], mergeOpts(opts), function (err) { 70 | if(!err) { emitter.emit('del', key); cb(null) } 71 | if(err) return cb(err) 72 | }) 73 | } 74 | 75 | emitter.batch = function (ops, opts, cb) { 76 | if('function' === typeof opts) 77 | cb = opts, opts = {} 78 | if(!cb) cb = errback 79 | 80 | ops = ops.map(function (op) { 81 | return { 82 | key: op.key, 83 | value: op.value, 84 | prefix: op.prefix || prefix, 85 | keyEncoding: op.keyEncoding, // * 86 | valueEncoding: op.valueEncoding, // * (TODO: encodings on sublevel) 87 | type: op.type 88 | } 89 | }) 90 | 91 | nut.apply(ops, mergeOpts(opts), function (err) { 92 | if(!err) { emitter.emit('batch', ops); cb(null) } 93 | if(err) return cb(err) 94 | }) 95 | } 96 | 97 | emitter.get = function (key, opts, cb) { 98 | if('function' === typeof opts) 99 | cb = opts, opts = {} 100 | nut.get(key, prefix, mergeOpts(opts), function (err, value) { 101 | if(err) cb(new errors.NotFoundError('Key not found in database', err)) 102 | else cb(null, value) 103 | }) 104 | } 105 | 106 | emitter.clone = function(opts) { 107 | return sublevel(nut, prefix, createStream, mergeOpts(opts)) 108 | } 109 | 110 | emitter.sublevel = function (name, opts) { 111 | return emitter.sublevels[name] = 112 | emitter.sublevels[name] || sublevel(nut, prefix.concat(name), createStream, mergeOpts(opts)) 113 | } 114 | 115 | emitter.pre = function (key, hook) { 116 | if(isFunction(key)) return nut.pre([prefix], key) 117 | if(isString(key)) return nut.pre([prefix, key], hook) 118 | if(isObject(key)) return nut.pre(addpre(prefix, key), hook) 119 | 120 | throw new Error('not implemented yet') 121 | } 122 | 123 | emitter.post = function (key, hook) { 124 | if(isFunction(key)) return nut.post([prefix], key) 125 | if(isString(key)) return nut.post([prefix, key], hook) 126 | if(isObject(key)) return nut.post(addpre(prefix, key), hook) 127 | 128 | //TODO: handle ranges, needed for level-live-stream, etc. 129 | throw new Error('not implemented yet') 130 | } 131 | 132 | emitter.readStream = 133 | emitter.createReadStream = function (opts) { 134 | opts = mergeOpts(opts) 135 | opts.prefix = prefix 136 | var stream 137 | var it = nut.iterator(opts, function (err, it) { 138 | stream.setIterator(it) 139 | }) 140 | 141 | stream = createStream(opts, nut.createDecoder(opts)) 142 | if(it) stream.setIterator(it) 143 | 144 | return stream 145 | } 146 | 147 | emitter.valueStream = 148 | emitter.createValueStream = function (opts) { 149 | opts = opts || {} 150 | opts.values = true 151 | opts.keys = false 152 | return emitter.createReadStream(opts) 153 | } 154 | 155 | emitter.keyStream = 156 | emitter.createKeyStream = function (opts) { 157 | opts = opts || {} 158 | opts.values = false 159 | opts.keys = true 160 | return emitter.createReadStream(opts) 161 | } 162 | 163 | emitter.close = function (cb) { 164 | //TODO: deregister all hooks 165 | cb = cb || function () {} 166 | if (!prefix.length) nut.close(cb) 167 | else process.nextTick(cb) 168 | } 169 | 170 | emitter.isOpen = nut.isOpen 171 | emitter.isClosed = nut.isClosed 172 | 173 | emitter.location = nut.location 174 | 175 | return emitter 176 | } 177 | -------------------------------------------------------------------------------- /test-runner.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | var childProcess = require("child_process"); 3 | 4 | var files = fs.readdirSync("test"); 5 | for (var i = 0; i < files.length; i++) { 6 | childProcess.execSync("node test/" + files[i], {stdio: "inherit"}); 7 | } 8 | -------------------------------------------------------------------------------- /test/close.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape'); 2 | 3 | var sublevel = require('../'); 4 | var level = require('level-test')() 5 | 6 | tape('can call close on root sublevel', function (t) { 7 | var _db = level('level-sublevel-root-close') 8 | var db = sublevel(_db) 9 | 10 | _db.once('open', function () { 11 | db.close(function (err) { 12 | t.error(err) 13 | 14 | t.ok(_db.isClosed()) 15 | t.end() 16 | }) 17 | }) 18 | }) 19 | 20 | tape('can call close on sub sublevel', function (t) { 21 | var _db = level('level-sublevel-sub-close') 22 | var db = sublevel(_db) 23 | 24 | _db.once('open', function () { 25 | db.sublevel('foo').close(function (err) { 26 | t.error(err) 27 | 28 | t.notOk(_db.isClosed()) 29 | t.end() 30 | }) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /test/codec.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | 3 | var expected = [ 4 | [[], 'foo'], 5 | [['foo'], 'bar'], 6 | [['foo', 'bar'], 'baz'], 7 | [['foo', 'bar'], 'blerg'], 8 | [['foobar'], 'barbaz'], 9 | ] 10 | 11 | //compare two array items 12 | function compare (a, b) { 13 | if(Array.isArray(a) && Array.isArray(b)) { 14 | var l = Math.min(a.length, b.length) 15 | for(var i = 0; i < l; i++) { 16 | var c = compare(a[i], b[i]) 17 | if(c) return c 18 | } 19 | return a.length - b.length 20 | } 21 | if('string' == typeof a && 'string' == typeof b) 22 | return a < b ? -1 : a > b ? 1 : 0 23 | 24 | throw new Error('items not comparable:' 25 | + JSON.stringify(a) + ' ' + JSON.stringify(b)) 26 | } 27 | 28 | function random () { 29 | return Math.random() - 0.5 30 | } 31 | 32 | module.exports = function (format) { 33 | 34 | var encoded = expected.map(format.encode) 35 | 36 | tape('ordering', function (t) { 37 | 38 | expected.sort(compare) 39 | 40 | var actual = 41 | expected.slice() 42 | .sort(random) 43 | .map(format.encode) 44 | .sort() 45 | .map(format.decode) 46 | 47 | console.log(actual) 48 | 49 | t.deepEqual(actual, expected) 50 | 51 | t.end() 52 | }) 53 | 54 | 55 | tape('ranges', function (t) { 56 | 57 | function gt (a, b, i, j) { 58 | t.equal(a > b, i > j, a + ' gt ' + b + '==' + i > j) 59 | } 60 | 61 | function gte (a, b, i, j) { 62 | t.equal(a >= b, i >= j, a + ' gte ' + b + '==' + i >= j) 63 | } 64 | 65 | function lt (a, b, i, j) { 66 | t.equal(a < b, i < j, a + ' lt ' + b + '==' + i < j) 67 | } 68 | 69 | function lte (a, b, i, j) { 70 | t.equal(a <= b, i <= j, a + ' lte ' + b + '==' + i <= j) 71 | } 72 | 73 | function check(j, cmp) { 74 | var item = encoded[j] 75 | for(var i = 0; i < expected.length; i++) { 76 | //first check less than. 77 | cmp(item, encoded[i], j, i) 78 | } 79 | } 80 | 81 | for(var i = 0; i < expected.length; i++) { 82 | check(i, gt) 83 | check(i, gte) 84 | check(i, lt) 85 | check(i, lte) 86 | } 87 | 88 | t.end() 89 | }) 90 | } 91 | 92 | module.exports(require('../codec')) 93 | 94 | -------------------------------------------------------------------------------- /test/errors.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | 3 | var sublevel = require('../') 4 | var level = require('level-test')() 5 | 6 | tape('not found error', function (t) { 7 | 8 | var db = sublevel(level('level-sublevel-notfound')) 9 | 10 | db.get('foo', function (err, value) { 11 | t.ok(err) 12 | t.notOk(value) 13 | t.equal(err.name, 'NotFoundError') 14 | t.end() 15 | }) 16 | 17 | }) 18 | -------------------------------------------------------------------------------- /test/falsey-value.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | var sublevel = require('../bytewise') 3 | var level = require('level-test')() 4 | 5 | var falsies = [ 6 | 0, null, false, '' 7 | ] 8 | 9 | var names = [ 10 | 'zero', 'null', 'false', 'emptystring' 11 | ] 12 | 13 | var db = sublevel( 14 | level('level-sublevel-falsey', {valueEncoding: 'json'}) 15 | ) 16 | 17 | falsies.forEach(function (falsey, i) { 18 | 19 | tape('allow falsey value:' + JSON.stringify(falsey), 20 | function (t) { 21 | 22 | db.put('foo', falsey, function (err) { 23 | if(err) throw err 24 | db.get('foo', function (err, value) { 25 | t.deepEqual(value, falsey) 26 | t.end() 27 | }) 28 | }) 29 | }) 30 | 31 | tape('allow falsey value in key', function (t) { 32 | var sdb = db.sublevel(names[i]) 33 | sdb.put(falsey, {index: i}, function (err) { 34 | if(err) throw err 35 | sdb.createReadStream({gte: falsey}) 36 | //this will error if the stream returns more than one item 37 | //which it shouldn't. 38 | .on('data', function (op) { 39 | t.equal(op.key, falsey) 40 | t.deepEqual(op.value, {index: i}) 41 | t.end() 42 | }) 43 | }) 44 | }) 45 | }) 46 | 47 | 48 | -------------------------------------------------------------------------------- /test/hooks.js: -------------------------------------------------------------------------------- 1 | var levelup = require('level-test')() 2 | 3 | var base = require('../')(levelup('test-sublevels', {valueEncoding: 'json'})) 4 | 5 | var test = require('tape') 6 | 7 | test('subsections', function (t) { 8 | 9 | var foo = base.sublevel('foo') 10 | var bar = base.sublevel('bar') 11 | 12 | var n, m, o = m = n = 0 13 | var q, r = q = 0 14 | 15 | foo.post(function (op) { 16 | n ++ 17 | }) 18 | 19 | //this should do the same 20 | foo.post({}, function (op) { 21 | m ++ 22 | }) 23 | 24 | foo.post({gte: 'm'}, function (op) { 25 | o ++ 26 | }) 27 | 28 | foo.pre(function (op) { 29 | t.equal(op.type, 'put') 30 | q ++ 31 | }) 32 | 33 | base.pre(function (op) { 34 | t.equal(op.type, 'put') 35 | r ++ 36 | }) 37 | 38 | base.batch([ 39 | { key: 'a', value: 1, type: 'put', prefix: foo }, 40 | { key: 'k', value: 2, type: 'put', prefix: foo }, 41 | { key: 'q', value: 3, type: 'put', prefix: foo }, 42 | { key: 'z', value: 4, type: 'put', prefix: foo }, 43 | //into the main base 44 | { key: 'b', value: 5, type: 'put'}, 45 | { key: 'b', value: 5, type: 'put', prefix: bar} 46 | ], function (err) { 47 | t.equal(n, 4) 48 | t.equal(m, 4) 49 | t.equal(o, 2) 50 | t.equal(q, 4) 51 | t.equal(r, 1) 52 | 53 | t.end() 54 | }) 55 | 56 | }) 57 | 58 | 59 | -------------------------------------------------------------------------------- /test/inherit-encodings.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var tape = require('tape') 4 | 5 | var sublevel = require('../') 6 | var level = require('level-test')() 7 | 8 | tape('inherit json encoding', function (t) { 9 | 10 | var db = sublevel(level('simple', {valueEncoding: 'json'})) 11 | db.put('hello', {ok: true}, function (err) { 12 | if(err) throw err 13 | 14 | db.get('hello', function (err, value) { 15 | if(err) throw err 16 | 17 | t.deepEqual(value, {ok: true}) 18 | var db2 = db.sublevel('sub') 19 | db2.put('hello', {ok: true}, function (err) { 20 | if(err) throw err 21 | 22 | db2.get('hello', function (err, value) { 23 | if(err) throw err 24 | t.deepEqual(value, {ok: true}) 25 | t.end() 26 | }) 27 | }) 28 | }) 29 | }) 30 | }) 31 | 32 | tape('override json encoding', function (t) { 33 | var db = sublevel(level('level-sublevel_override', {valueEncoding: 'json'})) 34 | var buf = new Buffer([1,2,3,4]) 35 | 36 | db.put('hello', buf, function (err) { 37 | if(err) throw err 38 | 39 | db.get('hello', function (err, value) { 40 | if(err) throw err 41 | 42 | t.deepEqual(value.data || value, [].slice.call(buf)) 43 | var db2 = db.sublevel('sub', {valueEncoding: 'binary'}) 44 | db2.put('hello', buf, function (err) { 45 | if(err) throw err 46 | 47 | db2.get('hello', function (err, value) { 48 | if(err) throw err 49 | t.deepEqual(value, buf) 50 | t.end() 51 | }) 52 | }) 53 | }) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /test/is-open.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape'); 2 | 3 | var sublevel = require('../'); 4 | var level = require('level-test')() 5 | 6 | tape('can call isOpen & isClosed', function (t) { 7 | var _db = level('level-sublevel-is-open') 8 | var db = sublevel(_db) 9 | 10 | t.equal(db.isOpen(), false) 11 | t.equal(db.isClosed(), false) 12 | 13 | _db.once('open', function () { 14 | t.equal(db.isOpen(), true) 15 | t.equal(db.isClosed(), false) 16 | 17 | _db.close(function () { 18 | t.equal(db.isOpen(), false) 19 | t.equal(db.isClosed(), true) 20 | 21 | t.end() 22 | }) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /test/key-stream-issues.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var SubLevel = require('../') 3 | var levelup = require('level-test')() 4 | var through = require('through') 5 | 6 | var base = SubLevel(levelup('test-sublevel')) 7 | 8 | var sub = base.sublevel('levelup-users') 9 | 10 | 11 | function put (user, contrib) { 12 | return { key: user, value: { contrib: contrib }, type: 'put', valueEncoding : 'json' }; 13 | } 14 | 15 | var batch = [ 16 | [ 'rvagg', 'leveldown' ] 17 | , [ 'dominictarr', 'levelup' ] 18 | , [ 'juliangruber', 'multilevel' ] 19 | ].map(function (x) { return put(x[0], x[1]) }) 20 | 21 | // the two following tests ensure that https://github.com/dominictarr/level-sublevel/issues/30 is and stays fixed 22 | test('key only stream keys do not include sublevel prefix', function (t) { 23 | 24 | sub.batch(batch, function (err, res) { 25 | if (err) return t.fail(err) 26 | }) 27 | 28 | var arr = [] 29 | sub.createReadStream({ keys: true, values: false }) 30 | .pipe(through(arr.push.bind(arr), function () { 31 | t.ok(~arr.indexOf('rvagg'), 'has rvagg without prefix') 32 | t.ok(~arr.indexOf('dominictarr'), 'has dominictarr without prefix') 33 | t.ok(~arr.indexOf('juliangruber'), 'has juliangruber without prefix') 34 | t.end() 35 | })) 36 | }) 37 | 38 | test('key/value stream keys don not include sublevel prefix', function (t) { 39 | 40 | sub.batch(batch, function (err, res) { 41 | if (err) return t.fail(err) 42 | }) 43 | 44 | var arr = [] 45 | sub.createReadStream({ keys: true, values: true }) 46 | .pipe(through(arr.push.bind(arr), function () { 47 | var keys = arr.map(function (x) { return x.key }) 48 | t.ok(~keys.indexOf('rvagg'), 'has rvagg without prefix') 49 | t.ok(~keys.indexOf('dominictarr'), 'has dominictarr without prefix') 50 | t.ok(~keys.indexOf('juliangruber'), 'has juliangruber without prefix') 51 | t.end() 52 | })) 53 | }) 54 | -------------------------------------------------------------------------------- /test/key-value-stream.js: -------------------------------------------------------------------------------- 1 | 2 | var pl = require('pull-level') 3 | var pull = require('pull-stream') 4 | var toPull = require('stream-to-pull-stream') 5 | 6 | var level = require('level-test')() 7 | var sublevel = require('../') 8 | var tape = require('tape') 9 | 10 | tape('keys', function (t) { 11 | 12 | var db = sublevel(level()).sublevel('test') 13 | 14 | pull( 15 | pull.count(10), 16 | pull.map(function (i) { 17 | return {key: 'key_'+i, value: 'value_' + i} 18 | }), 19 | pl.write(db, function (err) { 20 | if(err) { 21 | t.notOk(err) 22 | throw err 23 | } 24 | 25 | pull( 26 | toPull(db.createKeyStream()), 27 | pull.collect(function (err, ary) { 28 | console.log(ary) 29 | ary.forEach(function (e) { 30 | t.equal(typeof e, 'string') 31 | t.ok(/^key_/.test(e)) 32 | }) 33 | pull( 34 | toPull(db.createValueStream()), 35 | pull.collect(function (err, ary) { 36 | console.log(ary) 37 | ary.forEach(function (e) { 38 | t.equal(typeof e, 'string') 39 | t.ok(/^value_/.test(e)) 40 | console.log(e) 41 | }) 42 | 43 | t.end() 44 | }) 45 | ) 46 | }) 47 | ) 48 | }) 49 | ) 50 | }) 51 | 52 | -------------------------------------------------------------------------------- /test/keyvalue.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var SubLevel = require('../') 3 | var levelup = require('level-test')() 4 | 5 | var base = SubLevel(levelup('test-sublevel')) 6 | 7 | var sub = base.sublevel('fruit') 8 | 9 | var docs = { 10 | '001': 'apple', 11 | '002': 'orange', 12 | '003': 'banana' 13 | }; 14 | 15 | test('sublevel - key/value options', function (t) { 16 | 17 | sub.batch(Object.keys(docs).map(function (key) { 18 | return {key: key, value: docs[key], type: 'put'} 19 | }), function (err) { 20 | if (err) throw err 21 | 22 | t.plan(4) 23 | 24 | ;(function testCreateKeyStream () { 25 | var results = [] 26 | sub.createKeyStream() 27 | .on('data', function (data) { 28 | results.push(data) 29 | }) 30 | .on('end', function () { 31 | t.deepEqual(results, ['001', '002', '003']) 32 | }) 33 | })() 34 | 35 | ;(function testCreateKeyReadStream () { 36 | var results = [] 37 | sub.createReadStream({values: false}) 38 | .on('data', function (data) { 39 | results.push(data) 40 | }) 41 | .on('end', function () { 42 | t.deepEqual(results, ['001', '002', '003']) 43 | }) 44 | })() 45 | 46 | ;(function testCreateValueStream () { 47 | var results = [] 48 | sub.createValueStream({keys: false}) 49 | .on('data', function (data) { 50 | results.push(data) 51 | }) 52 | .on('end', function () { 53 | t.deepEqual(results, ['apple', 'orange', 'banana']) 54 | }) 55 | })() 56 | 57 | ;(function testCreateValueReadStream () { 58 | var results = [] 59 | sub.createReadStream({keys: false}) 60 | .on('data', function (data) { 61 | results.push(data) 62 | }) 63 | .on('end', function () { 64 | t.deepEqual(results, ['apple', 'orange', 'banana']) 65 | }) 66 | })() 67 | }) 68 | }) 69 | 70 | -------------------------------------------------------------------------------- /test/legacy-apis.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var through = require('through'); 3 | 4 | var sublevel = require('../'); 5 | var level = require('level-test')(); 6 | 7 | var db = level('key-stream-alias'); 8 | var sub = sublevel(db).sublevel('test'); 9 | 10 | function streamToArray(stream, cb) { 11 | var arr = []; 12 | stream.pipe(through(arr.push.bind(arr), function (err) { 13 | return cb(err, arr); 14 | })); 15 | } 16 | function testMethodsOnDb(db, name, alias, cb) { 17 | db.batch([ 18 | {key: 'a', value: 1, type: 'put'}, 19 | {key: 'b', value: 2, type: 'put'}, 20 | {key: 'c', value: 3, type: 'put'}, 21 | ], function (err) { 22 | 23 | streamToArray(db[name](), next); 24 | 25 | function next(err, arr1) { 26 | if (err) { return cb(err); } 27 | streamToArray(db[alias](), function (err, arr2) { 28 | return cb(err, arr1, arr2); 29 | }); 30 | } 31 | }); 32 | 33 | } 34 | 35 | test('keyStream/createKeyStream', function (t) { 36 | testMethodsOnDb(db, 'keyStream', 'createKeyStream', function (err, arr1, arr2) { 37 | t.notOk(err); 38 | t.same(arr1, arr2); 39 | t.end(); 40 | }); 41 | }); 42 | 43 | test('readStream/createReadStream', function (t) { 44 | testMethodsOnDb(db, 'readStream', 'createReadStream', function (err, arr1, arr2) { 45 | t.notOk(err); 46 | t.same(arr1, arr2); 47 | t.end(); 48 | }); 49 | }); 50 | 51 | test('valueStream/createValueStream', function (t) { 52 | testMethodsOnDb(db, 'valueStream', 'createValueStream', function (err, arr1, arr2) { 53 | t.notOk(err); 54 | t.same(arr1, arr2); 55 | t.end(); 56 | }); 57 | }); 58 | 59 | test('sublevel keyStream/createKeyStream', function (t) { 60 | testMethodsOnDb(sub, 'keyStream', 'createKeyStream', function (err, arr1, arr2) { 61 | t.notOk(err); 62 | t.same(arr1, arr2); 63 | t.end(); 64 | }); 65 | }); 66 | 67 | test('sublevel readStream/createReadStream', function (t) { 68 | testMethodsOnDb(sub, 'readStream', 'createReadStream', function (err, arr1, arr2) { 69 | t.notOk(err); 70 | t.same(arr1, arr2); 71 | t.end(); 72 | }); 73 | }); 74 | 75 | test('sublevel valueStream/createValueStream', function (t) { 76 | testMethodsOnDb(sub, 'valueStream', 'createValueStream', function (err, arr1, arr2) { 77 | t.notOk(err); 78 | t.same(arr1, arr2); 79 | t.end(); 80 | }); 81 | }); 82 | 83 | -------------------------------------------------------------------------------- /test/legacy-start-end.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | 3 | var sublevel = require('../') 4 | var level = require('level-test')() 5 | 6 | var mainDB = level('legacy-start-end') 7 | var subDB = sublevel(mainDB).sublevel('test', {valueEncoding: 'json'}) 8 | 9 | var batch = [ 10 | {key: 'a', value: 1, type: 'put'}, 11 | {key: 'b', value: 2, type: 'put'}, 12 | {key: 'c', value: 3, type: 'put'}, 13 | {key: 'd', value: 4, type: 'put'}, 14 | {key: 'e', value: 5, type: 'put'} 15 | ] 16 | 17 | function testRange(opts, expected, db, t, cb) { 18 | var res = [] 19 | 20 | db.createReadStream(opts).on('data', function (data) { 21 | res.push(data.key); 22 | }).on('end', function () { 23 | t.deepEqual(res, expected, 'using options: ' + JSON.stringify(opts)) 24 | cb(null) 25 | }).on('error', cb) 26 | } 27 | 28 | // run the same tests for the mainDB and subDB to show 29 | // we have the same behavior as vanilla leveldown 30 | 31 | [mainDB, subDB].forEach(function (db, i) { 32 | 33 | var testName = i === 0 ? 'leveldown' : 'sublevel' 34 | 35 | // test a bunch of combinations of start/end with and without reverse 36 | test('legacy start/end/reverse: ' + testName, function (t) { 37 | db.batch(batch, function (err) { 38 | t.notOk(err) 39 | 40 | var i = -1; 41 | var testCases = [ 42 | [{start: 'b'}, ['b', 'c', 'd', 'e']], 43 | [{start: 'b', end: 'd'}, ['b', 'c', 'd']], 44 | [{start: 'a'}, ['a', 'b', 'c', 'd', 'e']], 45 | [{start: 'e'}, ['e']], 46 | [{start: '0'}, ['a', 'b', 'c', 'd', 'e']], 47 | [{start: 'z'}, []], 48 | [{end: 'c'}, ['a', 'b', 'c']], 49 | [{end: 'a'}, ['a']], 50 | [{end: 'e'}, ['a', 'b', 'c', 'd', 'e']], 51 | [{end: '0'}, []], 52 | [{end: 'z'}, ['a', 'b', 'c', 'd', 'e']], 53 | [{start: 'd', end: 'b', reverse: true}, ['d', 'c', 'b']], 54 | [{start: 'd', reverse: true}, ['d', 'c', 'b', 'a']], 55 | [{start: 'a', reverse: true}, ['a']], 56 | [{start: 'e', reverse: true}, ['e', 'd', 'c', 'b', 'a']], 57 | [{start: '0', reverse: true}, []], 58 | [{start: 'z', reverse: true}, ['e', 'd', 'c', 'b', 'a']], 59 | [{end: 'c', reverse: true}, ['e', 'd', 'c']], 60 | [{end: 'a', reverse: true}, ['e', 'd', 'c', 'b', 'a']], 61 | [{end: 'e', reverse: true}, ['e']], 62 | [{end: '0', reverse: true}, ['e', 'd', 'c', 'b', 'a']], 63 | [{end: 'z', reverse: true}, []], 64 | ] 65 | 66 | function next(err) { 67 | t.notOk(err) 68 | if (++i === testCases.length) { 69 | return t.end() 70 | } 71 | var testCase = testCases[i] 72 | testRange(testCase[0], testCase[1], db, t, next) 73 | } 74 | 75 | next() 76 | }) 77 | }) 78 | 79 | // as a sanity check, test exactly the same options, but using the modern 80 | // lte/gte style instead of start/end 81 | test('modern start/end/reverse: ' + testName, function (t) { 82 | db.batch(batch, function (err) { 83 | t.notOk(err) 84 | 85 | var i = -1; 86 | var testCases = [ 87 | [{gte: 'b'}, ['b', 'c', 'd', 'e']], 88 | [{gte: 'b', lte: 'd'}, ['b', 'c', 'd']], 89 | [{gte: 'a'}, ['a', 'b', 'c', 'd', 'e']], 90 | [{gte: 'e'}, ['e']], 91 | [{gte: '0'}, ['a', 'b', 'c', 'd', 'e']], 92 | [{gte: 'z'}, []], 93 | [{lte: 'c'}, ['a', 'b', 'c']], 94 | [{lte: 'a'}, ['a']], 95 | [{lte: 'e'}, ['a', 'b', 'c', 'd', 'e']], 96 | [{lte: '0'}, []], 97 | [{lte: 'z'}, ['a', 'b', 'c', 'd', 'e']], 98 | [{lte: 'd', gte: 'b', reverse: true}, ['d', 'c', 'b']], 99 | [{lte: 'd', reverse: true}, ['d', 'c', 'b', 'a']], 100 | [{lte: 'a', reverse: true}, ['a']], 101 | [{lte: 'e', reverse: true}, ['e', 'd', 'c', 'b', 'a']], 102 | [{lte: '0', reverse: true}, []], 103 | [{lte: 'z', reverse: true}, ['e', 'd', 'c', 'b', 'a']], 104 | [{gte: 'c', reverse: true}, ['e', 'd', 'c']], 105 | [{gte: 'a', reverse: true}, ['e', 'd', 'c', 'b', 'a']], 106 | [{gte: 'e', reverse: true}, ['e']], 107 | [{gte: '0', reverse: true}, ['e', 'd', 'c', 'b', 'a']], 108 | [{gte: 'z', reverse: true}, []], 109 | ] 110 | 111 | function next(err) { 112 | t.notOk(err) 113 | if (++i === testCases.length) { 114 | return t.end() 115 | } 116 | var testCase = testCases[i] 117 | testRange(testCase[0], testCase[1], db, t, next) 118 | } 119 | 120 | next() 121 | }) 122 | }) 123 | }) -------------------------------------------------------------------------------- /test/level.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | var pull = require('pull-stream') 3 | var path = require('path') 4 | 5 | //the mock is partical levelup api. 6 | var mock = require('./mock') 7 | var nut = require('../nut') 8 | var shell = require('../shell') //the shell surrounds the nut 9 | var Codec = require('level-codec') 10 | var concat = require('../codec') 11 | var legacy = require('../codec/legacy') 12 | var bytewise = require('../codec/bytewise') 13 | 14 | 15 | var codex = [ 16 | concat, 17 | legacy, 18 | bytewise 19 | ] 20 | 21 | var pullReadStream = require('../pull') 22 | 23 | function create (precodec, db) { 24 | 25 | //convert pull stream to iterators 26 | return shell ( nut ( db || mock(), precodec, new Codec ), [], pullReadStream) 27 | } 28 | 29 | function prehookPut (db) { 30 | tape('test - prehook - put', function (t) { 31 | 32 | var log = db.sublevel('log') 33 | var c = 0 34 | db.pre(function (op, add) { 35 | add({key: ''+c++, value: op.key, prefix: log.prefix()}) 36 | }) 37 | 38 | db.put('hello', 'there?', function (err) { 39 | 40 | if(err) throw err 41 | log.get('0', function (err, value) { 42 | if(err) throw err 43 | t.equal(value, 'hello') 44 | t.end() 45 | }) 46 | }) 47 | }) 48 | } 49 | 50 | function prehookBatch (db) { 51 | tape('test - prehook - put', function (t) { 52 | // var db = shell ( nut ( mock(), precodec, codec ) ) 53 | 54 | var log = db.sublevel('log') 55 | var c = 0 56 | db.pre(function (op, add) { 57 | add({key: ''+c++, value: op.key, prefix: log.prefix()}) 58 | }) 59 | 60 | db.batch([ 61 | {key:'hello1', value: 'there.', type: 'put'}, 62 | {key:'hello2', value: 'there!', type: 'put'}, 63 | {key:'hello3', value: 'where?', type: 'put'}, 64 | ], function (err) { 65 | if(err) throw err 66 | log.get('0', function (err, value) { 67 | if(err) throw err 68 | t.equal(value, 'hello1') 69 | log.get('1', function (err, value) { 70 | if(err) throw err 71 | t.equal(value, 'hello2') 72 | log.get('2', function (err, value) { 73 | if(err) throw err 74 | t.equal(value, 'hello3') 75 | t.end() 76 | }) 77 | }) 78 | }) 79 | }) 80 | }) 81 | } 82 | 83 | function createPostHooks (db) { 84 | 85 | function posthook (args, calls, db) { 86 | //db = db || shell ( nut ( mock(), concat, codec ) ) 87 | 88 | var method = args.shift() 89 | tape('test - posthook - ' + method, function (t) { 90 | 91 | var cb = 0, hk = 0 92 | var rm = db.post(function (op) { 93 | hk ++ 94 | next() 95 | }) 96 | 97 | console.log(db, args) 98 | db[method].apply(db, args.concat(function (err) { 99 | console.log('**************8') 100 | if(err) console.log(err.stack) 101 | // if(err) throw err 102 | cb ++ 103 | next() 104 | })) 105 | 106 | function next () { 107 | if(cb + hk < calls + 1) return 108 | t.equal(cb, 1) 109 | t.equal(hk, calls) 110 | rm() 111 | t.end() 112 | } 113 | 114 | }) 115 | } 116 | 117 | // test posthooks trigger correct number of times 118 | 119 | 120 | posthook(['put', 'hello', 'there?'], 1, db) 121 | posthook(['del', 'hello'], 1, db) 122 | posthook(['batch', [ 123 | { key: 'foo', value: 'bar', type: 'put'}, 124 | { key: 'fuz', value: 'baz', type: 'put'}, 125 | { key: 'fum', value: 'boo', type: 'put'} 126 | ]], 3, db) 127 | 128 | } 129 | 130 | // test posthooks also work in sublevels 131 | 132 | //test removing hooks. 133 | 134 | function rmHook (db) { 135 | tape('test - prehook - put', function (t) { 136 | // db = db || shell ( nut ( mock(), precodec, codec ) ) 137 | 138 | var hk = 0 139 | var rm = db.pre(function (op, add) { 140 | hk ++ 141 | t.equal(op.key, 'hello') 142 | t.equal(op.value, 'there') 143 | }) 144 | 145 | db.put('hello', 'there', function (err) { 146 | if(err) throw err 147 | t.equal(hk, 1) 148 | db.put('hello', 'where?', function (err) { 149 | if(err) throw err 150 | t.equal(hk, 1) 151 | t.end() 152 | }) 153 | }) 154 | rm() 155 | 156 | }) 157 | 158 | } 159 | 160 | function stream (db) { 161 | 162 | tape('pull-stream', function (t) { 163 | var batch = [ 164 | { key: 'foo', value: 'bar'}, 165 | { key: 'fum', value: 'boo'}, 166 | { key: 'fuz', value: 'baz'} 167 | ] 168 | 169 | db.batch(batch, function (err) { 170 | if(err) throw err 171 | 172 | pull(db.createReadStream(), pull.collect(function (err, ary) { 173 | if(err) throw err 174 | console.log(ary) 175 | t.deepEqual(ary, batch) 176 | t.end() 177 | })) 178 | }) 179 | }) 180 | } 181 | 182 | 183 | var tests = [ 184 | prehookPut, 185 | prehookBatch, 186 | createPostHooks, 187 | rmHook, 188 | stream 189 | ] 190 | 191 | var LevelDown = require('leveldown') 192 | var i = 0 193 | var rimraf = require('rimraf') 194 | 195 | function createTestDb () { 196 | var dir = path.join('/tmp', 'level-sublevel_test' + (i++)) 197 | rimraf.sync(dir) 198 | return new LevelDown(dir) 199 | } 200 | 201 | codex.forEach(function (codec) { 202 | 203 | tests.forEach(function (test) { 204 | 205 | var db1 = create(codec) 206 | 207 | test(db1) 208 | test(db1.sublevel('foo')) 209 | test(db1.sublevel('foo').sublevel('blah')) 210 | 211 | var db3 = create(codec, createTestDb()) 212 | 213 | test(db3) 214 | test(db3.sublevel('foo')) 215 | test(db3.sublevel('foo').sublevel('blah')) 216 | 217 | }) 218 | 219 | }) 220 | 221 | -------------------------------------------------------------------------------- /test/mixed-value-encodings-per-action.js: -------------------------------------------------------------------------------- 1 | var levelup = require('level-test')() 2 | var base = require('../')(levelup('test-mixed-value-encodings-per-put')) 3 | 4 | var test = require('tape') 5 | 6 | test('subsections support mixed encodings per put', function (t) { 7 | t.plan(10) 8 | 9 | var foo = base.sublevel('foo') 10 | var bar = base.sublevel('bar') 11 | 12 | foo.put('foo1', 'foo1-value', { valueEncoding: 'utf8' }, function () { 13 | bar.put('bar1', { obj: 'ect' }, { valueEncoding: 'json' }, function () { 14 | 15 | foo.get('foo1', { valueEncoding: 'utf8' }, function (err, value) { 16 | t.notOk(err, 'getting string value by key has no error') 17 | t.equal(value, 'foo1-value', 'and returns value for that key') 18 | }) 19 | 20 | bar.get('bar1', { valueEncoding: 'json' }, function (err, value) { 21 | t.notOk(err, 'getting object value by key has no error') 22 | console.log(value) 23 | t.equal(value.obj, 'ect', 'and returns value for that key') 24 | }) 25 | 26 | var foodata, fooerr 27 | 28 | foo.createReadStream({ start: 'foo1', end: 'foo1\xff', valueEncoding: 'utf8' }) 29 | .on('data', function (d) { foodata = d }) 30 | .on('data', console.log.bind(null, '****')) 31 | .on('error', function (err) { fooerr = err }) 32 | .on('end', function () { 33 | console.log('END ***********8') 34 | t.notOk(fooerr, 'streaming string value by key emits no error') 35 | t.equal(foodata.key, 'foo1', 'streaming string value emits key') 36 | t.equal(foodata.value, 'foo1-value', 'streaming string value emits value') 37 | }) 38 | 39 | var bardata, barerr 40 | 41 | bar.createReadStream({ start: 'bar1', end: 'bar1\xff', valueEncoding: 'json' }) 42 | .on('data', console.log.bind(null, '%%%%')) 43 | .on('data', function (d) { bardata = d }) 44 | .on('error', function (err) { throw barerr = err }) 45 | .on('end', function () { 46 | t.notOk(barerr, 'streaming object value by key emits no error') 47 | t.equal(bardata.key, 'bar1', 'streaming string value emits key') 48 | t.equal(bardata.value.obj, 'ect', 'streaming object value emits value') 49 | }) 50 | }) 51 | }) 52 | 53 | }) 54 | -------------------------------------------------------------------------------- /test/mixed-value-encodings-per-sub.js: -------------------------------------------------------------------------------- 1 | var levelup = require('level-test')() 2 | var base = require('../')(levelup('test-mixed-value-encodings-per-sub')) 3 | 4 | var test = require('tape') 5 | 6 | test('subsections support mixed encodings per sub with put/get', function (t) { 7 | t.plan(10) 8 | 9 | var foo = base.sublevel('foo', { valueEncoding: 'utf8' }) 10 | var bar = base.sublevel('bar', { valueEncoding: 'json' }) 11 | 12 | foo.put('foo1', 'foo1-value', function () { 13 | bar.put('bar1', { obj: 'ect' }, function () { 14 | 15 | foo.get('foo1', function (err, value) { 16 | t.notOk(err, 'getting string value by key has no error') 17 | t.equal(value, 'foo1-value', 'and returns value for that key') 18 | }) 19 | 20 | bar.get('bar1', function (err, value) { 21 | t.notOk(err, 'getting object value by key has no error') 22 | t.equal(value.obj, 'ect', 'and returns value for that key') 23 | }) 24 | 25 | 26 | var foodata, fooerr 27 | 28 | foo.createReadStream({ start: 'foo1', end: 'foo1\xff' }) 29 | .on('data', function (d) { foodata = d }) 30 | .on('error', function (err) { fooerr = err }) 31 | .on('end', function () { 32 | console.error('foodata: ', foodata); 33 | 34 | t.notOk(fooerr, 'streaming string value by key emits no error') 35 | t.equal(foodata.key, 'foo1', 'streaming string value emits key') 36 | t.equal(foodata.value, 'foo1-value', 'streaming string value emits value') 37 | }) 38 | 39 | var bardata, barerr 40 | 41 | bar.createReadStream({ start: 'bar1', end: 'bar1\xff' }) 42 | .on('data', function (d) { bardata = d }) 43 | .on('error', function (err) { barerr = err }) 44 | .on('end', function () { 45 | t.notOk(barerr, 'streaming object value by key emits no error') 46 | t.equal(bardata.key, 'bar1', 'streaming string value emits key') 47 | t.equal(bardata.value.obj, 'ect', 'streaming object value emits value') 48 | }) 49 | }) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /test/mock.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter 2 | var range = require('../range') 3 | var pull = require('pull-stream') 4 | var compare = require('../range').compare 5 | 6 | var next = 'undefined' === typeof setImmediate ? setTimeout : setImmediate 7 | 8 | var I = 0 9 | 10 | function insert (ary, op) { 11 | for(var i in ary) { 12 | var c = compare(ary[i].key, op.key) 13 | if(c === 0) 14 | return op.type === 'del' ? ary.splice(i, 1) : ary[i] = op 15 | else if(c > 0) 16 | return ary.splice(i, 0, op) 17 | } 18 | ary.push(op) 19 | } 20 | 21 | function get (ary, _, key) { 22 | for(var i in ary) { 23 | if(compare(ary[i].key, key) === 0) 24 | return ary[i].value 25 | } 26 | return null 27 | } 28 | 29 | module.exports = function () { 30 | if(process.env.FOR_REAL) { 31 | var db = require('level-test')()('test-level-sublevel_' + I++) 32 | return db 33 | } 34 | 35 | var emitter = new EventEmitter() 36 | var data = emitter.data = [] 37 | 38 | emitter.batch = function (ops, opts, cb) { 39 | ops.forEach(function (op) { 40 | insert(data, op) 41 | }) 42 | next(function () { 43 | emitter.emit('post', ops); cb() 44 | }) 45 | } 46 | 47 | emitter.get = function (key, opts, cb) { 48 | var value = get(data, opts, key) 49 | next(function () { 50 | if(!value) cb(new Error('404')) 51 | else cb(null, value) 52 | }) 53 | } 54 | 55 | emitter.iterator = function (opts) { 56 | var values = data.filter(function (v) { 57 | return range(opts, v.key) 58 | }).map(function (op) { 59 | return {key: op.key, value: op.value} 60 | }) 61 | if(opts.reverse) values.reverse() 62 | 63 | var stream = pull.values(values) 64 | 65 | return { 66 | next: function (cb) { 67 | stream(null, function (err, d) { 68 | cb(err, d && d.key, d && d.value) 69 | }) 70 | }, 71 | end: function (cb) { 72 | stream(true, cb) 73 | } 74 | } 75 | } 76 | 77 | var emitter2 = new EventEmitter() 78 | 79 | emitter2.open = emitter.open = function (cb) { 80 | emitter2.emit('open') 81 | cb() 82 | } 83 | 84 | emitter2.db = emitter 85 | 86 | return emitter2 87 | } 88 | 89 | -------------------------------------------------------------------------------- /test/nested-prehooks.js: -------------------------------------------------------------------------------- 1 | var level = require('level-test')() 2 | var Sublevel = require('../') 3 | 4 | function sl (name) { 5 | return Sublevel(level(name), { sep: '~' }) 6 | 7 | } 8 | 9 | require('tape')('sublevel', function (t) { 10 | 11 | var base = sl('test-sublevel') 12 | 13 | var a = base.sublevel('A') 14 | var a_a = a.sublevel('A') 15 | 16 | var as = {} 17 | var aas = {} 18 | 19 | a.pre(function (e) { 20 | as[e.key] = e.value 21 | console.log('A :', e) 22 | }) 23 | 24 | a_a.pre(function (e) { 25 | aas[e.key] = e.value 26 | console.log('A_A :', e) 27 | }) 28 | 29 | var n = 3 30 | a.put('apple', '1', next) 31 | a.put('banana', '2', next) 32 | 33 | a_a.put('aardvark', 'animal1', next) 34 | 35 | function next() { 36 | if(--n) return 37 | t.deepEqual(as, {apple: '1', banana: '2'}) 38 | t.deepEqual(aas, {aardvark: 'animal1'}) 39 | t.end() 40 | } 41 | 42 | function all(db, cb) { 43 | var o = {} 44 | db.createReadStream().on('data', function (data) { 45 | o[data.key.toString()] = data.value.toString() 46 | }) 47 | .on('end', function () { 48 | cb(null, o) 49 | }) 50 | .on('error', cb) 51 | } 52 | }) 53 | -------------------------------------------------------------------------------- /test/range.js: -------------------------------------------------------------------------------- 1 | 2 | var tape = require('tape') 3 | 4 | var range = require('../range') 5 | 6 | tape('test prefix', function (t) { 7 | 8 | t.ok( range.prefix([['foo'], 'y'], [['foo'], 'yellow']) ) 9 | t.notOk( range.prefix([['foo'], 'y'], [['foo'], 'Yellow']) ) 10 | 11 | t.ok( range.prefix([['foo', 'bar']], [['foo', 'bar'], 'foo']) ) 12 | t.notOk( range.prefix([['foo', 'bar']], [['foo', 'bar', 'baz'], 'foo']) ) 13 | 14 | t.ok( range.prefix([[]], [[], 'hello']) ) 15 | 16 | t.end() 17 | }) 18 | 19 | 20 | tape('test range', function (t) { 21 | 22 | t.equal(range({lt: [[], 'apple']}, [[], 'apple']), false) 23 | t.equal(range({lte: [[], 'apple']}, [[], 'apple']), true) 24 | t.equal(range({gt: [[], 'apple']}, [[], 'apple']), false) 25 | t.equal(range({gte: [[], 'apple']}, [[], 'apple']), true) 26 | 27 | t.equal(range({lt: [['A'], 'apple']}, [['A'], 'apple']), false) 28 | t.equal(range({lte: [['A'], 'apple']}, [['A'], 'apple']), true) 29 | t.equal(range({gt: [['A'], 'apple']}, [['A'], 'apple']), false) 30 | t.equal(range({gte: [['A'], 'apple']}, [['A'], 'apple']), true) 31 | 32 | 33 | t.equal(range({lt: [[], 'apple']}, [[], 'a']), true) 34 | t.equal(range({lt: [['fruit']]}, [[], 'a']), true) 35 | 36 | t.equal(range({lte: [[], 'apple']}, [[], 'a']), true) 37 | t.equal(range({lte: [[], 'apple']}, [[], 'apples']), false) 38 | t.equal(range({lte: [[], 'apple']}, [[], 'b']), false) 39 | t.equal(range({lte: [[], 'apple']}, [['apples']]), false) 40 | 41 | t.equal(range({gte: [[], 'apple']}, [[], 'a']), false) 42 | t.equal(range({gte: [[], 'apple']}, [[], 'apples']), true) 43 | t.equal(range({gte: [[], 'apple']}, [[], 'b']), true) 44 | t.equal(range({gte: [[], 'apple']}, [['apples']]), true) 45 | 46 | t.equal(range({gt: [[], 'apple']}, [[], 'a']), false) 47 | t.equal(range({gt: [[], 'apple']}, [[], 'apples']), true) 48 | t.equal(range({gt: [[], 'apple']}, [[], 'b']), true) 49 | t.equal(range({gt: [[], 'apple']}, [['apples']]), true) 50 | 51 | t.equal(range({lte: [['x']]}, [[], 'x']), true) 52 | t.equal(range({lte: [['x'], 'y']}, [['x'], 'x']), true) 53 | t.equal(range({lte: [['x', 'z']]}, [['x', 'y'], 'x']), true) 54 | 55 | t.equal(range({lte: [[], 'x']}, [['x']]), false) 56 | t.equal(range({lte: [['x'], 'x']}, [['x'], 'y']), false) 57 | t.equal(range({lte: [['x', 'y'], 'x']}, [['x', 'z']]), false) 58 | 59 | t.equal(range({lt: [['x']]}, [[], 'x']), true) 60 | t.equal(range({lt: [['x'], 'y']}, [['x'], 'x']), true) 61 | t.equal(range({lt: [['x', 'z']]}, [['x', 'y'], 'x']), true) 62 | 63 | t.equal(range({lt: [[], 'x']}, [['x']]), false) 64 | t.equal(range({lt: [['x'], 'x']}, [['x'], 'y']), false) 65 | t.equal(range({lt: [['x', 'y'], 'x']}, [['x', 'z']]), false) 66 | 67 | t.equal(range({gt: [['x']]}, [[], 'x']), false) 68 | t.equal(range({gt: [['x'], 'y']}, [['x'], 'x']), false) 69 | t.equal(range({gt: [['x', 'z']]}, [['x', 'y'], 'x']), false) 70 | 71 | t.equal(range({gt: [[], 'x']}, [['x']]), true) 72 | t.equal(range({gt: [['x'], 'x']}, [['x'], 'y']), true) 73 | t.equal(range({gt: [['x', 'y'], 'x']}, [['x', 'z']]), true) 74 | 75 | t.equal(range({gte: [['x']]}, [[], 'x']), false) 76 | t.equal(range({gte: [['x'], 'y']}, [['x'], 'x']), false) 77 | t.equal(range({gte: [['x', 'z']]}, [['x', 'y'], 'x']), false) 78 | 79 | t.equal(range({gte: [[], 'x']}, [['x']]), true) 80 | t.equal(range({gte: [['x'], 'x']}, [['x'], 'y']), true) 81 | t.equal(range({gte: [['x', 'y'], 'x']}, [['x', 'z']]), true) 82 | 83 | t.end() 84 | }) 85 | -------------------------------------------------------------------------------- /test/reverse-order.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var test = require('tape') 4 | var LevelUp = require('level-test')(); 5 | var Sublevel = require('../'); 6 | var timestamp = require('monotonic-timestamp') 7 | 8 | var db = Sublevel( LevelUp('test-level-sublevel_myDB', {valueEncoding: 'json'}) ); 9 | var groups = db.sublevel('groups'); 10 | var topics = db.sublevel('topics'); 11 | 12 | var timeGroup1 = timestamp(); 13 | var timeGroup2 = timestamp(); 14 | 15 | var timeTopic1 = timestamp(); 16 | var timeTopic2 = timestamp(); 17 | var timeTopic3 = timestamp(); 18 | 19 | console.log(timeTopic1,timeTopic2,timeTopic3) 20 | 21 | test('reverse:true', function (t) { 22 | 23 | groups.put(timeGroup1, {name: 'Cats', title: 'discussion about cats!'}, function (err) { 24 | if (err) return console.log('Ooops!', err) 25 | topics.put(timeGroup1 + '!' + timeTopic1, {title: 'dancing cats'}, function (err) { 26 | if (err) return console.log('Ooops!', err) 27 | 28 | topics.put(timeGroup1 + '!' + timeTopic2, {title: 'cat in a box'}, function (err) { 29 | if (err) return console.log('Ooops!', err) 30 | 31 | // groups.put(timeGroup2, {name: 'Node.js', title: 'Node.js talk'}, function (err) { 32 | // if (err) return console.log('Ooops!', err) 33 | 34 | topics.put(timeGroup2 + '!' + timeTopic3, {title: 'Is there a good example for website without Express.js?'}, function (err) { 35 | if (err) return console.log('Ooops!', err) 36 | 37 | var order = [ 38 | timeGroup1 + '!' + timeTopic1, 39 | timeGroup1 + '!' + timeTopic2 40 | ].sort().reverse() 41 | 42 | topics.createReadStream({max: timeGroup1 + '!~', min: ''+timeGroup1, reverse: true }) 43 | .on('data', function (data) { 44 | t.equal(data.key, order.shift()) 45 | console.log('topic:', data.key, '=', data.value) 46 | }) 47 | .on('end', function () { 48 | t.end() 49 | console.log('Stream ended') 50 | }) 51 | }); 52 | }); 53 | // }); 54 | }); 55 | }); 56 | 57 | }) 58 | // output is not in revese order: 59 | // topic: 1366613791702!1366613791702.002 = { title: 'dancing cats' } 60 | // topic: 1366613791702!1366613791702.003 = { title: 'cat in a box' } 61 | 62 | -------------------------------------------------------------------------------- /test/reverse.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var level = require('level-test')() 3 | var base = require('../')(level('test-sublevel-reverse')) 4 | 5 | function all (db, range, cb) { 6 | var o = {} 7 | db.createReadStream(range) 8 | .on('data', function (data) { 9 | o[data.key] = data.value 10 | }) 11 | .on('end', function () { 12 | cb(null, o) 13 | }) 14 | } 15 | 16 | function makeTest(db, name) { 17 | 18 | test(name, function (t) { 19 | 20 | t.plan(19) 21 | 22 | var docs = { 23 | a: 'apple', 24 | b: 'banana', 25 | c: 'cherry', 26 | d: 'durian', 27 | e: 'elder-berry' 28 | } 29 | 30 | function order(a, b) { 31 | t.deepEqual(a, b) 32 | t.equal(JSON.stringify(a), JSON.stringify(b)) 33 | } 34 | 35 | db.batch(Object.keys(docs).map(function (key) { 36 | console.log(key, docs[key]) 37 | return {key: key, value: docs[key], type: 'put'} 38 | }), function (err) { 39 | t.notOk(err) 40 | 41 | all(db, {}, function (err, all) { 42 | order(all, docs) 43 | }) 44 | 45 | all(db, {min: 'a~'}, function (err, all) { 46 | order(all, { 47 | b: 'banana', 48 | c: 'cherry', 49 | d: 'durian', 50 | e: 'elder-berry' 51 | }) 52 | }) 53 | 54 | all(db, {min: 'b'}, function (err, all) { 55 | order(all, { 56 | b: 'banana', 57 | c: 'cherry', 58 | d: 'durian', 59 | e: 'elder-berry' 60 | }) 61 | }) 62 | 63 | 64 | all(db, {min: 'a~', reverse: true}, function (err, all) { 65 | order(all, { 66 | e: 'elder-berry', 67 | d: 'durian', 68 | c: 'cherry', 69 | b: 'banana' 70 | }) 71 | }) 72 | 73 | all(db, {min: 'c~', reverse: true}, function (err, all) { 74 | console.log(all) 75 | order(all, { 76 | e: 'elder-berry', 77 | d: 'durian' 78 | }) 79 | }) 80 | 81 | all(db, {min: 'c~', max: 'd~'}, function (err, all) { 82 | console.log(all) 83 | order(all, { 84 | d: 'durian', 85 | }) 86 | }) 87 | 88 | all(db, {min: 'a~'}, function (err, all) { 89 | order(all, { 90 | b: 'banana', 91 | c: 'cherry', 92 | d: 'durian', 93 | e: 'elder-berry' 94 | }) 95 | }) 96 | 97 | all(db, {min: 'c~'}, function (err, all) { 98 | console.log('d, e', all) 99 | order(all, { 100 | d: 'durian', 101 | e: 'elder-berry' 102 | }) 103 | }) 104 | 105 | all(db, {min: 'c~', max: 'd~', reverse: true}, function (err, all) { 106 | console.log(all) 107 | order(all, { 108 | d: 'durian', 109 | }) 110 | }) 111 | }) 112 | }) 113 | } 114 | 115 | var A = base.sublevel('A') 116 | makeTest(base, 'simple') 117 | 118 | makeTest(A, 'sublevel') 119 | 120 | makeTest(base, 'simple, again') 121 | 122 | var A_B = A.sublevel('B') 123 | makeTest(A_B, 'sublevel2') 124 | 125 | makeTest(A, 'sublevel, again') 126 | 127 | makeTest(base, 'simple, again 2') 128 | 129 | -------------------------------------------------------------------------------- /test/streams-sublevel-key-value.js: -------------------------------------------------------------------------------- 1 | var levelup = require('level-test')() 2 | 3 | var base = require('../')(levelup('test-streams-sublevel-key-value')) 4 | 5 | var test = require('tape') 6 | 7 | test('sublevel value streams emit values and sublevel key streams emit keys', function (t) { 8 | t.plan(2) 9 | 10 | var foo = base.sublevel('foo') 11 | 12 | foo.put('foo1', 'foo1-value', function () { 13 | 14 | var valdata, valerr 15 | 16 | foo.createValueStream({ start: 'foo1', end: 'foo1\xff' }) 17 | .on('data', function (d) { valdata = d }) 18 | .on('end', function () { 19 | t.equal(valdata, 'foo1-value', 'emits value only') 20 | }) 21 | 22 | var keydata, keyerr 23 | 24 | foo.createKeyStream({ start: 'foo1', end: 'foo1\xff' }) 25 | .on('data', function (d) { keydata = d }) 26 | .on('end', function () { 27 | t.equal(keydata, 'foo1', 'emits fully namespaced key only') 28 | }) 29 | }) 30 | 31 | }) 32 | -------------------------------------------------------------------------------- /test/streams.js: -------------------------------------------------------------------------------- 1 | var level = require('level-test')() 2 | var sublevel = require('../') 3 | 4 | require('tape')('sublevel', function (t) { 5 | 6 | require('rimraf').sync('/tmp/test-sublevel-readstream') 7 | 8 | var db = level('test-sublevel-readstream') 9 | var base = sublevel(db) 10 | 11 | var a = base.sublevel('A') 12 | 13 | var i = 0 14 | 15 | function all(db, cb) { 16 | var o = {} 17 | db.createReadStream({end: '\xff\xff'}).on('data', function (data) { 18 | o[data.key.toString()] = data.value.toString() 19 | }) 20 | .on('end', function () { 21 | cb(null, o) 22 | }) 23 | .on('error', cb) 24 | } 25 | 26 | var _a, _b, _c 27 | 28 | a.batch([ 29 | {key: 'a', value: _a ='AAA_'+Math.random(), type: 'put'}, 30 | {key: 'b', value: _b = 'BBB_'+Math.random(), type: 'put'}, 31 | {key: 'c', value: _c = 'CCC_'+Math.random(), type: 'put'}, 32 | ], function (err) { 33 | if(err) throw err 34 | all(db, function (err, obj) { 35 | console.log(obj) 36 | t.deepEqual(obj, 37 | { '!A!a': _a, 38 | '!A!b': _b, 39 | '!A!c': _c 40 | }) 41 | 42 | all(a, function (err, obj) { 43 | console.log(obj) 44 | t.deepEqual(obj, 45 | { 'a': _a, 46 | 'b': _b, 47 | 'c': _c 48 | }) 49 | t.end() 50 | }) 51 | }) 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /test/sublevels.js: -------------------------------------------------------------------------------- 1 | var levelup = require('level-test')() 2 | 3 | var base = require('../')(levelup('test-sublevels')) 4 | 5 | var test = require('tape') 6 | 7 | test('subsections', function (t) { 8 | t.deepEqual(base.sublevels, {}) 9 | 10 | var foo = base.sublevel('foo') 11 | var bar = base.sublevel('bar') 12 | 13 | t.deepEqual(base.sublevels, {foo: foo, bar: bar}) 14 | t.deepEqual(foo.sublevels, {}) 15 | 16 | t.strictEqual(base.sublevel('foo'), foo) 17 | t.strictEqual(base.sublevel('bar'), bar) 18 | 19 | console.log('prefix:', foo.prefix()) 20 | console.log('prefix:', bar.prefix()) 21 | 22 | var fooBlerg = foo.sublevel('blerg') 23 | t.deepEqual(foo.sublevels, {blerg: fooBlerg}) 24 | 25 | t.strictEqual(foo.sublevel('blerg'), fooBlerg) 26 | 27 | t.end() 28 | }) 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /test/version.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | 3 | var sublevel = require('../') 4 | var level = require('level-test')() 5 | 6 | tape('expose version', function (t) { 7 | 8 | t.equal( 9 | sublevel(level('level-sublevel-ver')).version, 10 | require('../package.json').version 11 | ) 12 | 13 | t.end() 14 | 15 | }) 16 | --------------------------------------------------------------------------------