Add comprehensive installation and setup documentation

- Add GETTING_STARTED.md with quick start guide and development modes
- Add INSTALL.sh automated installation script
- Add INSTALLATION_CHECKLIST.md, INSTALLATION_SUCCESS.md, and INSTALLATION_SUMMARY.md
- Add QUICK_REFERENCE.md for common commands
- Add SETUP_GUIDE.md with detailed setup instructions
- Update README.md with improved project overview
- Add did-wallet app dependencies and node_modules
This commit is contained in:
Dorian
2026-01-27 17:18:21 +00:00
parent a81f655133
commit 0d073fa89e
22658 changed files with 4494151 additions and 6 deletions
+15
View File
@@ -0,0 +1,15 @@
# Changelog
## [1.0.1] - 2022-03-06
### Fixed
- Fix TypeScript type declarations ([`d5db420`](https://github.com/Level/browser-level/commit/d5db420)) (Vincent Weevers)
## [1.0.0] - 2022-03-05
_:seedling: Initial release. If you are upgrading from `level-js`, please see [UPGRADING.md](UPGRADING.md)._
[1.0.1]: https://github.com/Level/browser-level/releases/tag/v1.0.1
[1.0.0]: https://github.com/Level/browser-level/releases/tag/v1.0.0
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2012 Max Ogden and the contributors to browser-level.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+149
View File
@@ -0,0 +1,149 @@
# browser-level
**An [`abstract-level`][abstract-level] database for browsers, backed by [IndexedDB][indexeddb].** The successor to [`level-js`](https://github.com/Level/level-js). If you are upgrading, please see [UPGRADING.md](UPGRADING.md).
> :pushpin: Which module should I use? What is `abstract-level`? Head over to the [FAQ](https://github.com/Level/community#faq).
[![level badge][level-badge]][awesome]
[![npm](https://img.shields.io/npm/v/browser-level.svg)](https://www.npmjs.com/package/browser-level)
[![Test](https://img.shields.io/github/workflow/status/Level/browser-level/Test?label=test)](https://github.com/Level/browser-level/actions/workflows/test.yml)
[![Coverage](https://img.shields.io/codecov/c/github/Level/browser-level?label=\&logo=codecov\&logoColor=fff)](https://codecov.io/gh/Level/browser-level)
[![Standard](https://img.shields.io/badge/standard-informational?logo=javascript\&logoColor=fff)](https://standardjs.com)
[![Common Changelog](https://common-changelog.org/badge.svg)](https://common-changelog.org)
[![Donate](https://img.shields.io/badge/donate-orange?logo=open-collective\&logoColor=fff)](https://opencollective.com/level)
## Table of Contents
<details><summary>Click to expand</summary>
- [Usage](#usage)
- [API](#api)
- [`db = new BrowserLevel(location[, options])`](#db--new-browserlevellocation-options)
- [`BrowserLevel.destroy(location[, prefix][, callback])`](#browserleveldestroylocation-prefix-callback)
- [Install](#install)
- [Contributing](#contributing)
- [Donate](#donate)
- [License](#license)
</details>
## Usage
```js
const { BrowserLevel } = require('browser-level')
// Create a database called 'example'
const db = new BrowserLevel('example', { valueEncoding: 'json' })
// Add an entry with key 'a' and value 1
await db.put('a', 1)
// Add multiple entries
await db.batch([{ type: 'put', key: 'b', value: 2 }])
// Get value of key 'a': 1
const value = await db.get('a')
// Iterate entries with keys that are greater than 'a'
for await (const [key, value] of db.iterator({ gt: 'a' })) {
console.log(value) // 2
}
```
<!-- ## Browser Support -->
<!-- [![Sauce Test Status](https://app.saucelabs.com/browser-matrix/level-js.svg)](https://app.saucelabs.com/u/level-js) -->
## API
The API of `browser-level` follows that of [`abstract-level`](https://github.com/Level/abstract-level) with just two additional constructor options (see below) and one additional method (see below). As such, the majority of the API is documented in `abstract-level`. The `createIfMissing` and `errorIfExists` options of `abstract-level` are not supported here.
Like other implementations of `abstract-level`, `browser-level` has first-class support of binary keys and values, using either [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) or [Buffer](https://nodejs.org/api/buffer.html). In order to sort string and binary keys the same way as other databases, `browser-level` internally converts data to a Uint8Array before passing them to IndexedDB. If you have no need to work with `Buffer` keys or values, you can choose to omit the [`buffer`](https://github.com/feross/buffer) shim from a JavaScript bundle (through configuration of Webpack, Browserify or other bundlers).
Due to limitations of IndexedDB, `browser-level` does not offer snapshot guarantees. Such a guarantee would mean that an iterator does not see the data of simultaneous writes - it would be reading from a snapshot in time. In contrast, a `browser-level` iterator reads a few entries ahead and then opens a new IndexedDB transaction on the next read. A "few" means all entries for `iterator.all()`, `size` amount of entries for `iterator.nextv(size)` and a hardcoded 100 entries for `iterator.next()`. Individual calls to those methods have snapshot guarantees but repeated calls do not.
The result is that an iterator will include the data of simultaneous writes, if `db.put()`, `db.del()` or `db.batch()` are called in between creating the iterator and consuming the iterator, or in between calls to `iterator.next()` or `iterator.nextv()`. For example:
```js
const iterator = db.iterator()
await db.put('abc', '123')
for await (const [key, value] of iterator) {
// This might be 'abc'
console.log(key)
}
```
If snapshot guarantees are a must for your application then use `iterator.all()` and call it immediately after creating the iterator:
```js
const entries = await db.iterator({ limit: 50 }).all()
// Synchronously iterate the result
for (const [key, value] of entries) {
console.log(key)
}
```
### `db = new BrowserLevel(location[, options])`
Create a new database or open an existing one. The required `location` argument is the string name of the [`IDBDatabase`](https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase) to be opened, as well as the name of the object store within that database. The name of the `IDBDatabase` will be prefixed with `options.prefix`.
Besides `abstract-level` options, the optional `options` argument may contain:
- `prefix` (string, default: `'level-js-'`): Prefix for the `IDBDatabase` name. Can be set to an empty string. The default is compatible with `level-js`.
- `version` (string or number, default: `1`): The version to open the `IDBDatabase` with.
See [`IDBFactory#open()`](https://developer.mozilla.org/en-US/docs/Web/API/IDBFactory/open) for more details about database name and version.
### `BrowserLevel.destroy(location[, prefix][, callback])`
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.
Before calling `destroy()`, close a database if it's using the same `location` and `prefix`:
```js
const db = new BrowserLevel('example')
await db.close()
await BrowserLevel.destroy('example')
```
## Install
With [npm](https://npmjs.org) do:
```bash
npm install browser-level
```
This module is best used with [`browserify`](http://browserify.org) or similar bundlers.
<!-- ## Big Thanks
Cross-browser Testing Platform and Open Source ♥ Provided by [Sauce Labs](https://saucelabs.com).
[![Sauce Labs logo](./sauce-labs.svg)](https://saucelabs.com) -->
## Contributing
[`Level/browser-level`](https://github.com/Level/browser-level) is an **OPEN Open Source Project**. This means that:
> 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.
See the [Contribution Guide](https://github.com/Level/community/blob/master/CONTRIBUTING.md) for more details.
## Donate
Support us with a monthly donation on [Open Collective](https://opencollective.com/level) and help us continue our work.
## License
[MIT](LICENSE)
[level-badge]: https://leveljs.org/img/badge.svg
[indexeddb]: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
[awesome]: https://github.com/Level/awesome
[abstract-level]: https://github.com/Level/abstract-level
+135
View File
@@ -0,0 +1,135 @@
# Upgrade Guide
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).
## 1.0.0
**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.**
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 replace `level-js` with `browser-level`. There will be a separate guide for `level`.
### What is not covered here
If you are using any of the following, please also read the upgrade guide of [`abstract-level@1`](https://github.com/Level/abstract-level/blob/main/UPGRADING.md#100) which goes into more detail about these:
- Specific error messages (replaced with error codes)
- The `db.iterator().end()` method (renamed to `close()`, with `end()` as an alias)
- Zero-length keys and range options (now valid)
- The `db.supports.bufferKeys` property.
### Changes to initialization
We started using classes, which means using `new` is now required. If you previously did:
```js
const levelJs = require('level-js')
const db = levelJs('example')
```
You must now do:
```js
const { BrowserLevel } = require('browser-level')
const db = new BrowserLevel('example')
```
Arguments and options are the same except that `browser-level` has additional options to control encodings. For backwards compatibility, the `prefix` option has the same default value. Namely `'level-js-'`.
### There is only encodings
The `asBuffer`, `valueAsBuffer` and `keyAsBuffer` options have been replaced with encoding options. The default encoding is `'utf8'` which means operations return strings rather than Buffers by default. If you previously did:
```js
db.get('example', { asBuffer: false }, callback)
db.get('example', callback)
```
You must now do:
```js
db.get('example', callback)
db.get('example', { valueEncoding: 'buffer' }, callback)
```
Or using promises (new):
```js
const str = await db.get('example')
const buf = await db.get('example', { valueEncoding: 'buffer' })
```
Or using Uint8Array (new):
```js
const arr = await db.get('example', { valueEncoding: 'view' })
```
### Uint8Array support
A `browser-level` database uses Uint8Array internally, instead of Buffer like `level-js` did. This doesn't change the ability to read existing databases: `browser-level` can read and write databases that were created with `level-js` and vice versa. Externally both Uint8Array and Buffer can be used (see README for details) to maintain backwards- and ecosystem compatibility. You can choose to use Uint8Array exclusively and omit the `buffer` shim from a JavaScript bundle (through configuration of Webpack, Browserify or other bundlers).
### Unwrapping the onion
If you were wrapping `level-js` with `levelup`, `encoding-down` and / or `subleveldown`, remove those modules. If you previously did:
```js
const levelJs = require('level-js')
const levelup = require('levelup')
const enc = require('encoding-down')
const subleveldown = require('subleveldown')
const db = levelup(enc(levelJs('example')))
const sublevel = subleveldown(db, 'foo')
```
You must now do:
```js
const { BrowserLevel } = require('browser-level')
const db = new BrowserLevel('example')
const sublevel = db.sublevel('foo')
```
### Prefers backpressure over snapshot guarantees
Previously (in `level-js`) an iterator would keep reading in the background, so as to keep the underlying IndexedDB transaction alive and thus not see the data of simultaneous writes. I.e. it was reading from a snapshot in time. This had two major downsides. It was not possible to lazily iterate a large database, as all of the data would be read into memory. Secondly, IndexedDB doesn't actually use a snapshot (the Chrome implementation used to, others never did) but rather a blocking transaction. Meaning you couldn't write to a database while an iterator was active; the write would wait for the iterator to end.
To solve those issues, an iterator now reads a few entries ahead and then opens a new transaction on the next read. A "few" means all entries for `iterator.all()`, `size` amount of entries for `iterator.nextv(size)` and a hardcoded 100 entries for `iterator.next()`. Individual calls to those methods still have snapshot guarantees, but repeated calls do not.
The resulting breaking change is that an iterator will include the data of simultaneous writes, if `db.put()`, `db.del()` or `db.batch()` are called in between creating the iterator and consuming the iterator, or in between calls to `iterator.next()` or `iterator.nextv()`. For example:
```js
const iterator = db.iterator()
await db.put('abc', '123')
for await (const [key, value] of iterator) {
// This might be 'abc'
console.log(key)
}
```
To reflect the new behavior, `db.supports.snapshots` is now false. If snapshot guarantees are a must for your application then use `iterator.all()` (which is a new method compared to `level-js`) and call it immediately after creating the iterator:
```js
const entries = await db.iterator({ limit: 50 }).all()
// Synchronously iterate the result
for (const [key, value] of entries) {
console.log(key)
}
```
Unrelated, iterating should now be faster because `browser-level` iterators use the `getAll()` and `getAllKeys()` methods of IndexedDB, instead of a cursor like `level-js` did. This means multiple entries are transferred from IndexedDB to JS in a single turn of the JS event loop, rather than one turn per entry. Reverse iterators do still use a cursor and are therefor slower.
Lastly, the new logic enabled us to implement and fully support `iterator.seek()`.
### Changes to lesser-used properties and methods
- The `db.prefix` property of `level-js`, conflicting with the `db.prefix` property of sublevels, has been renamed to `db.namePrefix`. The relevant constructor option (`prefix`) remains the same.
- The `db.location`, `db.version` and `db.db` properties are now read-only getters
- The internal `db.store()` and `db.await()` methods are no longer accessible
- The `db.upgrade()` utility for upgrading from v4.x to v5.x has been removed.
---
_For earlier releases, before `browser-level` was forked from `level-js` (v6.1.0), please see [the upgrade guide of `level-js`](https://github.com/Level/level-js/blob/HEAD/UPGRADING.md)._
+111
View File
@@ -0,0 +1,111 @@
import {
AbstractLevel,
AbstractDatabaseOptions,
NodeCallback,
AbstractOpenOptions,
AbstractGetOptions,
AbstractGetManyOptions,
AbstractPutOptions,
AbstractDelOptions,
AbstractBatchOptions,
AbstractChainedBatch,
AbstractIterator,
AbstractKeyIterator,
AbstractValueIterator,
AbstractIteratorOptions,
AbstractKeyIteratorOptions,
AbstractValueIteratorOptions,
AbstractBatchOperation
} from 'abstract-level'
/**
* An {@link AbstractLevel} database for browsers, backed by [IndexedDB][1].
*
* @template KDefault The default type of keys if not overridden on operations.
* @template VDefault The default type of values if not overridden on operations.
*
* [1]: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
*/
export class BrowserLevel<KDefault = string, VDefault = string>
extends AbstractLevel<Uint8Array, KDefault, VDefault> {
/**
* Database constructor.
*
* @param location The name of the [`IDBDatabase`](https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase)
* to be opened, as well as the name of the object store within that database. The name
* of the `IDBDatabase` will be prefixed with {@link DatabaseOptions.prefix}.
* @param options Options, of which some will be forwarded to {@link open}.
*/
constructor (location: string, options?: DatabaseOptions<KDefault, VDefault> | undefined)
/**
* Location that was passed to the constructor.
*/
get location (): string
/**
* Database name prefix that was passed to the constructor (as `prefix`).
*/
get namePrefix (): string
/**
* Version that was passed to the constructor.
*/
get version (): number
/**
* Delete the IndexedDB database at the given {@link location}.
*/
static destroy (location: string): Promise<void>
static destroy (location: string, prefix: string): Promise<void>
static destroy (location: string, callback: NodeCallback<void>): void
static destroy (location: string, prefix: string, callback: NodeCallback<void>): void
}
/**
* Options for the {@link BrowserLevel} constructor.
*/
export interface DatabaseOptions<K, V> extends AbstractDatabaseOptions<K, V> {
/**
* Prefix for the `IDBDatabase` name. Can be set to an empty string.
*
* @defaultValue `'level-js-'`
*/
prefix?: string
/**
* The version to open the `IDBDatabase` with.
*
* @defaultValue `1`
*/
version?: number | string
/**
* An {@link AbstractLevel} option that has no effect on {@link BrowserLevel}.
*/
createIfMissing?: boolean
/**
* An {@link AbstractLevel} option that has no effect on {@link BrowserLevel}.
*/
errorIfExists?: boolean
}
// Export types so that consumers don't have to guess whether they're extended
export type OpenOptions = AbstractOpenOptions
export type GetOptions<K, V> = AbstractGetOptions<K, V>
export type GetManyOptions<K, V> = AbstractGetManyOptions<K, V>
export type PutOptions<K, V> = AbstractPutOptions<K, V>
export type DelOptions<K> = AbstractDelOptions<K>
export type BatchOptions<K, V> = AbstractBatchOptions<K, V>
export type BatchOperation<TDatabase, K, V> = AbstractBatchOperation<TDatabase, K, V>
export type ChainedBatch<TDatabase, K, V> = AbstractChainedBatch<TDatabase, K, V>
export type Iterator<TDatabase, K, V> = AbstractIterator<TDatabase, K, V>
export type KeyIterator<TDatabase, K> = AbstractKeyIterator<TDatabase, K>
export type ValueIterator<TDatabase, K, V> = AbstractValueIterator<TDatabase, K, V>
export type IteratorOptions<K, V> = AbstractIteratorOptions<K, V>
export type KeyIteratorOptions<K> = AbstractKeyIteratorOptions<K>
export type ValueIteratorOptions<K, V> = AbstractValueIteratorOptions<K, V>
+291
View File
@@ -0,0 +1,291 @@
/* global indexedDB */
'use strict'
const { AbstractLevel } = require('abstract-level')
const ModuleError = require('module-error')
const parallel = require('run-parallel-limit')
const { fromCallback } = require('catering')
const { Iterator } = require('./iterator')
const deserialize = require('./util/deserialize')
const clear = require('./util/clear')
const createKeyRange = require('./util/key-range')
// Keep as-is for compatibility with existing level-js databases
const DEFAULT_PREFIX = 'level-js-'
const kIDB = Symbol('idb')
const kNamePrefix = Symbol('namePrefix')
const kLocation = Symbol('location')
const kVersion = Symbol('version')
const kStore = Symbol('store')
const kOnComplete = Symbol('onComplete')
const kPromise = Symbol('promise')
class BrowserLevel extends AbstractLevel {
constructor (location, options, _) {
// To help migrating to abstract-level
if (typeof options === 'function' || typeof _ === 'function') {
throw new ModuleError('The levelup-style callback argument has been removed', {
code: 'LEVEL_LEGACY'
})
}
const { prefix, version, ...forward } = options || {}
super({
encodings: { view: true },
snapshots: false,
createIfMissing: false,
errorIfExists: false,
seek: true
}, forward)
if (typeof location !== 'string') {
throw new Error('constructor requires a location string argument')
}
// TODO (next major): remove default prefix
this[kLocation] = location
this[kNamePrefix] = prefix == null ? DEFAULT_PREFIX : prefix
this[kVersion] = parseInt(version || 1, 10)
this[kIDB] = null
}
get location () {
return this[kLocation]
}
get namePrefix () {
return this[kNamePrefix]
}
get version () {
return this[kVersion]
}
// Exposed for backwards compat and unit tests
get db () {
return this[kIDB]
}
get type () {
return 'browser-level'
}
_open (options, callback) {
const req = indexedDB.open(this[kNamePrefix] + this[kLocation], this[kVersion])
req.onerror = function () {
callback(req.error || new Error('unknown error'))
}
req.onsuccess = () => {
this[kIDB] = req.result
callback()
}
req.onupgradeneeded = (ev) => {
const db = ev.target.result
if (!db.objectStoreNames.contains(this[kLocation])) {
db.createObjectStore(this[kLocation])
}
}
}
[kStore] (mode) {
const transaction = this[kIDB].transaction([this[kLocation]], mode)
return transaction.objectStore(this[kLocation])
}
[kOnComplete] (request, callback) {
const transaction = request.transaction
// Take advantage of the fact that a non-canceled request error aborts
// the transaction. I.e. no need to listen for "request.onerror".
transaction.onabort = function () {
callback(transaction.error || new Error('aborted by user'))
}
transaction.oncomplete = function () {
callback(null, request.result)
}
}
_get (key, options, callback) {
const store = this[kStore]('readonly')
let req
try {
req = store.get(key)
} catch (err) {
return this.nextTick(callback, err)
}
this[kOnComplete](req, function (err, value) {
if (err) return callback(err)
if (value === undefined) {
return callback(new ModuleError('Entry not found', {
code: 'LEVEL_NOT_FOUND'
}))
}
callback(null, deserialize(value))
})
}
_getMany (keys, options, callback) {
const store = this[kStore]('readonly')
const tasks = keys.map((key) => (next) => {
let request
try {
request = store.get(key)
} catch (err) {
return next(err)
}
request.onsuccess = () => {
const value = request.result
next(null, value === undefined ? value : deserialize(value))
}
request.onerror = (ev) => {
ev.stopPropagation()
next(request.error)
}
})
parallel(tasks, 16, callback)
}
_del (key, options, callback) {
const store = this[kStore]('readwrite')
let req
try {
req = store.delete(key)
} catch (err) {
return this.nextTick(callback, err)
}
this[kOnComplete](req, callback)
}
_put (key, value, options, callback) {
const store = this[kStore]('readwrite')
let req
try {
// Will throw a DataError or DataCloneError if the environment
// does not support serializing the key or value respectively.
req = store.put(value, key)
} catch (err) {
return this.nextTick(callback, err)
}
this[kOnComplete](req, callback)
}
// TODO: implement key and value iterators
_iterator (options) {
return new Iterator(this, this[kLocation], options)
}
_batch (operations, options, callback) {
const store = this[kStore]('readwrite')
const transaction = store.transaction
let index = 0
let error
transaction.onabort = function () {
callback(error || transaction.error || new Error('aborted by user'))
}
transaction.oncomplete = function () {
callback()
}
// Wait for a request to complete before making the next, saving CPU.
function loop () {
const op = operations[index++]
const key = op.key
let req
try {
req = op.type === 'del' ? store.delete(key) : store.put(op.value, key)
} catch (err) {
error = err
transaction.abort()
return
}
if (index < operations.length) {
req.onsuccess = loop
} else if (typeof transaction.commit === 'function') {
// Commit now instead of waiting for auto-commit
transaction.commit()
}
}
loop()
}
_clear (options, callback) {
let keyRange
let req
try {
keyRange = createKeyRange(options)
} catch (e) {
// The lower key is greater than the upper key.
// IndexedDB throws an error, but we'll just do nothing.
return this.nextTick(callback)
}
if (options.limit >= 0) {
// IDBObjectStore#delete(range) doesn't have such an option.
// Fall back to cursor-based implementation.
return clear(this, this[kLocation], keyRange, options, callback)
}
try {
const store = this[kStore]('readwrite')
req = keyRange ? store.delete(keyRange) : store.clear()
} catch (err) {
return this.nextTick(callback, err)
}
this[kOnComplete](req, callback)
}
_close (callback) {
this[kIDB].close()
this.nextTick(callback)
}
}
BrowserLevel.destroy = function (location, prefix, callback) {
if (typeof prefix === 'function') {
callback = prefix
prefix = DEFAULT_PREFIX
}
callback = fromCallback(callback, kPromise)
const request = indexedDB.deleteDatabase(prefix + location)
request.onsuccess = function () {
callback()
}
request.onerror = function (err) {
callback(err)
}
return callback[kPromise]
}
exports.BrowserLevel = BrowserLevel
+238
View File
@@ -0,0 +1,238 @@
'use strict'
const { AbstractIterator } = require('abstract-level')
const createKeyRange = require('./util/key-range')
const deserialize = require('./util/deserialize')
const kCache = Symbol('cache')
const kFinished = Symbol('finished')
const kOptions = Symbol('options')
const kCurrentOptions = Symbol('currentOptions')
const kPosition = Symbol('position')
const kLocation = Symbol('location')
const kFirst = Symbol('first')
const emptyOptions = {}
class Iterator extends AbstractIterator {
constructor (db, location, options) {
super(db, options)
this[kCache] = []
this[kFinished] = this.limit === 0
this[kOptions] = options
this[kCurrentOptions] = { ...options }
this[kPosition] = undefined
this[kLocation] = location
this[kFirst] = true
}
// Note: if called by _all() then size can be Infinity. This is an internal
// detail; by design AbstractIterator.nextv() does not support Infinity.
_nextv (size, options, callback) {
this[kFirst] = false
if (this[kFinished]) {
return this.nextTick(callback, null, [])
} else if (this[kCache].length > 0) {
// TODO: mixing next and nextv is not covered by test suite
size = Math.min(size, this[kCache].length)
return this.nextTick(callback, null, this[kCache].splice(0, size))
}
// Adjust range by what we already visited
if (this[kPosition] !== undefined) {
if (this[kOptions].reverse) {
this[kCurrentOptions].lt = this[kPosition]
this[kCurrentOptions].lte = undefined
} else {
this[kCurrentOptions].gt = this[kPosition]
this[kCurrentOptions].gte = undefined
}
}
let keyRange
try {
keyRange = createKeyRange(this[kCurrentOptions])
} catch (_) {
// The lower key is greater than the upper key.
// IndexedDB throws an error, but we'll just return 0 results.
this[kFinished] = true
return this.nextTick(callback, null, [])
}
const transaction = this.db.db.transaction([this[kLocation]], 'readonly')
const store = transaction.objectStore(this[kLocation])
const entries = []
if (!this[kOptions].reverse) {
let keys
let values
const complete = () => {
// Wait for both requests to complete
if (keys === undefined || values === undefined) return
const length = Math.max(keys.length, values.length)
if (length === 0 || size === Infinity) {
this[kFinished] = true
} else {
this[kPosition] = keys[length - 1]
}
// Resize
entries.length = length
// Merge keys and values
for (let i = 0; i < length; i++) {
const key = keys[i]
const value = values[i]
entries[i] = [
this[kOptions].keys && key !== undefined ? deserialize(key) : undefined,
this[kOptions].values && value !== undefined ? deserialize(value) : undefined
]
}
maybeCommit(transaction)
}
// If keys were not requested and size is Infinity, we don't have to keep
// track of position and can thus skip getting keys.
if (this[kOptions].keys || size < Infinity) {
store.getAllKeys(keyRange, size < Infinity ? size : undefined).onsuccess = (ev) => {
keys = ev.target.result
complete()
}
} else {
keys = []
this.nextTick(complete)
}
if (this[kOptions].values) {
store.getAll(keyRange, size < Infinity ? size : undefined).onsuccess = (ev) => {
values = ev.target.result
complete()
}
} else {
values = []
this.nextTick(complete)
}
} else {
// Can't use getAll() in reverse, so use a slower cursor that yields one item at a time
// TODO: test if all target browsers support openKeyCursor
const method = !this[kOptions].values && store.openKeyCursor ? 'openKeyCursor' : 'openCursor'
store[method](keyRange, 'prev').onsuccess = (ev) => {
const cursor = ev.target.result
if (cursor) {
const { key, value } = cursor
this[kPosition] = key
entries.push([
this[kOptions].keys && key !== undefined ? deserialize(key) : undefined,
this[kOptions].values && value !== undefined ? deserialize(value) : undefined
])
if (entries.length < size) {
cursor.continue()
} else {
maybeCommit(transaction)
}
} else {
this[kFinished] = true
}
}
}
// If an error occurs (on the request), the transaction will abort.
transaction.onabort = () => {
callback(transaction.error || new Error('aborted by user'))
callback = null
}
transaction.oncomplete = () => {
callback(null, entries)
callback = null
}
}
_next (callback) {
if (this[kCache].length > 0) {
const [key, value] = this[kCache].shift()
this.nextTick(callback, null, key, value)
} else if (this[kFinished]) {
this.nextTick(callback)
} else {
let size = Math.min(100, this.limit - this.count)
if (this[kFirst]) {
// It's common to only want one entry initially or after a seek()
this[kFirst] = false
size = 1
}
this._nextv(size, emptyOptions, (err, entries) => {
if (err) return callback(err)
this[kCache] = entries
this._next(callback)
})
}
}
_all (options, callback) {
this[kFirst] = false
// TODO: mixing next and all is not covered by test suite
const cache = this[kCache].splice(0, this[kCache].length)
const size = this.limit - this.count - cache.length
if (size <= 0) {
return this.nextTick(callback, null, cache)
}
this._nextv(size, emptyOptions, (err, entries) => {
if (err) return callback(err)
if (cache.length > 0) entries = cache.concat(entries)
callback(null, entries)
})
}
_seek (target, options) {
this[kFirst] = true
this[kCache] = []
this[kFinished] = false
this[kPosition] = undefined
// TODO: not covered by test suite
this[kCurrentOptions] = { ...this[kOptions] }
let keyRange
try {
keyRange = createKeyRange(this[kOptions])
} catch (_) {
this[kFinished] = true
return
}
if (keyRange !== null && !keyRange.includes(target)) {
this[kFinished] = true
} else if (this[kOptions].reverse) {
this[kCurrentOptions].lte = target
} else {
this[kCurrentOptions].gte = target
}
}
}
exports.Iterator = Iterator
function maybeCommit (transaction) {
// Commit (meaning close) now instead of waiting for auto-commit
if (typeof transaction.commit === 'function') {
transaction.commit()
}
}
+56
View File
@@ -0,0 +1,56 @@
{
"name": "browser-level",
"version": "1.0.1",
"description": "An abstract-level database for browsers, backed by IndexedDB",
"author": "max ogden",
"license": "MIT",
"main": "index.js",
"types": "./index.d.ts",
"scripts": {
"test": "standard && ts-standard *.ts && hallmark && airtap -p local --coverage test/index.js && nyc report",
"test-browsers": "standard && airtap --coverage test/index.js",
"coverage": "nyc report -r lcovonly",
"dependency-check": "dependency-check --no-dev .",
"prepublishOnly": "npm run dependency-check"
},
"files": [
"index.js",
"index.d.ts",
"iterator.js",
"util",
"CHANGELOG.md",
"UPGRADING.md",
"sauce-labs.svg"
],
"dependencies": {
"abstract-level": "^1.0.2",
"catering": "^2.1.1",
"module-error": "^1.0.2",
"run-parallel-limit": "^1.1.0"
},
"devDependencies": {
"@voxpelli/tsconfig": "^3.1.0",
"airtap": "^4.0.4",
"airtap-playwright": "^1.0.1",
"airtap-sauce": "^1.1.0",
"dependency-check": "^4.1.0",
"hallmark": "^4.1.0",
"nyc": "^15.0.0",
"standard": "^16.0.4",
"tape": "^5.5.2",
"ts-standard": "^11.0.0",
"typescript": "^4.5.5",
"uuid": "^3.3.2"
},
"repository": {
"type": "git",
"url": "https://github.com/Level/browser-level.git"
},
"homepage": "https://github.com/Level/browser-level",
"keywords": [
"level",
"leveldb",
"indexeddb",
"abstract-level"
]
}
+81
View File
@@ -0,0 +1,81 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="460"
height="64"
version="1.1"
viewBox="0 0 144.37044 20.09272">
<defs>
<style>
.cls-1{isolation:isolate;}
.cls-2{opacity:0.15;mix-blend-mode:multiply;}
.cls-3{fill:#fff;}
.cls-4{fill:#e1251b;}
.cls-5{fill:#474c55;}
</style>
</defs>
<title>Sauce Labs</title>
<g
transform="translate(-0.87516672,-1.0999856)"
style="isolation:isolate"
id="g3807"
class="cls-1">
<g
id="white">
<path
style="fill:#e1251b"
id="path3774"
d="M 3.33,14.05 A 8,8 0 0 1 2.79,11.13 8.17,8.17 0 0 1 11,3 8.77,8.77 0 0 1 12.15,3.08 l -0.92,0.93 h -0.25 a 7.15,7.15 0 0 0 -6.88,9.1 h 6.26 L 7.7,18 14,11.62 H 5.4 L 15.09,2 A 9.94,9.94 0 0 0 10.94,1.1 10.07,10.07 0 0 0 5.69,19.76 l 3,-5.67 z"
class="cls-4" />
<path
style="fill:#e1251b"
id="path3776"
d="m 16.19,2.54 -3,5.67 h 5.37 A 8.15,8.15 0 0 1 10.95,19.28 9.39,9.39 0 0 1 9.76,19.2 l 0.91,-0.92 h 0.28 A 7.15,7.15 0 0 0 18.1,11.13 7.83,7.83 0 0 0 17.83,9.19 h -6.26 l 2.61,-4.93 -6.42,6.39 h 8.62 L 6.8,20.31 A 10.07,10.07 0 0 0 16.19,2.54 Z"
class="cls-4" />
<path
style="fill:#474c55"
id="path3778"
d="m 26,17.35 0.86,-1.27 a 0.44,0.44 0 0 1 0.69,-0.09 4.09,4.09 0 0 0 2.76,1.16 1.94,1.94 0 0 0 2,-1.89 c 0,-1 -0.66,-1.62 -1.91,-2.44 -1.46,-1 -3.2,-2.22 -3.2,-4.44 0,-2.22 1.54,-4.62 5,-4.62 a 6.11,6.11 0 0 1 4,1.44 0.63,0.63 0 0 1 0.06,0.89 L 35.4,7.31 C 35.22,7.58 34.93,7.6 34.62,7.36 a 3.67,3.67 0 0 0 -2.51,-1 1.82,1.82 0 0 0 -1.89,1.73 c 0,0.89 0.6,1.33 1.93,2.22 1.33,0.89 3.42,2.2 3.42,4.64 0,2.6 -2,4.78 -5.3,4.78 A 6.21,6.21 0 0 1 26,18 c -0.14,-0.12 -0.25,-0.32 0,-0.65 z"
class="cls-5" />
<path
style="fill:#474c55"
id="path3780"
d="M 36.92,19 45.27,4 a 0.42,0.42 0 0 1 0.38,-0.24 h 0.2 A 0.32,0.32 0 0 1 46.16,4 l 4.09,15 a 0.44,0.44 0 0 1 -0.42,0.58 H 47.9 c -0.34,0 -0.52,-0.12 -0.58,-0.45 l -0.56,-2.46 h -5.53 l -1.29,2.46 a 0.79,0.79 0 0 1 -0.71,0.45 h -2 C 36.88,19.53 36.77,19.24 36.92,19 Z m 9.13,-4.73 -1,-4.84 v 0 l -2.42,4.84 z"
class="cls-5" />
<path
style="fill:#474c55"
id="path3782"
d="M 54.51,4.4 A 0.51,0.51 0 0 1 55,4 h 2.2 a 0.35,0.35 0 0 1 0.33,0.42 l -1.33,9.35 a 3.54,3.54 0 0 0 0,0.71 2.09,2.09 0 0 0 2.27,2.36 c 1.82,0 2.84,-1.18 3.11,-3 l 1.33,-9.37 a 0.47,0.47 0 0 1 0.44,-0.42 h 2.2 a 0.37,0.37 0 0 1 0.34,0.42 l -1.36,9.51 c -0.47,3.37 -2.82,5.84 -6.28,5.84 a 4.81,4.81 0 0 1 -5.07,-5 6.94,6.94 0 0 1 0.07,-0.89 z"
class="cls-5" />
<path
style="fill:#474c55"
id="path3784"
d="m 76.5,3.76 a 6,6 0 0 1 4.62,2.11 0.46,0.46 0 0 1 -0.09,0.62 l -1.2,1.15 a 0.49,0.49 0 0 1 -0.73,0 4,4 0 0 0 -2.75,-1.11 c -2.67,0 -5,2.76 -5,5.93 0,2.36 1.27,4.38 3.4,4.38 a 4.6,4.6 0 0 0 3,-1.31 c 0.29,-0.25 0.53,-0.2 0.71,0 L 79.68,17 a 0.53,0.53 0 0 1 -0.11,0.6 7.11,7.11 0 0 1 -5,2.16 c -3.84,0 -6.33,-3.09 -6.33,-6.91 C 68.22,7.62 71.9,3.76 76.5,3.76 Z"
class="cls-5" />
<path
style="fill:#474c55"
id="path3786"
d="m 85.3,4.4 a 0.47,0.47 0 0 1 0.42,-0.42 h 8.22 A 0.35,0.35 0 0 1 94.27,4.4 L 94,6.22 a 0.47,0.47 0 0 1 -0.44,0.43 h -5.69 l -0.51,3.64 h 4.71 a 0.36,0.36 0 0 1 0.33,0.42 l -0.26,1.84 a 0.49,0.49 0 0 1 -0.45,0.42 H 87 l -0.55,3.89 h 5.71 c 0.22,0 0.33,0.2 0.31,0.42 l -0.25,1.82 a 0.47,0.47 0 0 1 -0.44,0.43 H 83.54 A 0.34,0.34 0 0 1 83.23,19.1 Z"
class="cls-5" />
<path
style="fill:#e1251b"
id="path3788"
d="M 99.56,4.4 A 0.49,0.49 0 0 1 100,4 h 1 a 0.37,0.37 0 0 1 0.34,0.42 L 99.36,18 H 105 a 0.35,0.35 0 0 1 0.31,0.42 l -0.09,0.64 a 0.49,0.49 0 0 1 -0.44,0.43 h -7 a 0.36,0.36 0 0 1 -0.33,-0.43 z"
class="cls-4" />
<path
style="fill:#e1251b"
id="path3790"
d="m 107.15,19 8.2,-15 a 0.42,0.42 0 0 1 0.37,-0.24 h 0.2 A 0.32,0.32 0 0 1 116.23,4 l 4,15 a 0.47,0.47 0 0 1 -0.42,0.58 h -0.94 a 0.32,0.32 0 0 1 -0.33,-0.25 l -0.89,-3.66 h -6.93 l -1.89,3.66 a 0.47,0.47 0 0 1 -0.42,0.25 h -1 C 107.11,19.53 107,19.24 107.15,19 Z m 10,-4.71 L 115.44,7 h -0.14 l -3.8,7.28 z"
class="cls-4" />
<path
style="fill:#e1251b"
id="path3792"
d="m 130.94,11.71 a 3.37,3.37 0 0 1 1.88,3 4.67,4.67 0 0 1 -5,4.8 h -4.6 a 0.36,0.36 0 0 1 -0.33,-0.43 L 125,4.4 a 0.51,0.51 0 0 1 0.45,-0.42 h 4.42 a 3.42,3.42 0 0 1 3.57,3.53 4.44,4.44 0 0 1 -2.46,4.13 z m -2.8,6.29 a 3,3 0 0 0 2.86,-3.12 2.33,2.33 0 0 0 -2.38,-2.39 h -3.11 L 124.7,18 Z m 0.67,-7.09 c 1.64,0 2.7,-1.31 2.7,-3.26 0,-1.29 -0.66,-2.18 -1.95,-2.18 H 126.5 L 125.72,11 Z"
class="cls-4" />
<path
style="fill:#e1251b"
id="path3794"
d="m 135.56,17.19 0.35,-0.37 c 0.27,-0.27 0.45,-0.36 0.71,-0.11 a 4.32,4.32 0 0 0 3.29,1.55 2.65,2.65 0 0 0 2.8,-2.69 c 0,-1.4 -1.05,-2.22 -2.89,-3.28 -1.84,-1.06 -3,-2 -3,-4.05 0,-1.86 1.17,-4.48 4.81,-4.48 a 5.7,5.7 0 0 1 3.45,1.13 c 0.11,0.09 0.31,0.33 0,0.76 L 144.83,6 c -0.22,0.31 -0.44,0.4 -0.73,0.2 a 4.52,4.52 0 0 0 -2.64,-0.95 2.88,2.88 0 0 0 -3,2.79 c 0,1.25 0.85,2.07 2.25,2.82 2.13,1.16 3.75,2.25 3.75,4.54 0,2.46 -1.78,4.35 -4.86,4.35 a 5.69,5.69 0 0 1 -4.16,-1.91 c -0.08,-0.16 -0.17,-0.36 0.12,-0.65 z"
class="cls-4" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

+35
View File
@@ -0,0 +1,35 @@
'use strict'
module.exports = function clear (db, location, keyRange, options, callback) {
if (options.limit === 0) return db.nextTick(callback)
const transaction = db.db.transaction([location], 'readwrite')
const store = transaction.objectStore(location)
let count = 0
transaction.oncomplete = function () {
callback()
}
transaction.onabort = function () {
callback(transaction.error || new Error('aborted by user'))
}
// A key cursor is faster (skips reading values) but not supported by IE
// TODO: we no longer support IE. Test others
const method = store.openKeyCursor ? 'openKeyCursor' : 'openCursor'
const direction = options.reverse ? 'prev' : 'next'
store[method](keyRange, direction).onsuccess = function (ev) {
const cursor = ev.target.result
if (cursor) {
// Wait for a request to complete before continuing, saving CPU.
store.delete(cursor.key).onsuccess = function () {
if (options.limit <= 0 || ++count < options.limit) {
cursor.continue()
}
}
}
}
}
+14
View File
@@ -0,0 +1,14 @@
'use strict'
const textEncoder = new TextEncoder()
module.exports = function (data) {
if (data instanceof Uint8Array) {
return data
} else if (data instanceof ArrayBuffer) {
return new Uint8Array(data)
} else {
// Non-binary data stored with an old version (level-js < 5.0.0)
return textEncoder.encode(data)
}
}
+20
View File
@@ -0,0 +1,20 @@
/* global IDBKeyRange */
'use strict'
module.exports = function createKeyRange (options) {
const lower = options.gte !== undefined ? options.gte : options.gt !== undefined ? options.gt : undefined
const upper = options.lte !== undefined ? options.lte : options.lt !== undefined ? options.lt : undefined
const lowerExclusive = options.gte === undefined
const upperExclusive = options.lte === undefined
if (lower !== undefined && upper !== undefined) {
return IDBKeyRange.bound(lower, upper, lowerExclusive, upperExclusive)
} else if (lower !== undefined) {
return IDBKeyRange.lowerBound(lower, lowerExclusive)
} else if (upper !== undefined) {
return IDBKeyRange.upperBound(upper, upperExclusive)
} else {
return null
}
}