|
2 | 2 |
|
3 | 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.md).
|
4 | 4 |
|
| 5 | +## 8.0.0 |
| 6 | + |
| 7 | +**This release replaces `leveldown` and `level-js` with [`classic-level`](https://github.com/Level/classic-level) and [`browser-level`](https://github.com/Level/browser-level). These modules implement the [`abstract-level`](https://github.com/Level/abstract-level) interface instead of [`abstract-leveldown`](https://github.com/Level/abstract-leveldown). This gives them the same API as `level@7` without having to be wrapped with [`levelup`](https://github.com/Level/levelup) or [`encoding-down`](https://github.com/Level/encoding-down). In addition, you can now choose to use Uint8Array instead of Buffer. Sublevels are built-in.** |
| 8 | + |
| 9 | +We've put together several upgrade guides for different modules. See the [FAQ](https://github.com/Level/community#faq) to find the best upgrade guide for you. This one describes how to upgrade `level`. |
| 10 | + |
| 11 | +Support of Node.js 10 has been dropped. |
| 12 | + |
| 13 | +### Changes to initialization |
| 14 | + |
| 15 | +We started using classes, which means using `new` is now required. If you previously did: |
| 16 | + |
| 17 | +```js |
| 18 | +const level = require('level') |
| 19 | +const db = level('db') |
| 20 | +``` |
| 21 | + |
| 22 | +You must now do: |
| 23 | + |
| 24 | +```js |
| 25 | +const { Level } = require('level') |
| 26 | +const db = new Level('db') |
| 27 | +``` |
| 28 | + |
| 29 | +### TypeScript makes a win |
| 30 | + |
| 31 | +TypeScript type declarations are now included in the npm package(s). For `level` it's an intersection of `classic-level` and `browser-level` types that includes their options but excludes methods like `compactRange()` that can only be found in either. JavaScript folks using VSCode will also benefit from the new types because they enable auto-completion and now include documentation. |
| 32 | + |
| 33 | +### Waking up from limbo |
| 34 | + |
| 35 | +Deferred open - meaning that a database opens itself and any operations made in the mean time are queued up in memory - remains built-in. A new behavior is that those operations will yield errors if opening failed. They'd previously end up in limbo. |
| 36 | + |
| 37 | +An `abstract-level` and thus `level` database is not "patch-safe". If some form of plugin monkey-patches a database method, it must now also take the responsibility of deferring the operation (as well as handling promises and callbacks) using [`db.defer()`](https://github.com/Level/abstract-level#dbdeferfn). |
| 38 | + |
| 39 | +### Creating the location recursively |
| 40 | + |
| 41 | +To align behavior between platforms, `classic-level` and therefore `level@8` creates the location directory recursively. While `leveldown` and therefore `level@7` would only do so on Windows. In the following example, the `foo` directory does not have to exist beforehand: |
| 42 | + |
| 43 | +```js |
| 44 | +const db = new Level('foo/bar') |
| 45 | +``` |
| 46 | + |
| 47 | +This new behavior may break expectations, given typical filesystem behavior, or it could be a convenient feature, if the database is considered to abstract away the filesystem. We're [collecting feedback](https://github.com/Level/classic-level/issues/7) to determine what to do in a next (major) version. Your vote is most welcome! |
| 48 | + |
| 49 | +### No constructor callback |
| 50 | + |
| 51 | +The database constructor no longer takes a callback argument. Instead call `db.open()` if you wish to wait for opening (which is not necessary to use the database) or to capture an error. If that's your reason for using the callback and you previously initialized a database like so: |
| 52 | + |
| 53 | +```js |
| 54 | +level('fruits', function (err, db) { |
| 55 | + // .. |
| 56 | +}) |
| 57 | +``` |
| 58 | + |
| 59 | +You must now do one of: |
| 60 | + |
| 61 | +```js |
| 62 | +db.open(callback) |
| 63 | +await db.open() |
| 64 | +``` |
| 65 | + |
| 66 | +### There is only encodings |
| 67 | + |
| 68 | +Encodings have a new home in `abstract-level` and are now powered by [`level-transcoder`](https://github.com/Level/transcoder). The main change is that logic from the existing public API has been expanded down into the storage layer. There are however a few differences from `level@7`. Some breaking: |
| 69 | + |
| 70 | +- The lesser-used `'id'`, `'ascii'`, `'ucs2'` and `'utf16le'` encodings are not supported |
| 71 | +- The undocumented `encoding` option (as an alias for `valueEncoding`) is not supported. |
| 72 | + |
| 73 | +And some non-breaking: |
| 74 | + |
| 75 | +- The `'binary'` encoding has been renamed to `'buffer'`, with `'binary'` as an alias |
| 76 | +- The `'utf8'` encoding previously did not touch Buffers. Now it will call `buffer.toString('utf8')` for consistency. Consumers can use the `'buffer'` encoding to avoid this conversion. |
| 77 | + |
| 78 | +Both `classic-level` and `browser-level` support Uint8Array data, in addition to Buffer. It's a separate encoding called `'view'` that can be used interchangeably: |
| 79 | + |
| 80 | +```js |
| 81 | +const db = new Level('people', { valueEncoding: 'view' }) |
| 82 | + |
| 83 | +await db.put('elena', new Uint8Array([97, 98, 99])) |
| 84 | +await db.get('elena') // Uint8Array |
| 85 | +await db.get('elena', { valueEncoding: 'utf8' }) // 'abc' |
| 86 | +await db.get('elena', { valueEncoding: 'buffer' }) // Buffer |
| 87 | +``` |
| 88 | + |
| 89 | +For browsers you can choose to use Uint8Array exclusively and omit the [`buffer`](https://github.com/feross/buffer) shim from your JavaScript bundle (through configuration of Webpack, Browserify or other). |
| 90 | + |
| 91 | +### Streams have moved |
| 92 | + |
| 93 | +Node.js readable streams must now be created with a new standalone module called [`level-read-stream`](https://github.com/Level/read-stream) rather than database methods like `db.createReadStream()`. For browsers you might prefer [`level-web-stream`](https://github.com/Level/web-stream) which does not require bundling the [`buffer`](https://github.com/feross/buffer) or [`readable-stream`](https://github.com/nodejs/readable-stream) shims. Both `level-read-stream` and `level-web-stream` can be used in Node.js and browsers. The former is significantly faster (also compared to `level@7`, thanks to a new `nextv()` method on iterators). The latter is a step towards a standard library for JavaScript across Node.js, Deno and browsers. |
| 94 | + |
| 95 | +To offer an alternative to `db.createKeyStream()` and `db.createValueStream()`, two new types of iterators have been added: `db.keys()` and `db.values()`. |
| 96 | + |
| 97 | +### State checks for safety |
| 98 | + |
| 99 | +On any operation, an `abstract-level` and thus `level` database checks if it's open. If not, it will either throw an error (if the relevant API is synchronous) or asynchronously yield an error. For example: |
| 100 | + |
| 101 | +```js |
| 102 | +await db.close() |
| 103 | + |
| 104 | +try { |
| 105 | + db.iterator() |
| 106 | +} catch (err) { |
| 107 | + console.log(err.code) // LEVEL_DATABASE_NOT_OPEN |
| 108 | +} |
| 109 | +``` |
| 110 | + |
| 111 | +_Errors now have a `code` property. More on that below\._ |
| 112 | + |
| 113 | +### Zero-length keys and range options are now valid |
| 114 | + |
| 115 | +These keys sort before anything else. Historically they weren't supported for causing segmentation faults in `leveldown`. That doesn't apply to today's codebase. You can now do: |
| 116 | + |
| 117 | +```js |
| 118 | +await db.put('', 'abc') |
| 119 | + |
| 120 | +console.log(await db.get('')) // 'abc' |
| 121 | +console.log(await db.get(new Uint8Array(0), { keyEncoding: 'view' })) // 'abc' |
| 122 | + |
| 123 | +for await (const [key, value] of db.iterator({ lte: '' })) { |
| 124 | + console.log(value) // 'abc' |
| 125 | +} |
| 126 | +``` |
| 127 | + |
| 128 | +### It doesn't end there |
| 129 | + |
| 130 | +The `iterator.end()` method has been renamed to `iterator.close()`, with `end()` being an alias until a next major version. The term "close" makes it easier to differentiate between the iterator having reached its natural end (data-wise) versus closing it to cleanup resources. If you previously did: |
| 131 | + |
| 132 | +```js |
| 133 | +const iterator = db.iterator() |
| 134 | +iterator.end(callback) |
| 135 | +``` |
| 136 | + |
| 137 | +You should now do one of: |
| 138 | + |
| 139 | +```js |
| 140 | +iterator.close(callback) |
| 141 | +await iterator.close() |
| 142 | +``` |
| 143 | + |
| 144 | +On `db.close()`, non-closed iterators are now automatically closed (only for safety reasons). If a call like `next()` is in progress, closing the iterator or database will wait for that. Calling `iterator.close()` more than once is now allowed and makes no difference. |
| 145 | + |
| 146 | +### Other changes to iterators |
| 147 | + |
| 148 | +- In browsers, backpressure is now preferred over snapshot guarantees. For details, please see [`browser-level@1`](https://github.com/Level/browser-level/blob/main/UPGRADING.md#100). On the flip side, `iterator.seek()` now also works in browsers. |
| 149 | +- Use of [`level-concat-iterator`](https://github.com/Level/concat-iterator) can be replaced with [`iterator.all()`](https://github.com/Level/level#iteratoralloptions-callback). The former does support `abstract-level` databases but the latter is optimized and always has snapshot guarantees. |
| 150 | +- The previously undocumented `highWaterMark` option of `leveldown` is called [`highWaterMarkBytes`](https://github.com/Level/classic-level#about-high-water) in `classic-level` to remove a conflict with streams. |
| 151 | +- On iterators with `{ keys: false }` or `{ values: false }` options, the yielded key or value is now consistently `undefined`. |
| 152 | + |
| 153 | +### A chained batch should be closed |
| 154 | + |
| 155 | +Chained batch has a new method `close()` which is an idempotent operation and automatically called after `write()` (for backwards compatibility) or on `db.close()`. This to ensure batches can't be used after closing and reopening a db. If a `write()` is in progress, closing will wait for that. If `write()` is never called then `close()` must be and that's a breaking change because inaction will cause memory leaks. For example: |
| 156 | + |
| 157 | +```js |
| 158 | +const batch = db.batch() |
| 159 | + .put('elena', 'abc') |
| 160 | + .del('steve') |
| 161 | + |
| 162 | +if (someCondition) { |
| 163 | + await batch.write() |
| 164 | +} else { |
| 165 | + // Decided not to commit |
| 166 | + await batch.close() |
| 167 | +} |
| 168 | + |
| 169 | +// In either case this will throw |
| 170 | +batch.put('daniel', 'xyz') |
| 171 | +``` |
| 172 | + |
| 173 | +### Errors now use codes |
| 174 | + |
| 175 | +The [`level-errors`](https://github.com/Level/errors) module is no longer used or exposed by `level@8`. Instead errors thrown or yielded from a database [have a `code` property](https://github.com/Level/abstract-level#errors). Going forward, the semver contract will be on `code` and error messages will change without a semver-major bump. |
| 176 | + |
| 177 | +To minimize breakage, the most used error as yielded by `get()` when an entry is not found, has the same properties that `level-errors` added (`notFound` and `status`) in addition to code `LEVEL_NOT_FOUND`. Those properties will be removed in a future version. If you previously did: |
| 178 | + |
| 179 | +```js |
| 180 | +db.get('abc', function (err, value) { |
| 181 | + if (err && err.notFound) { |
| 182 | + // Handle missing entry |
| 183 | + } |
| 184 | +}) |
| 185 | +``` |
| 186 | + |
| 187 | +That will still work but it's preferred to do: |
| 188 | + |
| 189 | +```js |
| 190 | +db.get('abc', function (err, value) { |
| 191 | + if (err && err.code === 'LEVEL_NOT_FOUND') { |
| 192 | + // Handle missing entry |
| 193 | + } |
| 194 | +}) |
| 195 | +``` |
| 196 | + |
| 197 | +Or using promises: |
| 198 | + |
| 199 | +```js |
| 200 | +try { |
| 201 | + const value = await db.get('abc') |
| 202 | +} catch (err) { |
| 203 | + if (err.code === 'LEVEL_NOT_FOUND') { |
| 204 | + // Handle missing entry |
| 205 | + } |
| 206 | +} |
| 207 | +``` |
| 208 | + |
| 209 | +Side note: it's been suggested more than once to remove this error altogether and we likely will after the dust has settled on `abstract-level`. |
| 210 | + |
| 211 | +### Changes to lesser-used properties and methods |
| 212 | + |
| 213 | +The following properties and methods can no longer be accessed, as they've been removed, renamed or replaced with internal [symbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol). |
| 214 | + |
| 215 | +| Object | Property or method | Original module | New module | |
| 216 | +| :------------ | :------------------------ | :------------------- | :--------------- | |
| 217 | +| db | `_setupIteratorOptions()` | `abstract-leveldown` | `abstract-level` | |
| 218 | +| db | `prefix` <sup>1</sup> | `level-js` | `browser-level` | |
| 219 | +| db | `upgrade()` | `level-js` | `browser-level` | |
| 220 | +| iterator | `_nexting` | `abstract-leveldown` | `abstract-level` | |
| 221 | +| iterator | `_ended` | `abstract-leveldown` | `abstract-level` | |
| 222 | +| iterator | `cache` <sup>2</sup> | `leveldown` | `classic-level` | |
| 223 | +| iterator | `finished` | `leveldown` | `classic-level` | |
| 224 | +| chained batch | `_written` | `abstract-leveldown` | `abstract-level` | |
| 225 | +| chained batch | `_checkWritten()` | `abstract-leveldown` | `abstract-level` | |
| 226 | +| chained batch | `_operations` | `abstract-leveldown` | `abstract-level` | |
| 227 | + |
| 228 | +<small> |
| 229 | + |
| 230 | +1. Conflicted with the `db.prefix` property of sublevels. Renamed to `db.namePrefix`. |
| 231 | +2. If you were using this then you'll want to checkout the new [`nextv()`](https://github.com/Level/level#iteratornextvsize-options-callback) method. |
| 232 | + |
| 233 | +</small> |
| 234 | + |
| 235 | +The following properties are now read-only getters. |
| 236 | + |
| 237 | +| Object | Property | Original module | New module | |
| 238 | +| :------------ | :----------------- | :------------------- | :--------------- | |
| 239 | +| db | `status` | `abstract-leveldown` | `abstract-level` | |
| 240 | +| db | `location` | `leveldown` | `classic-level` | |
| 241 | +| db | `location` | `level-js` | `browser-level` | |
| 242 | +| db | `namePrefix` | `level-js` | `browser-level` | |
| 243 | +| db | `version` | `level-js` | `browser-level` | |
| 244 | +| db | `db` (IDBDatabase) | `level-js` | `browser-level` | |
| 245 | +| chained batch | `length` | `levelup` | `abstract-level` | |
| 246 | + |
| 247 | +### Sublevels are built-in |
| 248 | + |
| 249 | +_This section is only relevant if you use [`subleveldown`](https://github.com/Level/subleveldown), which can not wrap a `level@8` database._ |
| 250 | + |
| 251 | +If you previously did: |
| 252 | + |
| 253 | +```js |
| 254 | +const sub = require('subleveldown') |
| 255 | +const example1 = sub(db, 'example1') |
| 256 | +const example2 = sub(db, 'example2', { valueEncoding: 'json' }) |
| 257 | +``` |
| 258 | + |
| 259 | +You must now do: |
| 260 | + |
| 261 | +```js |
| 262 | +const example1 = db.sublevel('example1') |
| 263 | +const example2 = db.sublevel('example2', { valueEncoding: 'json' }) |
| 264 | +``` |
| 265 | + |
| 266 | +The key structure is equal to that of `subleveldown`. This means that a sublevel can read sublevels previously created with (and populated by) `subleveldown`. There are some new features: |
| 267 | + |
| 268 | +- `db.batch(..)` takes a `sublevel` option on operations, to atomically commit data to multiple sublevels |
| 269 | +- Sublevels support Uint8Array in addition to Buffer. |
| 270 | + |
| 271 | +To reduce function overloads, the prefix argument (`example1` above) is now required and it's called `name` here. If you previously did one of the following, resulting in an empty name: |
| 272 | + |
| 273 | +```js |
| 274 | +subleveldown(db) |
| 275 | +subleveldown(db, { separator: '@' }) |
| 276 | +``` |
| 277 | + |
| 278 | +You must now use an explicit empty name: |
| 279 | + |
| 280 | +```js |
| 281 | +db.sublevel('') |
| 282 | +db.sublevel('', { separator: '@' }) |
| 283 | +``` |
| 284 | + |
| 285 | +The string shorthand for `{ separator }` has also been removed. If you previously did: |
| 286 | + |
| 287 | +```js |
| 288 | +subleveldown(db, 'example', '@') |
| 289 | +``` |
| 290 | + |
| 291 | +You must now do: |
| 292 | + |
| 293 | +```js |
| 294 | +db.sublevel('example', { separator: '@' }) |
| 295 | +``` |
| 296 | + |
| 297 | +Third, the `open` option has been removed. If you need an asynchronous open hook, feel free to open an issue to discuss restoring this API. |
| 298 | + |
| 299 | +Lastly, the error message `Parent database is not open` (courtesy of `subleveldown` which had to check open state to prevent segmentation faults from underlying databases) changed to error code [`LEVEL_DATABASE_NOT_OPEN`](https://github.com/Level/abstract-level#errors) (courtesy of `abstract-level` which does those checks on any database). |
| 300 | + |
5 | 301 | ## 7.0.0
|
6 | 302 |
|
7 | 303 | Legacy range options have been removed ([Level/community#86](https://github.com/Level/community/issues/86)). If you previously did:
|
|
0 commit comments