├── .airtap.yml ├── .github ├── dependabot.yml └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── UPGRADING.md ├── index.js ├── iterator.js ├── package.json ├── sauce-labs.svg ├── test ├── custom-test.js ├── index.js ├── support-test.js └── upgrade-test.js └── util ├── clear.js ├── deserialize.js ├── key-range.js ├── serialize.js └── support.js /.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 | -------------------------------------------------------------------------------- /.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 | - dependency-name: uuid 10 | - package-ecosystem: github-actions 11 | directory: / 12 | schedule: 13 | interval: monthly 14 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: push 3 | concurrency: sauce-labs 4 | jobs: 5 | test: 6 | runs-on: ubuntu-latest 7 | name: Sauce Labs 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v3 11 | - name: Use node 14 12 | uses: actions/setup-node@v3 13 | with: 14 | node-version: 14 15 | - name: Install 16 | run: npm install 17 | env: 18 | # Download Sauce Connect binary now instead of on first run 19 | SAUCE_CONNECT_DOWNLOAD_ON_INSTALL: true 20 | - name: Add host 21 | run: echo "127.0.0.1 airtap.local" | sudo tee -a /etc/hosts 22 | - name: Test 23 | run: npm run test-browsers 24 | env: 25 | SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} 26 | SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} 27 | - name: Coverage 28 | run: npm run coverage 29 | - name: Codecov 30 | uses: codecov/codecov-action@v3 31 | with: 32 | file: coverage/lcov.info 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | npm-debug.log 4 | package-lock.json 5 | .nyc_output/ 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [6.1.0] - 2021-09-28 4 | 5 | ### Added 6 | 7 | - Add `db.getMany(keys)` ([#214](https://github.com/Level/level-js/issues/214)) ([`f5a3ca3`](https://github.com/Level/level-js/commit/f5a3ca3)) (Vincent Weevers). 8 | 9 | ## [6.0.0] - 2021-04-09 10 | 11 | _If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md)._ 12 | 13 | ### Changed 14 | 15 | - **Breaking:** bump `abstract-leveldown` ([`720aced`](https://github.com/Level/level-js/commit/720aced)) (Vincent Weevers) 16 | - **Breaking:** bump `buffer` from 5.x to 6.x ([#210](https://github.com/Level/level-js/issues/210)) ([`cc68b21`](https://github.com/Level/level-js/commit/cc68b21)) (Alex Potsides) 17 | - Modernize syntax and bump `standard` ([Level/community#98](https://github.com/Level/community/issues/98)) ([`0ce815f`](https://github.com/Level/level-js/commit/0ce815f)) (Vincent Weevers) 18 | - Every browser in our test matrix now supports binary keys ([`2c20127`](https://github.com/Level/level-js/commit/2c20127)) (Vincent Weevers) 19 | 20 | ## [5.0.2] - 2020-04-03 21 | 22 | ### Changed 23 | 24 | - Use `nextTick` of `abstract-leveldown` ([#195](https://github.com/Level/level-js/issues/195)) ([**@vweevers**](https://github.com/vweevers)) (same underlying code) 25 | - Upgrade `nyc` devDependency from `^14.0.0` to `^15.0.0` ([#187](https://github.com/Level/level-js/issues/187)) ([**@vweevers**](https://github.com/vweevers)) 26 | - Upgrade `airtap` devDependency from `^2.0.0` to `^3.0.0` ([#189](https://github.com/Level/level-js/issues/189)) ([**@vweevers**](https://github.com/vweevers)) 27 | 28 | ### Fixed 29 | 30 | - Add `buffer` for browsers ([#191](https://github.com/Level/level-js/issues/191)) ([**@hugomrdias**](https://github.com/hugomrdias)) 31 | 32 | ## [5.0.1] - 2019-11-29 33 | 34 | ### Fixed 35 | 36 | - Restore support of empty prefix option ([#184](https://github.com/Level/level-js/issues/184)) ([**@achingbrain**](https://github.com/achingbrain)). This restores a previous behavior (of `level-js` < 3) that unknown to us, was provided by the since-removed `IDBWrapper`. 37 | 38 | ## [5.0.0] - 2019-10-04 39 | 40 | _If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md)._ 41 | 42 | ### Changed 43 | 44 | - **Breaking**: Drop support of key & value types other than string and Buffer ([#179](https://github.com/Level/level-js/issues/179)) ([**@vweevers**](https://github.com/vweevers)) 45 | - Replace mentions of `level-browserify` with `level` ([`58b3e07`](https://github.com/Level/level-js/commit/58b3e07)) ([**@vweevers**](https://github.com/vweevers)) 46 | - Upgrade `hallmark` devDependency from `^0.1.0` to `^2.0.0` ([#172](https://github.com/Level/level-js/issues/172), [#177](https://github.com/Level/level-js/issues/177)) ([**@vweevers**](https://github.com/vweevers)) 47 | - Upgrade `nyc` devDependency from `^13.1.0` to `^14.0.0` ([#169](https://github.com/Level/level-js/issues/169)) ([**@vweevers**](https://github.com/vweevers)) 48 | - Upgrade `standard` devDependency from `^12.0.1` to `^14.0.2` ([#171](https://github.com/Level/level-js/issues/171), [`aacb0ea`](https://github.com/Level/level-js/commit/aacb0ea)) ([**@vweevers**](https://github.com/vweevers), [**@ralphtheninja**](https://github.com/ralphtheninja)) 49 | 50 | ### Added 51 | 52 | - Add manifest ([Level/community#83](https://github.com/Level/community/issues/83)) ([#183](https://github.com/Level/level-js/issues/183)) ([**@vweevers**](https://github.com/vweevers)) 53 | - Support `clear()` ([Level/community#79](https://github.com/Level/community/issues/79)) ([#182](https://github.com/Level/level-js/issues/182)) ([**@vweevers**](https://github.com/vweevers)) 54 | 55 | ## [4.0.1] - 2019-03-31 56 | 57 | ### Changed 58 | 59 | - Apply common project tweaks ([#164](https://github.com/Level/level-js/issues/164), [#165](https://github.com/Level/level-js/issues/165)) ([**@vweevers**](https://github.com/vweevers)) 60 | 61 | ### Removed 62 | 63 | - Remove outdated sentence about nullish values from README ([#166](https://github.com/Level/level-js/issues/166)) ([**@vweevers**](https://github.com/vweevers)) 64 | 65 | ## [4.0.0] - 2018-12-30 66 | 67 | _If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md)._ 68 | 69 | ### Changed 70 | 71 | - Upgrade `abstract-leveldown` from `~5.0.0` to `~6.0.1` ([#155](https://github.com/Level/level-js/issues/155), [#157](https://github.com/Level/level-js/issues/157)) ([**@vweevers**](https://github.com/vweevers)) 72 | - Don't serialize boolean or `NaN` keys, have IDB reject them ([#155](https://github.com/Level/level-js/issues/155)) ([**@vweevers**](https://github.com/vweevers)) 73 | - Update test of `key cannot be an empty Array` error ([#155](https://github.com/Level/level-js/issues/155)) ([**@vweevers**](https://github.com/vweevers)) 74 | - Change `iterator.db` to reference `level-js` instance, not IDB ([#155](https://github.com/Level/level-js/issues/155)) ([**@vweevers**](https://github.com/vweevers)) 75 | - Handle `location` in constructor, as it was removed from `abstract-leveldown` ([#155](https://github.com/Level/level-js/issues/155)) ([**@vweevers**](https://github.com/vweevers)) 76 | - Use `level-concat-iterator` and `testCommon.factory()` in custom tests ([#155](https://github.com/Level/level-js/issues/155)) ([**@vweevers**](https://github.com/vweevers)) 77 | - Invoke abstract tests from single function ([#155](https://github.com/Level/level-js/issues/155)) ([**@vweevers**](https://github.com/vweevers)) 78 | - Upgrade `airtap` devDependency from `0.0.7` to `^2.0.0` ([`2b71337`](https://github.com/Level/level-js/commit/2b71337), [#161](https://github.com/Level/level-js/issues/161)) ([**@ralphtheninja**](https://github.com/ralphtheninja), [**@vweevers**](https://github.com/vweevers)) 79 | - Upgrade `standard` devDependency from `^11.0.1` to `^12.0.1` ([#153](https://github.com/Level/level-js/issues/153)) ([**@vweevers**](https://github.com/vweevers), [**@ralphtheninja**](https://github.com/ralphtheninja)) 80 | - Replace `remark-cli` devDependency with `hallmark` ([#151](https://github.com/Level/level-js/issues/151), [#153](https://github.com/Level/level-js/issues/153)) ([**@vweevers**](https://github.com/vweevers)) 81 | 82 | ### Added 83 | 84 | - Test and document native sort order ([#157](https://github.com/Level/level-js/issues/157)) ([**@vweevers**](https://github.com/vweevers)) 85 | - Add iPhone and Android `latest` to test matrix ([#162](https://github.com/Level/level-js/issues/162)) ([**@vweevers**](https://github.com/vweevers)) 86 | - Add `nyc` and `coveralls` devDependencies ([#150](https://github.com/Level/level-js/issues/150), [#153](https://github.com/Level/level-js/issues/153)) ([`eb1aead`](https://github.com/Level/level-js/commit/eb1aead)) ([**@ralphtheninja**](https://github.com/ralphtheninja), [**@vweevers**](https://github.com/vweevers)) 87 | - Add Contributing section to README ([`c94a9a4`](https://github.com/Level/level-js/commit/c94a9a4)) ([**@ralphtheninja**](https://github.com/ralphtheninja)) 88 | 89 | ### Removed 90 | 91 | - Remove now irrelevant serialization of nullish values ([#155](https://github.com/Level/level-js/issues/155)) ([**@vweevers**](https://github.com/vweevers)) 92 | - Remove unused `IndexedDBShim` from tests ([#162](https://github.com/Level/level-js/issues/162)) ([**@vweevers**](https://github.com/vweevers)) 93 | 94 | ## [3.0.0] - 2018-06-17 95 | 96 | _If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md)._ 97 | 98 | ### Changed 99 | 100 | - Destroy with `location` and `prefix` only ([#116](https://github.com/Level/level-js/issues/116)) ([**@ralphtheninja**](https://github.com/ralphtheninja)) 101 | - Replace `util.inherits` with `inherits` module ([`8db16c1`](https://github.com/Level/level-js/commit/8db16c1)) ([**@ralphtheninja**](https://github.com/ralphtheninja)) 102 | - Change copyright years to "2012-present" ([`7017edd`](https://github.com/Level/level-js/commit/7017edd)) ([**@ralphtheninja**](https://github.com/ralphtheninja)) 103 | - Simplify license description ([#141](https://github.com/Level/level-js/issues/141)) ([**@vweevers**](https://github.com/vweevers)) 104 | - Update `package.json` description and keywords ([#141](https://github.com/Level/level-js/issues/141)) ([**@vweevers**](https://github.com/vweevers)) 105 | 106 | ### Added 107 | 108 | - Add `CHANGELOG.md` ([#107](https://github.com/Level/level-js/issues/107), [#115](https://github.com/Level/level-js/issues/115)) ([**@ralphtheninja**](https://github.com/ralphtheninja), [**@vweevers**](https://github.com/vweevers)) 109 | - Add `UPGRADING.md` ([#143](https://github.com/Level/level-js/issues/143)) ([**@vweevers**](https://github.com/vweevers)) 110 | - Add `CONTRIBUTORS.md` (replaces `COLLABORATORS.md`) ([#141](https://github.com/Level/level-js/issues/141)) ([**@vweevers**](https://github.com/vweevers)) 111 | - Add `standard` ([#112](https://github.com/Level/level-js/issues/112)) ([**@ralphtheninja**](https://github.com/ralphtheninja)) 112 | - Document constructor ([#119](https://github.com/Level/level-js/issues/119)) ([**@ralphtheninja**](https://github.com/ralphtheninja)) 113 | - Document type support ([#125](https://github.com/Level/level-js/issues/125), [#143](https://github.com/Level/level-js/issues/143)) ([**@vweevers**](https://github.com/vweevers)) 114 | - Add `remark` tooling ([#141](https://github.com/Level/level-js/issues/141), [#143](https://github.com/Level/level-js/issues/143), [#147](https://github.com/Level/level-js/issues/147)) ([**@vweevers**](https://github.com/vweevers)) 115 | - Test default and custom prefix ([#124](https://github.com/Level/level-js/issues/124)) ([**@vweevers**](https://github.com/vweevers)) 116 | - Test all key types of IndexedDB Second Edition ([#130](https://github.com/Level/level-js/issues/130)) ([**@vweevers**](https://github.com/vweevers)) 117 | - Test illegal value types ([#118](https://github.com/Level/level-js/issues/118)) ([**@vweevers**](https://github.com/vweevers)) 118 | - Test illegal and stringified key types ([#139](https://github.com/Level/level-js/issues/139)) ([**@vweevers**](https://github.com/vweevers)) 119 | - Test `Buffer`, `ArrayBuffer` and `Uint8Array` values with `asBuffer` option ([#146](https://github.com/Level/level-js/issues/146)) ([**@vweevers**](https://github.com/vweevers)) 120 | 121 | ### Fixed 122 | 123 | - Add original copyright owner (Max Ogden) ([#141](https://github.com/Level/level-js/issues/141)) ([**@vweevers**](https://github.com/vweevers)) 124 | - Replace `level.js` in documentation to match npm name `level-js` ([#121](https://github.com/Level/level-js/issues/121)) ([**@ralphtheninja**](https://github.com/ralphtheninja)) 125 | - Force airtap's browserify to use latest `buffer@5` ([#122](https://github.com/Level/level-js/issues/122)) ([**@vweevers**](https://github.com/vweevers)) 126 | - Don't stringify keys (except fallbacks, booleans and `NaN`) ([#130](https://github.com/Level/level-js/issues/130)) ([**@vweevers**](https://github.com/vweevers)) 127 | - Fix conversion of `ArrayBuffer` cursor key to `Buffer` ([#130](https://github.com/Level/level-js/issues/130)) ([**@vweevers**](https://github.com/vweevers)) 128 | - Catch IndexedDB key and value errors ([#139](https://github.com/Level/level-js/issues/139)) ([**@vweevers**](https://github.com/vweevers)) 129 | - Use `setImmediate` with callback in `_close()` ([#111](https://github.com/Level/level-js/issues/111)) ([**@ralphtheninja**](https://github.com/ralphtheninja)) 130 | - Whitelist npm package files ([#126](https://github.com/Level/level-js/issues/126)) ([**@vweevers**](https://github.com/vweevers)) 131 | - Avoid `instanceof Date` for cross-realm support ([#129](https://github.com/Level/level-js/issues/129)) ([**@vweevers**](https://github.com/vweevers)) 132 | - Fix wrong release date for `3.0.0-rc1` ([`43a702b`](https://github.com/Level/level-js/commit/43a702b)) ([**@ralphtheninja**](https://github.com/ralphtheninja)) 133 | 134 | ### Removed 135 | 136 | - Remove `test/levelup-test.js` ([#134](https://github.com/Level/level-js/issues/134)) ([**@ralphtheninja**](https://github.com/ralphtheninja)) 137 | - Remove `levelup` from destroy tests ([#136](https://github.com/Level/level-js/issues/136)) ([**@ralphtheninja**](https://github.com/ralphtheninja)) 138 | 139 | ## [3.0.0-rc1] - 2018-05-26 140 | 141 | ### Changed 142 | 143 | - Upgrade `abstract-leveldown` from `0.12.0` to `5.0.0` ([**@vweevers**](https://github.com/vweevers)) 144 | - Upgrade `typedarray-to-buffer` from `1.0.0` to `3.1.5` ([**@vweevers**](https://github.com/vweevers)) 145 | - Upgrade `levelup` devDependency from `0.18.2` to `3.0.0` ([**@vweevers**](https://github.com/vweevers)) 146 | - Upgrade `browserify` devDependency from `4.1.2` to `16.2.2` ([**@vweevers**](https://github.com/vweevers)) 147 | - Switch license from BSD to MIT ([**@ralphtheninja**](https://github.com/ralphtheninja)) 148 | - Replace `IDBWrapper` with straight IndexedDB code ([**@vweevers**](https://github.com/vweevers)) 149 | - Change default database prefix from `IDBWrapper-` to `level-js-` ([**@vweevers**](https://github.com/vweevers)) 150 | - Implement abstract `#_serializeKey` with support of all IndexedDB Second Edition types including binary keys (as Buffers) ([**@vweevers**](https://github.com/vweevers)) 151 | - Implement abstract `#_serializeValue` with support of all types of the structured clone algorithm except for `null` and `undefined` ([**@vweevers**](https://github.com/vweevers)) 152 | - Use `immediate` module for consistent microtask behavior ([**@vweevers**](https://github.com/vweevers)) 153 | - Replace `Buffer()` with `Buffer.from()` ([**@vweevers**](https://github.com/vweevers)) 154 | - Rename `Iterator#iterator` to `#transaction` ([**@vweevers**](https://github.com/vweevers)) 155 | - Replace `beefy` with `airtap --local` for local testing ([**@vweevers**](https://github.com/vweevers)) 156 | - Homogenize README title, description and headers ([**@vweevers**](https://github.com/vweevers)) 157 | - Make real `tape` tests out of `test-levelup.js` ([**@vweevers**](https://github.com/vweevers)) 158 | - Restructure custom tests to follow abstract test suite format ([**@vweevers**](https://github.com/vweevers)) 159 | 160 | ### Added 161 | 162 | - Add continuous browser tests with `airtap` and Sauce Labs ([**@vweevers**](https://github.com/vweevers)) 163 | - Add `prefix` and `version` options to constructor ([**@vweevers**](https://github.com/vweevers)) 164 | - Detect binary key support and fallback to `String(buffer)` ([**@vweevers**](https://github.com/vweevers)) 165 | - Detect array key support and fallback to `String(array)` ([**@vweevers**](https://github.com/vweevers)) 166 | - Test all value types of the structured clone algorithm ([**@vweevers**](https://github.com/vweevers)) 167 | - Catch `DataCloneError` if the environment does not support serializing the type of a key or value ([**@vweevers**](https://github.com/vweevers)) 168 | - Include Promise polyfill for `levelup` integration tests ([**@vweevers**](https://github.com/vweevers)) 169 | - Test that `Iterator` stringifies `Buffer.from()` argument ([**@vweevers**](https://github.com/vweevers)) 170 | - Add README badges, new goals and a code example with `levelup` ([**@vweevers**](https://github.com/vweevers)) 171 | - Add npm files to `.gitignore` ([**@vweevers**](https://github.com/vweevers)) 172 | 173 | ### Fixed 174 | 175 | - Start `Iterator` cursor immediately and fill an in-memory cache to fulfill `abstract-leveldown` snapshot guarantees ([**@vweevers**](https://github.com/vweevers)) 176 | - Stop advancing `Iterator` cursor when `options.limit` is reached ([**@vweevers**](https://github.com/vweevers)) 177 | - Rename public `#iterator` to private `#_iterator` ([**@vweevers**](https://github.com/vweevers)) 178 | - Fix `#_iterator({ limit: 0 })` to yield 0 entries ([**@vweevers**](https://github.com/vweevers)) 179 | - Handle transaction errors in `Iterator` ([**@vweevers**](https://github.com/vweevers)) 180 | - Fix constructor to call super ([**@vweevers**](https://github.com/vweevers)) 181 | - Make one request at a time in a batch transaction, saving CPU time ([**@vweevers**](https://github.com/vweevers)) 182 | - Properly close and destroy db's in custom tests ([**@vweevers**](https://github.com/vweevers)) 183 | - Update README links ([**@vweevers**](https://github.com/vweevers)) 184 | 185 | ### Removed 186 | 187 | - Remove support of `ArrayBuffer` values in favor of `Buffer` ([**@vweevers**](https://github.com/vweevers)) 188 | - Remove now unneeded `raw` option from `#_get()` and `#_iterator()` ([**@vweevers**](https://github.com/vweevers)) 189 | - Run tests without `IndexedDBShim` ([**@vweevers**](https://github.com/vweevers)) 190 | - Remove `Buffer` to `Uint8Array` conversion in `#_put()` and `#_batch()` ([**@vweevers**](https://github.com/vweevers)) 191 | - Remove obsolete `#_approximateSize` ([**@vweevers**](https://github.com/vweevers)) 192 | - Remove obsolete `#_isBuffer` ([**@vweevers**](https://github.com/vweevers)) 193 | - Remove obsolete `testBuffer` from abstract tests ([**@vweevers**](https://github.com/vweevers)) 194 | - Remove obsolete writestream test from `test-levelup.js` ([**@vweevers**](https://github.com/vweevers)) 195 | - Rely on `abstract-leveldown` defaults in `Iterator` constructor ([**@vweevers**](https://github.com/vweevers)) 196 | - Rely on `abstract-leveldown` callback defaults ([**@vweevers**](https://github.com/vweevers)) 197 | - Remove testling from `package.json` ([**@vweevers**](https://github.com/vweevers)) 198 | - Remove `level.js` logo ([**@vweevers**](https://github.com/vweevers)) 199 | 200 | ## [2.2.4] - 2016-05-09 201 | 202 | ### Changed 203 | 204 | - Use `toArrayBuffer()` only when present ([**@substack**](https://github.com/substack)) 205 | 206 | ## [2.2.3] - 2015-12-10 207 | 208 | ### Changed 209 | 210 | - Update `ltgt` to `^2.1.2` ([**@ryanramage**](https://github.com/ryanramage)) 211 | 212 | ## [2.2.2] - 2015-09-12 213 | 214 | _This release introduced `this._keyRangeError`._ 215 | 216 | ### Added 217 | 218 | - Add [**@nolanlawson**](https://github.com/nolanlawson) to collaborators ([**@maxogden**](https://github.com/maxogden)) 219 | 220 | ### Fixed 221 | 222 | - Fix iterator when start > end ([**@nolanlawson**](https://github.com/nolanlawson)) 223 | 224 | ## [2.2.1] - 2015-07-05 225 | 226 | ### Changed 227 | 228 | - Update collaborators ([**@maxogden**](https://github.com/maxogden)) 229 | - Roll back `abstract-leveldown` to `~0.12.0` ([**@maxogden**](https://github.com/maxogden)) 230 | 231 | ## [2.2.0] - 2015-07-03 232 | 233 | ### Added 234 | 235 | - Add `Collaborators` section to README ([**@maxogden**](https://github.com/maxogden)) 236 | 237 | ### Changed 238 | 239 | - Update syntax highlighting in README ([**@yoshuawuyts**](https://github.com/yoshuawuyts)) 240 | - Update `idb-wrapper` to `^1.5.0` ([**@JamesKyburz**](https://github.com/JamesKyburz)) 241 | - Update `abstract-leveldown` to `^2.4.0` ([**@maxogden**](https://github.com/maxogden)) 242 | - Update `tape` to `^4.0.0` ([**@maxogden**](https://github.com/maxogden)) 243 | - Move `tape` to devDependencies ([**@maxogden**](https://github.com/maxogden)) 244 | - Change license from BSD to BSD-2-Clause ([**@maxogden**](https://github.com/maxogden)) 245 | 246 | ### Removed 247 | 248 | - Remove Testling badge ([**@maxogden**](https://github.com/maxogden)) 249 | 250 | ## [2.1.6] - 2014-06-15 251 | 252 | ### Fixed 253 | 254 | - Avoid using keyword in `cursor.continue()` ([**@nolanlawson**](https://github.com/nolanlawson)) 255 | 256 | ## [2.1.5] - 2014-05-29 257 | 258 | ### Changed 259 | 260 | - Use `ltgt` module to handle ranges ([**@dominictarr**](https://github.com/dominictarr)) 261 | 262 | ## [2.1.4] - 2014-05-13 263 | 264 | ### Changed 265 | 266 | - Update `browserify` to `^4.1.2` ([**@maxogden**](https://github.com/maxogden)) 267 | - Move `browserify` to devDependencies ([**@maxogden**](https://github.com/maxogden)) 268 | 269 | ## [2.1.3] - 2014-04-09 270 | 271 | ### Added 272 | 273 | - Use `typedarray-to-buffer` to avoid copying to Buffer ([**@mafintosh**](https://github.com/mafintosh)) 274 | 275 | ## [2.1.2] - 2014-04-05 276 | 277 | ### Added 278 | 279 | - Add link to [**@brycebaril**](https://github.com/brycebaril)'s presentation to README ([**@maxogden**](https://github.com/maxogden)) 280 | 281 | ### Changed 282 | 283 | - Update browser configuration for Testling ([**@maxogden**](https://github.com/maxogden)) 284 | 285 | ## [2.1.1] - 2014-03-12 286 | 287 | _This was not published to npm. There's also a gap between `2.1.1` and `2.0.0` that is inconsistent. The `options.raw` property was introduced in this release._ 288 | 289 | ### Changed 290 | 291 | - Update browser configuration for Testling ([**@maxogden**](https://github.com/maxogden)) 292 | - Update `abstract-leveldown` to `~0.12.0` ([**@maxogden**](https://github.com/maxogden)) 293 | - Update `levelup` to `~0.18.2` ([**@maxogden**](https://github.com/maxogden)) 294 | - Make sure to store `Uint8Array` ([**@maxogden**](https://github.com/maxogden)) 295 | - Test storing native JS types with raw = true ([**@maxogden**](https://github.com/maxogden)) 296 | 297 | ## [2.0.0] - 2014-03-09 298 | 299 | _For some reason both `tape` and `browserify` were moved from devDependencies to dependencies. This release only had one commit._ 300 | 301 | ### Changed 302 | 303 | - Update `browserify` to `~3.32.0` ([**@maxogden**](https://github.com/maxogden)) 304 | - Update `tape` to `~2.10.2` ([**@maxogden**](https://github.com/maxogden)) 305 | - Change default encoding of values to strings to more closely match `leveldown` ([**@maxogden**](https://github.com/maxogden)) 306 | 307 | ### Fixed 308 | 309 | - Add missing `xtend` dependency ([**@maxogden**](https://github.com/maxogden)) 310 | 311 | ## [1.2.0] - 2014-03-09 312 | 313 | ### Added 314 | 315 | - Add `IndexedDBShim` to tests ([**@maxogden**](https://github.com/maxogden)) 316 | - Add `Level.destroy()` ([**@qs44**](https://github.com/qs44)) 317 | - Add prefix to pass `PouchDB` tests ([**@qs44**](https://github.com/qs44)) 318 | - Test `Level.destroy()` ([**@calvinmetcalf**](https://github.com/calvinmetcalf)) 319 | 320 | ### Changed 321 | 322 | - Update browser configuration for Testling ([**@maxogden**](https://github.com/maxogden)) 323 | - Pass through open options to idbwrapper ([**@maxogden**](https://github.com/maxogden)) 324 | 325 | ### Fixed 326 | 327 | - Don't use `indexedDB.webkitGetDatabasesNames()` in tests ([**@maxogden**](https://github.com/maxogden)) 328 | 329 | ## [1.1.2] - 2014-02-02 330 | 331 | ### Removed 332 | 333 | - Remove global leaks ([**@mcollina**](https://github.com/mcollina)) 334 | 335 | ## [1.1.1] - 2014-02-02 336 | 337 | ### Changed 338 | 339 | - Modify a copy of the batch array, not the original ([**@nrw**](https://github.com/nrw)) 340 | 341 | ### Fixed 342 | 343 | - Fix broken `package.json` ([**@maxogden**](https://github.com/maxogden)) 344 | - Fix testling path ([**@maxogden**](https://github.com/maxogden)) 345 | 346 | ## [1.1.0] - 2014-01-30 347 | 348 | _In this time period `bops` shows up and gets removed. Also, `._isBuffer()` uses `Buffer.isBuffer()` in favor of `is-buffer` module._ 349 | 350 | ### Added 351 | 352 | - Add Testling ([**@maxogden**](https://github.com/maxogden)) 353 | - Add npm badge ([**@maxogden**](https://github.com/maxogden)) 354 | - Test ranges ([**@rvagg**](https://github.com/rvagg), [**@maxogden**](https://github.com/maxogden)) 355 | 356 | ### Changed 357 | 358 | - Update README ([**@maxogden**](https://github.com/maxogden)) 359 | - Update `abstract-leveldown` to `~0.11.0` ([**@rvagg**](https://github.com/rvagg), [**@maxogden**](https://github.com/maxogden)) 360 | - Update to work with `abstract-leveldown@0.11.2` ([**@shama**](https://github.com/shama), [**@maxogden**](https://github.com/maxogden)) 361 | - Update iterator to pass all range tests ([**@shama**](https://github.com/shama), [**@maxogden**](https://github.com/maxogden)) 362 | 363 | ### Fixed 364 | 365 | - Fix incorrect version of `abstract-leveldown` ([**@maxogden**](https://github.com/maxogden)) 366 | - Pass error to callback in `approximateSize()` ([**@mcollina**](https://github.com/mcollina)) 367 | 368 | ### Removed 369 | 370 | - Remove unnecessary factor in tests ([**@rvagg**](https://github.com/rvagg), [**@maxogden**](https://github.com/maxogden)) 371 | 372 | ## [1.0.8] - 2013-08-12 373 | 374 | ### Changed 375 | 376 | - Move `levelup` to devDependencies ([**@juliangruber**](https://github.com/juliangruber)) 377 | 378 | ### Removed 379 | 380 | - Remove fn#bind from iterator ([**@juliangruber**](https://github.com/juliangruber)) 381 | 382 | ## [1.0.7] - 2013-07-02 383 | 384 | ### Changed 385 | 386 | - Implement full batch support ([**@mcollina**](https://github.com/mcollina)) 387 | 388 | ### Fixed 389 | 390 | - Fix git url to `abstract-leveldown` ([**@maxogden**](https://github.com/maxogden)) 391 | 392 | ## [1.0.6] - 2013-05-31 393 | 394 | ### Changed 395 | 396 | - Update `idb-wrapper` to `1.2.0` ([**@maxogden**](https://github.com/maxogden)) 397 | - Switch `abstract-leveldown#master` ([**@maxogden**](https://github.com/maxogden)) 398 | - Disable batch and chainable batch tests ([**@maxogden**](https://github.com/maxogden)) 399 | 400 | ## [1.0.5] - 2013-05-30 401 | 402 | ### Changed 403 | 404 | - Use upstream `idb-wrapper` ([**@maxogden**](https://github.com/maxogden)) 405 | 406 | ## [1.0.4] - 2013-05-30 407 | 408 | ### Added 409 | 410 | - Test batch and chainable batch ([**@rvagg**](https://github.com/rvagg)) 411 | 412 | ### Changed 413 | 414 | - Update `abstract-leveldown` to `~0.7.1` ([**@rvagg**](https://github.com/rvagg)) 415 | - Update `levelup` to `~0.9.0` ([**@brycebaril**](https://github.com/brycebaril)) 416 | 417 | ## [1.0.3] - 2013-05-14 418 | 419 | ### Changed 420 | 421 | - Use `is-buffer` ([**@juliangruber**](https://github.com/juliangruber)) 422 | 423 | ## [1.0.2] - 2013-05-04 424 | 425 | ### Fixed 426 | 427 | - Don't convert `ArrayBuffer` and typed arrays to strings ([**@maxogden**](https://github.com/maxogden)) 428 | 429 | ## [1.0.1] - 2013-05-03 430 | 431 | ### Added 432 | 433 | - Add optional options argument to `.open()` ([**@rvagg**](https://github.com/rvagg)) 434 | - Add `test-levelup.js` ([**@maxogden**](https://github.com/maxogden)) 435 | 436 | ### Changed 437 | 438 | - Update README ([**@maxogden**](https://github.com/maxogden)) 439 | - Use `npm test` instead of `npm start` ([**@shama**](https://github.com/shama)) 440 | - Properly delete test dbs ([**@maxogden**](https://github.com/maxogden)) 441 | - Inherit from `abstract-leveldown` ([**@rvagg**](https://github.com/rvagg)) 442 | 443 | ## [1.0.0] - 2013-05-03 444 | 445 | :seedling: Initial release. 446 | 447 | [6.1.0]: https://github.com/Level/level-js/releases/tag/v6.1.0 448 | 449 | [6.0.0]: https://github.com/Level/level-js/releases/tag/v6.0.0 450 | 451 | [5.0.2]: https://github.com/Level/level-js/releases/tag/v5.0.2 452 | 453 | [5.0.1]: https://github.com/Level/level-js/releases/tag/v5.0.1 454 | 455 | [5.0.0]: https://github.com/Level/level-js/releases/tag/v5.0.0 456 | 457 | [4.0.1]: https://github.com/Level/level-js/releases/tag/v4.0.1 458 | 459 | [4.0.0]: https://github.com/Level/level-js/releases/tag/v4.0.0 460 | 461 | [3.0.0]: https://github.com/Level/level-js/releases/tag/v3.0.0 462 | 463 | [3.0.0-rc1]: https://github.com/Level/level-js/releases/tag/v3.0.0-rc1 464 | 465 | [2.2.4]: https://github.com/Level/level-js/releases/tag/v2.2.4 466 | 467 | [2.2.3]: https://github.com/Level/level-js/releases/tag/v2.2.3 468 | 469 | [2.2.2]: https://github.com/Level/level-js/releases/tag/v2.2.2 470 | 471 | [2.2.1]: https://github.com/Level/level-js/releases/tag/v2.2.1 472 | 473 | [2.2.0]: https://github.com/Level/level-js/releases/tag/v2.2.0 474 | 475 | [2.1.6]: https://github.com/Level/level-js/releases/tag/v2.1.6 476 | 477 | [2.1.5]: https://github.com/Level/level-js/releases/tag/v2.1.5 478 | 479 | [2.1.4]: https://github.com/Level/level-js/releases/tag/v2.1.4 480 | 481 | [2.1.3]: https://github.com/Level/level-js/releases/tag/v2.1.3 482 | 483 | [2.1.2]: https://github.com/Level/level-js/releases/tag/v2.1.2 484 | 485 | [2.1.1]: https://github.com/Level/level-js/releases/tag/v2.1.1 486 | 487 | [2.0.0]: https://github.com/Level/level-js/releases/tag/v2.0.0 488 | 489 | [1.2.0]: https://github.com/Level/level-js/releases/tag/v1.2.0 490 | 491 | [1.1.2]: https://github.com/Level/level-js/releases/tag/v1.1.2 492 | 493 | [1.1.1]: https://github.com/Level/level-js/releases/tag/v1.1.1 494 | 495 | [1.1.0]: https://github.com/Level/level-js/releases/tag/v1.1.0 496 | 497 | [1.0.8]: https://github.com/Level/level-js/releases/tag/v1.0.8 498 | 499 | [1.0.7]: https://github.com/Level/level-js/releases/tag/v1.0.7 500 | 501 | [1.0.6]: https://github.com/Level/level-js/releases/tag/v1.0.6 502 | 503 | [1.0.5]: https://github.com/Level/level-js/releases/tag/v1.0.5 504 | 505 | [1.0.4]: https://github.com/Level/level-js/releases/tag/v1.0.4 506 | 507 | [1.0.3]: https://github.com/Level/level-js/releases/tag/v1.0.3 508 | 509 | [1.0.2]: https://github.com/Level/level-js/releases/tag/v1.0.2 510 | 511 | [1.0.1]: https://github.com/Level/level-js/releases/tag/v1.0.1 512 | 513 | [1.0.0]: https://github.com/Level/level-js/releases/tag/v1.0.0 514 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2012 Max Ogden and the contributors to level-js. 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # level-js 2 | 3 | **Superseded by [`browser-level`](https://github.com/Level/browser-level). Please see [Frequently Asked Questions](https://github.com/Level/community#faq).** 4 | 5 | ## Background 6 | 7 | Here are the goals of `level-js`: 8 | 9 | - Store large amounts of data in modern browsers 10 | - Pass the full [`abstract-leveldown`][abstract-leveldown] test suite 11 | - Support string and [`Buffer`][buffer] keys and values 12 | - Be as fast as possible 13 | - ~~Sync with [multilevel](https://github.com/juliangruber/multilevel) over ASCII or binary transports.~~ 14 | 15 | Being `abstract-leveldown` compliant means you can use many of the [Level modules][awesome] on top of this library. 16 | 17 | ## Example 18 | 19 | **If you are upgrading:** please see [UPGRADING.md](UPGRADING.md). 20 | 21 | ```js 22 | const levelup = require('levelup') 23 | const leveljs = require('level-js') 24 | const db = levelup(leveljs('bigdata')) 25 | 26 | db.put('hello', Buffer.from('world'), function (err) { 27 | if (err) throw err 28 | 29 | db.get('hello', function (err, value) { 30 | if (err) throw err 31 | 32 | console.log(value.toString()) // 'world' 33 | }) 34 | }) 35 | ``` 36 | 37 | With `async/await`: 38 | 39 | ```js 40 | const levelup = require('levelup') 41 | const leveljs = require('level-js') 42 | const db = levelup(leveljs('bigdata')) 43 | 44 | await db.put('hello', Buffer.from('world')) 45 | const value = await db.get('hello') 46 | ``` 47 | 48 | ## Type Support 49 | 50 | Keys and values can be a string or [`Buffer`][buffer]. Any other type will be irreversibly stringified. The only exceptions are `null` and `undefined`. Keys and values of that type are rejected. 51 | 52 | In order to sort string and Buffer keys the same way, for compatibility with `leveldown` and the larger ecosystem, `level-js` internally converts keys and values to binary before passing them to IndexedDB. 53 | 54 | If you desire non-destructive encoding (e.g. to store and retrieve numbers as-is), wrap `level-js` with [`encoding-down`][encoding-down]. Alternatively install [`level`][level] which conveniently bundles [`levelup`][levelup], `level-js` and `encoding-down`. Such an approach is also recommended if you want to achieve universal (isomorphic) behavior. For example, you could have [`leveldown`][leveldown] in a backend and `level-js` in the frontend. The `level` package does exactly that. 55 | 56 | When getting or iterating keys and values, regardless of the type with which they were stored, keys and values will return as a Buffer unless the `asBuffer`, `keyAsBuffer` or `valueAsBuffer` options are set, in which case strings are returned. Setting these options is not needed when `level-js` is wrapped with `encoding-down`, which determines the optimal return type by the chosen encoding. 57 | 58 | ```js 59 | db.get('key', { asBuffer: false }) 60 | db.iterator({ keyAsBuffer: false, valueAsBuffer: false }) 61 | ``` 62 | 63 | ## Install 64 | 65 | With [npm](https://npmjs.org) do: 66 | 67 | ```bash 68 | npm install level-js 69 | ``` 70 | 71 | Not to be confused with [leveljs](https://www.npmjs.com/package/leveljs). 72 | 73 | This library is best used with [browserify](http://browserify.org). 74 | 75 | ## API 76 | 77 | ### `db = leveljs(location[, options])` 78 | 79 | Returns a new `leveljs` instance. `location` is the string name of the [`IDBDatabase`](https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase) to be opened, as well as the object store within that database. The database name will be prefixed with `options.prefix`. 80 | 81 | #### `options` 82 | 83 | The optional `options` argument may contain: 84 | 85 | - `prefix` _(string, default: `'level-js-'`)_: Prefix for `IDBDatabase` name. 86 | - `version` _(string | number, default: `1`)_: The version to open the database with. 87 | 88 | See [`IDBFactory#open`](https://developer.mozilla.org/en-US/docs/Web/API/IDBFactory/open) for more details. 89 | 90 | ## Big Thanks 91 | 92 | Cross-browser Testing Platform and Open Source ♥ Provided by [Sauce Labs](https://saucelabs.com). 93 | 94 | [![Sauce Labs logo](./sauce-labs.svg)](https://saucelabs.com) 95 | 96 | ## Contributing 97 | 98 | [`Level/level-js`](https://github.com/Level/level-js) is an **OPEN Open Source Project**. This means that: 99 | 100 | > 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. 101 | 102 | See the [Contribution Guide](https://github.com/Level/community/blob/master/CONTRIBUTING.md) for more details. 103 | 104 | ## Donate 105 | 106 | Support us with a monthly donation on [Open Collective](https://opencollective.com/level) and help us continue our work. 107 | 108 | ## License 109 | 110 | [MIT](LICENSE) 111 | 112 | [indexeddb]: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API 113 | 114 | [buffer]: https://nodejs.org/api/buffer.html 115 | 116 | [awesome]: https://github.com/Level/awesome 117 | 118 | [abstract-leveldown]: https://github.com/Level/abstract-leveldown 119 | 120 | [levelup]: https://github.com/Level/levelup 121 | 122 | [leveldown]: https://github.com/Level/leveldown 123 | 124 | [level]: https://github.com/Level/level 125 | 126 | [encoding-down]: https://github.com/Level/encoding-down 127 | -------------------------------------------------------------------------------- /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 | - Internet Explorer 11 22 | - Safari 9-11. 23 | 24 | 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). 25 | 26 | ## 5.0.0 27 | 28 | Support of keys & values other than strings and Buffers has been dropped. Internally `level-js` now stores keys & values as binary which solves a number of compatibility issues ([Level/memdown#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. 29 | 30 | Existing IndexedDB databases created with `level-js@4` can be read only if they used binary keys and string or binary values. Other types will come out stringified, and string keys will sort incorrectly. Use the included `upgrade()` utility to convert stored data to binary (in so far the environment supports it): 31 | 32 | ```js 33 | var leveljs = require('level-js') 34 | var db = leveljs('my-db') 35 | 36 | db.open(function (err) { 37 | if (err) throw err 38 | 39 | db.upgrade(function (err) { 40 | if (err) throw err 41 | }) 42 | }) 43 | ``` 44 | 45 | Or with (the upcoming release of) `level`: 46 | 47 | ```js 48 | var level = require('level') 49 | var reachdown = require('reachdown') 50 | var db = level('my-db') 51 | 52 | db.open(function (err) { 53 | if (err) throw err 54 | 55 | reachdown(db, 'level-js').upgrade(function (err) { 56 | if (err) throw err 57 | }) 58 | }) 59 | ``` 60 | 61 | ## 4.0.0 62 | 63 | This is an upgrade to `abstract-leveldown@6` which solves long-standing issues around serialization and type support. 64 | 65 | ### Range options are now serialized 66 | 67 | Previously, range options like `lt` were passed through as-is by `abstract-leveldown`, unlike keys. For `level-js` it means that Buffers and arrays, if not supported by the environment (e.g. Microsoft Edge), will be stringified. 68 | 69 | ### The rules for range options have been relaxed 70 | 71 | 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({})`. 72 | 73 | In the case of `level-js`, when used by itself, the aforementioned change means that `db.iterator({ gt: undefined })` will throw an error as `undefined` is not a valid IndexedDB key type. On the other hand `db.iterator({ gt: '' })` is valid and thus now supported. For details on sort order (which is richer than in `leveldown`) please see [the readme](README.md). 74 | 75 | ### Nullish values are rejected 76 | 77 | 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. 78 | 79 | ### Zero-length array keys are rejected 80 | 81 | Though this was already the case (both in IndexedDB and `abstract-leveldown`), `abstract-leveldown` has replaced the behavior with an explicit `Array.isArray()` check and a new error message. 82 | 83 | ### Boolean and `NaN` keys (as well as range options) are rejected 84 | 85 | Previously, for compliance with `abstract-leveldown` tests that have since been removed, they were stringified. As of `level-js@4` they are rejected (by IndexedDB). 86 | 87 | ### Added mobile browser support 88 | 89 | iPhone and Android `latest` are now officially supported. At the time of writing that's iPhone 12.0 and Android 7.1 (note that's Chrome for Android, not the old stock browser). Older versions (iPhone 10+ and Android 6+) did pass our tests but are not included in the test matrix going forward. Feel free to open an issue if you need/want these versions to be supported. 90 | 91 | ### The value of `iterator#db` has changed 92 | 93 | Though this was undocumented and only for internal use, the `db` property on an iterator pointed to an `IDBDatabase`. To comply with `abstract-leveldown` the `db` property now points to the `level-js` instance that created that iterator. 94 | 95 | ## 3.0.0 96 | 97 | This release brings `level-js` up to par with latest [`levelup`][levelup] (v2), [`abstract-leveldown`][abstract-leveldown] (v5) and IndexedDB Second Edition. It targets modern `browserify` preferring [`Buffer`][buffer] over `ArrayBuffer`. Lastly, [`IDBWrapper`][idbwrapper] has been replaced with straight IndexedDB code. 98 | 99 | ### Usage with [`levelup`][levelup] 100 | 101 | Usage has changed to: 102 | 103 | ```js 104 | const levelup = require('levelup') 105 | const leveljs = require('leveljs') 106 | 107 | const db = levelup(leveljs('mydb')) 108 | ``` 109 | 110 | From the old: 111 | 112 | ```js 113 | const db = levelup('mydb', { db: leveljs }) 114 | ``` 115 | 116 | Friendly reminder: encodings have moved from [`levelup`][levelup] to [`encoding-down`][encoding-down]. To get identical functionality to `levelup < 2` please use the [`level-browserify`][level-browserify] convenience package or wrap `level-js` with `encoding-down`: 117 | 118 | ```js 119 | const encode = require('encoding-down') 120 | const db = levelup(encode(leveljs('mydb'))) 121 | ``` 122 | 123 | ### New database prefix 124 | 125 | The default prefix of the [`IDBDatabase`][idbdatabase] name has changed from `IDBWrapper-` to `level-js-`. To access databases created using `level-js < 3`, pass a custom prefix to the `level-js` constructor: 126 | 127 | ```js 128 | const db = levelup(leveljs('mydb', { prefix: 'IDBWrapper-' })) 129 | ``` 130 | 131 | ### Browser support 132 | 133 | As a result of removing [`IDBWrapper`][idbwrapper], only modern browsers with a non-prefixed `window.indexedDB` are supported in this release. The current test matrix of `level-js` includes the latest versions of Chrome, Firefox, Safari, Edge and IE. 134 | 135 | :fire: Internet Explorer 10 is no longer supported. 136 | 137 | ### Type support 138 | 139 | All value types of the [structured clone algorithm][structured-clone-algorithm] and all key types of IndexedDB Second Edition are now supported. This means you can store almost any JavaScript type without the need for [`encoding-down`][encoding-down]. In addition, you can use [`Buffer`][buffer] for both keys and values. For details and caveats please see the [readme][readme]. 140 | 141 | ### No backpressure 142 | 143 | In `level-js`, iterators are powered by IndexedDB cursors. To fulfill [`abstract-leveldown`][abstract-leveldown] snapshot guarantees (reads not being affected by simultaneous writes) cursors are started immediately and continuously read from, filling an in-memory cache. 144 | 145 | Though `level-js` now passes the full [`abstract-leveldown`][abstract-leveldown] test suite, fulfilling the snapshot guarantee means a loss of backpressure. Memory consumption might increase if an iterator is not consumed fast enough. A future release will have an option to favor backpressure over snapshot guarantees. 146 | 147 | ### Removed `raw` option 148 | 149 | Because `level-js` no longer stringifies values, the `raw` option (which bypassed conversion) became unnecessary and has been removed. If you use [`level-browserify`][level-browserify] or [`levelup`][levelup] with [`encoding-down`][encoding-down], you can store and retrieve raw values (as returned by IndexedDB) using the `id` encoding. Please refer to the [readme][readme] for an example. 150 | 151 | ### New `destroy()` function signature 152 | 153 | Previously, a `level-js` instance could be passed to `destroy()`: 154 | 155 | ```js 156 | leveljs.destroy(db, callback) 157 | ``` 158 | 159 | This was useful to destroy a database that used a custom prefix. The new signature is `destroy(location[, prefix], callback)`. 160 | 161 | ### Strict `.batch(array)` 162 | 163 | The upgrade to [`abstract-leveldown`][abstract-leveldown] comes with a [breaking change](https://github.com/Level/abstract-leveldown/commit/a2621ad70571f6ade9d2be42632ece042e068805) for the array version of `.batch()`. This change ensures all elements in the batch array are objects. If you previously passed arrays to `.batch()` that contained `undefined` or `null`, they would be silently ignored. Now this will produce an error. 164 | 165 | [readme]: README.md 166 | 167 | [changelog]: CHANGELOG.md 168 | 169 | [buffer]: https://nodejs.org/api/buffer.html 170 | 171 | [idbwrapper]: https://www.npmjs.com/package/idb-wrapper 172 | 173 | [abstract-leveldown]: https://github.com/Level/abstract-leveldown 174 | 175 | [levelup]: https://github.com/Level/levelup 176 | 177 | [encoding-down]: https://github.com/Level/encoding-down 178 | 179 | [level-browserify]: https://github.com/Level/level-browserify 180 | 181 | [idbdatabase]: https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase 182 | 183 | [structured-clone-algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm 184 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* global indexedDB */ 2 | 3 | 'use strict' 4 | 5 | module.exports = Level 6 | 7 | const AbstractLevelDOWN = require('abstract-leveldown').AbstractLevelDOWN 8 | const inherits = require('inherits') 9 | const parallel = require('run-parallel-limit') 10 | const Iterator = require('./iterator') 11 | const serialize = require('./util/serialize') 12 | const deserialize = require('./util/deserialize') 13 | const support = require('./util/support') 14 | const clear = require('./util/clear') 15 | const createKeyRange = require('./util/key-range') 16 | 17 | const DEFAULT_PREFIX = 'level-js-' 18 | 19 | function Level (location, opts) { 20 | if (!(this instanceof Level)) return new Level(location, opts) 21 | 22 | AbstractLevelDOWN.call(this, { 23 | bufferKeys: support.bufferKeys(indexedDB), 24 | snapshots: true, 25 | permanence: true, 26 | clear: true, 27 | getMany: true 28 | }) 29 | 30 | opts = opts || {} 31 | 32 | if (typeof location !== 'string') { 33 | throw new Error('constructor requires a location string argument') 34 | } 35 | 36 | this.location = location 37 | this.prefix = opts.prefix == null ? DEFAULT_PREFIX : opts.prefix 38 | this.version = parseInt(opts.version || 1, 10) 39 | } 40 | 41 | inherits(Level, AbstractLevelDOWN) 42 | 43 | Level.prototype.type = 'level-js' 44 | 45 | Level.prototype._open = function (options, callback) { 46 | const req = indexedDB.open(this.prefix + this.location, this.version) 47 | 48 | req.onerror = function () { 49 | callback(req.error || new Error('unknown error')) 50 | } 51 | 52 | req.onsuccess = () => { 53 | this.db = req.result 54 | callback() 55 | } 56 | 57 | req.onupgradeneeded = (ev) => { 58 | const db = ev.target.result 59 | 60 | if (!db.objectStoreNames.contains(this.location)) { 61 | db.createObjectStore(this.location) 62 | } 63 | } 64 | } 65 | 66 | Level.prototype.store = function (mode) { 67 | const transaction = this.db.transaction([this.location], mode) 68 | return transaction.objectStore(this.location) 69 | } 70 | 71 | Level.prototype.await = function (request, callback) { 72 | const transaction = request.transaction 73 | 74 | // Take advantage of the fact that a non-canceled request error aborts 75 | // the transaction. I.e. no need to listen for "request.onerror". 76 | transaction.onabort = function () { 77 | callback(transaction.error || new Error('aborted by user')) 78 | } 79 | 80 | transaction.oncomplete = function () { 81 | callback(null, request.result) 82 | } 83 | } 84 | 85 | Level.prototype._get = function (key, options, callback) { 86 | const store = this.store('readonly') 87 | let req 88 | 89 | try { 90 | req = store.get(key) 91 | } catch (err) { 92 | return this._nextTick(callback, err) 93 | } 94 | 95 | this.await(req, function (err, value) { 96 | if (err) return callback(err) 97 | 98 | if (value === undefined) { 99 | // 'NotFound' error, consistent with LevelDOWN API 100 | return callback(new Error('NotFound')) 101 | } 102 | 103 | callback(null, deserialize(value, options.asBuffer)) 104 | }) 105 | } 106 | 107 | Level.prototype._getMany = function (keys, options, callback) { 108 | const asBuffer = options.asBuffer 109 | const store = this.store('readonly') 110 | const tasks = keys.map((key) => (next) => { 111 | let request 112 | 113 | try { 114 | request = store.get(key) 115 | } catch (err) { 116 | return next(err) 117 | } 118 | 119 | request.onsuccess = () => { 120 | const value = request.result 121 | next(null, value === undefined ? value : deserialize(value, asBuffer)) 122 | } 123 | 124 | request.onerror = (ev) => { 125 | ev.stopPropagation() 126 | next(request.error) 127 | } 128 | }) 129 | 130 | parallel(tasks, 16, callback) 131 | } 132 | 133 | Level.prototype._del = function (key, options, callback) { 134 | const store = this.store('readwrite') 135 | let req 136 | 137 | try { 138 | req = store.delete(key) 139 | } catch (err) { 140 | return this._nextTick(callback, err) 141 | } 142 | 143 | this.await(req, callback) 144 | } 145 | 146 | Level.prototype._put = function (key, value, options, callback) { 147 | const store = this.store('readwrite') 148 | let req 149 | 150 | try { 151 | // Will throw a DataError or DataCloneError if the environment 152 | // does not support serializing the key or value respectively. 153 | req = store.put(value, key) 154 | } catch (err) { 155 | return this._nextTick(callback, err) 156 | } 157 | 158 | this.await(req, callback) 159 | } 160 | 161 | Level.prototype._serializeKey = function (key) { 162 | return serialize(key, this.supports.bufferKeys) 163 | } 164 | 165 | Level.prototype._serializeValue = function (value) { 166 | return serialize(value, true) 167 | } 168 | 169 | Level.prototype._iterator = function (options) { 170 | return new Iterator(this, this.location, options) 171 | } 172 | 173 | Level.prototype._batch = function (operations, options, callback) { 174 | if (operations.length === 0) return this._nextTick(callback) 175 | 176 | const store = this.store('readwrite') 177 | const transaction = store.transaction 178 | let index = 0 179 | let error 180 | 181 | transaction.onabort = function () { 182 | callback(error || transaction.error || new Error('aborted by user')) 183 | } 184 | 185 | transaction.oncomplete = function () { 186 | callback() 187 | } 188 | 189 | // Wait for a request to complete before making the next, saving CPU. 190 | function loop () { 191 | const op = operations[index++] 192 | const key = op.key 193 | 194 | let req 195 | 196 | try { 197 | req = op.type === 'del' ? store.delete(key) : store.put(op.value, key) 198 | } catch (err) { 199 | error = err 200 | transaction.abort() 201 | return 202 | } 203 | 204 | if (index < operations.length) { 205 | req.onsuccess = loop 206 | } 207 | } 208 | 209 | loop() 210 | } 211 | 212 | Level.prototype._clear = function (options, callback) { 213 | let keyRange 214 | let req 215 | 216 | try { 217 | keyRange = createKeyRange(options) 218 | } catch (e) { 219 | // The lower key is greater than the upper key. 220 | // IndexedDB throws an error, but we'll just do nothing. 221 | return this._nextTick(callback) 222 | } 223 | 224 | if (options.limit >= 0) { 225 | // IDBObjectStore#delete(range) doesn't have such an option. 226 | // Fall back to cursor-based implementation. 227 | return clear(this, this.location, keyRange, options, callback) 228 | } 229 | 230 | try { 231 | const store = this.store('readwrite') 232 | req = keyRange ? store.delete(keyRange) : store.clear() 233 | } catch (err) { 234 | return this._nextTick(callback, err) 235 | } 236 | 237 | this.await(req, callback) 238 | } 239 | 240 | Level.prototype._close = function (callback) { 241 | this.db.close() 242 | this._nextTick(callback) 243 | } 244 | 245 | // NOTE: remove in a next major release 246 | Level.prototype.upgrade = function (callback) { 247 | if (this.status !== 'open') { 248 | return this._nextTick(callback, new Error('cannot upgrade() before open()')) 249 | } 250 | 251 | const it = this.iterator() 252 | const batchOptions = {} 253 | const self = this 254 | 255 | it._deserializeKey = it._deserializeValue = identity 256 | next() 257 | 258 | function next (err) { 259 | if (err) return finish(err) 260 | it.next(each) 261 | } 262 | 263 | function each (err, key, value) { 264 | if (err || key === undefined) { 265 | return finish(err) 266 | } 267 | 268 | const newKey = self._serializeKey(deserialize(key, true)) 269 | const newValue = self._serializeValue(deserialize(value, true)) 270 | 271 | // To bypass serialization on the old key, use _batch() instead of batch(). 272 | // NOTE: if we disable snapshotting (#86) this could lead to a loop of 273 | // inserting and then iterating those same entries, because the new keys 274 | // possibly sort after the old keys. 275 | self._batch([ 276 | { type: 'del', key: key }, 277 | { type: 'put', key: newKey, value: newValue } 278 | ], batchOptions, next) 279 | } 280 | 281 | function finish (err) { 282 | it.end(function (err2) { 283 | callback(err || err2) 284 | }) 285 | } 286 | 287 | function identity (data) { 288 | return data 289 | } 290 | } 291 | 292 | Level.destroy = function (location, prefix, callback) { 293 | if (typeof prefix === 'function') { 294 | callback = prefix 295 | prefix = DEFAULT_PREFIX 296 | } 297 | const request = indexedDB.deleteDatabase(prefix + location) 298 | request.onsuccess = function () { 299 | callback() 300 | } 301 | request.onerror = function (err) { 302 | callback(err) 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /iterator.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const inherits = require('inherits') 4 | const AbstractIterator = require('abstract-leveldown').AbstractIterator 5 | const createKeyRange = require('./util/key-range') 6 | const deserialize = require('./util/deserialize') 7 | const noop = function () {} 8 | 9 | module.exports = Iterator 10 | 11 | function Iterator (db, location, options) { 12 | AbstractIterator.call(this, db) 13 | 14 | this._limit = options.limit 15 | this._count = 0 16 | this._callback = null 17 | this._cache = [] 18 | this._completed = false 19 | this._aborted = false 20 | this._error = null 21 | this._transaction = null 22 | 23 | this._keys = options.keys 24 | this._values = options.values 25 | this._keyAsBuffer = options.keyAsBuffer 26 | this._valueAsBuffer = options.valueAsBuffer 27 | 28 | if (this._limit === 0) { 29 | this._completed = true 30 | return 31 | } 32 | 33 | let keyRange 34 | 35 | try { 36 | keyRange = createKeyRange(options) 37 | } catch (e) { 38 | // The lower key is greater than the upper key. 39 | // IndexedDB throws an error, but we'll just return 0 results. 40 | this._completed = true 41 | return 42 | } 43 | 44 | this.createIterator(location, keyRange, options.reverse) 45 | } 46 | 47 | inherits(Iterator, AbstractIterator) 48 | 49 | Iterator.prototype.createIterator = function (location, keyRange, reverse) { 50 | const transaction = this.db.db.transaction([location], 'readonly') 51 | const store = transaction.objectStore(location) 52 | const req = store.openCursor(keyRange, reverse ? 'prev' : 'next') 53 | 54 | req.onsuccess = (ev) => { 55 | const cursor = ev.target.result 56 | if (cursor) this.onItem(cursor) 57 | } 58 | 59 | this._transaction = transaction 60 | 61 | // If an error occurs (on the request), the transaction will abort. 62 | transaction.onabort = () => { 63 | this.onAbort(this._transaction.error || new Error('aborted by user')) 64 | } 65 | 66 | transaction.oncomplete = () => { 67 | this.onComplete() 68 | } 69 | } 70 | 71 | Iterator.prototype.onItem = function (cursor) { 72 | this._cache.push(cursor.key, cursor.value) 73 | 74 | if (this._limit <= 0 || ++this._count < this._limit) { 75 | cursor.continue() 76 | } 77 | 78 | this.maybeNext() 79 | } 80 | 81 | Iterator.prototype.onAbort = function (err) { 82 | this._aborted = true 83 | this._error = err 84 | this.maybeNext() 85 | } 86 | 87 | Iterator.prototype.onComplete = function () { 88 | this._completed = true 89 | this.maybeNext() 90 | } 91 | 92 | Iterator.prototype.maybeNext = function () { 93 | if (this._callback) { 94 | this._next(this._callback) 95 | this._callback = null 96 | } 97 | } 98 | 99 | Iterator.prototype._next = function (callback) { 100 | if (this._aborted) { 101 | // The error should be picked up by either next() or end(). 102 | const err = this._error 103 | this._error = null 104 | this._nextTick(callback, err) 105 | } else if (this._cache.length > 0) { 106 | let key = this._cache.shift() 107 | let value = this._cache.shift() 108 | 109 | if (this._keys && key !== undefined) { 110 | key = this._deserializeKey(key, this._keyAsBuffer) 111 | } else { 112 | key = undefined 113 | } 114 | 115 | if (this._values && value !== undefined) { 116 | value = this._deserializeValue(value, this._valueAsBuffer) 117 | } else { 118 | value = undefined 119 | } 120 | 121 | this._nextTick(callback, null, key, value) 122 | } else if (this._completed) { 123 | this._nextTick(callback) 124 | } else { 125 | this._callback = callback 126 | } 127 | } 128 | 129 | // Exposed for the v4 to v5 upgrade utility 130 | Iterator.prototype._deserializeKey = deserialize 131 | Iterator.prototype._deserializeValue = deserialize 132 | 133 | Iterator.prototype._end = function (callback) { 134 | if (this._aborted || this._completed) { 135 | return this._nextTick(callback, this._error) 136 | } 137 | 138 | // Don't advance the cursor anymore, and the transaction will complete 139 | // on its own in the next tick. This approach is much cleaner than calling 140 | // transaction.abort() with its unpredictable event order. 141 | this.onItem = noop 142 | this.onAbort = callback 143 | this.onComplete = callback 144 | } 145 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "level-js", 3 | "version": "6.1.0", 4 | "description": "An abstract-leveldown compliant store on top of IndexedDB", 5 | "author": "max ogden", 6 | "license": "MIT", 7 | "main": "index.js", 8 | "scripts": { 9 | "test": "standard && hallmark && airtap -p local --coverage test/index.js", 10 | "test-browsers": "standard && airtap --coverage test/index.js", 11 | "coverage": "nyc report -r lcovonly", 12 | "hallmark": "hallmark --fix", 13 | "dependency-check": "dependency-check --no-dev -i buffer .", 14 | "prepublishOnly": "npm run dependency-check" 15 | }, 16 | "files": [ 17 | "index.js", 18 | "iterator.js", 19 | "util", 20 | "CHANGELOG.md", 21 | "UPGRADING.md", 22 | "sauce-labs.svg" 23 | ], 24 | "dependencies": { 25 | "abstract-leveldown": "^7.2.0", 26 | "buffer": "^6.0.3", 27 | "inherits": "^2.0.3", 28 | "ltgt": "^2.1.2", 29 | "run-parallel-limit": "^1.1.0" 30 | }, 31 | "devDependencies": { 32 | "airtap": "^4.0.1", 33 | "airtap-playwright": "^1.0.1", 34 | "airtap-sauce": "^1.1.0", 35 | "dependency-check": "^4.1.0", 36 | "hallmark": "^4.0.0", 37 | "level-concat-iterator": "^3.0.0", 38 | "nyc": "^15.0.0", 39 | "standard": "^16.0.3", 40 | "tape": "^5.0.0", 41 | "uuid": "^3.3.2" 42 | }, 43 | "repository": { 44 | "type": "git", 45 | "url": "https://github.com/Level/level-js.git" 46 | }, 47 | "homepage": "https://github.com/Level/level-js", 48 | "keywords": [ 49 | "level", 50 | "leveldb", 51 | "indexeddb", 52 | "abstract-leveldown" 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/custom-test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const concat = require('level-concat-iterator') 4 | 5 | module.exports = function (leveljs, test, testCommon) { 6 | test('setUp', testCommon.setUp) 7 | 8 | test('default prefix', function (t) { 9 | const db = testCommon.factory() 10 | 11 | t.ok(db.location, 'instance has location property') 12 | t.is(db.prefix, 'level-js-', 'instance has prefix property') 13 | 14 | db.open(function (err) { 15 | t.notOk(err, 'no open error') 16 | 17 | const idb = db.db 18 | const databaseName = idb.name 19 | const storeNames = idb.objectStoreNames 20 | 21 | t.is(databaseName, 'level-js-' + db.location, 'database name is prefixed') 22 | t.is(storeNames.length, 1, 'created 1 object store') 23 | t.is(storeNames.item(0), db.location, 'object store name equals location') 24 | 25 | db.close(t.end.bind(t)) 26 | }) 27 | }) 28 | 29 | test('custom prefix', function (t) { 30 | const db = testCommon.factory({ prefix: 'custom-' }) 31 | 32 | t.ok(db.location, 'instance has location property') 33 | t.is(db.prefix, 'custom-', 'instance has prefix property') 34 | 35 | db.open(function (err) { 36 | t.notOk(err, 'no open error') 37 | 38 | const idb = db.db 39 | const databaseName = idb.name 40 | const storeNames = idb.objectStoreNames 41 | 42 | t.is(databaseName, 'custom-' + db.location, 'database name is prefixed') 43 | t.is(storeNames.length, 1, 'created 1 object store') 44 | t.is(storeNames.item(0), db.location, 'object store name equals location') 45 | 46 | db.close(t.end.bind(t)) 47 | }) 48 | }) 49 | 50 | test('empty prefix', function (t) { 51 | const db = testCommon.factory({ prefix: '' }) 52 | 53 | t.ok(db.location, 'instance has location property') 54 | t.is(db.prefix, '', 'instance has prefix property') 55 | 56 | db.open(function (err) { 57 | t.notOk(err, 'no open error') 58 | 59 | const idb = db.db 60 | const databaseName = idb.name 61 | const storeNames = idb.objectStoreNames 62 | 63 | t.is(databaseName, db.location, 'database name is prefixed') 64 | t.is(storeNames.length, 1, 'created 1 object store') 65 | t.is(storeNames.item(0), db.location, 'object store name equals location') 66 | 67 | db.close(t.end.bind(t)) 68 | }) 69 | }) 70 | 71 | test('put Buffer value, get Buffer value', function (t) { 72 | const level = testCommon.factory() 73 | level.open(function (err) { 74 | t.notOk(err, 'no error') 75 | level.put('key', Buffer.from('00ff', 'hex'), function (err) { 76 | t.notOk(err, 'no error') 77 | level.get('key', function (err, value) { 78 | t.notOk(err, 'no error') 79 | t.ok(Buffer.isBuffer(value), 'is buffer') 80 | t.same(value, Buffer.from('00ff', 'hex')) 81 | level.close(t.end.bind(t)) 82 | }) 83 | }) 84 | }) 85 | }) 86 | 87 | test('put Buffer value, get string value', function (t) { 88 | const level = testCommon.factory() 89 | level.open(function (err) { 90 | t.notOk(err, 'no error') 91 | level.put('key', Buffer.from('abc'), function (err) { 92 | t.notOk(err, 'no error') 93 | level.get('key', { asBuffer: false }, function (err, value) { 94 | t.notOk(err, 'no error') 95 | t.is(value, 'abc') 96 | level.close(t.end.bind(t)) 97 | }) 98 | }) 99 | }) 100 | }) 101 | 102 | test('put utf8 string, get utf8 string', function (t) { 103 | const level = testCommon.factory() 104 | level.open(function (err) { 105 | t.notOk(err, 'no error') 106 | level.put('💩', '💩', function (err) { 107 | t.notOk(err, 'no error') 108 | level.get('💩', { asBuffer: false }, function (err, value) { 109 | t.notOk(err, 'no error') 110 | t.is(value, '💩') 111 | level.close(t.end.bind(t)) 112 | }) 113 | }) 114 | }) 115 | }) 116 | 117 | // This should be covered by abstract-leveldown tests, but that's 118 | // prevented by process.browser checks (Level/abstract-leveldown#121). 119 | // This test is adapted from memdown. 120 | test('buffer keys', function (t) { 121 | const db = testCommon.factory() 122 | 123 | if (!db.supports.bufferKeys) { 124 | t.fail('environment does not support buffer keys') 125 | return t.end() 126 | } 127 | 128 | db.open(function (err) { 129 | t.ifError(err, 'no open error') 130 | 131 | const one = Buffer.from('80', 'hex') 132 | const two = Buffer.from('c0', 'hex') 133 | 134 | t.ok(two.toString() === one.toString(), 'would be equal when not buffer-aware') 135 | t.ok(Buffer.compare(two, one) > 0, 'but greater when buffer-aware') 136 | 137 | db.put(one, 'one', function (err) { 138 | t.notOk(err, 'no error') 139 | 140 | db.get(one, { asBuffer: false }, function (err, value) { 141 | t.notOk(err, 'no error') 142 | t.equal(value, 'one', 'value one ok') 143 | 144 | db.put(two, 'two', function (err) { 145 | t.notOk(err, 'no error') 146 | 147 | db.get(one, { asBuffer: false }, function (err, value) { 148 | t.notOk(err, 'no error') 149 | t.equal(value, 'one', 'value one is the same') 150 | 151 | db.close(function (err) { 152 | t.ifError(err, 'no close error') 153 | t.end() 154 | }) 155 | }) 156 | }) 157 | }) 158 | }) 159 | }) 160 | }) 161 | 162 | // This should be covered by abstract-leveldown tests, but that's 163 | // prevented by process.browser checks (Level/abstract-leveldown#121). 164 | test('iterator yields buffer keys', function (t) { 165 | const db = testCommon.factory() 166 | 167 | if (!db.supports.bufferKeys) { 168 | t.fail('environment does not support buffer keys') 169 | return t.end() 170 | } 171 | 172 | db.open(function (err) { 173 | t.ifError(err, 'no open error') 174 | 175 | db.batch([ 176 | { type: 'put', key: Buffer.from([0]), value: '0' }, 177 | { type: 'put', key: Buffer.from([1]), value: '1' } 178 | ], function (err) { 179 | t.ifError(err, 'no batch error') 180 | 181 | const it = db.iterator({ valueAsBuffer: false }) 182 | concat(it, function (err, entries) { 183 | t.ifError(err, 'no iterator error') 184 | 185 | t.same(entries, [ 186 | { key: Buffer.from([0]), value: '0' }, 187 | { key: Buffer.from([1]), value: '1' } 188 | ], 'keys are Buffers') 189 | 190 | db.close(function (err) { 191 | t.ifError(err, 'no close error') 192 | t.end() 193 | }) 194 | }) 195 | }) 196 | }) 197 | }) 198 | 199 | test('buffer range option', function (t) { 200 | const db = testCommon.factory() 201 | 202 | if (!db.supports.bufferKeys) { 203 | t.fail('environment does not support buffer keys') 204 | return t.end() 205 | } 206 | 207 | db.open(function (err) { 208 | t.ifError(err, 'no open error') 209 | 210 | const one = Buffer.from('80', 'hex') 211 | const two = Buffer.from('c0', 'hex') 212 | 213 | db.batch([ 214 | { type: 'put', key: one, value: one }, 215 | { type: 'put', key: two, value: two } 216 | ], function (err) { 217 | t.ifError(err, 'no batch error') 218 | 219 | concat(db.iterator({ gt: one }), function (err, entries) { 220 | t.ifError(err, 'no iterator error') 221 | t.same(entries, [{ key: two, value: two }]) 222 | 223 | db.close(function (err) { 224 | t.ifError(err, 'no close error') 225 | t.end() 226 | }) 227 | }) 228 | }) 229 | }) 230 | }) 231 | 232 | // Adapted from a memdown test. 233 | test('iterator stringifies buffer input', function (t) { 234 | t.plan(6) 235 | 236 | const db = testCommon.factory() 237 | 238 | db.open(function (err) { 239 | t.ifError(err, 'no open error') 240 | 241 | db.put(1, 2, function (err) { 242 | t.ifError(err, 'no put error') 243 | 244 | concat(db.iterator(), function (err, entries) { 245 | t.ifError(err, 'no iterator error') 246 | t.same(entries[0].key, Buffer.from('1'), 'key is stringified') 247 | t.same(entries[0].value, Buffer.from('2'), 'value is stringified') 248 | 249 | db.close(function (err) { 250 | t.ifError(err, 'no close error') 251 | }) 252 | }) 253 | }) 254 | }) 255 | }) 256 | 257 | // NOTE: in chrome (at least) indexeddb gets buggy if you try and destroy a db, 258 | // then create it again, then try and destroy it again. these avoid doing that 259 | 260 | test('test .destroy', function (t) { 261 | const db = testCommon.factory() 262 | const location = db.location 263 | db.open(function (err) { 264 | t.notOk(err, 'no error') 265 | db.put('key', 'value', function (err) { 266 | t.notOk(err, 'no error') 267 | db.get('key', { asBuffer: false }, function (err, value) { 268 | t.notOk(err, 'no error') 269 | t.equal(value, 'value', 'should have value') 270 | db.close(function (err) { 271 | t.notOk(err, 'no error') 272 | leveljs.destroy(location, function (err) { 273 | t.notOk(err, 'no error') 274 | const db2 = leveljs(location) 275 | db2.open(function (err) { 276 | t.notOk(err, 'no error') 277 | db2.get('key', { asBuffer: false }, function (err, value) { 278 | t.is(err.message, 'NotFound', 'key is not there') 279 | db2.close(t.end.bind(t)) 280 | }) 281 | }) 282 | }) 283 | }) 284 | }) 285 | }) 286 | }) 287 | }) 288 | 289 | test('test .destroy and custom prefix', function (t) { 290 | const prefix = 'custom-' 291 | const db = testCommon.factory({ prefix: prefix }) 292 | const location = db.location 293 | 294 | db.open(function (err) { 295 | t.notOk(err, 'no error') 296 | db.put('key', 'value', function (err) { 297 | t.notOk(err, 'no error') 298 | db.get('key', { asBuffer: false }, function (err, value) { 299 | t.notOk(err, 'no error') 300 | t.equal(value, 'value', 'should have value') 301 | db.close(function (err) { 302 | t.notOk(err, 'no error') 303 | leveljs.destroy(location, prefix, function (err) { 304 | t.notOk(err, 'no error') 305 | const db2 = leveljs(location, { prefix: prefix }) 306 | db2.open(function (err) { 307 | t.notOk(err, 'no error') 308 | db2.get('key', { asBuffer: false }, function (err, value) { 309 | t.is(err.message, 'NotFound', 'key is not there') 310 | db2.close(t.end.bind(t)) 311 | }) 312 | }) 313 | }) 314 | }) 315 | }) 316 | }) 317 | }) 318 | }) 319 | 320 | // TODO: move to abstract-leveldown test suite (and add to iterator tests too) 321 | test('clear() with lower key greater than upper key', function (t) { 322 | const db = testCommon.factory() 323 | 324 | db.open(function (err) { 325 | t.ifError(err, 'no open error') 326 | 327 | db.put('a', 'a', function (err) { 328 | t.ifError(err, 'no put error') 329 | 330 | db.clear({ gt: 'b', lt: 'a' }, function (err) { 331 | t.ifError(err, 'no clear error') 332 | 333 | db.get('a', { asBuffer: false }, function (err, value) { 334 | t.ifError(err, 'no get error') 335 | t.is(value, 'a') 336 | 337 | db.close(t.end.bind(t)) 338 | }) 339 | }) 340 | }) 341 | }) 342 | }) 343 | 344 | test('teardown', testCommon.tearDown) 345 | } 346 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const test = require('tape') 4 | const uuid = require('uuid/v4') 5 | const suite = require('abstract-leveldown/test') 6 | const leveljs = require('..') 7 | 8 | // Test feature detection 9 | require('./support-test')(leveljs, test) 10 | 11 | const testCommon = suite.common({ 12 | test: test, 13 | factory: function (opts) { 14 | return leveljs(uuid(), opts) 15 | }, 16 | 17 | // Unsupported features 18 | createIfMissing: false, 19 | errorIfExists: false, 20 | seek: false, 21 | 22 | // Support of buffer keys depends on environment 23 | bufferKeys: leveljs(uuid()).supports.bufferKeys, 24 | 25 | // Opt-in to new tests 26 | clear: true, 27 | getMany: true 28 | }) 29 | 30 | // Test abstract-leveldown compliance 31 | suite(testCommon) 32 | 33 | // Additional tests for this implementation 34 | require('./custom-test')(leveljs, test, testCommon) 35 | require('./upgrade-test')(leveljs, test, testCommon) 36 | -------------------------------------------------------------------------------- /test/support-test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const support = require('../util/support') 4 | 5 | const pos = function () { } 6 | const neg = function () { throw new Error() } 7 | 8 | module.exports = function (leveljs, test) { 9 | test('mock bufferKeys support', function (t) { 10 | t.ok(support.bufferKeys({ cmp: pos })) 11 | t.notOk(support.bufferKeys({ cmp: neg })) 12 | t.end() 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /test/upgrade-test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const concat = require('level-concat-iterator') 4 | 5 | module.exports = function (leveljs, test, testCommon) { 6 | test('upgrade', function (t) { 7 | const db = testCommon.factory() 8 | 9 | const input = [ 10 | { key: -1, value: 'a' }, 11 | { key: '0', value: ab('b') }, 12 | { key: '1', value: 1 }, 13 | { key: ab('2'), value: new Uint8Array(ab('2')) } 14 | ] 15 | 16 | const output = [ 17 | { key: ab('-1'), value: new Uint8Array(ab('a')) }, 18 | { key: ab('0'), value: new Uint8Array(ab('b')) }, 19 | { key: ab('1'), value: new Uint8Array(ab('1')) }, 20 | { key: ab('2'), value: new Uint8Array(ab('2')) } 21 | ] 22 | 23 | db.open(function (err) { 24 | t.ifError(err, 'no open error') 25 | 26 | // To bypass serialization, use _batch() instead of batch(). 27 | db._batch(input.map(putOperation), {}, function (err) { 28 | t.ifError(err, 'no batch error') 29 | 30 | db.upgrade(function (err) { 31 | t.ifError(err, 'no upgrade error') 32 | 33 | concatRaw(function (err, entries) { 34 | t.ifError(err, 'no concat error') 35 | 36 | entries.forEach(function (entry) { 37 | t.ok(entry.key instanceof ArrayBuffer) 38 | t.ok(entry.value instanceof Uint8Array) 39 | }) 40 | 41 | t.same(entries.map(bufferEntry), output.map(bufferEntry)) 42 | t.end() 43 | }) 44 | }) 45 | }) 46 | }) 47 | 48 | function concatRaw (callback) { 49 | const it = db.iterator() 50 | it._deserializeKey = it._deserializeValue = identity 51 | concat(it, callback) 52 | } 53 | 54 | function identity (data) { 55 | return data 56 | } 57 | 58 | function ab (data) { 59 | return Buffer.from(data).buffer 60 | } 61 | 62 | function bufferEntry (entry) { 63 | return { key: Buffer.from(entry.key), value: Buffer.from(entry.value) } 64 | } 65 | 66 | function putOperation (entry) { 67 | return { type: 'put', key: entry.key, value: entry.value } 68 | } 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /util/clear.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function clear (db, location, keyRange, options, callback) { 4 | if (options.limit === 0) return db._nextTick(callback) 5 | 6 | const transaction = db.db.transaction([location], 'readwrite') 7 | const store = transaction.objectStore(location) 8 | let count = 0 9 | 10 | transaction.oncomplete = function () { 11 | callback() 12 | } 13 | 14 | transaction.onabort = function () { 15 | callback(transaction.error || new Error('aborted by user')) 16 | } 17 | 18 | // A key cursor is faster (skips reading values) but not supported by IE 19 | const method = store.openKeyCursor ? 'openKeyCursor' : 'openCursor' 20 | const direction = options.reverse ? 'prev' : 'next' 21 | 22 | store[method](keyRange, direction).onsuccess = function (ev) { 23 | const cursor = ev.target.result 24 | 25 | if (cursor) { 26 | // Wait for a request to complete before continuing, saving CPU. 27 | store.delete(cursor.key).onsuccess = function () { 28 | if (options.limit <= 0 || ++count < options.limit) { 29 | cursor.continue() 30 | } 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /util/deserialize.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Buffer = require('buffer').Buffer 4 | const ta2str = (function () { 5 | if (global.TextDecoder) { 6 | const decoder = new TextDecoder('utf-8') 7 | return decoder.decode.bind(decoder) 8 | } else { 9 | return function ta2str (ta) { 10 | return ta2buf(ta).toString() 11 | } 12 | } 13 | })() 14 | 15 | const ab2str = (function () { 16 | if (global.TextDecoder) { 17 | const decoder = new TextDecoder('utf-8') 18 | return decoder.decode.bind(decoder) 19 | } else { 20 | return function ab2str (ab) { 21 | return Buffer.from(ab).toString() 22 | } 23 | } 24 | })() 25 | 26 | function ta2buf (ta) { 27 | const buf = Buffer.from(ta.buffer) 28 | 29 | if (ta.byteLength === ta.buffer.byteLength) { 30 | return buf 31 | } else { 32 | return buf.slice(ta.byteOffset, ta.byteOffset + ta.byteLength) 33 | } 34 | } 35 | 36 | module.exports = function (data, asBuffer) { 37 | if (data instanceof Uint8Array) { 38 | return asBuffer ? ta2buf(data) : ta2str(data) 39 | } else if (data instanceof ArrayBuffer) { 40 | return asBuffer ? Buffer.from(data) : ab2str(data) 41 | } else { 42 | return asBuffer ? Buffer.from(String(data)) : String(data) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /util/key-range.js: -------------------------------------------------------------------------------- 1 | /* global IDBKeyRange */ 2 | 3 | 'use strict' 4 | 5 | const ltgt = require('ltgt') 6 | const NONE = Symbol('none') 7 | 8 | module.exports = function createKeyRange (options) { 9 | const lower = ltgt.lowerBound(options, NONE) 10 | const upper = ltgt.upperBound(options, NONE) 11 | const lowerOpen = ltgt.lowerBoundExclusive(options, NONE) 12 | const upperOpen = ltgt.upperBoundExclusive(options, NONE) 13 | 14 | if (lower !== NONE && upper !== NONE) { 15 | return IDBKeyRange.bound(lower, upper, lowerOpen, upperOpen) 16 | } else if (lower !== NONE) { 17 | return IDBKeyRange.lowerBound(lower, lowerOpen) 18 | } else if (upper !== NONE) { 19 | return IDBKeyRange.upperBound(upper, upperOpen) 20 | } else { 21 | return null 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /util/serialize.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Buffer = require('buffer').Buffer 4 | // Returns either a Uint8Array or Buffer (doesn't matter to 5 | // IndexedDB, because Buffer is a subclass of Uint8Array) 6 | const str2bin = (function () { 7 | if (global.TextEncoder) { 8 | const encoder = new TextEncoder('utf-8') 9 | return encoder.encode.bind(encoder) 10 | } else { 11 | return Buffer.from 12 | } 13 | })() 14 | 15 | module.exports = function (data, asBuffer) { 16 | if (asBuffer) { 17 | return Buffer.isBuffer(data) ? data : str2bin(String(data)) 18 | } else { 19 | return String(data) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /util/support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Buffer = require('buffer').Buffer 4 | 5 | exports.test = function (key) { 6 | return function test (impl) { 7 | try { 8 | impl.cmp(key, 0) 9 | return true 10 | } catch (err) { 11 | return false 12 | } 13 | } 14 | } 15 | 16 | // Detect binary key support (IndexedDB Second Edition) 17 | exports.bufferKeys = exports.test(Buffer.alloc(0)) 18 | --------------------------------------------------------------------------------