├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── release.yml │ ├── test.yml │ └── sauce.yml ├── .airtap.yml ├── LICENSE ├── package.json ├── README.md ├── UPGRADING.md ├── sauce-labs.svg ├── memdown.js ├── CHANGELOG.md └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | npm-debug.log 4 | .nyc_output 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: / 5 | schedule: 6 | interval: monthly 7 | ignore: 8 | - dependency-name: dependency-check 9 | - package-ecosystem: github-actions 10 | directory: / 11 | schedule: 12 | interval: monthly 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: ['*'] 5 | permissions: 6 | contents: write 7 | jobs: 8 | release: 9 | name: Release 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | - name: Create GitHub release 15 | uses: docker://antonyurchenko/git-release:v4 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | -------------------------------------------------------------------------------- /.airtap.yml: -------------------------------------------------------------------------------- 1 | providers: 2 | - airtap-sauce 3 | 4 | browsers: 5 | - name: chrome 6 | - name: firefox 7 | - name: safari 8 | version: 12..latest 9 | - name: ios_saf 10 | version: 12..latest 11 | - name: chrome for android 12 | version: 6..latest 13 | - name: msedge 14 | 15 | presets: 16 | local: 17 | providers: 18 | - airtap-playwright 19 | browsers: 20 | - name: chromium 21 | - name: firefox 22 | - name: webkit -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | matrix: 8 | node: [10, 12, 14] 9 | name: Node ${{ matrix.node }} 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v3 13 | - name: Use node ${{ matrix.node }} 14 | uses: actions/setup-node@v3 15 | with: 16 | node-version: ${{ matrix.node }} 17 | - name: Install 18 | run: npm install 19 | - name: Test 20 | run: npm test 21 | - name: Coverage 22 | run: npm run coverage 23 | - name: Codecov 24 | uses: codecov/codecov-action@v3 25 | with: 26 | file: coverage/lcov.info 27 | -------------------------------------------------------------------------------- /.github/workflows/sauce.yml: -------------------------------------------------------------------------------- 1 | name: Sauce Labs 2 | on: push 3 | concurrency: sauce-labs 4 | jobs: 5 | test: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v3 10 | - name: Set up node 11 | uses: actions/setup-node@v3 12 | with: 13 | node-version: 14 14 | - name: Install 15 | run: npm install 16 | env: 17 | # Download Sauce Connect binary now instead of on first run 18 | SAUCE_CONNECT_DOWNLOAD_ON_INSTALL: true 19 | - name: Add host 20 | run: echo "127.0.0.1 airtap.local" | sudo tee -a /etc/hosts 21 | - name: Test 22 | run: npm run test-browsers 23 | env: 24 | SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} 25 | SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} 26 | - name: Coverage 27 | run: npm run coverage 28 | - name: Codecov 29 | uses: codecov/codecov-action@v3 30 | with: 31 | file: coverage/lcov.info 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2013-present Rod Vagg and the contributors to memdown. 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "memdown", 3 | "version": "6.1.1", 4 | "description": "An drop-in replacement for LevelDOWN that works in memory only", 5 | "authors": [ 6 | "Rod Vagg (https://github.com/rvagg)" 7 | ], 8 | "license": "MIT", 9 | "main": "memdown.js", 10 | "scripts": { 11 | "test": "standard && hallmark && (nyc -s node test.js | faucet) && nyc report", 12 | "test-browsers": "airtap --coverage --verbose test.js", 13 | "test-browsers-local": "airtap --coverage -p local test.js", 14 | "coverage": "nyc report -r lcovonly", 15 | "hallmark": "hallmark --fix", 16 | "dependency-check": "dependency-check . test.js", 17 | "prepublishOnly": "npm run dependency-check" 18 | }, 19 | "files": [ 20 | "memdown.js", 21 | "UPGRADING.md", 22 | "CHANGELOG.md" 23 | ], 24 | "dependencies": { 25 | "abstract-leveldown": "^7.2.0", 26 | "buffer": "^6.0.3", 27 | "functional-red-black-tree": "^1.0.1", 28 | "inherits": "^2.0.1", 29 | "ltgt": "^2.2.0" 30 | }, 31 | "devDependencies": { 32 | "airtap": "^4.0.3", 33 | "airtap-playwright": "^1.0.1", 34 | "airtap-sauce": "^1.1.0", 35 | "dependency-check": "^3.3.0", 36 | "faucet": "^0.0.1", 37 | "hallmark": "^4.0.0", 38 | "level-concat-iterator": "^3.0.0", 39 | "nyc": "^15.1.0", 40 | "standard": "^17.0.0", 41 | "tape": "^5.0.1" 42 | }, 43 | "repository": { 44 | "type": "git", 45 | "url": "https://github.com/Level/memdown.git" 46 | }, 47 | "homepage": "https://github.com/Level/memdown", 48 | "keywords": [ 49 | "level", 50 | "leveldb", 51 | "leveldown", 52 | "levelup", 53 | "memory" 54 | ], 55 | "engines": { 56 | "node": ">=10" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # memdown 2 | 3 | **Superseded by [`memory-level`](https://github.com/Level/memory-level). Please see [Frequently Asked Questions](https://github.com/Level/community#faq).** 4 | 5 | ## Example 6 | 7 | _If you are upgrading: please see [`UPGRADING.md`](./UPGRADING.md)._ 8 | 9 | ```js 10 | const levelup = require('levelup') 11 | const memdown = require('memdown') 12 | 13 | const db = levelup(memdown()) 14 | 15 | db.put('hey', 'you', (err) => { 16 | if (err) throw err 17 | 18 | db.get('hey', { asBuffer: false }, (err, value) => { 19 | if (err) throw err 20 | console.log(value) // 'you' 21 | }) 22 | }) 23 | ``` 24 | 25 | With `async/await`: 26 | 27 | ```js 28 | await db.put('hey', 'you') 29 | const value = await db.get('hey', { asBuffer: false }) 30 | ``` 31 | 32 | Your data is discarded when the process ends or you release a reference to the store. Note as well, though the internals of `memdown` operate synchronously - [`levelup`] does not. 33 | 34 | ## Browser support 35 | 36 | [![Sauce Test Status](https://app.saucelabs.com/browser-matrix/level-ci.svg)](https://app.saucelabs.com/u/level-ci) 37 | 38 | ## Data types 39 | 40 | Keys and values can be strings or Buffers. Any other key type will be irreversibly stringified. The only exceptions are `null` and `undefined`. Keys and values of that type are rejected. 41 | 42 | ```js 43 | const db = levelup(memdown()) 44 | 45 | db.put('example', 123, (err) => { 46 | if (err) throw err 47 | 48 | db.createReadStream({ 49 | keyAsBuffer: false, 50 | valueAsBuffer: false 51 | }).on('data', (entry) => { 52 | console.log(typeof entry.key) // 'string' 53 | console.log(typeof entry.value) // 'string' 54 | }) 55 | }) 56 | ``` 57 | 58 | If you desire non-destructive encoding (e.g. to store and retrieve numbers as-is), wrap `memdown` with [`encoding-down`]. Alternatively install [`level-mem`] which conveniently bundles [`levelup`], `memdown` and [`encoding-down`]. Such an approach is also recommended if you want to achieve universal (isomorphic) behavior. For example, you could have [`leveldown`] in a backend and `memdown` in the frontend. 59 | 60 | ```js 61 | const encode = require('encoding-down') 62 | const db = levelup(encode(memdown(), { valueEncoding: 'json' })) 63 | 64 | db.put('example', 123, (err) => { 65 | if (err) throw err 66 | 67 | db.createReadStream({ 68 | keyAsBuffer: false, 69 | valueAsBuffer: false 70 | }).on('data', (entry) => { 71 | console.log(typeof entry.key) // 'string' 72 | console.log(typeof entry.value) // 'number' 73 | }) 74 | }) 75 | ``` 76 | 77 | ## Snapshot guarantees 78 | 79 | A `memdown` store is backed by [a fully persistent data structure](https://www.npmjs.com/package/functional-red-black-tree) and thus has snapshot guarantees. Meaning that reads operate on a snapshot in time, unaffected by simultaneous writes. 80 | 81 | ## Test 82 | 83 | In addition to the regular `npm test`, you can test `memdown` in a browser of choice with: 84 | 85 | ``` 86 | npm run test-browser-local 87 | ``` 88 | 89 | To check code coverage: 90 | 91 | ``` 92 | npm run coverage 93 | ``` 94 | 95 | ## Contributing 96 | 97 | [`Level/memdown`](https://github.com/Level/memdown) is an **OPEN Open Source Project**. This means that: 98 | 99 | > Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project. 100 | 101 | See the [Contribution Guide](https://github.com/Level/community/blob/master/CONTRIBUTING.md) for more details. 102 | 103 | ## Big Thanks 104 | 105 | Cross-browser Testing Platform and Open Source ♥ Provided by [Sauce Labs](https://saucelabs.com). 106 | 107 | [![Sauce Labs logo](./sauce-labs.svg)](https://saucelabs.com) 108 | 109 | ## Donate 110 | 111 | Support us with a monthly donation on [Open Collective](https://opencollective.com/level) and help us continue our work. 112 | 113 | ## License 114 | 115 | [MIT](LICENSE) 116 | 117 | [`abstract-leveldown`]: https://github.com/Level/abstract-leveldown 118 | 119 | [`levelup`]: https://github.com/Level/levelup 120 | 121 | [`encoding-down`]: https://github.com/Level/encoding-down 122 | 123 | [`leveldown`]: https://github.com/Level/leveldown 124 | 125 | [`level-mem`]: https://github.com/Level/mem 126 | 127 | [level-badge]: https://leveljs.org/img/badge.svg 128 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | # Upgrade Guide 2 | 3 | This document describes breaking changes and how to upgrade. For a complete list of changes including minor and patch releases, please refer to the [`CHANGELOG`][changelog]. 4 | 5 | ## 6.0.0 6 | 7 | Legacy range options have been removed ([Level/community#86](https://github.com/Level/community/issues/86)). If you previously did: 8 | 9 | ```js 10 | db.iterator({ start: 'a', end: 'z' }) 11 | ``` 12 | 13 | An error would now be thrown and you must instead do: 14 | 15 | ```js 16 | db.iterator({ gte: 'a', lte: 'z' }) 17 | ``` 18 | 19 | This release also drops support of legacy runtime environments ([Level/community#98](https://github.com/Level/community/issues/98)): 20 | 21 | - Node.js 6 and 8 22 | - Internet Explorer 11 23 | - Safari 9-11 24 | - Stock Android browser (AOSP). 25 | 26 | Lastly, and less likely to be a breaking change, the [`immediate`](https://github.com/calvinmetcalf/immediate) browser shim for `process.nextTick()` has been replaced with the smaller [`queue-microtask`](https://github.com/feross/queue-microtask). 27 | 28 | ## 5.0.0 29 | 30 | Support of keys & values other than strings and Buffers has been dropped. Internally `memdown` now stores keys & values as Buffers which solves a number of compatibility issues ([#186](https://github.com/Level/memdown/issues/186)). If you pass in a key or value that isn't a string or Buffer, it will be irreversibly stringified. 31 | 32 | ## 4.0.0 33 | 34 | This is an upgrade to `abstract-leveldown@6` which solves long-standing issues around serialization and type support. 35 | 36 | ### Range options are now serialized 37 | 38 | Previously, range options like `lt` were passed through as-is by `abstract-leveldown`, unlike keys. This makes no difference for `memdown` as it does not serialize anything. 39 | 40 | ### The rules for range options have been relaxed 41 | 42 | Because `null`, `undefined`, zero-length strings and zero-length buffers are significant types in encodings like `bytewise` and `charwise`, they became valid as range options in `abstract-leveldown`. This means `db.iterator({ gt: undefined })` is not the same as `db.iterator({})`. 43 | 44 | For `memdown`, when used by itself, the behavior of `null`, `undefined`, zero-length strings and zero-length buffers is undefined. 45 | 46 | ### Nullish values are rejected 47 | 48 | In addition to rejecting `null` and `undefined` as _keys_, `abstract-leveldown` now also rejects these types as _values_, due to preexisting significance in streams and iterators. 49 | 50 | ### Zero-length array keys are rejected 51 | 52 | Though this was already the case, `abstract-leveldown` has replaced the behavior with an explicit `Array.isArray()` check and a new error message. 53 | 54 | ### Browser support 55 | 56 | IE10 has been dropped. 57 | 58 | ## 3.0.0 59 | 60 | Dropped support for node 4. No other breaking changes. 61 | 62 | ## 2.0.0 63 | 64 | This release drops Node.js 0.12, brings `memdown` up to par with latest [`levelup`][levelup] (v2) and [`abstract-leveldown`][abstract-leveldown] (v4), simplifies serialization and removes global state. 65 | 66 | ### Targets latest [`levelup`][levelup] 67 | 68 | Usage has changed to: 69 | 70 | ```js 71 | const levelup = require('levelup') 72 | const memdown = require('memdown') 73 | 74 | const db = levelup(memdown()) 75 | ``` 76 | 77 | From the old: 78 | 79 | ```js 80 | const db = levelup('mydb', { db: memdown }) 81 | ``` 82 | 83 | ### No stringification of keys and values 84 | 85 | This means that in addition to Buffers, you can store any JS type without the need for [`encoding-down`][encoding-down]. This release also makes behavior consistent in Node.js and browsers. Please refer to the [README](./README.md) for a detailed explanation. 86 | 87 | ### No global state or `location` argument 88 | 89 | If you previously did this to make a global store: 90 | 91 | ```js 92 | const db = levelup('mydb', { db: memdown }) 93 | ``` 94 | 95 | You must now attach the store to a global yourself (if you desire global state): 96 | 97 | ```js 98 | const db = window.mydb = levelup(memdown()) 99 | ``` 100 | 101 | ### No `null` batch operations 102 | 103 | Instead of skipping `null` operations, `db.batch([null])` will throw an error courtesy of [`abstract-leveldown`][abstract-leveldown]. 104 | 105 | [changelog]: CHANGELOG.md 106 | 107 | [abstract-leveldown]: https://github.com/Level/abstract-leveldown 108 | 109 | [levelup]: https://github.com/Level/levelup 110 | 111 | [encoding-down]: https://github.com/Level/encoding-down 112 | -------------------------------------------------------------------------------- /sauce-labs.svg: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | 16 | Sauce Labs 17 | 22 | 24 | 29 | 34 | 39 | 44 | 49 | 54 | 59 | 64 | 69 | 74 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /memdown.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const inherits = require('inherits') 4 | const { AbstractLevelDOWN, AbstractIterator } = require('abstract-leveldown') 5 | const ltgt = require('ltgt') 6 | const createRBT = require('functional-red-black-tree') 7 | const { Buffer } = require('buffer') 8 | 9 | const rangeOptions = ['gt', 'gte', 'lt', 'lte'] 10 | const kNone = Symbol('none') 11 | const kKeys = Symbol('keys') 12 | const kValues = Symbol('values') 13 | const kIncrement = Symbol('increment') 14 | 15 | // TODO (perf): replace ltgt.compare with a simpler, buffer-only comparator 16 | function gt (value) { 17 | return ltgt.compare(value, this._upperBound) > 0 18 | } 19 | 20 | function gte (value) { 21 | return ltgt.compare(value, this._upperBound) >= 0 22 | } 23 | 24 | function lt (value) { 25 | return ltgt.compare(value, this._upperBound) < 0 26 | } 27 | 28 | function lte (value) { 29 | return ltgt.compare(value, this._upperBound) <= 0 30 | } 31 | 32 | function MemIterator (db, options) { 33 | AbstractIterator.call(this, db) 34 | this._limit = options.limit 35 | 36 | if (this._limit === -1) this._limit = Infinity 37 | 38 | const tree = db._store 39 | 40 | this.keyAsBuffer = options.keyAsBuffer !== false 41 | this.valueAsBuffer = options.valueAsBuffer !== false 42 | this[kKeys] = options.keys 43 | this[kValues] = options.values 44 | this._reverse = options.reverse 45 | this._options = options 46 | this._done = 0 47 | 48 | if (!this._reverse) { 49 | this._incr = 'next' 50 | this._lowerBound = ltgt.lowerBound(options, kNone) 51 | this._upperBound = ltgt.upperBound(options, kNone) 52 | 53 | if (this._lowerBound === kNone) { 54 | this._tree = tree.begin 55 | } else if (ltgt.lowerBoundInclusive(options)) { 56 | this._tree = tree.ge(this._lowerBound) 57 | } else { 58 | this._tree = tree.gt(this._lowerBound) 59 | } 60 | 61 | if (this._upperBound !== kNone) { 62 | if (ltgt.upperBoundInclusive(options)) { 63 | this._test = lte 64 | } else { 65 | this._test = lt 66 | } 67 | } 68 | } else { 69 | this._incr = 'prev' 70 | this._lowerBound = ltgt.upperBound(options, kNone) 71 | this._upperBound = ltgt.lowerBound(options, kNone) 72 | 73 | if (this._lowerBound === kNone) { 74 | this._tree = tree.end 75 | } else if (ltgt.upperBoundInclusive(options)) { 76 | this._tree = tree.le(this._lowerBound) 77 | } else { 78 | this._tree = tree.lt(this._lowerBound) 79 | } 80 | 81 | if (this._upperBound !== kNone) { 82 | if (ltgt.lowerBoundInclusive(options)) { 83 | this._test = gte 84 | } else { 85 | this._test = gt 86 | } 87 | } 88 | } 89 | } 90 | 91 | inherits(MemIterator, AbstractIterator) 92 | 93 | MemIterator.prototype._next = function (callback) { 94 | if (!this[kIncrement]()) return this._nextTick(callback) 95 | if (!this._tree.valid) return this._nextTick(callback) 96 | 97 | let key = this._tree.key 98 | let value = this._tree.value 99 | 100 | if (!this._test(key)) return this._nextTick(callback) 101 | 102 | key = !this[kKeys] ? undefined : this.keyAsBuffer ? key : key.toString() 103 | value = !this[kValues] ? undefined : this.valueAsBuffer ? value : value.toString() 104 | 105 | this._tree[this._incr]() 106 | this._nextTick(callback, null, key, value) 107 | } 108 | 109 | MemIterator.prototype[kIncrement] = function () { 110 | return this._done++ < this._limit 111 | } 112 | 113 | MemIterator.prototype._test = function () { 114 | return true 115 | } 116 | 117 | MemIterator.prototype._outOfRange = function (target) { 118 | if (!this._test(target)) { 119 | return true 120 | } else if (this._lowerBound === kNone) { 121 | return false 122 | } else if (!this._reverse) { 123 | if (ltgt.lowerBoundInclusive(this._options)) { 124 | return ltgt.compare(target, this._lowerBound) < 0 125 | } else { 126 | return ltgt.compare(target, this._lowerBound) <= 0 127 | } 128 | } else { 129 | if (ltgt.upperBoundInclusive(this._options)) { 130 | return ltgt.compare(target, this._lowerBound) > 0 131 | } else { 132 | return ltgt.compare(target, this._lowerBound) >= 0 133 | } 134 | } 135 | } 136 | 137 | MemIterator.prototype._seek = function (target) { 138 | if (target.length === 0) { 139 | throw new Error('cannot seek() to an empty target') 140 | } 141 | 142 | if (this._outOfRange(target)) { 143 | this._tree = this.db._store.end 144 | this._tree.next() 145 | } else if (this._reverse) { 146 | this._tree = this.db._store.le(target) 147 | } else { 148 | this._tree = this.db._store.ge(target) 149 | } 150 | } 151 | 152 | function MemDOWN () { 153 | if (!(this instanceof MemDOWN)) return new MemDOWN() 154 | 155 | AbstractLevelDOWN.call(this, { 156 | bufferKeys: true, 157 | snapshots: true, 158 | permanence: false, 159 | seek: true, 160 | clear: true, 161 | getMany: true 162 | }) 163 | 164 | this._store = createRBT(ltgt.compare) 165 | } 166 | 167 | inherits(MemDOWN, AbstractLevelDOWN) 168 | 169 | MemDOWN.prototype._open = function (options, callback) { 170 | this._nextTick(callback) 171 | } 172 | 173 | MemDOWN.prototype._serializeKey = function (key) { 174 | return Buffer.isBuffer(key) ? key : Buffer.from(String(key)) 175 | } 176 | 177 | MemDOWN.prototype._serializeValue = function (value) { 178 | return Buffer.isBuffer(value) ? value : Buffer.from(String(value)) 179 | } 180 | 181 | MemDOWN.prototype._put = function (key, value, options, callback) { 182 | const iter = this._store.find(key) 183 | 184 | if (iter.valid) { 185 | this._store = iter.update(value) 186 | } else { 187 | this._store = this._store.insert(key, value) 188 | } 189 | 190 | this._nextTick(callback) 191 | } 192 | 193 | MemDOWN.prototype._get = function (key, options, callback) { 194 | let value = this._store.get(key) 195 | 196 | if (typeof value === 'undefined') { 197 | // 'NotFound' error, consistent with LevelDOWN API 198 | return this._nextTick(function callNext () { 199 | callback(new Error('NotFound')) 200 | }) 201 | } 202 | 203 | if (!options.asBuffer) { 204 | value = value.toString() 205 | } 206 | 207 | this._nextTick(callback, null, value) 208 | } 209 | 210 | MemDOWN.prototype._getMany = function (keys, options, callback) { 211 | this._nextTick(callback, null, keys.map((key) => { 212 | const value = this._store.get(key) 213 | return value === undefined || options.asBuffer ? value : value.toString() 214 | })) 215 | } 216 | 217 | MemDOWN.prototype._del = function (key, options, callback) { 218 | this._store = this._store.remove(key) 219 | this._nextTick(callback) 220 | } 221 | 222 | MemDOWN.prototype._batch = function (array, options, callback) { 223 | let i = -1 224 | let key 225 | let value 226 | let iter 227 | const len = array.length 228 | let tree = this._store 229 | 230 | while (++i < len) { 231 | key = array[i].key 232 | iter = tree.find(key) 233 | 234 | if (array[i].type === 'put') { 235 | value = array[i].value 236 | tree = iter.valid ? iter.update(value) : tree.insert(key, value) 237 | } else { 238 | tree = iter.remove() 239 | } 240 | } 241 | 242 | this._store = tree 243 | this._nextTick(callback) 244 | } 245 | 246 | MemDOWN.prototype._clear = function (options, callback) { 247 | if (!hasLimit(options) && !Object.keys(options).some(isRangeOption)) { 248 | // Delete everything by creating a new empty tree. 249 | this._store = createRBT(ltgt.compare) 250 | return this._nextTick(callback) 251 | } 252 | 253 | const iterator = this._iterator({ 254 | ...options, 255 | keys: true, 256 | values: false, 257 | keyAsBuffer: true 258 | }) 259 | 260 | const loop = () => { 261 | // TODO: add option to control "batch size" 262 | for (let i = 0; i < 500; i++) { 263 | if (!iterator[kIncrement]()) return callback() 264 | if (!iterator._tree.valid) return callback() 265 | if (!iterator._test(iterator._tree.key)) return callback() 266 | 267 | // Must also include changes made in parallel to clear() 268 | this._store = this._store.remove(iterator._tree.key) 269 | iterator._tree[iterator._incr]() 270 | } 271 | 272 | // Some time to breathe 273 | this._nextTick(loop) 274 | } 275 | 276 | this._nextTick(loop) 277 | } 278 | 279 | MemDOWN.prototype._iterator = function (options) { 280 | return new MemIterator(this, options) 281 | } 282 | 283 | module.exports = MemDOWN 284 | 285 | // Exposed for unit tests only 286 | module.exports.MemIterator = MemIterator 287 | 288 | // Use setImmediate() in Node.js to allow IO in between our callbacks 289 | if (typeof process !== 'undefined' && !process.browser && typeof global !== 'undefined' && typeof global.setImmediate === 'function') { 290 | const setImmediate = global.setImmediate 291 | 292 | MemDOWN.prototype._nextTick = MemIterator.prototype._nextTick = function (fn, ...args) { 293 | if (args.length === 0) { 294 | setImmediate(fn) 295 | } else { 296 | setImmediate(() => fn(...args)) 297 | } 298 | } 299 | } 300 | 301 | function isRangeOption (k) { 302 | return rangeOptions.includes(k) 303 | } 304 | 305 | function hasLimit (options) { 306 | return options.limit != null && 307 | options.limit >= 0 && 308 | options.limit < Infinity 309 | } 310 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [6.1.1] - 2021-10-02 4 | 5 | ### Fixed 6 | 7 | - Optimize `db.clear()` ([#213](https://github.com/Level/memdown/issues/213)) ([`a0856c4`](https://github.com/Level/memdown/commit/a0856c4)) (Vincent Weevers). 8 | 9 | ## [6.1.0] - 2021-09-28 10 | 11 | ### Added 12 | 13 | - Add `db.getMany(keys)` ([#212](https://github.com/Level/memdown/issues/212)) ([`9d9691a`](https://github.com/Level/memdown/commit/9d9691a)) (Vincent Weevers). 14 | 15 | ## [6.0.0] - 2021-04-10 16 | 17 | _If you are upgrading: please see [`UPGRADING.md`](https://github.com/Level/memdown/blob/master/UPGRADING.md)._ 18 | 19 | ### Changed 20 | 21 | - **Breaking:** bump `abstract-leveldown` ([`4e2f320`](https://github.com/Level/memdown/commit/4e2f320), [`dee72a3`](https://github.com/Level/memdown/commit/dee72a3)) (Vincent Weevers) 22 | - **Breaking:** drop node 6 and 8 ([Level/community#98](https://github.com/Level/community/issues/98)) ([`3708b5f`](https://github.com/Level/memdown/commit/3708b5f)) (Vincent Weevers) 23 | - **Breaking:** modernize syntax and bump `standard` ([Level/community#98](https://github.com/Level/community/issues/98)) ([`c1f98c3`](https://github.com/Level/memdown/commit/c1f98c3)) (Vincent Weevers) 24 | - Use `_nextTick()` utility for browsers ([`ba525c4`](https://github.com/Level/memdown/commit/ba525c4)) (Vincent Weevers) 25 | - Replace `safe-buffer` with `buffer` ([`d3dab23`](https://github.com/Level/memdown/commit/d3dab23)) (Vincent Weevers) 26 | - Remove default export ([Level/community#87](https://github.com/Level/community/issues/87)) ([`5fd8368`](https://github.com/Level/memdown/commit/5fd8368)) (Vincent Weevers) 27 | - Unlock dependencies ([`7126df1`](https://github.com/Level/memdown/commit/7126df1)) (Vincent Weevers) 28 | 29 | ## [5.1.0] - 2019-10-04 30 | 31 | ### Changed 32 | 33 | - Upgrade `hallmark` devDependency from `^1.0.0` to `^2.0.0` ([#196](https://github.com/Level/memdown/issues/196)) ([**@vweevers**](https://github.com/vweevers)) 34 | - Upgrade `standard` devDependency from `^13.0.1` to `^14.0.0` ([#195](https://github.com/Level/memdown/issues/195)) ([**@vweevers**](https://github.com/vweevers)) 35 | 36 | ### Added 37 | 38 | - Add manifest ([Level/community#83](https://github.com/Level/community/issues/83)) ([#198](https://github.com/Level/memdown/issues/198)) ([**@vweevers**](https://github.com/vweevers)) 39 | 40 | ## [5.0.0] - 2019-08-18 41 | 42 | _If you are upgrading: please see [`UPGRADING.md`](https://github.com/Level/memdown/blob/master/UPGRADING.md)._ 43 | 44 | ### Changed 45 | 46 | - Upgrade `abstract-leveldown` from `~6.0.1` to `~6.1.0` ([#194](https://github.com/Level/memdown/issues/194)) ([**@vweevers**](https://github.com/vweevers)) 47 | - Upgrade `safe-buffer` from `~5.1.1` to `~5.2.0` ([#187](https://github.com/Level/memdown/issues/187)) ([**@vweevers**](https://github.com/vweevers)) 48 | - Upgrade `hallmark` devDependency from `^0.1.0` to `^1.0.0` ([#189](https://github.com/Level/memdown/issues/189)) ([**@vweevers**](https://github.com/vweevers)) 49 | - Upgrade `standard` devDependency from `^12.0.0` to `^13.0.1` ([#188](https://github.com/Level/memdown/issues/188)) ([**@vweevers**](https://github.com/vweevers)) 50 | 51 | ### Added 52 | 53 | - Opt-in to new [`clear()`](https://github.com/Level/abstract-leveldown#dbclearoptions-callback) tests ([#194](https://github.com/Level/memdown/issues/194)) ([**@vweevers**](https://github.com/vweevers)) 54 | 55 | ### Removed 56 | 57 | - Drop support of key & value types other than string and Buffer ([#191](https://github.com/Level/memdown/issues/191), [#192](https://github.com/Level/memdown/issues/192)) ([**@vweevers**](https://github.com/vweevers)) 58 | 59 | ## [4.1.0] - 2019-06-28 60 | 61 | ### Changed 62 | 63 | - Upgrade `nyc` devDependency from `^13.2.0` to `^14.0.0` ([#182](https://github.com/Level/memdown/issues/182)) ([**@vweevers**](https://github.com/vweevers)) 64 | 65 | ### Added 66 | 67 | - Support seeking ([#184](https://github.com/Level/memdown/issues/184)) ([**@MeirionHughes**](https://github.com/MeirionHughes)) 68 | 69 | ## [4.0.0] - 2019-03-29 70 | 71 | _If you are upgrading: please see [`UPGRADING.md`](https://github.com/Level/memdown/blob/master/UPGRADING.md)._ 72 | 73 | ### Changed 74 | 75 | - Upgrade `abstract-leveldown` from `~5.0.0` to `~6.0.1` ([#174](https://github.com/Level/memdown/issues/174)) ([**@vweevers**](https://github.com/vweevers)) 76 | - Invoke abstract tests from single function ([#174](https://github.com/Level/memdown/issues/174)) ([**@vweevers**](https://github.com/vweevers)) 77 | - Use `level-concat-iterator` and `testCommon.factory()` in custom tests ([#174](https://github.com/Level/memdown/issues/174)) ([**@vweevers**](https://github.com/vweevers)) 78 | - Don't use falsy or undefined as not-defined signal ([#174](https://github.com/Level/memdown/issues/174)) ([**@vweevers**](https://github.com/vweevers)) 79 | ([**@vweevers**](https://github.com/vweevers)) 80 | - Upgrade `standard` devDependency from `^11.0.0` to `^12.0.0` ([#173](https://github.com/Level/memdown/issues/173)) ([**@ralphtheninja**](https://github.com/ralphtheninja)) 81 | - Upgrade `airtap` devDependency from `^0.0.5` to `^2.0.0` ([#161](https://github.com/Level/memdown/issues/161), [#163](https://github.com/Level/memdown/issues/163), [#168](https://github.com/Level/memdown/issues/168), [#172](https://github.com/Level/memdown/issues/172), [#177](https://github.com/Level/memdown/issues/177)) ([**@vweevers**](https://github.com/vweevers)) 82 | - Tweak copyright years for less maintenance ([`760b375`](https://github.com/Level/memdown/commit/760b375)) ([**@ralphtheninja**](https://github.com/ralphtheninja)) 83 | - Apply common project tweaks ([#178](https://github.com/Level/memdown/issues/178), [#179](https://github.com/Level/memdown/issues/179)) ([**@vweevers**](https://github.com/vweevers)) 84 | 85 | ### Added 86 | 87 | - Add `nyc` and browser code coverage ([#169](https://github.com/Level/memdown/issues/169), [#180](https://github.com/Level/memdown/issues/180)) ([**@vweevers**](https://github.com/vweevers)) 88 | - Add Sauce Labs logo to README ([#165](https://github.com/Level/memdown/issues/165)) ([**@ralphtheninja**](https://github.com/ralphtheninja)) 89 | - Document that nullish values are now also rejected ([#174](https://github.com/Level/memdown/issues/174)) ([**@vweevers**](https://github.com/vweevers)) 90 | - Test negative and positive `Infinity` as keys ([#174](https://github.com/Level/memdown/issues/174)) ([**@vweevers**](https://github.com/vweevers)) 91 | 92 | ### Removed 93 | 94 | - Remove contributors from `package.json` ([`80b3e3a`](https://github.com/Level/memdown/commit/80b3e3a)) ([**@ralphtheninja**](https://github.com/ralphtheninja)) 95 | - Remove node 9 ([`0de8721`](https://github.com/Level/memdown/commit/0de8721)) ([**@ralphtheninja**](https://github.com/ralphtheninja)) 96 | - Remove now irrelevant serialization of nullish values ([#174](https://github.com/Level/memdown/issues/174)) ([**@vweevers**](https://github.com/vweevers)) 97 | - Remove dummy location from `abstract-leveldown` constructor call ([#174](https://github.com/Level/memdown/issues/174)) ([**@vweevers**](https://github.com/vweevers)) 98 | - Remove `rimraf` devDependency ([#174](https://github.com/Level/memdown/issues/174)) ([**@vweevers**](https://github.com/vweevers)) 99 | 100 | ### Fixed 101 | 102 | - Fix link references in `UPGRADING.md` for latest `remark` ([`f111a6f`](https://github.com/Level/memdown/commit/f111a6f)) ([**@vweevers**](https://github.com/vweevers)) 103 | 104 | ## [3.0.0] - 2018-05-22 105 | 106 | _If you are upgrading: please see [`UPGRADING.md`](https://github.com/Level/memdown/blob/master/UPGRADING.md)._ 107 | 108 | ### Added 109 | 110 | - Add node 9 and 10 to Travis ([**@vweevers**](https://github.com/vweevers), [**@ralphtheninja**](https://github.com/ralphtheninja)) 111 | 112 | ### Changed 113 | 114 | - Upgrade `abstract-leveldown` to `5.0.0` ([**@ralphtheninja**](https://github.com/ralphtheninja)) 115 | - Upgrade `standard` to `11.0.0` ([**@ralphtheninja**](https://github.com/ralphtheninja)) 116 | - Tweak readme ([**@ralphtheninja**](https://github.com/ralphtheninja)) 117 | - Use `airtap` instead of `zuul` ([**@vweevers**](https://github.com/vweevers)) 118 | - Switch to plain MIT license ([**@vweevers**](https://github.com/vweevers)) 119 | 120 | ### Removed 121 | 122 | - Remove TypeScript typings ([**@ralphtheninja**](https://github.com/ralphtheninja)) 123 | - Remove TypeScript tests ([**@vweevers**](https://github.com/vweevers)) 124 | - Remove node 4 from Travis ([**@ralphtheninja**](https://github.com/ralphtheninja)) 125 | - Remove deprecated JWT addon from Travis ([**@vweevers**](https://github.com/vweevers)) 126 | - Remove obsolete `--stderr` flag ([**@vweevers**](https://github.com/vweevers)) 127 | 128 | ## [2.0.0] - 2018-02-11 129 | 130 | _If you are upgrading: please see [`UPGRADING.md`](https://github.com/Level/memdown/blob/master/UPGRADING.md)._ 131 | 132 | ### Added 133 | 134 | - Run test suite with TypeScript in addition to Node.js ([**@vweevers**](https://github.com/vweevers)) 135 | - Add `UPGRADING.md` ([**@vweevers**](https://github.com/vweevers)) 136 | - Add `CHANGELOG.md` ([**@vweevers**](https://github.com/vweevers)) 137 | - README: explain types and snapshot guarantees ([**@vweevers**](https://github.com/vweevers)) 138 | - README: add level badge ([**@ralphtheninja**](https://github.com/ralphtheninja)) 139 | - README: add node version badge ([**@ralphtheninja**](https://github.com/ralphtheninja)) 140 | 141 | ### Changed 142 | 143 | - Update `abstract-leveldown` to 4.0.0 ([**@vweevers**](https://github.com/vweevers)) 144 | - Perform serialization through idiomatic `_serializeKey` and `_serializeValue` ([**@vweevers**](https://github.com/vweevers)) 145 | - Don't stringify anything except nullish values ([**@vweevers**](https://github.com/vweevers)) 146 | - Use `Buffer.isBuffer()` instead of `AbstractLevelDOWN#isBuffer` ([**@vweevers**](https://github.com/vweevers)) 147 | - README: update instantiation instructions for latest `levelup` ([**@kumavis**](https://github.com/kumavis)) 148 | - README: rename "database" to "store" ([**@ralphtheninja**](https://github.com/ralphtheninja)) 149 | - README: simplify example and prefer ES6 ([**@vweevers**](https://github.com/vweevers)) 150 | - Configure Greenkeeper to ignore updates to `@types/node` ([**@ralphtheninja**](https://github.com/ralphtheninja)) 151 | 152 | ### Fixed 153 | 154 | - Don't clone `Buffer` in iterator ([**@vweevers**](https://github.com/vweevers)) 155 | - Stringify `Buffer.from()` argument in iterator ([**@vweevers**](https://github.com/vweevers)) 156 | - README: use SVG rather than PNG badge for Travis ([**@ralphtheninja**](https://github.com/ralphtheninja)) 157 | - README: link to `abstract-leveldown` ([**@vweevers**](https://github.com/vweevers)) 158 | - README: normalize markdown headers ([**@ralphtheninja**](https://github.com/ralphtheninja)) 159 | - README: fix license typos ([**@ralphtheninja**](https://github.com/ralphtheninja)) 160 | - README: fix code example ([**@ralphtheninja**](https://github.com/ralphtheninja)) 161 | - Rename `iterator#_end` to fix conflict with `abstract-leveldown` ([**@vweevers**](https://github.com/vweevers)) 162 | - Set `zuul --concurrency` to 1 to avoid hitting Sauce Labs limit ([**@vweevers**](https://github.com/vweevers)) 163 | - Test on Android 6.0 instead of latest (7.1) due to Sauce Labs issue ([**@vweevers**](https://github.com/vweevers)) 164 | 165 | ### Removed 166 | 167 | - Remove global store ([**@vweevers**](https://github.com/vweevers)) 168 | - Remove skipping of falsy elements in `MemDOWN#batch` ([**@vweevers**](https://github.com/vweevers)) 169 | - Remove obsolete benchmarks ([**@vweevers**](https://github.com/vweevers)) 170 | - Remove obsolete `testBuffer` from `test.js` ([**@vweevers**](https://github.com/vweevers)) 171 | - Remove redundant `testCommon` parameter from most tests ([**@vweevers**](https://github.com/vweevers)) 172 | - Remove unnecessary `rimraf` replacement for Browserify ([**@vweevers**](https://github.com/vweevers)) 173 | - README: remove Greenkeeper badge ([**@ralphtheninja**](https://github.com/ralphtheninja)) 174 | 175 | [6.1.1]: https://github.com/Level/memdown/releases/tag/v6.1.1 176 | 177 | [6.1.0]: https://github.com/Level/memdown/releases/tag/v6.1.0 178 | 179 | [6.0.0]: https://github.com/Level/memdown/releases/tag/v6.0.0 180 | 181 | [5.1.0]: https://github.com/Level/memdown/releases/tag/v5.1.0 182 | 183 | [5.0.0]: https://github.com/Level/memdown/releases/tag/v5.0.0 184 | 185 | [4.1.0]: https://github.com/Level/memdown/releases/tag/v4.1.0 186 | 187 | [4.0.0]: https://github.com/Level/memdown/releases/tag/v4.0.0 188 | 189 | [3.0.0]: https://github.com/Level/memdown/releases/tag/v3.0.0 190 | 191 | [2.0.0]: https://github.com/Level/memdown/releases/tag/v2.0.0 192 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const test = require('tape') 4 | const suite = require('abstract-leveldown/test') 5 | const concat = require('level-concat-iterator') 6 | const memdown = require('.') 7 | const ltgt = require('ltgt') 8 | const { Buffer } = require('buffer') 9 | const noop = function () { } 10 | 11 | const testCommon = suite.common({ 12 | test: test, 13 | factory: function () { 14 | return memdown() 15 | }, 16 | 17 | // Opt-in to new tests 18 | clear: true, 19 | getMany: true, 20 | 21 | // Opt-out of unsupported features 22 | createIfMissing: false, 23 | errorIfExists: false 24 | }) 25 | 26 | // Test abstract-leveldown compliance 27 | suite(testCommon) 28 | 29 | // Additional tests for this implementation 30 | test('unsorted entry, sorted iterator', function (t) { 31 | const db = testCommon.factory() 32 | 33 | db.open(function (err) { 34 | t.ifError(err, 'no open error') 35 | 36 | db.put('f', 'F', noop) 37 | db.put('a', 'A', noop) 38 | db.put('c', 'C', noop) 39 | db.put('e', 'E', noop) 40 | 41 | db.batch( 42 | [ 43 | { type: 'put', key: 'd', value: 'D' }, 44 | { type: 'put', key: 'b', value: 'B' }, 45 | { type: 'put', key: 'g', value: 'G' } 46 | ], 47 | noop 48 | ) 49 | 50 | concat( 51 | db.iterator({ keyAsBuffer: false, valueAsBuffer: false }), 52 | function (err, data) { 53 | t.notOk(err, 'no error') 54 | t.equal(data.length, 7, 'correct number of entries') 55 | 56 | const expected = [ 57 | { key: 'a', value: 'A' }, 58 | { key: 'b', value: 'B' }, 59 | { key: 'c', value: 'C' }, 60 | { key: 'd', value: 'D' }, 61 | { key: 'e', value: 'E' }, 62 | { key: 'f', value: 'F' }, 63 | { key: 'g', value: 'G' } 64 | ] 65 | 66 | t.deepEqual(data, expected) 67 | t.end() 68 | } 69 | ) 70 | }) 71 | }) 72 | 73 | test('reading while putting', function (t) { 74 | const db = testCommon.factory() 75 | 76 | db.open(function (err) { 77 | t.ifError(err, 'no open error') 78 | 79 | db.put('f', 'F', noop) 80 | db.put('c', 'C', noop) 81 | db.put('e', 'E', noop) 82 | 83 | const iterator = db.iterator({ keyAsBuffer: false, valueAsBuffer: false }) 84 | 85 | iterator.next(function (err, key, value) { 86 | t.ifError(err, 'no next error') 87 | t.equal(key, 'c') 88 | t.equal(value, 'C') 89 | 90 | db.put('a', 'A', noop) 91 | 92 | iterator.next(function (err, key, value) { 93 | t.ifError(err, 'no next error') 94 | t.equal(key, 'e') 95 | t.equal(value, 'E') 96 | t.end() 97 | }) 98 | }) 99 | }) 100 | }) 101 | 102 | test('reading while deleting', function (t) { 103 | const db = testCommon.factory() 104 | 105 | db.open(function (err) { 106 | t.ifError(err, 'no open error') 107 | 108 | db.put('f', 'F', noop) 109 | db.put('a', 'A', noop) 110 | db.put('c', 'C', noop) 111 | db.put('e', 'E', noop) 112 | 113 | const iterator = db.iterator({ keyAsBuffer: false, valueAsBuffer: false }) 114 | 115 | iterator.next(function (err, key, value) { 116 | t.ifError(err, 'no next error') 117 | t.equal(key, 'a') 118 | t.equal(value, 'A') 119 | 120 | db.del('a', noop) 121 | 122 | iterator.next(function (err, key, value) { 123 | t.ifError(err, 'no next error') 124 | t.equal(key, 'c') 125 | t.equal(value, 'C') 126 | t.end() 127 | }) 128 | }) 129 | }) 130 | }) 131 | 132 | test('reverse ranges', function (t) { 133 | const db = testCommon.factory() 134 | 135 | db.open(function (err) { 136 | t.ifError(err, 'no open error') 137 | 138 | db.put('a', 'A', noop) 139 | db.put('c', 'C', noop) 140 | 141 | const iterator = db.iterator({ 142 | keyAsBuffer: false, 143 | valueAsBuffer: false, 144 | lte: 'b', 145 | reverse: true 146 | }) 147 | 148 | iterator.next(function (err, key, value) { 149 | t.ifError(err, 'no next error') 150 | t.equal(key, 'a') 151 | t.equal(value, 'A') 152 | t.end() 153 | }) 154 | }) 155 | }) 156 | 157 | test('delete while iterating', function (t) { 158 | const db = testCommon.factory() 159 | 160 | db.open(function (err) { 161 | t.ifError(err, 'no open error') 162 | 163 | db.put('a', 'A', noop) 164 | db.put('b', 'B', noop) 165 | db.put('c', 'C', noop) 166 | 167 | const iterator = db.iterator({ 168 | keyAsBuffer: false, 169 | valueAsBuffer: false, 170 | gte: 'a' 171 | }) 172 | 173 | iterator.next(function (err, key, value) { 174 | t.ifError(err, 'no next error') 175 | t.equal(key, 'a') 176 | t.equal(value, 'A') 177 | 178 | db.del('b', function (err) { 179 | t.notOk(err, 'no error') 180 | 181 | iterator.next(function (err, key, value) { 182 | t.notOk(err, 'no error') 183 | t.equals(key, 'b') 184 | t.equal(value, 'B') 185 | t.end() 186 | }) 187 | }) 188 | }) 189 | }) 190 | }) 191 | 192 | test('iterator with byte range', function (t) { 193 | const db = testCommon.factory() 194 | 195 | db.open(function (err) { 196 | t.ifError(err, 'no open error') 197 | 198 | db.put(Buffer.from('a0', 'hex'), 'A', function (err) { 199 | t.ifError(err) 200 | 201 | const iterator = db.iterator({ valueAsBuffer: false, lt: Buffer.from('ff', 'hex') }) 202 | 203 | iterator.next(function (err, key, value) { 204 | t.notOk(err, 'no error') 205 | t.equal(key.toString('hex'), 'a0') 206 | t.equal(value, 'A') 207 | t.end() 208 | }) 209 | }) 210 | }) 211 | }) 212 | 213 | test('iterator does not clone buffers', function (t) { 214 | t.plan(4) 215 | 216 | const db = testCommon.factory() 217 | const buf = Buffer.from('a') 218 | 219 | db.open(function (err) { 220 | t.ifError(err, 'no open error') 221 | 222 | db.put(buf, buf, noop) 223 | 224 | concat(db.iterator(), function (err, entries) { 225 | t.ifError(err, 'no iterator error') 226 | t.ok(entries[0].key === buf, 'key is same buffer') 227 | t.ok(entries[0].value === buf, 'value is same buffer') 228 | }) 229 | }) 230 | }) 231 | 232 | test('iterator stringifies buffer input', function (t) { 233 | t.plan(4) 234 | 235 | const db = testCommon.factory() 236 | 237 | db.open(function (err) { 238 | t.ifError(err, 'no open error') 239 | 240 | db.put(1, 2, noop) 241 | 242 | concat(db.iterator(), function (err, entries) { 243 | t.ifError(err, 'no iterator error') 244 | t.same(entries[0].key, Buffer.from('1'), 'key is stringified') 245 | t.same(entries[0].value, Buffer.from('2'), 'value is stringified') 246 | }) 247 | }) 248 | }) 249 | 250 | test('backing rbtree is buffer-aware', function (t) { 251 | const db = testCommon.factory() 252 | 253 | db.open(function (err) { 254 | t.ifError(err, 'no open error') 255 | 256 | const one = Buffer.from('80', 'hex') 257 | const two = Buffer.from('c0', 'hex') 258 | 259 | t.ok(two.toString() === one.toString(), 'would be equal when not buffer-aware') 260 | t.ok(ltgt.compare(two, one) > 0, 'but greater when buffer-aware') 261 | 262 | db.put(one, 'one', function (err) { 263 | t.notOk(err, 'no error') 264 | 265 | db.get(one, { asBuffer: false }, function (err, value) { 266 | t.notOk(err, 'no error') 267 | t.equal(value, 'one', 'value one ok') 268 | 269 | db.put(two, 'two', function (err) { 270 | t.notOk(err, 'no error') 271 | 272 | db.get(one, { asBuffer: false }, function (err, value) { 273 | t.notOk(err, 'no error') 274 | t.equal(value, 'one', 'value one is the same') 275 | t.end() 276 | }) 277 | }) 278 | }) 279 | }) 280 | }) 281 | }) 282 | 283 | test('empty value in batch', function (t) { 284 | t.plan(6) 285 | 286 | const db = testCommon.factory() 287 | 288 | db.open(function (err) { 289 | t.ifError(err, 'no open error') 290 | 291 | db.batch([ 292 | { 293 | type: 'put', 294 | key: 'empty-string', 295 | value: '' 296 | }, 297 | { 298 | type: 'put', 299 | key: 'empty-buffer', 300 | value: Buffer.alloc(0) 301 | } 302 | ], function (err) { 303 | t.ifError(err, 'no error') 304 | 305 | db.get('empty-string', function (err, val) { 306 | t.ifError(err, 'no error') 307 | t.same(val, Buffer.alloc(0), 'empty string') 308 | }) 309 | 310 | db.get('empty-buffer', function (err, val) { 311 | t.ifError(err, 'no error') 312 | t.same(val, Buffer.alloc(0), 'empty buffer') 313 | }) 314 | }) 315 | }) 316 | }) 317 | 318 | test('empty buffer key in batch', function (t) { 319 | const db = testCommon.factory() 320 | 321 | db.open(function (err) { 322 | t.ifError(err, 'no open error') 323 | 324 | db.batch([{ 325 | type: 'put', 326 | key: Buffer.alloc(0), 327 | value: '' 328 | }], function (err) { 329 | t.ok(err, 'got an error') 330 | t.end() 331 | }) 332 | }) 333 | }) 334 | 335 | test('buffer key in batch', function (t) { 336 | const db = testCommon.factory() 337 | 338 | db.open(function (err) { 339 | t.ifError(err, 'no open error') 340 | 341 | db.batch([{ 342 | type: 'put', 343 | key: Buffer.from('foo', 'utf8'), 344 | value: 'val1' 345 | }], function (err) { 346 | t.ifError(err, 'no error') 347 | 348 | db.get(Buffer.from('foo', 'utf8'), { asBuffer: false }, function (err, val) { 349 | t.ifError(err, 'no error') 350 | t.same(val, 'val1') 351 | t.end() 352 | }) 353 | }) 354 | }) 355 | }) 356 | 357 | test('put multiple times', function (t) { 358 | t.plan(5) 359 | 360 | const db = testCommon.factory() 361 | 362 | db.open(function (err) { 363 | t.ifError(err, 'no open error') 364 | 365 | db.put('key', 'val', function (err) { 366 | t.ifError(err, 'no error') 367 | 368 | db.put('key', 'val2', function (err) { 369 | t.ifError(err, 'no error') 370 | 371 | db.get('key', { asBuffer: false }, function (err, val) { 372 | t.ifError(err, 'no error') 373 | t.same(val, 'val2') 374 | }) 375 | }) 376 | }) 377 | }) 378 | }) 379 | 380 | test('put as string, get as buffer and vice versa', function (t) { 381 | t.plan(7) 382 | 383 | const db = testCommon.factory() 384 | 385 | db.open(function (err) { 386 | t.ifError(err, 'no open error') 387 | 388 | db.put('a', 'a', function (err) { 389 | t.ifError(err, 'no put error') 390 | 391 | db.get(Buffer.from('a'), { asBuffer: true }, function (err, value) { 392 | t.ifError(err, 'no get error') 393 | t.same(value, Buffer.from('a'), 'got value') 394 | }) 395 | }) 396 | 397 | db.put(Buffer.from('b'), Buffer.from('b'), function (err) { 398 | t.ifError(err, 'no put error') 399 | 400 | db.get('b', { asBuffer: false }, function (err, value) { 401 | t.ifError(err, 'no get error') 402 | t.is(value, 'b', 'got value') 403 | }) 404 | }) 405 | }) 406 | }) 407 | 408 | test('put as string, iterate as buffer', function (t) { 409 | t.plan(4) 410 | 411 | const db = testCommon.factory() 412 | 413 | db.open(function (err) { 414 | t.ifError(err, 'no open error') 415 | 416 | db.put('a', 'a', function (err) { 417 | t.ifError(err, 'no put error') 418 | 419 | concat(db.iterator({ keyAsBuffer: true, valueAsBuffer: true }), function (err, entries) { 420 | t.ifError(err, 'no concat error') 421 | t.same(entries, [{ key: Buffer.from('a'), value: Buffer.from('a') }]) 422 | }) 423 | }) 424 | }) 425 | }) 426 | 427 | test('put as buffer, iterate as string', function (t) { 428 | t.plan(4) 429 | 430 | const db = testCommon.factory() 431 | 432 | db.open(function (err) { 433 | t.ifError(err, 'no open error') 434 | 435 | db.put(Buffer.from('a'), Buffer.from('a'), function (err) { 436 | t.ifError(err, 'no put error') 437 | 438 | concat(db.iterator({ keyAsBuffer: false, valueAsBuffer: false }), function (err, entries) { 439 | t.ifError(err, 'no concat error') 440 | t.same(entries, [{ key: 'a', value: 'a' }]) 441 | }) 442 | }) 443 | }) 444 | }) 445 | 446 | test('number keys', function (t) { 447 | t.plan(5) 448 | 449 | const db = testCommon.factory() 450 | const numbers = [-Infinity, 0, 12, 2, +Infinity] 451 | const strings = numbers.map(String) 452 | const buffers = numbers.map(stringBuffer) 453 | 454 | db.open(function (err) { 455 | t.ifError(err, 'no open error') 456 | 457 | db.batch(numbers.map(putKey), noop) 458 | 459 | const iterator1 = db.iterator({ keyAsBuffer: false }) 460 | const iterator2 = db.iterator({ keyAsBuffer: true }) 461 | 462 | concat(iterator1, function (err, entries) { 463 | t.ifError(err, 'no iterator error') 464 | t.same(entries.map(getKey), strings, 'sorts lexicographically') 465 | }) 466 | 467 | concat(iterator2, function (err, entries) { 468 | t.ifError(err, 'no iterator error') 469 | t.same(entries.map(getKey), buffers, 'buffer input is stringified') 470 | }) 471 | }) 472 | }) 473 | 474 | function stringBuffer (value) { 475 | return Buffer.from(String(value)) 476 | } 477 | 478 | function putKey (key) { 479 | return { type: 'put', key: key, value: 'value' } 480 | } 481 | 482 | function getKey (entry) { 483 | return entry.key 484 | } 485 | --------------------------------------------------------------------------------