├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── batch.js ├── index.js ├── namespace.js ├── package.json └── test ├── index.js └── post-hooks.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | test/__bytespace* 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.12" 5 | - "4.1" 6 | - "5.0" 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Dean Landolt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bytespace 2 | 3 | Efficient keypath subspaces prefixed with bytewise tuples. 4 | 5 | [![build status](https://travis-ci.org/deanlandolt/bytespace.svg?branch=master)](https://travis-ci.org/deanlandolt/bytespace) 6 | 7 | This library is very much like `level-sublevel`, and is API-compatible in several key way, but intents to be simpler in principle. A `bytespace` is essentially just a collection of wrappers over `levelup` methods to control key encoding behavior. 8 | 9 | Prefixes are constructed as `bytewise`-encoded arrays, which allows you to use arbirarily complex keys in your subspace prefixes (any encoding supported by `bytewise`), and any `keyEncoding` you prefer for your subspace suffix keyspace. 10 | 11 | ## Usage 12 | 13 | ```js 14 | var bytespace = require('bytespace') 15 | var levelup = require('levelup') 16 | var db = levelup('./mydb') 17 | 18 | // all the standard levelup options 19 | var options = { 20 | // keyEncoding defaults to "utf8" just like levelup 21 | keyEncoding: require('bytewise'), 22 | valueEncoding: 'json' 23 | } 24 | 25 | // same API as levelup 26 | var appDb = bytespace(db, 'myapp', options) 27 | 28 | // you can mount subspaces within subspaces 29 | var nestedDb = bytespace(myapp, 'nested') 30 | 31 | // namespace can be any bytewise-serializable value 32 | var testDb = bytespace(appDb, new Date()) 33 | 34 | // subspaces may also be mounted sublevel-style 35 | var subDb = testDb.sublevel('another') 36 | ``` 37 | 38 | ## Rooted keypaths 39 | 40 | The subspace db instance itself is essentially a keyspace `chroot` -- a jail you cannot escape with just a reference to the subspace. While a subspace must be provided a reference to a backing db to initialize, this capability should not be surfaced on any properties or methods of the subspace. The capabilities of a subspace are restricted to the subset of of keyspace allocated to it. 41 | 42 | 43 | ### Nested subspaces 44 | 45 | When instantiating a subspace, it will test the provided `db` reference to determine if it's a `bytespace` instance. If so, it will call the `sublevel` method with the provided options to create the new subspace. Rather than running through the encode/decode process mulitple times, the responsibility of encoding and decoding keys is delegated to the root subspace. All keys will be correctly prefixed to the appropriate subspace. 46 | 47 | The `sublevel` method is API-compatible with [level-sublevel](https://github.com/dominictarr/level-sublevel), though we also take an extra `options` argument to allow `levelup` db options to be provided to configure subspaces separate from their ancestor spaces. 48 | 49 | 50 | ### Remote subspaces 51 | 52 | Since `bytespace` is mostly just a set of `levelup` method wrappers this allows you to use over a `multilevel`-backed database, creating arbitrary subspaces on the client at runtime. If the `multilevel` client database has access to a `createLiveStream` method you can even create live streams observing ranges within your sublevel, all without the server having to know the sublevel layout ahead of time. 53 | 54 | 55 | ### Hooks 56 | 57 | Precommit and postcommit hooks are implemented using the `pre` and `post` methods from [level-sublevel](https://github.com/dominictarr/level-sublevel)'s API. The optional `range` argument is not yet implemented but it is otherwise API-compatible, allowing a `bytespace` instance with libraries expected a `sublevel` instance. 58 | 59 | 60 | ## Encoding 61 | 62 | Subspace keys are encoded as bytewise-prefixed arrays. This allows subspace keys to be appended as the last element of a namespace without overhead. Encoded subspace keys can be appended to the precomputed namespace buffer with a single `Buffer.concat` operation. Mounting a subspace adds another element to the prefix tuple. This serialization ensures that keys of different subspaces cannot interleave. 63 | 64 | When encoding keys, the encoded namespace buffer can be efficiently concatenated with a subspace key. When decoding, the namespace portion can be sliced off and ignored. Testing for subspace inclusion is also just a single buffer slice. 65 | -------------------------------------------------------------------------------- /batch.js: -------------------------------------------------------------------------------- 1 | function Batch(space) { 2 | this.ops = [] 3 | this._space = space 4 | } 5 | 6 | Batch.prototype.put = function (key, value, options) { 7 | this.ops.push({ type: 'put', key: key, value: value, options: options }) 8 | return this 9 | } 10 | 11 | Batch.prototype.del = function (key, options) { 12 | this.ops.push({ type: 'del', key: key, options: options }) 13 | return this 14 | } 15 | 16 | Batch.prototype.clear = function () { 17 | this.ops = [] 18 | return this 19 | } 20 | 21 | Batch.prototype.write = function (cb) { 22 | this._space.batch(this.ops, null, cb) 23 | } 24 | 25 | module.exports = Batch 26 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var Codec = require('level-codec') 4 | var EventEmitter = require('events').EventEmitter 5 | var inherits = require('util').inherits 6 | var NotFoundError = require('level-errors').NotFoundError 7 | var Transform = require('stream').Transform 8 | var xtend = require('xtend') 9 | 10 | var Batch = require('./batch') 11 | var Namespace = require('./namespace') 12 | var NOT_FOUND = /notfound/i 13 | 14 | module.exports = Bytespace 15 | 16 | function getCallback (opts, cb) { 17 | return typeof opts == 'function' ? opts : cb 18 | } 19 | 20 | // from https://github.com/Level/levelup/blob/master/lib/util.js 21 | function getOptions (options) { 22 | if (typeof options == 'string') 23 | return { valueEncoding: options } 24 | if (typeof options != 'object') 25 | return {} 26 | return xtend(options) 27 | } 28 | 29 | // create a bytespace within a remote levelup instance 30 | // TODO: remove ns from signature to align w/ sublevel 31 | function Bytespace(db, ns, opts) { 32 | if (!(this instanceof Bytespace)) 33 | return new Bytespace(db, ns, opts) 34 | 35 | if (!(ns instanceof Namespace)) { 36 | 37 | // if db is a subspace mount as a nested subspace 38 | if (db.namespace instanceof Namespace) 39 | return db.sublevel(ns, opts) 40 | 41 | // otherwise it's a root subspace 42 | ns = new Namespace([ ns ], opts && opts.hexNamespace) 43 | } 44 | 45 | var space = this 46 | 47 | space.namespace = ns 48 | opts = space.options = xtend(Bytespace.options, db.options, opts) 49 | ns.codec = new Codec(opts) 50 | 51 | // use provided methods manifest in options or get from db 52 | // TODO: can we remove this? 53 | space.methods = xtend(opts.methods || db.methods) 54 | 55 | // sublevel@6-compatible-ish 56 | Object.defineProperty(db, 'version', { 57 | value: 6, 58 | configurable: true 59 | }) 60 | 61 | // forward open and close events from base db w/o affecting listener count 62 | ;(function forwardOpen() { 63 | db.once('open', function () { 64 | space.emit('open', space) 65 | forwardOpen() 66 | }) 67 | })() 68 | 69 | // TODO: level emits closed, multilevel emits close...damn... 70 | ;(function forwardClose() { 71 | var closeEvent = db.createRpcStream ? 'close' : 'closed' 72 | db.once(closeEvent, function () { 73 | // only emit 'close' for sanity's sake 74 | space.emit('close') 75 | forwardClose() 76 | }) 77 | })() 78 | 79 | // proxy `isOpen` to underlying db 80 | space.isOpen = function () { 81 | return db.isOpen() 82 | } 83 | 84 | // set multilevel `isClient` boolean 85 | space.isClient = !!db.isClient 86 | 87 | // for sublevel api-compatibility 88 | space.sublevel = function (ns_, opts_) { 89 | var index = space.sublevels || (space.sublevels = {}) 90 | 91 | // memoize the sublevels we create 92 | // TODO: memoize with bytewise-encoded hex string instead 93 | if (index[ns_]) return index[ns_] 94 | 95 | return index[ns_] = new Bytespace(db, ns.append(ns_), xtend(opts, opts_)) 96 | } 97 | 98 | space.clone = function () { 99 | return new Bytespace(db, ns, opts) 100 | } 101 | 102 | function kOpts(initial) { 103 | return xtend(initial, { keyEncoding: ns.keyEncoding, keyAsBuffer: !ns.hex }) 104 | } 105 | 106 | function vOpts(initial) { 107 | return xtend({ valueEncoding: opts.valueEncoding }, initial) 108 | } 109 | 110 | function kvOpts(initial) { 111 | return vOpts(kOpts(initial)) 112 | } 113 | 114 | function addEncodings(op, db) { 115 | if (db && db.options) { 116 | op.keyEncoding || (op.keyEncoding = db.options.keyEncoding) 117 | op.valueEncoding || (op.valueEncoding = db.options.valueEncoding) 118 | } 119 | return op 120 | } 121 | 122 | // method proxy implementations 123 | if (typeof db.get === 'function') { 124 | space.get = function (k, opts, cb) { 125 | cb = getCallback(opts, cb) 126 | opts = getOptions(opts) 127 | 128 | try { 129 | db.get(ns.encode(k, opts), kvOpts(opts), handler) 130 | } 131 | catch (err) { 132 | process.nextTick(cb.bind(null, err)) 133 | } 134 | 135 | function handler(err, v) { 136 | // sanitize full keypath for notFound errors 137 | if (err && err.notFound || NOT_FOUND.test(err)) { 138 | try { 139 | err = new NotFoundError('Key not found in database [' + k + ']') 140 | } 141 | catch (_) {} 142 | } 143 | 144 | cb(err, v) 145 | } 146 | } 147 | } 148 | 149 | // helper to register pre and post commit hooks 150 | function addHook(hooks, hook) { 151 | hooks.push(hook) 152 | return function () { 153 | var i = hooks.indexOf(hook) 154 | if (i >= 0) return hooks.splice(i, 1) 155 | } 156 | } 157 | 158 | if (typeof db.batch === 'function') { 159 | 160 | space.del = function (k, opts, cb) { 161 | // redirect to batch 162 | space.batch([{ type: 'del', key: k }], opts, cb) 163 | } 164 | 165 | space.put = function (k, v, opts, cb) { 166 | // redirect to batch 167 | space.batch([{ type: 'put', key: k, value: v }], opts, cb) 168 | } 169 | 170 | space.batch = function (ops, opts, cb) { 171 | if (!arguments.length) return new Batch(space) 172 | 173 | cb = getCallback(opts, cb) 174 | opts = getOptions(opts) 175 | 176 | function add(op) { 177 | if (op === false) { 178 | return delete ops[i] 179 | } 180 | ops.push(op) 181 | } 182 | 183 | try { 184 | // encode batch ops and apply precommit hooks 185 | for (var i = 0, len = ops.length; i < len; i++) { 186 | var op = ops[i] 187 | 188 | addEncodings(op, op.prefix) 189 | 190 | op.prefix || (op.prefix = space) 191 | 192 | var ns = op.prefix.namespace 193 | if (!(ns instanceof Namespace)) 194 | return cb('Unknown prefix in batch commit') 195 | 196 | if (ns.prehooks.length) { 197 | ns.trigger(ns.prehooks, op.prefix, [ op, add, ops ]) 198 | } 199 | } 200 | 201 | if (!ops.length) return cb() 202 | 203 | var encodedOps = ops.map(function (op) { 204 | return { 205 | type: op.type, 206 | key: op.prefix.namespace.encode(op.key, opts, op), 207 | keyEncoding: ns.keyEncoding, 208 | value: op.value, 209 | // TODO: multilevel json serialization issue? 210 | valueEncoding: op.valueEncoding, 211 | sync: op.sync 212 | } 213 | }) 214 | 215 | db.batch(encodedOps, kvOpts(opts), function (err) { 216 | if (err) return cb(err) 217 | 218 | // apply postcommit hooks for ops, setting encoded keys to initial state 219 | try { 220 | ops.forEach(function (op) { 221 | var ns = op.prefix.namespace 222 | 223 | if (ns.posthooks.length) { 224 | ns.trigger(ns.posthooks, op.prefix, [ op ]) 225 | } 226 | }) 227 | } 228 | catch (err) { 229 | return cb(err) 230 | } 231 | 232 | cb() 233 | }) 234 | } 235 | catch (err) { 236 | process.nextTick(cb.bind(null, err)) 237 | } 238 | } 239 | 240 | space.pre = function (range, hook) { 241 | // range is not (yet) implemented but here for sublevel compatibility 242 | if (typeof range === 'function') hook = range 243 | return addHook(ns.prehooks, hook) 244 | } 245 | 246 | space.post = function (range, hook) { 247 | if (typeof range === 'function') hook = range 248 | return addHook(ns.posthooks, hook) 249 | } 250 | } 251 | 252 | // if no batch available on db, replace write methods individually 253 | else { 254 | if (typeof db.del === 'function') { 255 | space.del = function (k, opts, cb) { 256 | cb = getCallback(opts, cb) 257 | opts = getOptions(opts) 258 | 259 | try { 260 | db.del(ns.encode(k, opts), kOpts(opts), cb) 261 | } 262 | catch (err) { 263 | process.nextTick(cb.bind(null, err)) 264 | } 265 | } 266 | } 267 | 268 | if (typeof db.put === 'function') { 269 | space.put = function (k, v, opts, cb) { 270 | cb = getCallback(opts, cb) 271 | opts = getOptions(opts) 272 | 273 | try { 274 | db.put(ns.encode(k, opts), v, kvOpts(opts), cb) 275 | } 276 | catch (err) { 277 | process.nextTick(cb.bind(null, err)) 278 | } 279 | } 280 | } 281 | } 282 | 283 | // transform stream to decode data keys 284 | function decodeStream(opts) { 285 | opts || (opts = {}) 286 | var stream = Transform({ objectMode: true }) 287 | 288 | stream._transform = function (data, _, cb) { 289 | try { 290 | // decode keys even when keys or values aren't requested specifically 291 | if ((opts.keys && opts.values) || (!opts.keys && !opts.values)) { 292 | data.key = ns.decode(data.key, opts) 293 | } 294 | else if (opts.keys) { 295 | data = ns.decode(data, opts) 296 | } 297 | } 298 | catch (err) { 299 | return cb(err) 300 | } 301 | cb(null, data) 302 | } 303 | 304 | return stream 305 | } 306 | 307 | // add read stream proxy methods if createReadStream is available 308 | // TODO: clean all this duplication up 309 | function readStream(opts) { 310 | return db.createReadStream(ns.encodeRange(opts)).pipe(decodeStream(opts)) 311 | } 312 | 313 | function liveStream(opts) { 314 | return db.createLiveStream(ns.encodeRange(opts)).pipe(decodeStream(opts)) 315 | } 316 | 317 | if (typeof db.createReadStream === 'function') { 318 | space.createReadStream = space.readStream = function (opts) { 319 | return readStream(xtend({ keys: true, values: true }, vOpts(opts))) 320 | } 321 | if (db.readStream) space.readStream = space.createReadStream 322 | } 323 | 324 | if (typeof db.createKeyStream === 'function') { 325 | space.createKeyStream = function (opts) { 326 | return readStream(xtend(vOpts(opts), { keys: true, values: false })) 327 | } 328 | if (db.keyStream) space.keyStream = space.createKeyStream 329 | } 330 | 331 | if (typeof db.createValueStream === 'function') { 332 | space.createValueStream = function (opts) { 333 | return readStream(xtend(vOpts(opts), { keys: false, values: true })) 334 | } 335 | if (db.valueStream) space.valueStream = space.createValueStream 336 | } 337 | 338 | // add createLiveStream proxy if available 339 | if (typeof db.createLiveStream === 'function') { 340 | space.createLiveStream = function (opts) { 341 | var o = xtend(vOpts(opts), ns.encodeRange(opts)) 342 | return db.createLiveStream(o).pipe(decodeStream(opts)) 343 | } 344 | if (db.liveStream) space.liveStream = space.createLiveStream 345 | } 346 | } 347 | 348 | inherits(Bytespace, EventEmitter) 349 | 350 | // default options for root subspace db (from levelup/lib/util.js) 351 | Bytespace.options = { 352 | keyEncoding: 'utf8', 353 | valueEncoding: 'utf8' 354 | } 355 | -------------------------------------------------------------------------------- /namespace.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var bytewise = require('bytewise-core') 4 | var equal = require('bytewise-core/util').equal 5 | var xtend = require('xtend') 6 | 7 | var LOWER_BOUND = new Buffer([]) 8 | var UPPER_BOUND = new Buffer([ 0xff ]) 9 | var RANGE_KEYS = [ 'gt', 'lt', 'gte', 'lte', 'min', 'max', 'start', 'end' ] 10 | 11 | // brand namespace instance to keep track of subspace root 12 | function Namespace(path, hex) { 13 | this.hex = !!hex 14 | this.keyEncoding = hex ? 'utf8' : 'binary' 15 | 16 | this.path = path 17 | this.buffer = bytewise.encode(path) 18 | this.prehooks = [] 19 | this.posthooks = [] 20 | } 21 | 22 | Namespace.prototype.append = function (ns) { 23 | return new Namespace(this.path.concat(ns), this.hex) 24 | } 25 | 26 | Namespace.prototype.contains = function (k) { 27 | // slice full key to get prefix to compare against buffer 28 | return equal(this.buffer, k.slice(0, this.buffer.length)) 29 | } 30 | 31 | Namespace.prototype.decode = function (k, opts) { 32 | if (this.hex) { 33 | if (typeof k !== 'string') { 34 | throw new TypeError('Key must be encoded as a hex string') 35 | } 36 | 37 | k = new Buffer(k, 'hex') 38 | } 39 | 40 | else if (!Buffer.isBuffer(k)) 41 | throw new TypeError('Key must be encoded as a buffer') 42 | 43 | // TODO: throw? 44 | if (!this.contains(k)) 45 | return k 46 | 47 | // slice off prefix and run through codec 48 | var encoded = k.slice(this.buffer.length) 49 | var coerce = this.codec.keyAsBuffer(opts) ? Buffer : String 50 | return this.codec.decodeKey(coerce(encoded)) 51 | } 52 | 53 | Namespace.prototype.encode = function (k, opts, batchOpts) { 54 | var buffer = this.buffer 55 | 56 | // TODO: this could be a lot more efficient 57 | if (k === LOWER_BOUND) { 58 | // noop 59 | } 60 | else if (k === UPPER_BOUND) { 61 | buffer = Buffer.concat([ buffer, k ]) 62 | } 63 | else { 64 | var encoded = this.codec.encodeKey(k, opts, batchOpts) 65 | buffer = Buffer.concat([ buffer, new Buffer(encoded) ]) 66 | } 67 | 68 | return this.hex ? buffer.toString('hex') : buffer 69 | } 70 | 71 | Namespace.prototype.encodeRange = function (range) { 72 | var opts = xtend(range, { 73 | keyAsBuffer: !this.hex, 74 | keyEncoding: this.keyEncoding 75 | }) 76 | 77 | // TODO: use ltgt rather than this hand-rolled crap? 78 | var has = {} 79 | RANGE_KEYS.forEach(function (k) { 80 | has[k] = k in opts 81 | }) 82 | 83 | if (has.gt || has.lt || has.gte || has.lte) { 84 | // TODO: getting a phantom start value -- but from where? 85 | delete opts.start 86 | } 87 | 88 | else if (has.min || has.max) { 89 | if (opts.min) { 90 | opts.gte = opts.min 91 | delete opts.min 92 | } 93 | if (opts.max) { 94 | opts.lte = opts.max 95 | delete opts.max 96 | } 97 | has.gte = has.lte = true 98 | } 99 | 100 | else if (has.start || has.end) { 101 | if (!opts.reverse) { 102 | opts.gte = has.start ? opts.start : LOWER_BOUND 103 | opts.lte = has.end ? opts.end : UPPER_BOUND 104 | } 105 | else { 106 | opts.gte = has.end ? opts.end : LOWER_BOUND 107 | opts.lte = has.start ? opts.start : UPPER_BOUND 108 | } 109 | 110 | has.gte = has.lte = true 111 | delete opts.start 112 | delete opts.end 113 | } 114 | 115 | if (has.gt) { 116 | opts.gt = this.encode(opts.gt) 117 | delete opts.gte 118 | } 119 | else if (has.gte) { 120 | opts.gte = this.encode(opts.gte) 121 | } 122 | else { 123 | opts.gt = this.encode(LOWER_BOUND) 124 | } 125 | 126 | if (has.lt) { 127 | opts.lt = this.encode(opts.lt) 128 | delete opts.lte 129 | } 130 | else if (has.lte) 131 | opts.lte = this.encode(opts.lte) 132 | else { 133 | opts.lt = this.encode(UPPER_BOUND) 134 | } 135 | 136 | return opts 137 | } 138 | 139 | Namespace.prototype.hasHooks = function (ns) { 140 | return !!(this.prehooks.length || this.posthooks.length) 141 | } 142 | 143 | // loop over hooks and trigger in the context of subspace 144 | Namespace.prototype.trigger = function(hooks, space, args) { 145 | for (var i = 0, len = hooks.length; i < len; i++) { 146 | hooks[i].apply(space, args) 147 | } 148 | } 149 | 150 | module.exports = Namespace 151 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bytespace", 3 | "version": "0.11.0", 4 | "description": "Efficient keypath subspaces prefixed with bytewise tuples", 5 | "main": "index.js", 6 | "scripts": { 7 | "release:minor": "npm version minor && npm publish && git push --follow-tags", 8 | "release:patch": "npm version patch && npm publish && git push --follow-tags", 9 | "test": "tape test/*.js | faucet" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/deanlandolt/bytespace.git" 14 | }, 15 | "keywords": [ 16 | "level", 17 | "sublevel", 18 | "bytewise", 19 | "tuple", 20 | "space", 21 | "namespace" 22 | ], 23 | "author": "Dean Landolt ", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/deanlandolt/bytespace/issues" 27 | }, 28 | "homepage": "https://github.com/deanlandolt/bytespace", 29 | "dependencies": { 30 | "bytewise-core": "^1.2.3", 31 | "level-codec": "^6.1.0", 32 | "level-errors": "^1.0.4", 33 | "xtend": "^4.0.1" 34 | }, 35 | "devDependencies": { 36 | "after": "^0.8.1", 37 | "faucet": "^0.0.1", 38 | "leveldown": "^1.4.4", 39 | "levelup": "^1.3.1", 40 | "list-stream": "^1.0.1", 41 | "memdown": "^1.1.2", 42 | "multilevel": "^7.3.0", 43 | "rimraf": "^2.5.1", 44 | "tape": "^4.4.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var after = require('after') 4 | var bytewise = require('bytewise-core') 5 | var encode = bytewise.encode 6 | var levelup = require('levelup') 7 | var list = require('list-stream') 8 | var inspect = require('util').inspect 9 | var memdown = require('memdown') 10 | var rimraf = require('rimraf') 11 | var test = require('tape') 12 | var xtend = require('xtend') 13 | 14 | var bytespace = require('../') 15 | var DB_PATH = __dirname + '/__bytespace.db' 16 | 17 | 18 | test('levelup, buffer namespaces', run.bind(null, levelup, false)) 19 | test('levelup, hex namespaces', run.bind(null, levelup, true)) 20 | // test('memdown, buffer namespaces', run.bind(null, memup, false)) 21 | // test('memdown, hex namespaces', run.bind(null, memup, true)) 22 | // test('multilevel, buffer namespaces', run.bind(null, multilevel, false)) 23 | // test('multilevel, hex namespaces', run.bind(null, multilevel, true)) 24 | 25 | function memup(opts, fn) { 26 | if (typeof opts === 'function') { 27 | fn = opts 28 | opts = {} 29 | } 30 | if (typeof opts == 'string') opts = {} 31 | opts || (opts = {}) 32 | opts.db = function () { 33 | return new memdown((Math.random()+'').slice(2)) 34 | } 35 | 36 | return levelup('', opts, fn) 37 | } 38 | 39 | function run(dbFactory, hexNamespace, t) { 40 | 41 | function drop(path) { 42 | if (dbFactory === memup) return 43 | rimraf.sync(path) 44 | } 45 | 46 | function dbWrap(dbOpts, testFn) { 47 | if (typeof dbOpts === 'function') { 48 | testFn = dbOpts 49 | dbOpts = {} 50 | } 51 | 52 | return function (t) { 53 | drop(DB_PATH) 54 | levelup(DB_PATH, dbOpts, function (err, base) { 55 | t.ifError(err, 'no error') 56 | 57 | t.$end = t.end 58 | t.end = function (err) { 59 | if (err !== undefined) 60 | t.ifError(err, 'no error') 61 | 62 | base.close(function (err) { 63 | t.ifError(err, 'no error') 64 | drop(DB_PATH) 65 | t.$end() 66 | }) 67 | } 68 | t.dbEquals = dbEquals(base, t) 69 | 70 | testFn(t, base) 71 | }) 72 | } 73 | } 74 | 75 | function subspace(db, ns, opts) { 76 | if (hexNamespace) { 77 | opts = xtend({ hexNamespace: true }, opts) 78 | } 79 | 80 | return bytespace(db, ns, opts) 81 | } 82 | 83 | function readStreamToList(readStream, cb) { 84 | readStream.pipe(list.obj(function (err, data) { 85 | if (err) 86 | return cb(err) 87 | 88 | data = data.map(function (entry) { 89 | if (entry.key || entry.value) { 90 | return [ entry.key, entry.value ] 91 | } 92 | return entry 93 | }) 94 | 95 | cb(null, data) 96 | })) 97 | } 98 | 99 | function dbEquals(base, t) { 100 | return function (expected, cb) { 101 | readStreamToList(base.createReadStream({ 102 | keyEncoding: 'binary' 103 | }), function (err, data) { 104 | t.ifError(err, 'no error') 105 | 106 | t.equal(data.length, expected.length, 'expected length') 107 | 108 | var hexed = expected.map(function (kv, i) { 109 | var d = data[i] 110 | var dataKey = d[0] 111 | var expectedKey = kv[0] 112 | if (typeof expectedKey === 'string') { 113 | d[0] = String(dataKey) 114 | } 115 | else { 116 | expectedKey = hex(expectedKey) 117 | d[0] = hexNamespace ? String(dataKey) : hex(dataKey) 118 | } 119 | return [ expectedKey, kv[1] ] 120 | }) 121 | 122 | t.deepEqual(data, hexed, 'database contains expected entries') 123 | cb() 124 | }) 125 | } 126 | } 127 | 128 | function hex(key) { 129 | return Buffer(key).toString('hex') 130 | } 131 | 132 | function encodeNs(ns, key) { 133 | if (typeof key === 'string') 134 | key = Buffer(key) 135 | 136 | return Buffer.concat([ encode(ns), key ]) 137 | } 138 | 139 | t.test('test puts', dbWrap(function (t, base) { 140 | var dbs = [ 141 | base, 142 | subspace(base, 'test space 1'), 143 | subspace(base, 'test space 2'), 144 | ] 145 | var done = after(dbs.length * 2, verify) 146 | 147 | function verify (err) { 148 | t.ifError(err, 'no error') 149 | 150 | t.dbEquals([ 151 | [ '.bar0', 'foo0' ], 152 | [ '.foo0', 'bar0' ], 153 | [ encodeNs([ 'test space 1' ], '.bar1'), 'foo1' ], 154 | [ encodeNs([ 'test space 1' ], '.foo1'), 'bar1' ], 155 | [ encodeNs([ 'test space 2' ], '.bar2'), 'foo2' ], 156 | [ encodeNs([ 'test space 2' ], '.foo2'), 'bar2' ], 157 | ], t.end) 158 | } 159 | 160 | dbs.forEach(function (db, i) { 161 | db.put('.foo' + i, 'bar' + i, done) 162 | db.put('.bar' + i, 'foo' + i, done) 163 | }) 164 | })) 165 | 166 | 167 | t.test('test puts @ multiple levels', dbWrap(function (t, base) { 168 | var sdb1 = subspace(base, 'test space 1') 169 | var sdb2 = subspace(base, 'test space 2') 170 | var sdb11 = subspace(sdb1, 'inner space 1') 171 | var sdb12 = subspace(sdb1, 'inner space 2') 172 | var sdb21 = subspace(sdb2, 'inner space 1') 173 | var dbs = [ base, sdb1, sdb2, sdb11, sdb12, sdb21 ] 174 | var done = after(dbs.length * 2, verify) 175 | 176 | function verify (err) { 177 | t.ifError(err, 'no error') 178 | 179 | t.dbEquals([ 180 | [ '.bar0', 'foo0' ], 181 | [ '.foo0', 'bar0' ], 182 | [ encodeNs([ 'test space 1' ], '.bar1'), 'foo1' ], 183 | [ encodeNs([ 'test space 1' ], '.foo1'), 'bar1' ], 184 | [ encodeNs([ 'test space 1', 'inner space 1' ], '.bar3'), 'foo3' ], 185 | [ encodeNs([ 'test space 1', 'inner space 1' ], '.foo3'), 'bar3' ], 186 | [ encodeNs([ 'test space 1', 'inner space 2' ], '.bar4'), 'foo4' ], 187 | [ encodeNs([ 'test space 1', 'inner space 2' ], '.foo4'), 'bar4' ], 188 | [ encodeNs([ 'test space 2' ], '.bar2'), 'foo2' ], 189 | [ encodeNs([ 'test space 2' ], '.foo2'), 'bar2' ], 190 | [ encodeNs([ 'test space 2', 'inner space 1' ], '.bar5'), 'foo5' ], 191 | [ encodeNs([ 'test space 2', 'inner space 1' ], '.foo5'), 'bar5' ], 192 | ], t.end) 193 | } 194 | 195 | dbs.forEach(function (db, i) { 196 | db.put('.foo' + i, 'bar' + i, done) 197 | db.put('.bar' + i, 'foo' + i, done) 198 | }) 199 | })) 200 | 201 | 202 | t.test('test gets', dbWrap(function (t, base) { 203 | var dbs = [ 204 | base, 205 | subspace(base, 'test space 1'), 206 | subspace(base, 'test space 2'), 207 | ] 208 | var done = after(dbs.length * 2, verify) 209 | 210 | function verify (err) { 211 | t.ifError(err, 'no error') 212 | 213 | var done = after(dbs.length * 3, t.end) 214 | 215 | dbs.forEach(function (db, i) { 216 | db.get('foo' + i, function (err, value) { 217 | t.ifError(err, 'no error') 218 | t.equal(value, 'bar' + i, 'got expected value') 219 | done() 220 | }) 221 | db.get('bar' + i, function (err, value) { 222 | t.ifError(err, 'no error') 223 | t.equal(value, 'foo' + i, 'got expected value') 224 | done() 225 | }) 226 | db.get('baz' + i, function (err, value) { 227 | t.equal(err && err.notFound, true, 'notFound') 228 | t.equal(value, undefined) 229 | if (db !== base) { 230 | var expected = 'Key not found in database [baz' + i + ']' 231 | t.equal(err && err.message, expected, 'message') 232 | } 233 | done() 234 | }) 235 | }) 236 | } 237 | 238 | dbs.forEach(function (db, i) { 239 | db.put('foo' + i, 'bar' + i, done) 240 | db.put('bar' + i, 'foo' + i, done) 241 | }) 242 | })) 243 | 244 | 245 | t.test('test gets @ multiple levels', dbWrap(function (t, base) { 246 | var sdb1 = subspace(base, 'test space 1') 247 | var sdb2 = subspace(base, 'test space 2') 248 | var sdb11 = subspace(sdb1, 'inner space 1') 249 | var sdb12 = subspace(sdb1, 'inner space 2') 250 | var sdb21 = subspace(sdb2, 'inner space 1') 251 | var dbs = [ base, sdb1, sdb2, sdb11, sdb12, sdb21 ] 252 | var done = after(dbs.length * 2, verify) 253 | 254 | function verify (err) { 255 | t.ifError(err, 'no error') 256 | 257 | var done = after(dbs.length * 2, t.end) 258 | 259 | dbs.forEach(function (db, i) { 260 | db.get('foo' + i, function (err, value) { 261 | t.ifError(err, 'no error') 262 | t.equal(value, 'bar' + i, 'got expected value') 263 | done() 264 | }) 265 | db.get('bar' + i, function (err, value) { 266 | t.ifError(err, 'no error') 267 | t.equal(value, 'foo' + i, 'got expected value') 268 | done() 269 | }) 270 | }) 271 | } 272 | 273 | dbs.forEach(function (db, i) { 274 | db.put('foo' + i, 'bar' + i, done) 275 | db.put('bar' + i, 'foo' + i, done) 276 | }) 277 | })) 278 | 279 | 280 | t.test('test dels', dbWrap(function (t, base) { 281 | var dbs = [ 282 | base, 283 | subspace(base, 'test space 1'), 284 | subspace(base, 'test space 2'), 285 | ] 286 | done = after(dbs.length * 2, afterPut) 287 | 288 | function afterPut (err) { 289 | t.ifError(err, 'no error') 290 | 291 | var done = after(dbs.length, verify) 292 | 293 | dbs.forEach(function (db, i) { 294 | db.del('.bar' + i, function (err) { 295 | t.ifError(err, 'no error') 296 | done() 297 | }) 298 | }) 299 | } 300 | 301 | function verify (err) { 302 | t.ifError(err, 'no error') 303 | 304 | t.dbEquals([ 305 | [ '.foo0', 'bar0' ], 306 | [ encodeNs([ 'test space 1' ], '.foo1'), 'bar1' ], 307 | [ encodeNs([ 'test space 2' ], '.foo2'), 'bar2' ], 308 | ], t.end) 309 | } 310 | 311 | 312 | dbs.forEach(function (db, i) { 313 | db.put('.foo' + i, 'bar' + i, done) 314 | db.put('.bar' + i, 'foo' + i, done) 315 | }) 316 | })) 317 | 318 | 319 | t.test('test dels @ multiple levels', dbWrap(function (t, base) { 320 | var sdb1 = subspace(base, 'test space 1') 321 | var sdb2 = subspace(base, 'test space 2') 322 | var sdb11 = subspace(sdb1, 'inner space 1') 323 | var sdb12 = subspace(sdb1, 'inner space 2') 324 | var sdb21 = subspace(sdb2, 'inner space 1') 325 | var dbs = [ base, sdb1, sdb2, sdb11, sdb12, sdb21 ] 326 | var done = after(dbs.length * 2, afterPut) 327 | 328 | function afterPut (err) { 329 | t.ifError(err, 'no error') 330 | 331 | var done = after(dbs.length, verify) 332 | 333 | dbs.forEach(function (db, i) { 334 | db.del('.bar' + i, function (err) { 335 | t.ifError(err, 'no error') 336 | done() 337 | }) 338 | }) 339 | } 340 | 341 | function verify (err) { 342 | t.ifError(err, 'no error') 343 | 344 | t.dbEquals([ 345 | [ '.foo0', 'bar0' ], 346 | [ encodeNs([ 'test space 1' ], '.foo1'), 'bar1' ], 347 | [ encodeNs([ 'test space 1', 'inner space 1' ], '.foo3'), 'bar3' ], 348 | [ encodeNs([ 'test space 1', 'inner space 2' ], '.foo4'), 'bar4' ], 349 | [ encodeNs([ 'test space 2' ], '.foo2'), 'bar2' ], 350 | [ encodeNs([ 'test space 2', 'inner space 1' ], '.foo5'), 'bar5' ], 351 | ], t.end) 352 | } 353 | 354 | 355 | dbs.forEach(function (db, i) { 356 | db.put('.foo' + i, 'bar' + i, done) 357 | db.put('.bar' + i, 'foo' + i, done) 358 | }) 359 | })) 360 | 361 | 362 | t.test('test batch', dbWrap(function (t, base) { 363 | var dbs = [ 364 | base, 365 | subspace(base, 'test space 1'), 366 | subspace(base, 'test space 2'), 367 | ] 368 | var done = after(dbs.length * 2, afterPut) 369 | 370 | function afterPut (err) { 371 | t.ifError(err, 'no error') 372 | 373 | var done = after(dbs.length, verify) 374 | 375 | dbs.forEach(function (db, i) { 376 | db.batch([ 377 | { type: 'put', key: '.boom' + i, value: 'bang' + i }, 378 | { type: 'del', key: '.bar' + i }, 379 | { type: 'put', key: '.bang' + i, value: 'boom' + i }, 380 | ], function (err) { 381 | t.ifError(err, 'no error') 382 | done() 383 | }) 384 | }) 385 | } 386 | 387 | function verify (err) { 388 | t.ifError(err, 'no error') 389 | 390 | t.dbEquals([ 391 | [ '.bang0', 'boom0' ], 392 | [ '.boom0', 'bang0' ], 393 | [ '.foo0', 'bar0' ], 394 | [ encodeNs([ 'test space 1' ], '.bang1'), 'boom1' ], 395 | [ encodeNs([ 'test space 1' ], '.boom1'), 'bang1' ], 396 | [ encodeNs([ 'test space 1' ], '.foo1'), 'bar1' ], 397 | [ encodeNs([ 'test space 2' ], '.bang2'), 'boom2' ], 398 | [ encodeNs([ 'test space 2' ], '.boom2'), 'bang2' ], 399 | [ encodeNs([ 'test space 2' ], '.foo2'), 'bar2' ], 400 | ], t.end) 401 | } 402 | 403 | 404 | dbs.forEach(function (db, i) { 405 | db.put('.foo' + i, 'bar' + i, done) 406 | db.put('.bar' + i, 'foo' + i, done) 407 | }) 408 | })) 409 | 410 | 411 | t.test('test batch @ multiple levels', dbWrap(function (t, base) { 412 | var sdb1 = subspace(base, 'test space 1') 413 | var sdb2 = subspace(base, 'test space 2') 414 | var sdb11 = subspace(sdb1, 'inner space 1') 415 | var sdb12 = subspace(sdb1, 'inner space 2') 416 | var sdb21 = subspace(sdb2, 'inner space 1') 417 | var dbs = [ base, sdb1, sdb2, sdb11, sdb12, sdb21 ] 418 | var done = after(dbs.length * 2, afterPut) 419 | 420 | function afterPut (err) { 421 | t.ifError(err, 'no error') 422 | 423 | var done = after(dbs.length, verify) 424 | 425 | dbs.forEach(function (db, i) { 426 | db.batch([ 427 | { type: 'put', key: '.boom' + i, value: 'bang' + i }, 428 | { type: 'del', key: '.bar' + i }, 429 | { type: 'put', key: '.bang' + i, value: 'boom' + i }, 430 | ], function (err) { 431 | t.ifError(err, 'no error') 432 | done() 433 | }) 434 | }) 435 | } 436 | 437 | function verify (err) { 438 | t.ifError(err, 'no error') 439 | 440 | t.dbEquals([ 441 | [ '.bang0', 'boom0' ], 442 | [ '.boom0', 'bang0' ], 443 | [ '.foo0', 'bar0' ], 444 | [ encodeNs([ 'test space 1' ], '.bang1'), 'boom1' ], 445 | [ encodeNs([ 'test space 1' ], '.boom1'), 'bang1' ], 446 | [ encodeNs([ 'test space 1' ], '.foo1'), 'bar1' ], 447 | [ encodeNs([ 'test space 1', 'inner space 1' ], '.bang3'), 'boom3' ], 448 | [ encodeNs([ 'test space 1', 'inner space 1' ], '.boom3'), 'bang3' ], 449 | [ encodeNs([ 'test space 1', 'inner space 1' ], '.foo3'), 'bar3' ], 450 | [ encodeNs([ 'test space 1', 'inner space 2' ], '.bang4'), 'boom4' ], 451 | [ encodeNs([ 'test space 1', 'inner space 2' ], '.boom4'), 'bang4' ], 452 | [ encodeNs([ 'test space 1', 'inner space 2' ], '.foo4'), 'bar4' ], 453 | [ encodeNs([ 'test space 2' ], '.bang2'), 'boom2' ], 454 | [ encodeNs([ 'test space 2' ], '.boom2'), 'bang2' ], 455 | [ encodeNs([ 'test space 2' ], '.foo2'), 'bar2' ], 456 | [ encodeNs([ 'test space 2', 'inner space 1' ], '.bang5'), 'boom5' ], 457 | [ encodeNs([ 'test space 2', 'inner space 1' ], '.boom5'), 'bang5' ], 458 | [ encodeNs([ 'test space 2', 'inner space 1' ], '.foo5'), 'bar5' ], 459 | ], t.end) 460 | } 461 | 462 | 463 | dbs.forEach(function (db, i) { 464 | db.put('.foo' + i, 'bar' + i, done) 465 | db.put('.bar' + i, 'foo' + i, done) 466 | }) 467 | })) 468 | 469 | 470 | t.test('test chained batch', dbWrap(function (t, base) { 471 | var dbs = [ 472 | base, 473 | subspace(base, 'test space 1'), 474 | subspace(base, 'test space 2'), 475 | ] 476 | var done = after(dbs.length * 2, afterPut) 477 | 478 | function afterPut (err) { 479 | t.ifError(err, 'no error') 480 | 481 | var done = after(dbs.length, verify) 482 | 483 | dbs.forEach(function (db, i) { 484 | db.batch() 485 | .put('.boom' + i, 'bang' + i) 486 | .del('.bar' + i) 487 | .put('.bang' + i, 'boom' + i) 488 | .write(function (err) { 489 | t.ifError(err, 'no error') 490 | done() 491 | }) 492 | }) 493 | } 494 | 495 | function verify (err) { 496 | t.ifError(err, 'no error') 497 | 498 | t.dbEquals([ 499 | [ '.bang0', 'boom0' ], 500 | [ '.boom0', 'bang0' ], 501 | [ '.foo0', 'bar0' ], 502 | [ encodeNs([ 'test space 1' ], '.bang1'), 'boom1' ], 503 | [ encodeNs([ 'test space 1' ], '.boom1'), 'bang1' ], 504 | [ encodeNs([ 'test space 1' ], '.foo1'), 'bar1' ], 505 | [ encodeNs([ 'test space 2' ], '.bang2'), 'boom2' ], 506 | [ encodeNs([ 'test space 2' ], '.boom2'), 'bang2' ], 507 | [ encodeNs([ 'test space 2' ], '.foo2'), 'bar2' ], 508 | ], t.end) 509 | } 510 | 511 | 512 | dbs.forEach(function (db, i) { 513 | db.put('.foo' + i, 'bar' + i, done) 514 | db.put('.bar' + i, 'foo' + i, done) 515 | }) 516 | })) 517 | 518 | 519 | t.test('test batch @ multiple levels', dbWrap(function (t, base) { 520 | var sdb1 = subspace(base, 'test space 1') 521 | var sdb2 = subspace(base, 'test space 2') 522 | var sdb11 = subspace(sdb1, 'inner space 1') 523 | var sdb12 = subspace(sdb1, 'inner space 2') 524 | var sdb21 = subspace(sdb2, 'inner space 1') 525 | var dbs = [ base, sdb1, sdb2, sdb11, sdb12, sdb21 ] 526 | var done = after(dbs.length * 2, afterPut) 527 | 528 | function afterPut (err) { 529 | t.ifError(err, 'no error') 530 | 531 | var done = after(dbs.length, verify) 532 | 533 | dbs.forEach(function (db, i) { 534 | db.batch() 535 | .put('.boom' + i, 'bang' + i) 536 | .del('.bar' + i) 537 | .put('.bang' + i, 'boom' + i) 538 | .write(function (err) { 539 | t.ifError(err, 'no error') 540 | done() 541 | }) 542 | }) 543 | } 544 | 545 | function verify (err) { 546 | t.ifError(err, 'no error') 547 | 548 | t.dbEquals([ 549 | [ '.bang0', 'boom0' ], 550 | [ '.boom0', 'bang0' ], 551 | [ '.foo0', 'bar0' ], 552 | [ encodeNs([ 'test space 1' ], '.bang1'), 'boom1' ], 553 | [ encodeNs([ 'test space 1' ], '.boom1'), 'bang1' ], 554 | [ encodeNs([ 'test space 1' ], '.foo1'), 'bar1' ], 555 | [ encodeNs([ 'test space 1', 'inner space 1' ], '.bang3'), 'boom3' ], 556 | [ encodeNs([ 'test space 1', 'inner space 1' ], '.boom3'), 'bang3' ], 557 | [ encodeNs([ 'test space 1', 'inner space 1' ], '.foo3'), 'bar3' ], 558 | [ encodeNs([ 'test space 1', 'inner space 2' ], '.bang4'), 'boom4' ], 559 | [ encodeNs([ 'test space 1', 'inner space 2' ], '.boom4'), 'bang4' ], 560 | [ encodeNs([ 'test space 1', 'inner space 2' ], '.foo4'), 'bar4' ], 561 | [ encodeNs([ 'test space 2' ], '.bang2'), 'boom2' ], 562 | [ encodeNs([ 'test space 2' ], '.boom2'), 'bang2' ], 563 | [ encodeNs([ 'test space 2' ], '.foo2'), 'bar2' ], 564 | [ encodeNs([ 'test space 2', 'inner space 1' ], '.bang5'), 'boom5' ], 565 | [ encodeNs([ 'test space 2', 'inner space 1' ], '.boom5'), 'bang5' ], 566 | [ encodeNs([ 'test space 2', 'inner space 1' ], '.foo5'), 'bar5' ], 567 | ], t.end) 568 | } 569 | 570 | 571 | dbs.forEach(function (db, i) { 572 | db.put('.foo' + i, 'bar' + i, done) 573 | db.put('.bar' + i, 'foo' + i, done) 574 | }) 575 | })) 576 | 577 | 578 | t.test('explicit json valueEncoding', dbWrap(function (t, base) { 579 | var thing = { one: 'two', three: 'four' } 580 | var opt = { valueEncoding: 'json'} 581 | var jsonDb = subspace(base, 'json-things', opt) 582 | 583 | jsonDb.put('thing', thing, opt, function (err) { 584 | t.ifError(err, 'no error') 585 | 586 | jsonDb.get('thing', opt, function (err, got) { 587 | t.ifError(err, 'no error') 588 | t.ok(got, 'got something back!') 589 | t.equal(typeof got, 'object', 'got back an object') 590 | t.deepEqual(got, thing, 'got back the right thing') 591 | t.end() 592 | }) 593 | }) 594 | })) 595 | 596 | 597 | t.test('read stream on explicit json valueEncoding', dbWrap(function (t, base) { 598 | var sdb = subspace(base, 'json-things', { valueEncoding: 'json' }) 599 | var v = { an: 'object' } 600 | 601 | sdb.put('k', v, function (err) { 602 | t.error(err) 603 | 604 | readStreamToList(sdb.createReadStream(), function(err, data) { 605 | t.error(err) 606 | t.deepEqual(data, [ [ 'k', v ] ]) 607 | t.end() 608 | }) 609 | }) 610 | })) 611 | 612 | 613 | t.test('value stream on explicit json valueEncoding', dbWrap(function (t, base) { 614 | var sdb = subspace(base, 'json-things', { valueEncoding: 'json' }) 615 | var v = { an: 'object' } 616 | 617 | sdb.put('k', v, function (err) { 618 | t.error(err) 619 | 620 | readStreamToList(sdb.createValueStream(), function(err, data) { 621 | t.error(err) 622 | t.deepEqual(data, [ v ]) 623 | t.end() 624 | }) 625 | }) 626 | })) 627 | 628 | 629 | t.test('explicit json on base db valueEncoding', dbWrap({ 630 | valueEncoding: 'json' 631 | }, function (t, base) { 632 | var thing = { one: 'two', three: 'four' } 633 | var opt = {} 634 | var jsonDb = subspace(base, 'json-things', opt) 635 | 636 | jsonDb.put('thing', thing, opt, function (err) { 637 | t.ifError(err, 'no error') 638 | 639 | jsonDb.get('thing', opt, function (err, got) { 640 | t.ifError(err, 'no error') 641 | t.ok(got, 'got something back!') 642 | t.equal(typeof got, 'object', 'got back an object') 643 | t.deepEqual(got, thing, 'got back the right thing') 644 | t.end() 645 | }) 646 | }) 647 | })) 648 | 649 | 650 | t.test('explicit json on base db valueEncoding, iterator', dbWrap({ 651 | valueEncoding: 'json' 652 | }, function (t, base) { 653 | var thing = { one: 'two', three: 'four' } 654 | var opt = {} 655 | var jsonDb = subspace(base, 'json-things', opt) 656 | 657 | jsonDb.put('thing', thing, opt, function (err) { 658 | t.ifError(err, 'no error') 659 | 660 | readStreamToList( 661 | jsonDb.createReadStream(opt), 662 | function (err, data) { 663 | t.ifError(err, 'no error') 664 | t.equal(data.length, 1) 665 | 666 | t.equal(data[0][0], 'thing') 667 | var got = data[0][1] 668 | t.ok(got, 'got something back!') 669 | t.equal(typeof got, 'object', 'got back an object') 670 | t.deepEqual(got, thing, 'got back the right thing') 671 | t.end() 672 | } 673 | ) 674 | }) 675 | })) 676 | 677 | 678 | t.test('explicit json on db valueEncoding raw entry', dbWrap(function (t, base) { 679 | var sdb = subspace(base, 'json-things', { valueEncoding: 'json' }) 680 | var thing = { one: 'two', three: 'four' } 681 | 682 | sdb.put('thing', thing, function (err) { 683 | t.error(err) 684 | 685 | var key = encodeNs([ 'json-things' ], 'thing') 686 | base.get(hexNamespace ? hex(key) : key, { 687 | valueEncoding: 'utf8' 688 | }, function (err, value) { 689 | t.error(err) 690 | t.equal(typeof value, 'string') 691 | t.equal(value, JSON.stringify(thing)) 692 | t.end() 693 | }) 694 | }) 695 | })) 696 | 697 | 698 | t.test('explicit json on put valueEncoding raw entry', dbWrap(function (t, base) { 699 | var sdb = subspace(base, 'json-things') 700 | var thing = { one: 'two', three: 'four' } 701 | 702 | sdb.put('thing', thing, { 703 | valueEncoding: 'json' 704 | }, function (err) { 705 | t.error(err) 706 | 707 | var key = encodeNs([ 'json-things' ], 'thing') 708 | base.get(hexNamespace ? hex(key) : key, { 709 | valueEncoding: 'utf8' 710 | }, function (err, value) { 711 | t.error(err) 712 | t.equal(typeof value, 'string') 713 | t.equal(value, JSON.stringify(thing)) 714 | t.end() 715 | }) 716 | }) 717 | })) 718 | 719 | 720 | t.test('nested value encodings, utf8 on top', dbWrap({ 721 | valueEncoding: 'json' 722 | }, function (t, base) { 723 | var sp1 = subspace(base, 'sp1', { valueEncoding: 'utf8' }) 724 | var sp2 = subspace(sp1, 'sp2', { valueEncoding: 'json' }) 725 | var sp3 = subspace(sp2, 'sp3', { valueEncoding: 'utf8' }) 726 | var v = '{"an":"object"}' 727 | sp3.put('k', v, function (err) { 728 | t.error(err) 729 | sp3.get('k', function (err, value) { 730 | t.error(err) 731 | t.equal(typeof value, 'string') 732 | t.equal(value, v) 733 | t.end() 734 | }) 735 | }) 736 | })) 737 | 738 | 739 | t.test('nested value encodings, json on top', dbWrap({ 740 | valueEncoding: 'json' 741 | }, function (t, base) { 742 | var sp1 = subspace(base, 'sp1', { valueEncoding: 'utf8' }) 743 | var sp2 = subspace(sp1, 'sp2', { valueEncoding: 'json' }) 744 | var sp3 = subspace(sp2, 'sp3', { valueEncoding: 'utf8' }) 745 | var sp4 = subspace(sp3, 'sp4', { valueEncoding: 'json' }) 746 | var v = { an: 'object' } 747 | sp4.put('k', v, function (err) { 748 | t.error(err) 749 | sp4.get('k', function (err, value) { 750 | t.error(err) 751 | t.equal(typeof value, 'object') 752 | t.deepEqual(value, v) 753 | t.end() 754 | }) 755 | }) 756 | })) 757 | 758 | 759 | t.test('nested value encodings, override', dbWrap({ 760 | valueEncoding: 'json' 761 | }, function (t, base) { 762 | var sp1 = subspace(base, 'sp1', { valueEncoding: 'utf8' }) 763 | var sp2 = subspace(sp1, 'sp2', { valueEncoding: 'json' }) 764 | var sp3 = subspace(sp2, 'sp3', { valueEncoding: 'utf8' }) 765 | var v = { an: 'object' } 766 | sp3.put('k', v, { valueEncoding: 'json' }, function (err) { 767 | t.error(err) 768 | sp3.get('k', { valueEncoding: 'json' }, function (err, value) { 769 | t.error(err) 770 | t.equal(typeof value, 'object') 771 | t.deepEqual(value, v) 772 | t.end() 773 | }) 774 | }) 775 | })) 776 | 777 | 778 | t.test('custom keyEncoding on get', dbWrap(function (t, base) { 779 | // skip get tests for hex mode 780 | if (hexNamespace) { 781 | return t.end() 782 | } 783 | var dbs = [ 784 | base, 785 | subspace(base, 'test space 1'), 786 | subspace(base, 'test space 2'), 787 | ] 788 | var done = after(dbs.length * 2, verify) 789 | 790 | function verify (err) { 791 | t.ifError(err, 'no error') 792 | 793 | var done = after(dbs.length * 5, t.end) 794 | 795 | dbs.forEach(function (db, i) { 796 | 797 | db.get(encode([ '.foo', i ]), function (err, value) { 798 | t.ifError(err, 'no error') 799 | t.equal(value, 'bar' + i, 'got expected value') 800 | done() 801 | }) 802 | 803 | db.get([ '.foo', i ], { keyEncoding: bytewise }, function (err, value) { 804 | t.ifError(err, 'no error') 805 | t.equal(value, 'bar' + i, 'got expected value') 806 | done() 807 | }) 808 | 809 | db.get(encode([ '.bar', i ]), function (err, value) { 810 | t.ifError(err, 'no error') 811 | t.equal(value, 'foo' + i, 'got expected value') 812 | done() 813 | }) 814 | 815 | db.get([ '.bar', i ], { keyEncoding: bytewise }, function (err, value) { 816 | t.ifError(err, 'no error') 817 | t.equal(value, 'foo' + i, 'got expected value') 818 | done() 819 | }) 820 | 821 | var possibilities = [ [ 822 | [ encode([ '.bar', 0 ]), 'foo0' ], 823 | [ encode([ '.foo', 0 ]), 'bar0' ], 824 | [ encodeNs([ 'test space 1' ], encode([ '.bar', 1 ])), 'foo1' ], 825 | [ encodeNs([ 'test space 1' ], encode([ '.foo', 1 ])), 'bar1' ], 826 | [ encodeNs([ 'test space 2' ], encode([ '.bar', 2 ])), 'foo2' ], 827 | [ encodeNs([ 'test space 2' ], encode([ '.foo', 2 ])), 'bar2' ], 828 | ], [ 829 | [ encode([ '.bar', i ]), 'foo' + i ], 830 | [ encode([ '.foo', i ]), 'bar' + i ], 831 | ], [ 832 | [ encode([ '.bar', i ]), 'foo' + i ], 833 | [ encode([ '.foo', i ]), 'bar' + i ], 834 | ] ] 835 | 836 | var expected = possibilities[i] 837 | dbEquals(db, t)(expected, done) 838 | }) 839 | } 840 | 841 | dbs.forEach(function (db, i) { 842 | db.put(encode([ '.foo', i ]), 'bar' + i, done) 843 | db.put(encode([ '.bar', i ]), 'foo' + i, { keyEncoding: 'binary' }, done) 844 | }) 845 | })) 846 | 847 | 848 | t.test('custom keyEncoding on put', dbWrap(function (t, base) { 849 | var dbs = [ 850 | base, 851 | subspace(base, 'test space 1'), 852 | subspace(base, 'test space 2'), 853 | ] 854 | var done = after(dbs.length * 2, verify) 855 | 856 | function verify (err) { 857 | t.ifError(err, 'no error') 858 | 859 | t.dbEquals([ 860 | [ encode([ '.bar', 0 ]), 'foo0'], 861 | [ encode([ '.foo', 0 ]), 'bar0' ], 862 | [ encodeNs([ 'test space 1' ], encode([ '.bar', 1 ])), 'foo1' ], 863 | [ encodeNs([ 'test space 1' ], encode([ '.foo', 1 ])), 'bar1' ], 864 | [ encodeNs([ 'test space 2' ], encode([ '.bar', 2 ])), 'foo2' ], 865 | [ encodeNs([ 'test space 2' ], encode([ '.foo', 2 ])), 'bar2' ], 866 | ], t.end) 867 | } 868 | 869 | dbs.forEach(function (db, i) { 870 | var k0 = encode([ '.foo', i ]) 871 | var k1 = [ '.bar', i ] 872 | var opts1 = { keyEncoding: bytewise } 873 | 874 | // special treatment for base level keys in hex tests 875 | if (!i && hexNamespace) { 876 | k0 = hex(k0) 877 | k1 = hex(encode(k1)) 878 | opts1.keyEncoding = 'utf8' 879 | } 880 | 881 | db.put(k0, 'bar' + i, done) 882 | db.put(k1, 'foo' + i, opts1, done) 883 | }) 884 | })) 885 | 886 | 887 | t.test('custom keyEncoding on db', dbWrap(function (t, base) { 888 | var dbs = [ 889 | base, 890 | subspace(base, 'test space 1'), 891 | subspace(base, 'test space 2', { keyEncoding: bytewise }), 892 | ] 893 | 894 | var done = after(dbs.length * 2, verify) 895 | 896 | function verify (err) { 897 | t.ifError(err, 'no error') 898 | 899 | t.dbEquals([ 900 | [ '.bar,0', 'foo0' ], 901 | [ encode([ '.foo', 0 ]), 'bar0' ], 902 | [ encodeNs([ 'test space 1' ], '.bar,1'), 'foo1' ], 903 | [ encodeNs([ 'test space 1' ], encode([ '.foo', 1 ])), 'bar1' ], 904 | [ encodeNs([ 'test space 2' ], encode(encode([ '.foo', 2 ]))), 'bar2' ], 905 | [ encodeNs([ 'test space 2' ], encode([ '.bar', 2 ])), 'foo2' ], 906 | ], t.end) 907 | } 908 | 909 | dbs.forEach(function (db, i) { 910 | var opts = {} 911 | var k0 = encode([ '.foo', i ]) 912 | var k1 = [ '.bar', i ] 913 | 914 | // special treatment for base level keys in hex tests 915 | if (!i && hexNamespace) { 916 | opts.keyEncoding = 'utf8' 917 | k0 = hex(k0) 918 | k1 = String(k1) 919 | } 920 | 921 | db.put(k0, 'bar' + i, opts, done) 922 | db.put(k1, 'foo' + i, opts, done) 923 | }) 924 | })) 925 | 926 | 927 | function readStreamTest(options) { 928 | t.test('test readStream with ' + inspect(options), function (t) { 929 | var base = dbFactory(DB_PATH) 930 | var ref1Db = dbFactory(DB_PATH + '.ref') 931 | var ref2Db = dbFactory(DB_PATH + '.ref2') 932 | var sdb1 = subspace(base, 'test space') 933 | var sdb2 = subspace(sdb1, 'inner space') 934 | var ref1List 935 | var ref2List 936 | var sdb1List 937 | var sdb2List 938 | var done = after(3, prepare) 939 | 940 | ref1Db.on('ready', done) 941 | ref2Db.on('ready', done) 942 | base.on('ready', done) 943 | 944 | function prepare() { 945 | var batches = [ 946 | ref1Db.batch(), 947 | ref2Db.batch(), 948 | base.batch(), 949 | sdb1.batch(), 950 | sdb2.batch() 951 | ] 952 | var done = after(batches.length, exec) 953 | 954 | for (var i = 0; i < 200; i++) { 955 | batches.forEach(function (batch) { 956 | batch.put('key' + i, 'value' + i) 957 | }) 958 | } 959 | 960 | batches.forEach(function (batch) { 961 | batch.write(done) 962 | }) 963 | } 964 | 965 | function exec() { 966 | var done = after(4, verify) 967 | 968 | readStreamToList( 969 | ref1Db.createReadStream(options), 970 | function (err, data) { 971 | t.ifError(err, 'no error') 972 | ref1List = data 973 | done() 974 | } 975 | ) 976 | 977 | readStreamToList( 978 | ref2Db.createReadStream(options), 979 | function (err, data) { 980 | t.ifError(err, 'no error') 981 | ref2List = data 982 | done() 983 | } 984 | ) 985 | 986 | readStreamToList( 987 | sdb1.createReadStream(options), 988 | function (err, data) { 989 | t.ifError(err, 'no error') 990 | sdb1List = data 991 | done() 992 | } 993 | ) 994 | 995 | readStreamToList( 996 | sdb2.createReadStream(options), 997 | function (err, data) { 998 | t.ifError(err, 'no error') 999 | sdb2List = data 1000 | done() 1001 | } 1002 | ) 1003 | } 1004 | 1005 | function verify () { 1006 | var done = after(3, function (err) { 1007 | t.ifError(err, 'no error') 1008 | drop(DB_PATH) 1009 | drop(DB_PATH + '.ref') 1010 | drop(DB_PATH + '.ref2') 1011 | t.end() 1012 | }) 1013 | 1014 | t.equal( 1015 | sdb1List.length, 1016 | ref1List.length, 1017 | 'test subspace db returned correct number of entries (' + ref1List.length + ')' 1018 | ) 1019 | t.deepEqual( 1020 | sdb1List, 1021 | ref1List, 1022 | 'test subspace db returned same entries as reference db' 1023 | ) 1024 | 1025 | t.equal( 1026 | sdb2List.length, 1027 | ref2List.length, 1028 | 'inner subspace db returned correct number of entries (' + ref2List.length + ')' 1029 | ) 1030 | t.deepEqual( 1031 | sdb2List, 1032 | ref2List, 1033 | 'inner subspace db returned same entries as reference db' 1034 | ) 1035 | 1036 | ref1Db.close(done) 1037 | ref2Db.close(done) 1038 | base.close(done) 1039 | } 1040 | }) 1041 | } 1042 | 1043 | 1044 | readStreamTest({}) 1045 | readStreamTest({ start: 'key0', end: 'key50' }) 1046 | readStreamTest({ start: 'key0', end: 'key150' }) 1047 | readStreamTest({ gte: 'key0', lte: 'key50' }) 1048 | readStreamTest({ gt: 'key0', lt: 'key50' }) 1049 | readStreamTest({ gte: 'key0', lte: 'key150' }) 1050 | readStreamTest({ gt: 'key0', lt: 'key150' }) 1051 | readStreamTest({ start: 'key0', end: 'key50' }) 1052 | readStreamTest({ start: 'key50', end: 'key150' }) 1053 | readStreamTest({ start: 'key50' }) 1054 | readStreamTest({ end: 'key50' }) 1055 | readStreamTest({ gt: 'key50' }) 1056 | readStreamTest({ gte: 'key50' }) 1057 | readStreamTest({ lt: 'key50' }) 1058 | readStreamTest({ lte: 'key50' }) 1059 | readStreamTest({ reverse: true }) 1060 | readStreamTest({ start: 'key0', end: 'key50', reverse: true }) 1061 | readStreamTest({ start: 'key50', end: 'key150', reverse: true }) 1062 | readStreamTest({ gte: 'key0', lte: 'key50', reverse: true }) 1063 | readStreamTest({ gt: 'key0', lt: 'key50', reverse: true }) 1064 | readStreamTest({ gte: 'key0', lte: 'key150', reverse: true }) 1065 | readStreamTest({ gt: 'key0', lt: 'key150', reverse: true }) 1066 | readStreamTest({ start: 'key50', reverse: true }) 1067 | readStreamTest({ end: 'key50', reverse: true }) 1068 | readStreamTest({ gt: 'key50', reverse: true }) 1069 | readStreamTest({ gte: 'key50', reverse: true }) 1070 | readStreamTest({ lt: 'key50', reverse: true }) 1071 | readStreamTest({ lte: 'key50', reverse: true }) 1072 | readStreamTest({ limit: 40 }) 1073 | readStreamTest({ start: 'key0', end: 'key50', limit: 40 }) 1074 | readStreamTest({ start: 'key50', end: 'key150', limit: 40 }) 1075 | readStreamTest({ start: 'key50', limit: 40 }) 1076 | readStreamTest({ reverse: true, limit: 40 }) 1077 | readStreamTest({ gte: 'key0', lte: 'key50', limit: 40 }) 1078 | readStreamTest({ gt: 'key0', lt: 'key50', limit: 40 }) 1079 | readStreamTest({ gte: 'key0', lte: 'key150', limit: 40 }) 1080 | readStreamTest({ gt: 'key0', lt: 'key150', limit: 40 }) 1081 | readStreamTest({ start: 'key50', limit: 40 }) 1082 | readStreamTest({ end: 'key50', limit: 40 }) 1083 | readStreamTest({ gt: 'key50', limit: 40 }) 1084 | readStreamTest({ gte: 'key50', limit: 40 }) 1085 | readStreamTest({ lt: 'key50', limit: 40 }) 1086 | readStreamTest({ lte: 'key50', limit: 40 }) 1087 | readStreamTest({ start: 'key0', end: 'key50', reverse: true, limit: 40 }) 1088 | readStreamTest({ start: 'key50', end: 'key150', reverse: true, limit: 40 }) 1089 | readStreamTest({ start: 'key50', reverse: true, limit: 40 }) 1090 | readStreamTest({ gte: 'key0', lte: 'key50', reverse: true, limit: 40 }) 1091 | readStreamTest({ gt: 'key0', lt: 'key50', reverse: true, limit: 40 }) 1092 | readStreamTest({ gte: 'key0', lte: 'key150', reverse: true, limit: 40 }) 1093 | readStreamTest({ gt: 'key0', lt: 'key150', reverse: true, limit: 40 }) 1094 | readStreamTest({ start: 'key50', reverse: true, limit: 40 }) 1095 | readStreamTest({ end: 'key50', reverse: true, limit: 40 }) 1096 | readStreamTest({ gt: 'key50', reverse: true, limit: 40 }) 1097 | readStreamTest({ gte: 'key50', reverse: true, limit: 40 }) 1098 | readStreamTest({ lt: 'key50', reverse: true, limit: 40 }) 1099 | readStreamTest({ lte: 'key50', reverse: true, limit: 40 }) 1100 | 1101 | 1102 | t.test('precommit hooks', dbWrap(function (t, base) { 1103 | var dbs = [ 1104 | base, 1105 | subspace(base, 'test space 1'), 1106 | subspace(base, 'test space 2'), 1107 | ] 1108 | var calls = [ 0, 0, 0 ] 1109 | 1110 | // 1111 | // add pre hooks 1112 | // 1113 | dbs[1].pre(function (op, add, ops) { 1114 | t.equal(typeof op.key, 'string') 1115 | calls[1]++ 1116 | op.key = op.key.toUpperCase() 1117 | }) 1118 | dbs[2].pre(function (op, add, ops) { 1119 | t.equal(typeof op.key, 'string') 1120 | calls[2]++ 1121 | op = xtend(op) 1122 | op.key += ' xxx' 1123 | add(op) 1124 | }) 1125 | 1126 | var done = after(dbs.length * 2, afterPut) 1127 | 1128 | function afterPut (err) { 1129 | t.ifError(err, 'no error') 1130 | t.deepEqual(calls, [ 0, 2, 2 ]) 1131 | 1132 | var done = after(dbs.length, verify) 1133 | 1134 | dbs.forEach(function (db, i) { 1135 | db.batch([ 1136 | { type: 'put', key: '.boom' + i, value: 'bang' + i }, 1137 | { type: 'del', key: '.bar' + i }, 1138 | { type: 'put', key: '.bang' + i, value: 'boom' + i }, 1139 | ], done) 1140 | }) 1141 | } 1142 | 1143 | function verify (err) { 1144 | t.ifError(err, 'no error') 1145 | 1146 | t.deepEqual(calls, [ 0, 5, 5 ]) 1147 | 1148 | t.dbEquals([ 1149 | [ '.bang0', 'boom0' ], 1150 | [ '.boom0', 'bang0' ], 1151 | [ '.foo0', 'bar0' ], 1152 | [ encodeNs([ 'test space 1' ], '.BANG1'), 'boom1' ], 1153 | [ encodeNs([ 'test space 1' ], '.BOOM1'), 'bang1' ], 1154 | [ encodeNs([ 'test space 1' ], '.FOO1'), 'bar1' ], 1155 | [ encodeNs([ 'test space 2' ], '.bang2'), 'boom2' ], 1156 | [ encodeNs([ 'test space 2' ], '.bang2 xxx'), 'boom2' ], 1157 | [ encodeNs([ 'test space 2' ], '.boom2'), 'bang2' ], 1158 | [ encodeNs([ 'test space 2' ], '.boom2 xxx'), 'bang2' ], 1159 | [ encodeNs([ 'test space 2' ], '.foo2'), 'bar2' ], 1160 | [ encodeNs([ 'test space 2' ], '.foo2 xxx'), 'bar2' ], 1161 | ], t.end) 1162 | } 1163 | 1164 | dbs.forEach(function (db, i) { 1165 | db.put('.foo' + i, 'bar' + i, done) 1166 | db.put('.bar' + i, 'foo' + i, done) 1167 | }) 1168 | 1169 | })) 1170 | 1171 | 1172 | t.test('precommit hooks, chained batches', dbWrap(function (t, base) { 1173 | var dbs = [ 1174 | base, 1175 | subspace(base, 'test space 1'), 1176 | subspace(base, 'test space 2'), 1177 | ] 1178 | var calls = [ 0, 0, 0 ] 1179 | 1180 | // 1181 | // add pre hooks 1182 | // 1183 | dbs[1].pre(function (op, add, ops) { 1184 | t.equal(typeof op.key, 'string') 1185 | calls[1]++ 1186 | op.key = op.key.toUpperCase() 1187 | }) 1188 | dbs[2].pre(function (op, add, ops) { 1189 | t.equal(typeof op.key, 'string') 1190 | calls[2]++ 1191 | op = xtend(op) 1192 | op.key += ' xxx' 1193 | add(op) 1194 | }) 1195 | 1196 | var done = after(dbs.length * 2, afterPut) 1197 | 1198 | function afterPut (err) { 1199 | t.ifError(err, 'no error') 1200 | t.deepEqual(calls, [ 0, 2, 2 ]) 1201 | 1202 | var done = after(dbs.length, verify) 1203 | 1204 | dbs.forEach(function (db, i) { 1205 | db.batch() 1206 | .put('.boom' + i, 'bang' + i) 1207 | .del('.bar' + i) 1208 | .put('.bang' + i, 'boom' + i) 1209 | .write(function (err) { 1210 | t.ifError(err, 'no error') 1211 | done() 1212 | }) 1213 | }) 1214 | } 1215 | 1216 | function verify (err) { 1217 | t.ifError(err, 'no error') 1218 | 1219 | t.deepEqual(calls, [ 0, 5, 5 ]) 1220 | 1221 | t.dbEquals([ 1222 | [ '.bang0', 'boom0' ], 1223 | [ '.boom0', 'bang0' ], 1224 | [ '.foo0', 'bar0' ], 1225 | [ encodeNs([ 'test space 1' ], '.BANG1'), 'boom1' ], 1226 | [ encodeNs([ 'test space 1' ], '.BOOM1'), 'bang1' ], 1227 | [ encodeNs([ 'test space 1' ], '.FOO1'), 'bar1' ], 1228 | [ encodeNs([ 'test space 2' ], '.bang2'), 'boom2' ], 1229 | [ encodeNs([ 'test space 2' ], '.bang2 xxx'), 'boom2' ], 1230 | [ encodeNs([ 'test space 2' ], '.boom2'), 'bang2' ], 1231 | [ encodeNs([ 'test space 2' ], '.boom2 xxx'), 'bang2' ], 1232 | [ encodeNs([ 'test space 2' ], '.foo2'), 'bar2' ], 1233 | [ encodeNs([ 'test space 2' ], '.foo2 xxx'), 'bar2' ], 1234 | ], t.end) 1235 | } 1236 | 1237 | dbs.forEach(function (db, i) { 1238 | db.put('.foo' + i, 'bar' + i, done) 1239 | db.put('.bar' + i, 'foo' + i, done) 1240 | }) 1241 | 1242 | })) 1243 | 1244 | t.test('precommit hooks, noop range argument', dbWrap(function (t, base) { 1245 | var db = subspace(base, 'test space 1', { valueEncoding: 'json' }) 1246 | 1247 | db.pre('this argument is ignored', function (op, add, ops) { 1248 | op.value*= 2 1249 | }) 1250 | 1251 | db.pre({ and: 'this too' }, function (op, add, ops) { 1252 | op.key = op.key.toUpperCase() 1253 | }) 1254 | 1255 | db.pre(function (op, add, ops) { 1256 | op.key+= op.key 1257 | }) 1258 | 1259 | db.put('foo', 2, function(err){ 1260 | t.ifError(err, 'no put error') 1261 | 1262 | db.get('FOOFOO', function(err, value){ 1263 | t.ifError(err, 'no get error') 1264 | t.is(value, 4, 'value is doubled') 1265 | t.end() 1266 | }) 1267 | }) 1268 | })) 1269 | } 1270 | -------------------------------------------------------------------------------- /test/post-hooks.js: -------------------------------------------------------------------------------- 1 | var levelup = require('levelup') 2 | var memdown = require('memdown') 3 | var test = require('tape') 4 | var bytespace = require('../') 5 | 6 | function factory() { 7 | return bytespace(levelup(memdown)) 8 | } 9 | 10 | test('post hooks, multiple namespaces', function(t){ 11 | t.plan(10) 12 | 13 | var db = factory() 14 | var sub1 = db.sublevel('sub1') 15 | var sub2 = db.sublevel('sub2') 16 | var called = [] 17 | var expected = ['root', 'sub1', 'sub2'] 18 | 19 | db.post(function(op){ 20 | called.push(op.key) 21 | t.is(op.key, 'root', 'root hook') 22 | }) 23 | 24 | sub1.post(function(op){ 25 | called.push(op.key) 26 | t.is(op.key, 'sub1', 'sub1 hook') 27 | }) 28 | 29 | sub2.post(function(op){ 30 | called.push(op.key) 31 | t.is(op.key, 'sub2', 'sub2 hook') 32 | }) 33 | 34 | db.batch([ 35 | { key: 'root', value: 'a' }, 36 | { key: 'sub1', prefix: sub1, value: 'b' }, 37 | { key: 'sub2', prefix: sub2, value: 'c' } 38 | ], function(err){ 39 | t.ifError(err, 'no error') 40 | t.same(called, expected, 'order') 41 | 42 | called = [] 43 | 44 | sub2.batch([ 45 | { key: 'root', prefix: db, value: 'a' }, 46 | { key: 'sub1', prefix: sub1, value: 'b' }, 47 | { key: 'sub2', prefix: sub2, value: 'c' } 48 | ], function(err){ 49 | t.ifError(err, 'no error') 50 | t.same(called, expected, 'order') 51 | }) 52 | }) 53 | }) 54 | 55 | test('post hook throwing error', function(t) { 56 | t.plan(1) 57 | 58 | var db = factory() 59 | 60 | db.post(function(op){ 61 | throw new Error('beep') 62 | }) 63 | 64 | db.put('a', 'a', function(err){ 65 | t.is(err.message, 'beep') 66 | }) 67 | }) 68 | --------------------------------------------------------------------------------