- 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
292 lines
6.9 KiB
JavaScript
292 lines
6.9 KiB
JavaScript
/* 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
|