Skip to content

Commit 6296322

Browse files
authored
Breaking: upgrade to abstract-level 2 (#16)
Category: change Ref: #14
1 parent a31ab82 commit 6296322

10 files changed

+212
-262
lines changed

.npmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock=false

README.md

+2-17
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,6 @@
1212
[![Common Changelog](https://common-changelog.org/badge.svg)](https://common-changelog.org)
1313
[![Donate](https://img.shields.io/badge/donate-orange?logo=open-collective\&logoColor=fff)](https://opencollective.com/level)
1414

15-
## Table of Contents
16-
17-
<details><summary>Click to expand</summary>
18-
19-
- [Usage](#usage)
20-
- [API](#api)
21-
- [`db = new BrowserLevel(location[, options])`](#db--new-browserlevellocation-options)
22-
- [`BrowserLevel.destroy(location[, prefix][, callback])`](#browserleveldestroylocation-prefix-callback)
23-
- [Install](#install)
24-
- [Contributing](#contributing)
25-
- [Donate](#donate)
26-
- [License](#license)
27-
28-
</details>
29-
3015
## Usage
3116

3217
```js
@@ -92,9 +77,9 @@ Besides `abstract-level` options, the optional `options` argument may contain:
9277

9378
See [`IDBFactory#open()`](https://developer.mozilla.org/en-US/docs/Web/API/IDBFactory/open) for more details about database name and version.
9479

95-
### `BrowserLevel.destroy(location[, prefix][, callback])`
80+
### `BrowserLevel.destroy(location[, prefix])`
9681

97-
Delete the IndexedDB database at the given `location`. If `prefix` is not given, it defaults to the same value as the `BrowserLevel` constructor does. The `callback` function will be called when the destroy operation is complete, with a possible error argument. If no callback is provided, a promise is returned. This method is an additional method that is not part of the [`abstract-level`](https://github.com/Level/abstract-level) interface.
82+
Delete the IndexedDB database at the given `location`. Returns a promise. If `prefix` is not given, it defaults to the same value as the `BrowserLevel` constructor does. This method is an additional method that is not part of the [`abstract-level`](https://github.com/Level/abstract-level) interface.
9883

9984
Before calling `destroy()`, close a database if it's using the same `location` and `prefix`:
10085

UPGRADING.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
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).
44

5+
## 2.0.0
6+
7+
This release upgrades to `abstract-level` 2 which adds [hooks](https://github.com/Level/abstract-level#hooks) and drops callbacks and not-found errors. Please refer to the [upgrade guide of `abstract-level`](https://github.com/Level/abstract-level/blob/v2.0.0/UPGRADING.md) for details.
8+
59
## 1.0.0
610

711
**Introducing `browser-level`: a fork of [`level-js`](https://github.com/Level/level-js) that removes the need for [`levelup`](https://github.com/Level/levelup) and more. It implements the [`abstract-level`](https://github.com/Level/abstract-level) interface instead of [`abstract-leveldown`](https://github.com/Level/abstract-leveldown) and thus has the same API as `level` and `levelup` including encodings, promises and events. In addition, you can now choose to use Uint8Array instead of Buffer. Sublevels are builtin.**

index.d.ts

-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {
22
AbstractLevel,
33
AbstractDatabaseOptions,
4-
NodeCallback,
54
AbstractOpenOptions,
65
AbstractGetOptions,
76
AbstractGetManyOptions,
@@ -58,8 +57,6 @@ export class BrowserLevel<KDefault = string, VDefault = string>
5857
*/
5958
static destroy (location: string): Promise<void>
6059
static destroy (location: string, prefix: string): Promise<void>
61-
static destroy (location: string, callback: NodeCallback<void>): void
62-
static destroy (location: string, prefix: string, callback: NodeCallback<void>): void
6360
}
6461

6562
/**

index.js

+95-111
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
const { AbstractLevel } = require('abstract-level')
66
const ModuleError = require('module-error')
7-
const parallel = require('run-parallel-limit')
8-
const { fromCallback } = require('catering')
97
const { Iterator } = require('./iterator')
108
const deserialize = require('./util/deserialize')
119
const clear = require('./util/clear')
@@ -20,7 +18,6 @@ const kLocation = Symbol('location')
2018
const kVersion = Symbol('version')
2119
const kStore = Symbol('store')
2220
const kOnComplete = Symbol('onComplete')
23-
const kPromise = Symbol('promise')
2421

2522
class BrowserLevel extends AbstractLevel {
2623
constructor (location, options, _) {
@@ -73,140 +70,139 @@ class BrowserLevel extends AbstractLevel {
7370
return 'browser-level'
7471
}
7572

76-
_open (options, callback) {
77-
const req = indexedDB.open(this[kNamePrefix] + this[kLocation], this[kVersion])
73+
async _open (options) {
74+
const request = indexedDB.open(this[kNamePrefix] + this[kLocation], this[kVersion])
7875

79-
req.onerror = function () {
80-
callback(req.error || new Error('unknown error'))
81-
}
82-
83-
req.onsuccess = () => {
84-
this[kIDB] = req.result
85-
callback()
86-
}
87-
88-
req.onupgradeneeded = (ev) => {
76+
request.onupgradeneeded = (ev) => {
8977
const db = ev.target.result
9078

9179
if (!db.objectStoreNames.contains(this[kLocation])) {
9280
db.createObjectStore(this[kLocation])
9381
}
9482
}
83+
84+
return new Promise((resolve, reject) => {
85+
request.onerror = function () {
86+
reject(request.error || new Error('unknown error'))
87+
}
88+
89+
request.onsuccess = () => {
90+
this[kIDB] = request.result
91+
resolve()
92+
}
93+
})
9594
}
9695

9796
[kStore] (mode) {
9897
const transaction = this[kIDB].transaction([this[kLocation]], mode)
9998
return transaction.objectStore(this[kLocation])
10099
}
101100

102-
[kOnComplete] (request, callback) {
101+
[kOnComplete] (request) {
103102
const transaction = request.transaction
104103

105-
// Take advantage of the fact that a non-canceled request error aborts
106-
// the transaction. I.e. no need to listen for "request.onerror".
107-
transaction.onabort = function () {
108-
callback(transaction.error || new Error('aborted by user'))
109-
}
104+
return new Promise(function (resolve, reject) {
105+
// Take advantage of the fact that a non-canceled request error aborts
106+
// the transaction. I.e. no need to listen for "request.onerror".
107+
transaction.onabort = function () {
108+
reject(transaction.error || new Error('aborted by user'))
109+
}
110110

111-
transaction.oncomplete = function () {
112-
callback(null, request.result)
113-
}
111+
transaction.oncomplete = function () {
112+
resolve(request.result)
113+
}
114+
})
114115
}
115116

116-
_get (key, options, callback) {
117+
async _get (key, options) {
117118
const store = this[kStore]('readonly')
118-
let req
119-
120-
try {
121-
req = store.get(key)
122-
} catch (err) {
123-
return this.nextTick(callback, err)
124-
}
119+
const request = store.get(key)
120+
const value = await this[kOnComplete](request)
125121

126-
this[kOnComplete](req, function (err, value) {
127-
if (err) return callback(err)
128-
129-
if (value === undefined) {
130-
return callback(new ModuleError('Entry not found', {
131-
code: 'LEVEL_NOT_FOUND'
132-
}))
133-
}
134-
135-
callback(null, deserialize(value))
136-
})
122+
return deserialize(value)
137123
}
138124

139-
_getMany (keys, options, callback) {
125+
async _getMany (keys, options) {
140126
const store = this[kStore]('readonly')
141-
const tasks = keys.map((key) => (next) => {
142-
let request
127+
const iterator = keys.values()
128+
129+
// Consume the iterator with N parallel worker bees
130+
const n = Math.min(16, keys.length)
131+
const bees = new Array(n)
132+
const values = new Array(keys.length)
133+
134+
let keyIndex = 0
135+
let abort = false
143136

137+
const bee = async function () {
144138
try {
145-
request = store.get(key)
139+
for (const key of iterator) {
140+
if (abort) break
141+
142+
const valueIndex = keyIndex++
143+
const request = store.get(key)
144+
145+
await new Promise(function (resolve, reject) {
146+
request.onsuccess = () => {
147+
values[valueIndex] = deserialize(request.result)
148+
resolve()
149+
}
150+
151+
request.onerror = (ev) => {
152+
ev.stopPropagation()
153+
reject(request.error)
154+
}
155+
})
156+
}
146157
} catch (err) {
147-
return next(err)
148-
}
149-
150-
request.onsuccess = () => {
151-
const value = request.result
152-
next(null, value === undefined ? value : deserialize(value))
158+
abort = true
159+
throw err
153160
}
161+
}
154162

155-
request.onerror = (ev) => {
156-
ev.stopPropagation()
157-
next(request.error)
158-
}
159-
})
163+
for (let i = 0; i < n; i++) {
164+
bees[i] = bee()
165+
}
160166

161-
parallel(tasks, 16, callback)
167+
await Promise.allSettled(bees)
168+
return values
162169
}
163170

164-
_del (key, options, callback) {
171+
async _del (key, options) {
165172
const store = this[kStore]('readwrite')
166-
let req
167-
168-
try {
169-
req = store.delete(key)
170-
} catch (err) {
171-
return this.nextTick(callback, err)
172-
}
173+
const request = store.delete(key)
173174

174-
this[kOnComplete](req, callback)
175+
return this[kOnComplete](request)
175176
}
176177

177-
_put (key, value, options, callback) {
178+
async _put (key, value, options) {
178179
const store = this[kStore]('readwrite')
179-
let req
180180

181-
try {
182-
// Will throw a DataError or DataCloneError if the environment
183-
// does not support serializing the key or value respectively.
184-
req = store.put(value, key)
185-
} catch (err) {
186-
return this.nextTick(callback, err)
187-
}
181+
// Will throw a DataError or DataCloneError if the environment
182+
// does not support serializing the key or value respectively.
183+
const request = store.put(value, key)
188184

189-
this[kOnComplete](req, callback)
185+
return this[kOnComplete](request)
190186
}
191187

192188
// TODO: implement key and value iterators
193189
_iterator (options) {
194190
return new Iterator(this, this[kLocation], options)
195191
}
196192

197-
_batch (operations, options, callback) {
193+
async _batch (operations, options) {
198194
const store = this[kStore]('readwrite')
199195
const transaction = store.transaction
200196
let index = 0
201197
let error
202198

203-
transaction.onabort = function () {
204-
callback(error || transaction.error || new Error('aborted by user'))
205-
}
199+
const promise = new Promise(function (resolve, reject) {
200+
transaction.onabort = function () {
201+
reject(error || transaction.error || new Error('aborted by user'))
202+
}
206203

207-
transaction.oncomplete = function () {
208-
callback()
209-
}
204+
transaction.oncomplete = resolve
205+
})
210206

211207
// Wait for a request to complete before making the next, saving CPU.
212208
function loop () {
@@ -232,60 +228,48 @@ class BrowserLevel extends AbstractLevel {
232228
}
233229

234230
loop()
231+
return promise
235232
}
236233

237-
_clear (options, callback) {
234+
async _clear (options) {
238235
let keyRange
239-
let req
240236

241237
try {
242238
keyRange = createKeyRange(options)
243239
} catch (e) {
244240
// The lower key is greater than the upper key.
245241
// IndexedDB throws an error, but we'll just do nothing.
246-
return this.nextTick(callback)
242+
return
247243
}
248244

249245
if (options.limit >= 0) {
250246
// IDBObjectStore#delete(range) doesn't have such an option.
251247
// Fall back to cursor-based implementation.
252-
return clear(this, this[kLocation], keyRange, options, callback)
248+
return clear(this, this[kLocation], keyRange, options)
253249
}
254250

255-
try {
256-
const store = this[kStore]('readwrite')
257-
req = keyRange ? store.delete(keyRange) : store.clear()
258-
} catch (err) {
259-
return this.nextTick(callback, err)
260-
}
251+
const store = this[kStore]('readwrite')
252+
const request = keyRange ? store.delete(keyRange) : store.clear()
261253

262-
this[kOnComplete](req, callback)
254+
return this[kOnComplete](request)
263255
}
264256

265-
_close (callback) {
257+
async _close () {
266258
this[kIDB].close()
267-
this.nextTick(callback)
268259
}
269260
}
270261

271-
BrowserLevel.destroy = function (location, prefix, callback) {
272-
if (typeof prefix === 'function') {
273-
callback = prefix
262+
BrowserLevel.destroy = async function (location, prefix) {
263+
if (prefix == null) {
274264
prefix = DEFAULT_PREFIX
275265
}
276266

277-
callback = fromCallback(callback, kPromise)
278267
const request = indexedDB.deleteDatabase(prefix + location)
279268

280-
request.onsuccess = function () {
281-
callback()
282-
}
283-
284-
request.onerror = function (err) {
285-
callback(err)
286-
}
287-
288-
return callback[kPromise]
269+
return new Promise(function (resolve, reject) {
270+
request.onsuccess = resolve
271+
request.onerror = reject
272+
})
289273
}
290274

291275
exports.BrowserLevel = BrowserLevel

0 commit comments

Comments
 (0)