forked from public/fvtt-cthulhu-eternal
819 lines
23 KiB
JavaScript
819 lines
23 KiB
JavaScript
'use strict'
|
|
|
|
const { supports } = require('level-supports')
|
|
const { Transcoder } = require('level-transcoder')
|
|
const { EventEmitter } = require('events')
|
|
const { fromCallback } = require('catering')
|
|
const ModuleError = require('module-error')
|
|
const { AbstractIterator } = require('./abstract-iterator')
|
|
const { DefaultKeyIterator, DefaultValueIterator } = require('./lib/default-kv-iterator')
|
|
const { DeferredIterator, DeferredKeyIterator, DeferredValueIterator } = require('./lib/deferred-iterator')
|
|
const { DefaultChainedBatch } = require('./lib/default-chained-batch')
|
|
const { getCallback, getOptions } = require('./lib/common')
|
|
const rangeOptions = require('./lib/range-options')
|
|
|
|
const kPromise = Symbol('promise')
|
|
const kLanded = Symbol('landed')
|
|
const kResources = Symbol('resources')
|
|
const kCloseResources = Symbol('closeResources')
|
|
const kOperations = Symbol('operations')
|
|
const kUndefer = Symbol('undefer')
|
|
const kDeferOpen = Symbol('deferOpen')
|
|
const kOptions = Symbol('options')
|
|
const kStatus = Symbol('status')
|
|
const kDefaultOptions = Symbol('defaultOptions')
|
|
const kTranscoder = Symbol('transcoder')
|
|
const kKeyEncoding = Symbol('keyEncoding')
|
|
const kValueEncoding = Symbol('valueEncoding')
|
|
const noop = () => {}
|
|
|
|
class AbstractLevel extends EventEmitter {
|
|
constructor (manifest, options) {
|
|
super()
|
|
|
|
if (typeof manifest !== 'object' || manifest === null) {
|
|
throw new TypeError("The first argument 'manifest' must be an object")
|
|
}
|
|
|
|
options = getOptions(options)
|
|
const { keyEncoding, valueEncoding, passive, ...forward } = options
|
|
|
|
this[kResources] = new Set()
|
|
this[kOperations] = []
|
|
this[kDeferOpen] = true
|
|
this[kOptions] = forward
|
|
this[kStatus] = 'opening'
|
|
|
|
this.supports = supports(manifest, {
|
|
status: true,
|
|
promises: true,
|
|
clear: true,
|
|
getMany: true,
|
|
deferredOpen: true,
|
|
|
|
// TODO (next major): add seek
|
|
snapshots: manifest.snapshots !== false,
|
|
permanence: manifest.permanence !== false,
|
|
|
|
// TODO: remove from level-supports because it's always supported
|
|
keyIterator: true,
|
|
valueIterator: true,
|
|
iteratorNextv: true,
|
|
iteratorAll: true,
|
|
|
|
encodings: manifest.encodings || {},
|
|
events: Object.assign({}, manifest.events, {
|
|
opening: true,
|
|
open: true,
|
|
closing: true,
|
|
closed: true,
|
|
put: true,
|
|
del: true,
|
|
batch: true,
|
|
clear: true
|
|
})
|
|
})
|
|
|
|
this[kTranscoder] = new Transcoder(formats(this))
|
|
this[kKeyEncoding] = this[kTranscoder].encoding(keyEncoding || 'utf8')
|
|
this[kValueEncoding] = this[kTranscoder].encoding(valueEncoding || 'utf8')
|
|
|
|
// Add custom and transcoder encodings to manifest
|
|
for (const encoding of this[kTranscoder].encodings()) {
|
|
if (!this.supports.encodings[encoding.commonName]) {
|
|
this.supports.encodings[encoding.commonName] = true
|
|
}
|
|
}
|
|
|
|
this[kDefaultOptions] = {
|
|
empty: Object.freeze({}),
|
|
entry: Object.freeze({
|
|
keyEncoding: this[kKeyEncoding].commonName,
|
|
valueEncoding: this[kValueEncoding].commonName
|
|
}),
|
|
key: Object.freeze({
|
|
keyEncoding: this[kKeyEncoding].commonName
|
|
})
|
|
}
|
|
|
|
// Let subclass finish its constructor
|
|
this.nextTick(() => {
|
|
if (this[kDeferOpen]) {
|
|
this.open({ passive: false }, noop)
|
|
}
|
|
})
|
|
}
|
|
|
|
get status () {
|
|
return this[kStatus]
|
|
}
|
|
|
|
keyEncoding (encoding) {
|
|
return this[kTranscoder].encoding(encoding != null ? encoding : this[kKeyEncoding])
|
|
}
|
|
|
|
valueEncoding (encoding) {
|
|
return this[kTranscoder].encoding(encoding != null ? encoding : this[kValueEncoding])
|
|
}
|
|
|
|
open (options, callback) {
|
|
callback = getCallback(options, callback)
|
|
callback = fromCallback(callback, kPromise)
|
|
|
|
options = { ...this[kOptions], ...getOptions(options) }
|
|
|
|
options.createIfMissing = options.createIfMissing !== false
|
|
options.errorIfExists = !!options.errorIfExists
|
|
|
|
const maybeOpened = (err) => {
|
|
if (this[kStatus] === 'closing' || this[kStatus] === 'opening') {
|
|
// Wait until pending state changes are done
|
|
this.once(kLanded, err ? () => maybeOpened(err) : maybeOpened)
|
|
} else if (this[kStatus] !== 'open') {
|
|
callback(new ModuleError('Database is not open', {
|
|
code: 'LEVEL_DATABASE_NOT_OPEN',
|
|
cause: err
|
|
}))
|
|
} else {
|
|
callback()
|
|
}
|
|
}
|
|
|
|
if (options.passive) {
|
|
if (this[kStatus] === 'opening') {
|
|
this.once(kLanded, maybeOpened)
|
|
} else {
|
|
this.nextTick(maybeOpened)
|
|
}
|
|
} else if (this[kStatus] === 'closed' || this[kDeferOpen]) {
|
|
this[kDeferOpen] = false
|
|
this[kStatus] = 'opening'
|
|
this.emit('opening')
|
|
|
|
this._open(options, (err) => {
|
|
if (err) {
|
|
this[kStatus] = 'closed'
|
|
|
|
// Resources must be safe to close in any db state
|
|
this[kCloseResources](() => {
|
|
this.emit(kLanded)
|
|
maybeOpened(err)
|
|
})
|
|
|
|
this[kUndefer]()
|
|
return
|
|
}
|
|
|
|
this[kStatus] = 'open'
|
|
this[kUndefer]()
|
|
this.emit(kLanded)
|
|
|
|
// Only emit public event if pending state changes are done
|
|
if (this[kStatus] === 'open') this.emit('open')
|
|
|
|
// TODO (next major): remove this alias
|
|
if (this[kStatus] === 'open') this.emit('ready')
|
|
|
|
maybeOpened()
|
|
})
|
|
} else if (this[kStatus] === 'open') {
|
|
this.nextTick(maybeOpened)
|
|
} else {
|
|
this.once(kLanded, () => this.open(options, callback))
|
|
}
|
|
|
|
return callback[kPromise]
|
|
}
|
|
|
|
_open (options, callback) {
|
|
this.nextTick(callback)
|
|
}
|
|
|
|
close (callback) {
|
|
callback = fromCallback(callback, kPromise)
|
|
|
|
const maybeClosed = (err) => {
|
|
if (this[kStatus] === 'opening' || this[kStatus] === 'closing') {
|
|
// Wait until pending state changes are done
|
|
this.once(kLanded, err ? maybeClosed(err) : maybeClosed)
|
|
} else if (this[kStatus] !== 'closed') {
|
|
callback(new ModuleError('Database is not closed', {
|
|
code: 'LEVEL_DATABASE_NOT_CLOSED',
|
|
cause: err
|
|
}))
|
|
} else {
|
|
callback()
|
|
}
|
|
}
|
|
|
|
if (this[kStatus] === 'open') {
|
|
this[kStatus] = 'closing'
|
|
this.emit('closing')
|
|
|
|
const cancel = (err) => {
|
|
this[kStatus] = 'open'
|
|
this[kUndefer]()
|
|
this.emit(kLanded)
|
|
maybeClosed(err)
|
|
}
|
|
|
|
this[kCloseResources](() => {
|
|
this._close((err) => {
|
|
if (err) return cancel(err)
|
|
|
|
this[kStatus] = 'closed'
|
|
this[kUndefer]()
|
|
this.emit(kLanded)
|
|
|
|
// Only emit public event if pending state changes are done
|
|
if (this[kStatus] === 'closed') this.emit('closed')
|
|
|
|
maybeClosed()
|
|
})
|
|
})
|
|
} else if (this[kStatus] === 'closed') {
|
|
this.nextTick(maybeClosed)
|
|
} else {
|
|
this.once(kLanded, () => this.close(callback))
|
|
}
|
|
|
|
return callback[kPromise]
|
|
}
|
|
|
|
[kCloseResources] (callback) {
|
|
if (this[kResources].size === 0) {
|
|
return this.nextTick(callback)
|
|
}
|
|
|
|
let pending = this[kResources].size
|
|
let sync = true
|
|
|
|
const next = () => {
|
|
if (--pending === 0) {
|
|
// We don't have tests for generic resources, so dezalgo
|
|
if (sync) this.nextTick(callback)
|
|
else callback()
|
|
}
|
|
}
|
|
|
|
// In parallel so that all resources know they are closed
|
|
for (const resource of this[kResources]) {
|
|
resource.close(next)
|
|
}
|
|
|
|
sync = false
|
|
this[kResources].clear()
|
|
}
|
|
|
|
_close (callback) {
|
|
this.nextTick(callback)
|
|
}
|
|
|
|
get (key, options, callback) {
|
|
callback = getCallback(options, callback)
|
|
callback = fromCallback(callback, kPromise)
|
|
options = getOptions(options, this[kDefaultOptions].entry)
|
|
|
|
if (this[kStatus] === 'opening') {
|
|
this.defer(() => this.get(key, options, callback))
|
|
return callback[kPromise]
|
|
}
|
|
|
|
if (maybeError(this, callback)) {
|
|
return callback[kPromise]
|
|
}
|
|
|
|
const err = this._checkKey(key)
|
|
|
|
if (err) {
|
|
this.nextTick(callback, err)
|
|
return callback[kPromise]
|
|
}
|
|
|
|
const keyEncoding = this.keyEncoding(options.keyEncoding)
|
|
const valueEncoding = this.valueEncoding(options.valueEncoding)
|
|
const keyFormat = keyEncoding.format
|
|
const valueFormat = valueEncoding.format
|
|
|
|
// Forward encoding options to the underlying store
|
|
if (options.keyEncoding !== keyFormat || options.valueEncoding !== valueFormat) {
|
|
// Avoid spread operator because of https://bugs.chromium.org/p/chromium/issues/detail?id=1204540
|
|
options = Object.assign({}, options, { keyEncoding: keyFormat, valueEncoding: valueFormat })
|
|
}
|
|
|
|
this._get(this.prefixKey(keyEncoding.encode(key), keyFormat), options, (err, value) => {
|
|
if (err) {
|
|
// Normalize not found error for backwards compatibility with abstract-leveldown and level(up)
|
|
if (err.code === 'LEVEL_NOT_FOUND' || err.notFound || /NotFound/i.test(err)) {
|
|
if (!err.code) err.code = 'LEVEL_NOT_FOUND' // Preferred way going forward
|
|
if (!err.notFound) err.notFound = true // Same as level-errors
|
|
if (!err.status) err.status = 404 // Same as level-errors
|
|
}
|
|
|
|
return callback(err)
|
|
}
|
|
|
|
try {
|
|
value = valueEncoding.decode(value)
|
|
} catch (err) {
|
|
return callback(new ModuleError('Could not decode value', {
|
|
code: 'LEVEL_DECODE_ERROR',
|
|
cause: err
|
|
}))
|
|
}
|
|
|
|
callback(null, value)
|
|
})
|
|
|
|
return callback[kPromise]
|
|
}
|
|
|
|
_get (key, options, callback) {
|
|
this.nextTick(callback, new Error('NotFound'))
|
|
}
|
|
|
|
getMany (keys, options, callback) {
|
|
callback = getCallback(options, callback)
|
|
callback = fromCallback(callback, kPromise)
|
|
options = getOptions(options, this[kDefaultOptions].entry)
|
|
|
|
if (this[kStatus] === 'opening') {
|
|
this.defer(() => this.getMany(keys, options, callback))
|
|
return callback[kPromise]
|
|
}
|
|
|
|
if (maybeError(this, callback)) {
|
|
return callback[kPromise]
|
|
}
|
|
|
|
if (!Array.isArray(keys)) {
|
|
this.nextTick(callback, new TypeError("The first argument 'keys' must be an array"))
|
|
return callback[kPromise]
|
|
}
|
|
|
|
if (keys.length === 0) {
|
|
this.nextTick(callback, null, [])
|
|
return callback[kPromise]
|
|
}
|
|
|
|
const keyEncoding = this.keyEncoding(options.keyEncoding)
|
|
const valueEncoding = this.valueEncoding(options.valueEncoding)
|
|
const keyFormat = keyEncoding.format
|
|
const valueFormat = valueEncoding.format
|
|
|
|
// Forward encoding options
|
|
if (options.keyEncoding !== keyFormat || options.valueEncoding !== valueFormat) {
|
|
options = Object.assign({}, options, { keyEncoding: keyFormat, valueEncoding: valueFormat })
|
|
}
|
|
|
|
const mappedKeys = new Array(keys.length)
|
|
|
|
for (let i = 0; i < keys.length; i++) {
|
|
const key = keys[i]
|
|
const err = this._checkKey(key)
|
|
|
|
if (err) {
|
|
this.nextTick(callback, err)
|
|
return callback[kPromise]
|
|
}
|
|
|
|
mappedKeys[i] = this.prefixKey(keyEncoding.encode(key), keyFormat)
|
|
}
|
|
|
|
this._getMany(mappedKeys, options, (err, values) => {
|
|
if (err) return callback(err)
|
|
|
|
try {
|
|
for (let i = 0; i < values.length; i++) {
|
|
if (values[i] !== undefined) {
|
|
values[i] = valueEncoding.decode(values[i])
|
|
}
|
|
}
|
|
} catch (err) {
|
|
return callback(new ModuleError(`Could not decode one or more of ${values.length} value(s)`, {
|
|
code: 'LEVEL_DECODE_ERROR',
|
|
cause: err
|
|
}))
|
|
}
|
|
|
|
callback(null, values)
|
|
})
|
|
|
|
return callback[kPromise]
|
|
}
|
|
|
|
_getMany (keys, options, callback) {
|
|
this.nextTick(callback, null, new Array(keys.length).fill(undefined))
|
|
}
|
|
|
|
put (key, value, options, callback) {
|
|
callback = getCallback(options, callback)
|
|
callback = fromCallback(callback, kPromise)
|
|
options = getOptions(options, this[kDefaultOptions].entry)
|
|
|
|
if (this[kStatus] === 'opening') {
|
|
this.defer(() => this.put(key, value, options, callback))
|
|
return callback[kPromise]
|
|
}
|
|
|
|
if (maybeError(this, callback)) {
|
|
return callback[kPromise]
|
|
}
|
|
|
|
const err = this._checkKey(key) || this._checkValue(value)
|
|
|
|
if (err) {
|
|
this.nextTick(callback, err)
|
|
return callback[kPromise]
|
|
}
|
|
|
|
const keyEncoding = this.keyEncoding(options.keyEncoding)
|
|
const valueEncoding = this.valueEncoding(options.valueEncoding)
|
|
const keyFormat = keyEncoding.format
|
|
const valueFormat = valueEncoding.format
|
|
|
|
// Forward encoding options
|
|
if (options.keyEncoding !== keyFormat || options.valueEncoding !== valueFormat) {
|
|
options = Object.assign({}, options, { keyEncoding: keyFormat, valueEncoding: valueFormat })
|
|
}
|
|
|
|
const mappedKey = this.prefixKey(keyEncoding.encode(key), keyFormat)
|
|
const mappedValue = valueEncoding.encode(value)
|
|
|
|
this._put(mappedKey, mappedValue, options, (err) => {
|
|
if (err) return callback(err)
|
|
this.emit('put', key, value)
|
|
callback()
|
|
})
|
|
|
|
return callback[kPromise]
|
|
}
|
|
|
|
_put (key, value, options, callback) {
|
|
this.nextTick(callback)
|
|
}
|
|
|
|
del (key, options, callback) {
|
|
callback = getCallback(options, callback)
|
|
callback = fromCallback(callback, kPromise)
|
|
options = getOptions(options, this[kDefaultOptions].key)
|
|
|
|
if (this[kStatus] === 'opening') {
|
|
this.defer(() => this.del(key, options, callback))
|
|
return callback[kPromise]
|
|
}
|
|
|
|
if (maybeError(this, callback)) {
|
|
return callback[kPromise]
|
|
}
|
|
|
|
const err = this._checkKey(key)
|
|
|
|
if (err) {
|
|
this.nextTick(callback, err)
|
|
return callback[kPromise]
|
|
}
|
|
|
|
const keyEncoding = this.keyEncoding(options.keyEncoding)
|
|
const keyFormat = keyEncoding.format
|
|
|
|
// Forward encoding options
|
|
if (options.keyEncoding !== keyFormat) {
|
|
options = Object.assign({}, options, { keyEncoding: keyFormat })
|
|
}
|
|
|
|
this._del(this.prefixKey(keyEncoding.encode(key), keyFormat), options, (err) => {
|
|
if (err) return callback(err)
|
|
this.emit('del', key)
|
|
callback()
|
|
})
|
|
|
|
return callback[kPromise]
|
|
}
|
|
|
|
_del (key, options, callback) {
|
|
this.nextTick(callback)
|
|
}
|
|
|
|
batch (operations, options, callback) {
|
|
if (!arguments.length) {
|
|
if (this[kStatus] === 'opening') return new DefaultChainedBatch(this)
|
|
if (this[kStatus] !== 'open') {
|
|
throw new ModuleError('Database is not open', {
|
|
code: 'LEVEL_DATABASE_NOT_OPEN'
|
|
})
|
|
}
|
|
return this._chainedBatch()
|
|
}
|
|
|
|
if (typeof operations === 'function') callback = operations
|
|
else callback = getCallback(options, callback)
|
|
|
|
callback = fromCallback(callback, kPromise)
|
|
options = getOptions(options, this[kDefaultOptions].empty)
|
|
|
|
if (this[kStatus] === 'opening') {
|
|
this.defer(() => this.batch(operations, options, callback))
|
|
return callback[kPromise]
|
|
}
|
|
|
|
if (maybeError(this, callback)) {
|
|
return callback[kPromise]
|
|
}
|
|
|
|
if (!Array.isArray(operations)) {
|
|
this.nextTick(callback, new TypeError("The first argument 'operations' must be an array"))
|
|
return callback[kPromise]
|
|
}
|
|
|
|
if (operations.length === 0) {
|
|
this.nextTick(callback)
|
|
return callback[kPromise]
|
|
}
|
|
|
|
const mapped = new Array(operations.length)
|
|
const { keyEncoding: ke, valueEncoding: ve, ...forward } = options
|
|
|
|
for (let i = 0; i < operations.length; i++) {
|
|
if (typeof operations[i] !== 'object' || operations[i] === null) {
|
|
this.nextTick(callback, new TypeError('A batch operation must be an object'))
|
|
return callback[kPromise]
|
|
}
|
|
|
|
const op = Object.assign({}, operations[i])
|
|
|
|
if (op.type !== 'put' && op.type !== 'del') {
|
|
this.nextTick(callback, new TypeError("A batch operation must have a type property that is 'put' or 'del'"))
|
|
return callback[kPromise]
|
|
}
|
|
|
|
const err = this._checkKey(op.key)
|
|
|
|
if (err) {
|
|
this.nextTick(callback, err)
|
|
return callback[kPromise]
|
|
}
|
|
|
|
const db = op.sublevel != null ? op.sublevel : this
|
|
const keyEncoding = db.keyEncoding(op.keyEncoding || ke)
|
|
const keyFormat = keyEncoding.format
|
|
|
|
op.key = db.prefixKey(keyEncoding.encode(op.key), keyFormat)
|
|
op.keyEncoding = keyFormat
|
|
|
|
if (op.type === 'put') {
|
|
const valueErr = this._checkValue(op.value)
|
|
|
|
if (valueErr) {
|
|
this.nextTick(callback, valueErr)
|
|
return callback[kPromise]
|
|
}
|
|
|
|
const valueEncoding = db.valueEncoding(op.valueEncoding || ve)
|
|
|
|
op.value = valueEncoding.encode(op.value)
|
|
op.valueEncoding = valueEncoding.format
|
|
}
|
|
|
|
// Prevent double prefixing
|
|
if (db !== this) {
|
|
op.sublevel = null
|
|
}
|
|
|
|
mapped[i] = op
|
|
}
|
|
|
|
this._batch(mapped, forward, (err) => {
|
|
if (err) return callback(err)
|
|
this.emit('batch', operations)
|
|
callback()
|
|
})
|
|
|
|
return callback[kPromise]
|
|
}
|
|
|
|
_batch (operations, options, callback) {
|
|
this.nextTick(callback)
|
|
}
|
|
|
|
sublevel (name, options) {
|
|
return this._sublevel(name, AbstractSublevel.defaults(options))
|
|
}
|
|
|
|
_sublevel (name, options) {
|
|
return new AbstractSublevel(this, name, options)
|
|
}
|
|
|
|
prefixKey (key, keyFormat) {
|
|
return key
|
|
}
|
|
|
|
clear (options, callback) {
|
|
callback = getCallback(options, callback)
|
|
callback = fromCallback(callback, kPromise)
|
|
options = getOptions(options, this[kDefaultOptions].empty)
|
|
|
|
if (this[kStatus] === 'opening') {
|
|
this.defer(() => this.clear(options, callback))
|
|
return callback[kPromise]
|
|
}
|
|
|
|
if (maybeError(this, callback)) {
|
|
return callback[kPromise]
|
|
}
|
|
|
|
const original = options
|
|
const keyEncoding = this.keyEncoding(options.keyEncoding)
|
|
|
|
options = rangeOptions(options, keyEncoding)
|
|
options.keyEncoding = keyEncoding.format
|
|
|
|
if (options.limit === 0) {
|
|
this.nextTick(callback)
|
|
} else {
|
|
this._clear(options, (err) => {
|
|
if (err) return callback(err)
|
|
this.emit('clear', original)
|
|
callback()
|
|
})
|
|
}
|
|
|
|
return callback[kPromise]
|
|
}
|
|
|
|
_clear (options, callback) {
|
|
this.nextTick(callback)
|
|
}
|
|
|
|
iterator (options) {
|
|
const keyEncoding = this.keyEncoding(options && options.keyEncoding)
|
|
const valueEncoding = this.valueEncoding(options && options.valueEncoding)
|
|
|
|
options = rangeOptions(options, keyEncoding)
|
|
options.keys = options.keys !== false
|
|
options.values = options.values !== false
|
|
|
|
// We need the original encoding options in AbstractIterator in order to decode data
|
|
options[AbstractIterator.keyEncoding] = keyEncoding
|
|
options[AbstractIterator.valueEncoding] = valueEncoding
|
|
|
|
// Forward encoding options to private API
|
|
options.keyEncoding = keyEncoding.format
|
|
options.valueEncoding = valueEncoding.format
|
|
|
|
if (this[kStatus] === 'opening') {
|
|
return new DeferredIterator(this, options)
|
|
} else if (this[kStatus] !== 'open') {
|
|
throw new ModuleError('Database is not open', {
|
|
code: 'LEVEL_DATABASE_NOT_OPEN'
|
|
})
|
|
}
|
|
|
|
return this._iterator(options)
|
|
}
|
|
|
|
_iterator (options) {
|
|
return new AbstractIterator(this, options)
|
|
}
|
|
|
|
keys (options) {
|
|
// Also include valueEncoding (though unused) because we may fallback to _iterator()
|
|
const keyEncoding = this.keyEncoding(options && options.keyEncoding)
|
|
const valueEncoding = this.valueEncoding(options && options.valueEncoding)
|
|
|
|
options = rangeOptions(options, keyEncoding)
|
|
|
|
// We need the original encoding options in AbstractKeyIterator in order to decode data
|
|
options[AbstractIterator.keyEncoding] = keyEncoding
|
|
options[AbstractIterator.valueEncoding] = valueEncoding
|
|
|
|
// Forward encoding options to private API
|
|
options.keyEncoding = keyEncoding.format
|
|
options.valueEncoding = valueEncoding.format
|
|
|
|
if (this[kStatus] === 'opening') {
|
|
return new DeferredKeyIterator(this, options)
|
|
} else if (this[kStatus] !== 'open') {
|
|
throw new ModuleError('Database is not open', {
|
|
code: 'LEVEL_DATABASE_NOT_OPEN'
|
|
})
|
|
}
|
|
|
|
return this._keys(options)
|
|
}
|
|
|
|
_keys (options) {
|
|
return new DefaultKeyIterator(this, options)
|
|
}
|
|
|
|
values (options) {
|
|
const keyEncoding = this.keyEncoding(options && options.keyEncoding)
|
|
const valueEncoding = this.valueEncoding(options && options.valueEncoding)
|
|
|
|
options = rangeOptions(options, keyEncoding)
|
|
|
|
// We need the original encoding options in AbstractValueIterator in order to decode data
|
|
options[AbstractIterator.keyEncoding] = keyEncoding
|
|
options[AbstractIterator.valueEncoding] = valueEncoding
|
|
|
|
// Forward encoding options to private API
|
|
options.keyEncoding = keyEncoding.format
|
|
options.valueEncoding = valueEncoding.format
|
|
|
|
if (this[kStatus] === 'opening') {
|
|
return new DeferredValueIterator(this, options)
|
|
} else if (this[kStatus] !== 'open') {
|
|
throw new ModuleError('Database is not open', {
|
|
code: 'LEVEL_DATABASE_NOT_OPEN'
|
|
})
|
|
}
|
|
|
|
return this._values(options)
|
|
}
|
|
|
|
_values (options) {
|
|
return new DefaultValueIterator(this, options)
|
|
}
|
|
|
|
defer (fn) {
|
|
if (typeof fn !== 'function') {
|
|
throw new TypeError('The first argument must be a function')
|
|
}
|
|
|
|
this[kOperations].push(fn)
|
|
}
|
|
|
|
[kUndefer] () {
|
|
if (this[kOperations].length === 0) {
|
|
return
|
|
}
|
|
|
|
const operations = this[kOperations]
|
|
this[kOperations] = []
|
|
|
|
for (const op of operations) {
|
|
op()
|
|
}
|
|
}
|
|
|
|
// TODO: docs and types
|
|
attachResource (resource) {
|
|
if (typeof resource !== 'object' || resource === null ||
|
|
typeof resource.close !== 'function') {
|
|
throw new TypeError('The first argument must be a resource object')
|
|
}
|
|
|
|
this[kResources].add(resource)
|
|
}
|
|
|
|
// TODO: docs and types
|
|
detachResource (resource) {
|
|
this[kResources].delete(resource)
|
|
}
|
|
|
|
_chainedBatch () {
|
|
return new DefaultChainedBatch(this)
|
|
}
|
|
|
|
_checkKey (key) {
|
|
if (key === null || key === undefined) {
|
|
return new ModuleError('Key cannot be null or undefined', {
|
|
code: 'LEVEL_INVALID_KEY'
|
|
})
|
|
}
|
|
}
|
|
|
|
_checkValue (value) {
|
|
if (value === null || value === undefined) {
|
|
return new ModuleError('Value cannot be null or undefined', {
|
|
code: 'LEVEL_INVALID_VALUE'
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// Expose browser-compatible nextTick for dependents
|
|
// TODO: after we drop node 10, also use queueMicrotask in node
|
|
AbstractLevel.prototype.nextTick = require('./lib/next-tick')
|
|
|
|
const { AbstractSublevel } = require('./lib/abstract-sublevel')({ AbstractLevel })
|
|
|
|
exports.AbstractLevel = AbstractLevel
|
|
exports.AbstractSublevel = AbstractSublevel
|
|
|
|
const maybeError = function (db, callback) {
|
|
if (db[kStatus] !== 'open') {
|
|
db.nextTick(callback, new ModuleError('Database is not open', {
|
|
code: 'LEVEL_DATABASE_NOT_OPEN'
|
|
}))
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
const formats = function (db) {
|
|
return Object.keys(db.supports.encodings)
|
|
.filter(k => !!db.supports.encodings[k])
|
|
}
|