From df94011113b12bf0a72030eef5d7dc020a048ee9 Mon Sep 17 00:00:00 2001 From: Vincent Weevers Date: Sun, 2 Jan 2022 16:02:45 +0100 Subject: [PATCH] Use classes --- abstract-chained-batch.js | 255 +++++---- abstract-iterator.js | 297 +++++------ abstract-level.js | 1023 ++++++++++++++++++------------------- test/self.js | 9 +- 4 files changed, 790 insertions(+), 794 deletions(-) diff --git a/abstract-chained-batch.js b/abstract-chained-batch.js index eedb98b..3f77b3c 100644 --- a/abstract-chained-batch.js +++ b/abstract-chained-batch.js @@ -10,172 +10,171 @@ const kOperations = Symbol('operations') const kFinishClose = Symbol('finishClose') const kCloseCallbacks = Symbol('closeCallbacks') -function AbstractChainedBatch (db) { - if (typeof db !== 'object' || db === null) { - const hint = db === null ? 'null' : typeof db - throw new TypeError(`The first argument must be an abstract-level database, received ${hint}`) - } - - this[kOperations] = [] - this[kCloseCallbacks] = [] - this[kStatus] = 'open' - this[kFinishClose] = this[kFinishClose].bind(this) +class AbstractChainedBatch { + constructor (db) { + if (typeof db !== 'object' || db === null) { + const hint = db === null ? 'null' : typeof db + throw new TypeError(`The first argument must be an abstract-level database, received ${hint}`) + } - this.db = db - this.db.attachResource(this) - this.nextTick = db.nextTick -} + this[kOperations] = [] + this[kCloseCallbacks] = [] + this[kStatus] = 'open' + this[kFinishClose] = this[kFinishClose].bind(this) -Object.defineProperty(AbstractChainedBatch.prototype, 'length', { - enumerable: true, - get () { - return this[kOperations].length + this.db = db + this.db.attachResource(this) + this.nextTick = db.nextTick } -}) -AbstractChainedBatch.prototype.put = function (key, value, options) { - if (this[kStatus] !== 'open') { - throw new ModuleError('Batch is not open: cannot call put() after write() or close()', { - code: 'LEVEL_BATCH_NOT_OPEN' - }) + get length () { + return this[kOperations].length } - const err = this.db._checkKey(key) || this.db._checkValue(value) - if (err) throw err + put (key, value, options) { + if (this[kStatus] !== 'open') { + throw new ModuleError('Batch is not open: cannot call put() after write() or close()', { + code: 'LEVEL_BATCH_NOT_OPEN' + }) + } - const db = options && options.sublevel != null ? options.sublevel : this.db - const original = options - const keyEncoding = db.keyEncoding(options && options.keyEncoding) - const valueEncoding = db.valueEncoding(options && options.valueEncoding) - const keyFormat = keyEncoding.format + const err = this.db._checkKey(key) || this.db._checkValue(value) + if (err) throw err - // Forward encoding options - options = { ...options, keyEncoding: keyFormat, valueEncoding: valueEncoding.format } + const db = options && options.sublevel != null ? options.sublevel : this.db + const original = options + const keyEncoding = db.keyEncoding(options && options.keyEncoding) + const valueEncoding = db.valueEncoding(options && options.valueEncoding) + const keyFormat = keyEncoding.format - // Prevent double prefixing - if (db !== this.db) { - options.sublevel = null - } + // Forward encoding options + options = { ...options, keyEncoding: keyFormat, valueEncoding: valueEncoding.format } - const mappedKey = db.prefixKey(keyEncoding.encode(key), keyFormat) - const mappedValue = valueEncoding.encode(value) - - this._put(mappedKey, mappedValue, options) - this[kOperations].push({ ...original, type: 'put', key, value }) + // Prevent double prefixing + if (db !== this.db) { + options.sublevel = null + } - return this -} + const mappedKey = db.prefixKey(keyEncoding.encode(key), keyFormat) + const mappedValue = valueEncoding.encode(value) -AbstractChainedBatch.prototype._put = function (key, value, options) {} + this._put(mappedKey, mappedValue, options) + this[kOperations].push({ ...original, type: 'put', key, value }) -AbstractChainedBatch.prototype.del = function (key, options) { - if (this[kStatus] !== 'open') { - throw new ModuleError('Batch is not open: cannot call del() after write() or close()', { - code: 'LEVEL_BATCH_NOT_OPEN' - }) + return this } - const err = this.db._checkKey(key) - if (err) throw err + _put (key, value, options) {} - const db = options && options.sublevel != null ? options.sublevel : this.db - const original = options - const keyEncoding = db.keyEncoding(options && options.keyEncoding) - const keyFormat = keyEncoding.format + del (key, options) { + if (this[kStatus] !== 'open') { + throw new ModuleError('Batch is not open: cannot call del() after write() or close()', { + code: 'LEVEL_BATCH_NOT_OPEN' + }) + } - // Forward encoding options - options = { ...options, keyEncoding: keyFormat } + const err = this.db._checkKey(key) + if (err) throw err - // Prevent double prefixing - if (db !== this.db) { - options.sublevel = null - } + const db = options && options.sublevel != null ? options.sublevel : this.db + const original = options + const keyEncoding = db.keyEncoding(options && options.keyEncoding) + const keyFormat = keyEncoding.format - this._del(db.prefixKey(keyEncoding.encode(key), keyFormat), options) - this[kOperations].push({ ...original, type: 'del', key }) + // Forward encoding options + options = { ...options, keyEncoding: keyFormat } - return this -} + // Prevent double prefixing + if (db !== this.db) { + options.sublevel = null + } -AbstractChainedBatch.prototype._del = function (key, options) {} + this._del(db.prefixKey(keyEncoding.encode(key), keyFormat), options) + this[kOperations].push({ ...original, type: 'del', key }) -AbstractChainedBatch.prototype.clear = function () { - if (this[kStatus] !== 'open') { - throw new ModuleError('Batch is not open: cannot call clear() after write() or close()', { - code: 'LEVEL_BATCH_NOT_OPEN' - }) + return this } - this._clear() - this[kOperations] = [] + _del (key, options) {} - return this -} + clear () { + if (this[kStatus] !== 'open') { + throw new ModuleError('Batch is not open: cannot call clear() after write() or close()', { + code: 'LEVEL_BATCH_NOT_OPEN' + }) + } -AbstractChainedBatch.prototype._clear = function () {} - -AbstractChainedBatch.prototype.write = function (options, callback) { - callback = getCallback(options, callback) - callback = fromCallback(callback, kPromise) - options = getOptions(options) - - if (this[kStatus] !== 'open') { - this.nextTick(callback, new ModuleError('Batch is not open: cannot call write() after write() or close()', { - code: 'LEVEL_BATCH_NOT_OPEN' - })) - } else if (this.length === 0) { - this.close(callback) - } else { - this[kStatus] = 'writing' - this._write(options, (err) => { - this[kStatus] = 'closing' - this[kCloseCallbacks].push(() => callback(err)) - - // Emit after setting 'closing' status, because event may trigger a - // db close which in turn triggers (idempotently) closing this batch. - if (!err) this.db.emit('batch', this[kOperations]) - - this._close(this[kFinishClose]) - }) + this._clear() + this[kOperations] = [] + + return this } - return callback[kPromise] -} + _clear () {} + + write (options, callback) { + callback = getCallback(options, callback) + callback = fromCallback(callback, kPromise) + options = getOptions(options) + + if (this[kStatus] !== 'open') { + this.nextTick(callback, new ModuleError('Batch is not open: cannot call write() after write() or close()', { + code: 'LEVEL_BATCH_NOT_OPEN' + })) + } else if (this.length === 0) { + this.close(callback) + } else { + this[kStatus] = 'writing' + this._write(options, (err) => { + this[kStatus] = 'closing' + this[kCloseCallbacks].push(() => callback(err)) + + // Emit after setting 'closing' status, because event may trigger a + // db close which in turn triggers (idempotently) closing this batch. + if (!err) this.db.emit('batch', this[kOperations]) + + this._close(this[kFinishClose]) + }) + } + + return callback[kPromise] + } -AbstractChainedBatch.prototype._write = function (options, callback) {} + _write (options, callback) {} -AbstractChainedBatch.prototype.close = function (callback) { - callback = fromCallback(callback, kPromise) + close (callback) { + callback = fromCallback(callback, kPromise) - if (this[kStatus] === 'closing') { - this[kCloseCallbacks].push(callback) - } else if (this[kStatus] === 'closed') { - this.nextTick(callback) - } else { - this[kCloseCallbacks].push(callback) + if (this[kStatus] === 'closing') { + this[kCloseCallbacks].push(callback) + } else if (this[kStatus] === 'closed') { + this.nextTick(callback) + } else { + this[kCloseCallbacks].push(callback) - if (this[kStatus] !== 'writing') { - this[kStatus] = 'closing' - this._close(this[kFinishClose]) + if (this[kStatus] !== 'writing') { + this[kStatus] = 'closing' + this._close(this[kFinishClose]) + } } - } - return callback[kPromise] -} + return callback[kPromise] + } -AbstractChainedBatch.prototype._close = function (callback) { - this.nextTick(callback) -} + _close (callback) { + this.nextTick(callback) + } -AbstractChainedBatch.prototype[kFinishClose] = function () { - this[kStatus] = 'closed' - this.db.detachResource(this) + [kFinishClose] () { + this[kStatus] = 'closed' + this.db.detachResource(this) - const callbacks = this[kCloseCallbacks] - this[kCloseCallbacks] = [] + const callbacks = this[kCloseCallbacks] + this[kCloseCallbacks] = [] - for (const cb of callbacks) { - cb() + for (const cb of callbacks) { + cb() + } } } diff --git a/abstract-iterator.js b/abstract-iterator.js index 29ba826..66347df 100644 --- a/abstract-iterator.js +++ b/abstract-iterator.js @@ -17,189 +17,192 @@ const kValueEncoding = Symbol('valueEncoding') const kKeys = Symbol('keys') const kValues = Symbol('values') -function AbstractIterator (db, options) { - if (typeof db !== 'object' || db === null) { - const hint = db === null ? 'null' : typeof db - throw new TypeError(`The first argument must be an abstract-level database, received ${hint}`) - } +let warnedEnd = false + +class AbstractIterator { + constructor (db, options) { + if (typeof db !== 'object' || db === null) { + const hint = db === null ? 'null' : typeof db + throw new TypeError(`The first argument must be an abstract-level database, received ${hint}`) + } + + if (typeof options !== 'object' || options === null) { + throw new TypeError('The second argument must be an options object') + } - if (typeof options !== 'object' || options === null) { - throw new TypeError('The second argument must be an options object') + this[kClosed] = false + this[kCloseCallbacks] = [] + this[kNexting] = false + this[kClosing] = false + this[kNextCallback] = null + this[kFinishNext] = this[kFinishNext].bind(this) + this[kFinishClose] = this[kFinishClose].bind(this) + this[kKeyEncoding] = options[kKeyEncoding] + this[kValueEncoding] = options[kValueEncoding] + this[kKeys] = options.keys !== false + this[kValues] = options.values !== false + + this.db = db + this.db.attachResource(this) + this.nextTick = db.nextTick } - this[kClosed] = false - this[kCloseCallbacks] = [] - this[kNexting] = false - this[kClosing] = false - this[kNextCallback] = null - this[kFinishNext] = this[kFinishNext].bind(this) - this[kFinishClose] = this[kFinishClose].bind(this) - this[kKeyEncoding] = options[kKeyEncoding] - this[kValueEncoding] = options[kValueEncoding] - this[kKeys] = options.keys !== false - this[kValues] = options.values !== false - - this.db = db - this.db.attachResource(this) - this.nextTick = db.nextTick -} + next (callback) { + let promise + + if (callback === undefined) { + promise = new Promise(function (resolve, reject) { + callback = function (err, key, value) { + if (err) reject(err) + else if (key === undefined && value === undefined) resolve() + else resolve([key, value]) + } + }) + } else if (typeof callback !== 'function') { + throw new TypeError('The first argument must be a function or undefined') + } -AbstractIterator.prototype.next = function (callback) { - let promise + if (this[kClosing]) { + this.nextTick(callback, new ModuleError('Iterator is not open: cannot call next() after close()', { + code: 'LEVEL_ITERATOR_NOT_OPEN' + })) + } else if (this[kNexting]) { + this.nextTick(callback, new ModuleError('Iterator is busy: cannot call next() until previous call has completed', { + code: 'LEVEL_ITERATOR_BUSY' + })) + } else { + this[kNexting] = true + this[kNextCallback] = callback - if (callback === undefined) { - promise = new Promise(function (resolve, reject) { - callback = function (err, key, value) { - if (err) reject(err) - else if (key === undefined && value === undefined) resolve() - else resolve([key, value]) - } - }) - } else if (typeof callback !== 'function') { - throw new TypeError('The first argument must be a function or undefined') - } + this._next(this[kFinishNext]) + } - if (this[kClosing]) { - this.nextTick(callback, new ModuleError('Iterator is not open: cannot call next() after close()', { - code: 'LEVEL_ITERATOR_NOT_OPEN' - })) - } else if (this[kNexting]) { - this.nextTick(callback, new ModuleError('Iterator is busy: cannot call next() until previous call has completed', { - code: 'LEVEL_ITERATOR_BUSY' - })) - } else { - this[kNexting] = true - this[kNextCallback] = callback - - this._next(this[kFinishNext]) + return promise } - return promise -} + _next (callback) { + this.nextTick(callback) + } -AbstractIterator.prototype._next = function (callback) { - this.nextTick(callback) -} + [kFinishNext] (err, key, value) { + const cb = this[kNextCallback] -AbstractIterator.prototype[kFinishNext] = function (err, key, value) { - const cb = this[kNextCallback] + this[kNexting] = false + this[kNextCallback] = null - this[kNexting] = false - this[kNextCallback] = null + if (this[kClosing]) this._close(this[kFinishClose]) - if (this[kClosing]) this._close(this[kFinishClose]) + try { + if (this[kKeys] && key != null) { + key = this[kKeyEncoding].decode(key) + } else { + key = undefined + } + } catch (err) { + return cb(new ModuleError('Iterator could not decode key', { + code: 'LEVEL_DECODE_ERROR', + cause: err + })) + } - try { - if (this[kKeys] && key != null) { - key = this[kKeyEncoding].decode(key) - } else { - key = undefined + try { + if (this[kValues] && value != null) { + value = this[kValueEncoding].decode(value) + } else { + value = undefined + } + } catch (err) { + return cb(new ModuleError('Iterator could not decode value', { + code: 'LEVEL_DECODE_ERROR', + cause: err + })) } - } catch (err) { - return cb(new ModuleError('Iterator could not decode key', { - code: 'LEVEL_DECODE_ERROR', - cause: err - })) + + cb(err, key, value) } - try { - if (this[kValues] && value != null) { - value = this[kValueEncoding].decode(value) + seek (target, options) { + options = getOptions(options) + + if (this[kClosing]) { + // Don't throw here, to be kind to implementations that wrap + // another db and don't necessarily control when the db is closed + } else if (this[kNexting]) { + throw new ModuleError('Iterator is busy: cannot call seek() until next() has completed', { + code: 'LEVEL_ITERATOR_BUSY' + }) } else { - value = undefined + const keyEncoding = this.db.keyEncoding(options.keyEncoding || this[kKeyEncoding]) + const keyFormat = keyEncoding.format + + if (options.keyEncoding !== keyFormat) { + options = { ...options, keyEncoding: keyFormat } + } + + const mapped = this.db.prefixKey(keyEncoding.encode(target), keyFormat) + this._seek(mapped, options) } - } catch (err) { - return cb(new ModuleError('Iterator could not decode value', { - code: 'LEVEL_DECODE_ERROR', - cause: err - })) } - cb(err, key, value) -} + _seek (target, options) {} -AbstractIterator.prototype.seek = function (target, options) { - options = getOptions(options) - - if (this[kClosing]) { - // Don't throw here, to be kind to implementations that wrap - // another db and don't necessarily control when the db is closed - } else if (this[kNexting]) { - throw new ModuleError('Iterator is busy: cannot call seek() until next() has completed', { - code: 'LEVEL_ITERATOR_BUSY' - }) - } else { - const keyEncoding = this.db.keyEncoding(options.keyEncoding || this[kKeyEncoding]) - const keyFormat = keyEncoding.format - - if (options.keyEncoding !== keyFormat) { - options = { ...options, keyEncoding: keyFormat } - } + close (callback) { + callback = fromCallback(callback, kPromise) - const mapped = this.db.prefixKey(keyEncoding.encode(target), keyFormat) - this._seek(mapped, options) - } -} + if (this[kClosed]) { + this.nextTick(callback) + } else if (this[kClosing]) { + this[kCloseCallbacks].push(callback) + } else { + this[kClosing] = true + this[kCloseCallbacks].push(callback) -AbstractIterator.prototype._seek = function (target, options) {} + if (!this[kNexting]) { + this._close(this[kFinishClose]) + } + } -AbstractIterator.prototype.close = function (callback) { - callback = fromCallback(callback, kPromise) + return callback[kPromise] + } - if (this[kClosed]) { + _close (callback) { this.nextTick(callback) - } else if (this[kClosing]) { - this[kCloseCallbacks].push(callback) - } else { - this[kClosing] = true - this[kCloseCallbacks].push(callback) - - if (!this[kNexting]) { - this._close(this[kFinishClose]) - } } - return callback[kPromise] -} + [kFinishClose] () { + this[kClosed] = true + this.db.detachResource(this) -AbstractIterator.prototype._close = function (callback) { - this.nextTick(callback) -} + const callbacks = this[kCloseCallbacks] + this[kCloseCallbacks] = [] -let warnedEnd = false -AbstractIterator.prototype.end = function (callback) { - if (!warnedEnd && typeof console !== 'undefined') { - warnedEnd = true - console.warn(new ModuleError( - 'The iterator.end() method was renamed to close() and end() is an alias that will be removed in a future version', - { code: 'LEVEL_LEGACY' } - )) + for (const cb of callbacks) { + cb() + } } - return this.close(callback) -} - -AbstractIterator.prototype[kFinishClose] = function () { - this[kClosed] = true - this.db.detachResource(this) - - const callbacks = this[kCloseCallbacks] - this[kCloseCallbacks] = [] + async * [Symbol.asyncIterator] () { + try { + let kv - for (const cb of callbacks) { - cb() + while ((kv = (await this.next())) !== undefined) { + yield kv + } + } finally { + if (!this[kClosed]) await this.close() + } } -} -AbstractIterator.prototype[Symbol.asyncIterator] = async function * () { - try { - let kv - - while ((kv = (await this.next())) !== undefined) { - yield kv + end (callback) { + if (!warnedEnd && typeof console !== 'undefined') { + warnedEnd = true + console.warn(new ModuleError( + 'The iterator.end() method was renamed to close() and end() is an alias that will be removed in a future version', + { code: 'LEVEL_LEGACY' } + )) } - } finally { - if (!this[kClosed]) await this.close() + + return this.close(callback) } } diff --git a/abstract-level.js b/abstract-level.js index e1ba72d..371c936 100644 --- a/abstract-level.js +++ b/abstract-level.js @@ -26,697 +26,694 @@ const kKeyEncoding = Symbol('keyEncoding') const kValueEncoding = Symbol('valueEncoding') const noop = () => {} -function AbstractLevel (manifest, options) { - if (typeof manifest !== 'object' || manifest === null) { - throw new TypeError("The first argument 'manifest' must be an object") - } - - options = getOptions(options) - EventEmitter.call(this) - - 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, - snapshots: manifest.snapshots !== false, - permanence: manifest.permanence !== false, - 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') +class AbstractLevel extends EventEmitter { + constructor (manifest, options) { + super() - // 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 + if (typeof manifest !== 'object' || manifest === null) { + throw new TypeError("The first argument 'manifest' must be an object") } - } - this[kDefaultOptions] = { - empty: Object.freeze({}), - entry: Object.freeze({ - keyEncoding: this[kKeyEncoding].commonName, - valueEncoding: this[kValueEncoding].commonName - }), - key: Object.freeze({ - keyEncoding: this[kKeyEncoding].commonName - }) - } + options = getOptions(options) + const { keyEncoding, valueEncoding, passive, ...forward } = options - // Let subclass finish its constructor - this.nextTick(() => { - if (this[kDeferOpen]) { - this.open({ passive: false }, noop) - } - }) -} + this[kResources] = new Set() + this[kOperations] = [] + this[kDeferOpen] = true + this[kOptions] = forward + this[kStatus] = 'opening' -Object.setPrototypeOf(AbstractLevel.prototype, EventEmitter.prototype) + this.supports = supports(manifest, { + status: true, + promises: true, + clear: true, + getMany: true, + deferredOpen: true, + snapshots: manifest.snapshots !== false, + permanence: manifest.permanence !== false, + encodings: manifest.encodings || {}, + events: Object.assign({}, manifest.events, { + opening: true, + open: true, + closing: true, + closed: true, + put: true, + del: true, + batch: true, + clear: true + }) + }) -Object.defineProperty(AbstractLevel.prototype, 'status', { - enumerable: true, - get () { - return this[kStatus] - } -}) + this[kTranscoder] = new Transcoder(formats(this)) + this[kKeyEncoding] = this[kTranscoder].encoding(keyEncoding || 'utf8') + this[kValueEncoding] = this[kTranscoder].encoding(valueEncoding || 'utf8') -AbstractLevel.prototype.keyEncoding = function (encoding) { - return this[kTranscoder].encoding(encoding != null ? encoding : this[kKeyEncoding]) -} + // 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 + } + } -AbstractLevel.prototype.valueEncoding = function (encoding) { - return this[kTranscoder].encoding(encoding != null ? encoding : this[kValueEncoding]) -} + this[kDefaultOptions] = { + empty: Object.freeze({}), + entry: Object.freeze({ + keyEncoding: this[kKeyEncoding].commonName, + valueEncoding: this[kValueEncoding].commonName + }), + key: Object.freeze({ + keyEncoding: this[kKeyEncoding].commonName + }) + } -AbstractLevel.prototype.open = function (options, callback) { - callback = getCallback(options, callback) - callback = fromCallback(callback, kPromise) + // Let subclass finish its constructor + this.nextTick(() => { + if (this[kDeferOpen]) { + this.open({ passive: false }, noop) + } + }) + } - options = { ...this[kOptions], ...getOptions(options) } + get status () { + return this[kStatus] + } - options.createIfMissing = options.createIfMissing !== false - options.errorIfExists = !!options.errorIfExists + keyEncoding (encoding) { + return this[kTranscoder].encoding(encoding != null ? encoding : this[kKeyEncoding]) + } - 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() - } + valueEncoding (encoding) { + return this[kTranscoder].encoding(encoding != null ? encoding : this[kValueEncoding]) } - 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') + open (options, callback) { + callback = getCallback(options, callback) + callback = fromCallback(callback, kPromise) - this._open(options, (err) => { - if (err) { - this[kStatus] = 'closed' + options = { ...this[kOptions], ...getOptions(options) } - // Resources must be safe to close in any db state - this[kCloseResources](() => { - this.emit(kLanded) - maybeOpened(err) - }) + options.createIfMissing = options.createIfMissing !== false + options.errorIfExists = !!options.errorIfExists - this[kUndefer]() - return + 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() } + } - 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') - - maybeOpened() - }) - } else if (this[kStatus] === 'open') { - this.nextTick(maybeOpened) - } else { - this.once(kLanded, () => this.open(options, 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 + } - return callback[kPromise] -} + this[kStatus] = 'open' + this[kUndefer]() + this.emit(kLanded) -AbstractLevel.prototype._open = function (options, callback) { - this.nextTick(callback) -} + // Only emit public event if pending state changes are done + if (this[kStatus] === 'open') this.emit('open') -AbstractLevel.prototype.close = function (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 - })) + maybeOpened() + }) + } else if (this[kStatus] === 'open') { + this.nextTick(maybeOpened) } else { - callback() + this.once(kLanded, () => this.open(options, callback)) } + + return callback[kPromise] } - if (this[kStatus] === 'open') { - this[kStatus] = 'closing' - this.emit('closing') + _open (options, callback) { + this.nextTick(callback) + } - const cancel = (err) => { - this[kStatus] = 'open' - this[kUndefer]() - this.emit(kLanded) - maybeClosed(err) + 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() + } } - this[kCloseResources](() => { - this._close((err) => { - if (err) return cancel(err) + if (this[kStatus] === 'open') { + this[kStatus] = 'closing' + this.emit('closing') - this[kStatus] = 'closed' + const cancel = (err) => { + this[kStatus] = 'open' this[kUndefer]() this.emit(kLanded) + maybeClosed(err) + } - // 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] -} + this[kCloseResources](() => { + this._close((err) => { + if (err) return cancel(err) -AbstractLevel.prototype[kCloseResources] = function (callback) { - if (this[kResources].size === 0) { - return this.nextTick(callback) - } + this[kStatus] = 'closed' + this[kUndefer]() + this.emit(kLanded) - let pending = this[kResources].size - let sync = true + // Only emit public event if pending state changes are done + if (this[kStatus] === 'closed') this.emit('closed') - const next = () => { - if (--pending === 0) { - // We don't have tests for generic resources, so dezalgo - if (sync) this.nextTick(callback) - else callback() + maybeClosed() + }) + }) + } else if (this[kStatus] === 'closed') { + this.nextTick(maybeClosed) + } else { + this.once(kLanded, () => this.close(callback)) } - } - // In parallel so that all resources know they are closed - for (const resource of this[kResources]) { - resource.close(next) + return callback[kPromise] } - sync = false - this[kResources].clear() -} + [kCloseResources] (callback) { + if (this[kResources].size === 0) { + return this.nextTick(callback) + } -AbstractLevel.prototype._close = function (callback) { - this.nextTick(callback) -} + let pending = this[kResources].size + let sync = true -AbstractLevel.prototype.get = function (key, options, callback) { - callback = getCallback(options, callback) - callback = fromCallback(callback, kPromise) - options = getOptions(options, this[kDefaultOptions].entry) + const next = () => { + if (--pending === 0) { + // We don't have tests for generic resources, so dezalgo + if (sync) this.nextTick(callback) + else callback() + } + } - if (this[kStatus] === 'opening') { - this.defer(() => this.get(key, options, callback)) - return callback[kPromise] + // In parallel so that all resources know they are closed + for (const resource of this[kResources]) { + resource.close(next) + } + + sync = false + this[kResources].clear() } - if (maybeError(this, callback)) { - return callback[kPromise] + _close (callback) { + this.nextTick(callback) } - const err = this._checkKey(key) + get (key, options, callback) { + callback = getCallback(options, callback) + callback = fromCallback(callback, kPromise) + options = getOptions(options, this[kDefaultOptions].entry) - if (err) { - this.nextTick(callback, err) - return callback[kPromise] - } + if (this[kStatus] === 'opening') { + this.defer(() => this.get(key, options, callback)) + return callback[kPromise] + } - const keyEncoding = this.keyEncoding(options.keyEncoding) - const valueEncoding = this.valueEncoding(options.valueEncoding) - const keyFormat = keyEncoding.format - const valueFormat = valueEncoding.format + if (maybeError(this, callback)) { + return callback[kPromise] + } - // Forward encoding options to the underlying store - if (options.keyEncoding !== keyFormat || options.valueEncoding !== valueFormat) { - options = { ...options, keyEncoding: keyFormat, valueEncoding: valueFormat } - } + const err = this._checkKey(key) - 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) + this.nextTick(callback, err) + return callback[kPromise] } - try { - value = valueEncoding.decode(value) - } catch (err) { - return callback(new ModuleError('Could not decode value', { - code: 'LEVEL_DECODE_ERROR', - cause: err - })) + 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) { + options = { ...options, keyEncoding: keyFormat, valueEncoding: valueFormat } } - callback(null, value) - }) + 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[kPromise] -} + return callback(err) + } -AbstractLevel.prototype._get = function (key, options, callback) { - this.nextTick(callback, new Error('NotFound')) -} + try { + value = valueEncoding.decode(value) + } catch (err) { + return callback(new ModuleError('Could not decode value', { + code: 'LEVEL_DECODE_ERROR', + cause: err + })) + } -AbstractLevel.prototype.getMany = function (keys, options, callback) { - callback = getCallback(options, callback) - callback = fromCallback(callback, kPromise) - options = getOptions(options, this[kDefaultOptions].entry) + callback(null, value) + }) - if (this[kStatus] === 'opening') { - this.defer(() => this.getMany(keys, options, callback)) return callback[kPromise] } - if (maybeError(this, callback)) { - return callback[kPromise] + _get (key, options, callback) { + this.nextTick(callback, new Error('NotFound')) } - if (!Array.isArray(keys)) { - this.nextTick(callback, new TypeError("The first argument 'keys' must be an array")) - return callback[kPromise] - } + getMany (keys, options, callback) { + callback = getCallback(options, callback) + callback = fromCallback(callback, kPromise) + options = getOptions(options, this[kDefaultOptions].entry) - if (keys.length === 0) { - this.nextTick(callback, null, []) - return callback[kPromise] - } + if (this[kStatus] === 'opening') { + this.defer(() => this.getMany(keys, options, callback)) + return callback[kPromise] + } - const keyEncoding = this.keyEncoding(options.keyEncoding) - const valueEncoding = this.valueEncoding(options.valueEncoding) - const keyFormat = keyEncoding.format - const valueFormat = valueEncoding.format + if (maybeError(this, callback)) { + return callback[kPromise] + } - // Forward encoding options - if (options.keyEncoding !== keyFormat || options.valueEncoding !== valueFormat) { - options = { ...options, keyEncoding: keyFormat, valueEncoding: valueFormat } - } + if (!Array.isArray(keys)) { + this.nextTick(callback, new TypeError("The first argument 'keys' must be an array")) + return callback[kPromise] + } - const mappedKeys = new Array(keys.length) + if (keys.length === 0) { + this.nextTick(callback, null, []) + return callback[kPromise] + } - for (let i = 0; i < keys.length; i++) { - const key = keys[i] - const err = this._checkKey(key) + const keyEncoding = this.keyEncoding(options.keyEncoding) + const valueEncoding = this.valueEncoding(options.valueEncoding) + const keyFormat = keyEncoding.format + const valueFormat = valueEncoding.format - if (err) { - this.nextTick(callback, err) - return callback[kPromise] + // Forward encoding options + if (options.keyEncoding !== keyFormat || options.valueEncoding !== valueFormat) { + options = { ...options, keyEncoding: keyFormat, valueEncoding: valueFormat } } - mappedKeys[i] = this.prefixKey(keyEncoding.encode(key), keyFormat) - } + const mappedKeys = new Array(keys.length) - this._getMany(mappedKeys, options, (err, values) => { - if (err) return callback(err) + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + const err = this._checkKey(key) - try { - for (let i = 0; i < values.length; i++) { - if (values[i] !== undefined) { - values[i] = valueEncoding.decode(values[i]) - } + if (err) { + this.nextTick(callback, err) + return callback[kPromise] } - } 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) - }) + mappedKeys[i] = this.prefixKey(keyEncoding.encode(key), keyFormat) + } - return callback[kPromise] -} + this._getMany(mappedKeys, options, (err, values) => { + if (err) return callback(err) -AbstractLevel.prototype._getMany = function (keys, options, callback) { - this.nextTick(callback, null, new Array(keys.length).fill(undefined)) -} + 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 + })) + } -AbstractLevel.prototype.put = function (key, value, options, callback) { - callback = getCallback(options, callback) - callback = fromCallback(callback, kPromise) - options = getOptions(options, this[kDefaultOptions].entry) + callback(null, values) + }) - if (this[kStatus] === 'opening') { - this.defer(() => this.put(key, value, options, callback)) return callback[kPromise] } - if (maybeError(this, callback)) { - return callback[kPromise] + _getMany (keys, options, callback) { + this.nextTick(callback, null, new Array(keys.length).fill(undefined)) } - const err = this._checkKey(key) || this._checkValue(value) + put (key, value, options, callback) { + callback = getCallback(options, callback) + callback = fromCallback(callback, kPromise) + options = getOptions(options, this[kDefaultOptions].entry) - if (err) { - this.nextTick(callback, err) - return callback[kPromise] - } + if (this[kStatus] === 'opening') { + this.defer(() => this.put(key, value, options, callback)) + return callback[kPromise] + } - const keyEncoding = this.keyEncoding(options.keyEncoding) - const valueEncoding = this.valueEncoding(options.valueEncoding) - const keyFormat = keyEncoding.format - const valueFormat = valueEncoding.format + if (maybeError(this, callback)) { + return callback[kPromise] + } - // Forward encoding options - if (options.keyEncoding !== keyFormat || options.valueEncoding !== valueFormat) { - options = { ...options, keyEncoding: keyFormat, valueEncoding: valueFormat } - } + const err = this._checkKey(key) || this._checkValue(value) - const mappedKey = this.prefixKey(keyEncoding.encode(key), keyFormat) - const mappedValue = valueEncoding.encode(value) + if (err) { + this.nextTick(callback, err) + return callback[kPromise] + } - this._put(mappedKey, mappedValue, options, (err) => { - if (err) return callback(err) - this.emit('put', key, value) - callback() - }) + const keyEncoding = this.keyEncoding(options.keyEncoding) + const valueEncoding = this.valueEncoding(options.valueEncoding) + const keyFormat = keyEncoding.format + const valueFormat = valueEncoding.format - return callback[kPromise] -} + // Forward encoding options + if (options.keyEncoding !== keyFormat || options.valueEncoding !== valueFormat) { + options = { ...options, keyEncoding: keyFormat, valueEncoding: valueFormat } + } -AbstractLevel.prototype._put = function (key, value, options, callback) { - this.nextTick(callback) -} + const mappedKey = this.prefixKey(keyEncoding.encode(key), keyFormat) + const mappedValue = valueEncoding.encode(value) -AbstractLevel.prototype.del = function (key, options, callback) { - callback = getCallback(options, callback) - callback = fromCallback(callback, kPromise) - options = getOptions(options, this[kDefaultOptions].key) + this._put(mappedKey, mappedValue, options, (err) => { + if (err) return callback(err) + this.emit('put', key, value) + callback() + }) - if (this[kStatus] === 'opening') { - this.defer(() => this.del(key, options, callback)) return callback[kPromise] } - if (maybeError(this, callback)) { - return callback[kPromise] + _put (key, value, options, callback) { + this.nextTick(callback) } - const err = this._checkKey(key) + del (key, options, callback) { + callback = getCallback(options, callback) + callback = fromCallback(callback, kPromise) + options = getOptions(options, this[kDefaultOptions].key) - if (err) { - this.nextTick(callback, err) - return callback[kPromise] - } - - const keyEncoding = this.keyEncoding(options.keyEncoding) - const keyFormat = keyEncoding.format + if (this[kStatus] === 'opening') { + this.defer(() => this.del(key, options, callback)) + return callback[kPromise] + } - // Forward encoding options - if (options.keyEncoding !== keyFormat) { - options = { ...options, keyEncoding: keyFormat } - } + if (maybeError(this, callback)) { + return callback[kPromise] + } - this._del(this.prefixKey(keyEncoding.encode(key), keyFormat), options, (err) => { - if (err) return callback(err) - this.emit('del', key) - callback() - }) + const err = this._checkKey(key) - return callback[kPromise] -} + if (err) { + this.nextTick(callback, err) + return callback[kPromise] + } -AbstractLevel.prototype._del = function (key, options, callback) { - this.nextTick(callback) -} + const keyEncoding = this.keyEncoding(options.keyEncoding) + const keyFormat = keyEncoding.format -AbstractLevel.prototype.batch = function (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' - }) + // Forward encoding options + if (options.keyEncoding !== keyFormat) { + options = { ...options, keyEncoding: keyFormat } } - return this._chainedBatch() - } - - if (typeof operations === 'function') callback = operations - else callback = getCallback(options, callback) - callback = fromCallback(callback, kPromise) - options = getOptions(options, this[kDefaultOptions].empty) + this._del(this.prefixKey(keyEncoding.encode(key), keyFormat), options, (err) => { + if (err) return callback(err) + this.emit('del', key) + callback() + }) - if (this[kStatus] === 'opening') { - this.defer(() => this.batch(operations, options, callback)) return callback[kPromise] } - if (maybeError(this, callback)) { - return callback[kPromise] + _del (key, options, callback) { + this.nextTick(callback) } - if (!Array.isArray(operations)) { - this.nextTick(callback, new TypeError("The first argument 'operations' must be an array")) - return callback[kPromise] - } + 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 (operations.length === 0) { - this.nextTick(callback) - return callback[kPromise] - } + if (typeof operations === 'function') callback = operations + else callback = getCallback(options, callback) - const mapped = new Array(operations.length) - const { keyEncoding: ke, valueEncoding: ve, ...forward } = options + callback = fromCallback(callback, kPromise) + options = getOptions(options, this[kDefaultOptions].empty) - 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')) + if (this[kStatus] === 'opening') { + this.defer(() => this.batch(operations, options, callback)) 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'")) + if (maybeError(this, callback)) { return callback[kPromise] } - const err = this._checkKey(op.key) + if (!Array.isArray(operations)) { + this.nextTick(callback, new TypeError("The first argument 'operations' must be an array")) + return callback[kPromise] + } - if (err) { - this.nextTick(callback, err) + if (operations.length === 0) { + this.nextTick(callback) return callback[kPromise] } - const db = op.sublevel != null ? op.sublevel : this - const keyEncoding = db.keyEncoding(op.keyEncoding || ke) - const keyFormat = keyEncoding.format + const mapped = new Array(operations.length) + const { keyEncoding: ke, valueEncoding: ve, ...forward } = options - op.key = db.prefixKey(keyEncoding.encode(op.key), keyFormat) - op.keyEncoding = keyFormat + 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] + } - if (op.type === 'put') { - const valueErr = this._checkValue(op.value) + const op = Object.assign({}, operations[i]) - if (valueErr) { - this.nextTick(callback, valueErr) + 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 valueEncoding = db.valueEncoding(op.valueEncoding || ve) + const err = this._checkKey(op.key) - op.value = valueEncoding.encode(op.value) - op.valueEncoding = valueEncoding.format - } + if (err) { + this.nextTick(callback, err) + return callback[kPromise] + } - // Prevent double prefixing - if (db !== this) { - op.sublevel = null - } + const db = op.sublevel != null ? op.sublevel : this + const keyEncoding = db.keyEncoding(op.keyEncoding || ke) + const keyFormat = keyEncoding.format - mapped[i] = op - } + op.key = db.prefixKey(keyEncoding.encode(op.key), keyFormat) + op.keyEncoding = keyFormat - this._batch(mapped, forward, (err) => { - if (err) return callback(err) - this.emit('batch', operations) - callback() - }) + if (op.type === 'put') { + const valueErr = this._checkValue(op.value) - return callback[kPromise] -} + if (valueErr) { + this.nextTick(callback, valueErr) + return callback[kPromise] + } -AbstractLevel.prototype._batch = function (operations, options, callback) { - this.nextTick(callback) -} + const valueEncoding = db.valueEncoding(op.valueEncoding || ve) -AbstractLevel.prototype.sublevel = function (name, options) { - return this._sublevel(name, AbstractSublevel.defaults(options)) -} + op.value = valueEncoding.encode(op.value) + op.valueEncoding = valueEncoding.format + } -AbstractLevel.prototype._sublevel = function (name, options) { - return new AbstractSublevel(this, name, options) -} + // Prevent double prefixing + if (db !== this) { + op.sublevel = null + } -AbstractLevel.prototype.prefixKey = function (key, keyFormat) { - return key -} + mapped[i] = op + } -AbstractLevel.prototype.clear = function (options, callback) { - callback = getCallback(options, callback) - callback = fromCallback(callback, kPromise) - options = getOptions(options, this[kDefaultOptions].empty) + this._batch(mapped, forward, (err) => { + if (err) return callback(err) + this.emit('batch', operations) + callback() + }) - if (this[kStatus] === 'opening') { - this.defer(() => this.clear(options, callback)) return callback[kPromise] } - if (maybeError(this, callback)) { - return callback[kPromise] + _batch (operations, options, callback) { + this.nextTick(callback) } - const original = options - const keyEncoding = this.keyEncoding(options.keyEncoding) + sublevel (name, options) { + return this._sublevel(name, AbstractSublevel.defaults(options)) + } - options = rangeOptions(options, keyEncoding) - options.keyEncoding = keyEncoding.format + _sublevel (name, options) { + return new AbstractSublevel(this, name, options) + } - this._clear(options, (err) => { - if (err) return callback(err) - this.emit('clear', original) - callback() - }) + prefixKey (key, keyFormat) { + return key + } - return callback[kPromise] -} + clear (options, callback) { + callback = getCallback(options, callback) + callback = fromCallback(callback, kPromise) + options = getOptions(options, this[kDefaultOptions].empty) -AbstractLevel.prototype._clear = function (options, callback) { - this.nextTick(callback) -} + if (this[kStatus] === 'opening') { + this.defer(() => this.clear(options, callback)) + return callback[kPromise] + } -AbstractLevel.prototype.iterator = function (options) { - const keyEncoding = this.keyEncoding(options && options.keyEncoding) - const valueEncoding = this.valueEncoding(options && options.valueEncoding) + if (maybeError(this, callback)) { + return callback[kPromise] + } - options = rangeOptions(options, keyEncoding) - options.keys = options.keys !== false - options.values = options.values !== false + const original = options + const keyEncoding = this.keyEncoding(options.keyEncoding) - // We need the original encoding options in AbstractIterator in order to decode data - Object.defineProperty(options, AbstractIterator.keyEncoding, { value: keyEncoding }) - Object.defineProperty(options, AbstractIterator.valueEncoding, { value: valueEncoding }) + options = rangeOptions(options, keyEncoding) + options.keyEncoding = keyEncoding.format - // Forward encoding options to the underlying store - options.keyEncoding = keyEncoding.format - options.valueEncoding = valueEncoding.format + this._clear(options, (err) => { + if (err) return callback(err) + this.emit('clear', original) + callback() + }) - if (this[kStatus] === 'opening') { - return new DeferredIterator(this, options) + return callback[kPromise] } - if (this[kStatus] !== 'open') { - throw new ModuleError('Database is not open', { - code: 'LEVEL_DATABASE_NOT_OPEN' - }) + _clear (options, callback) { + this.nextTick(callback) } - return this._iterator(options) -} + iterator (options) { + const keyEncoding = this.keyEncoding(options && options.keyEncoding) + const valueEncoding = this.valueEncoding(options && options.valueEncoding) -AbstractLevel.prototype._iterator = function (options) { - return new AbstractIterator(this, options) -} + options = rangeOptions(options, keyEncoding) + options.keys = options.keys !== false + options.values = options.values !== false -AbstractLevel.prototype.defer = function (fn) { - if (typeof fn !== 'function') { - throw new TypeError('The first argument must be a function') - } + // We need the original encoding options in AbstractIterator in order to decode data + Object.defineProperty(options, AbstractIterator.keyEncoding, { value: keyEncoding }) + Object.defineProperty(options, AbstractIterator.valueEncoding, { value: valueEncoding }) - this[kOperations].push(fn) -} + // Forward encoding options to the underlying store + options.keyEncoding = keyEncoding.format + options.valueEncoding = valueEncoding.format -AbstractLevel.prototype[kUndefer] = function () { - if (this[kOperations].length === 0) { - return + if (this[kStatus] === 'opening') { + return new DeferredIterator(this, options) + } + + 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) } - const operations = this[kOperations] - this[kOperations] = [] + defer (fn) { + if (typeof fn !== 'function') { + throw new TypeError('The first argument must be a function') + } - for (const op of operations) { - op() + this[kOperations].push(fn) } -} -// TODO: docs and types -AbstractLevel.prototype.attachResource = function (resource) { - if (typeof resource !== 'object' || resource === null || - typeof resource.close !== 'function') { - throw new TypeError('The first argument must be a resource object') + [kUndefer] () { + if (this[kOperations].length === 0) { + return + } + + const operations = this[kOperations] + this[kOperations] = [] + + for (const op of operations) { + op() + } } - this[kResources].add(resource) -} + // 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') + } -// TODO: docs and types -AbstractLevel.prototype.detachResource = function (resource) { - this[kResources].delete(resource) -} + this[kResources].add(resource) + } -AbstractLevel.prototype._chainedBatch = function () { - return new DefaultChainedBatch(this) -} + // TODO: docs and types + detachResource (resource) { + this[kResources].delete(resource) + } -AbstractLevel.prototype._checkKey = function (key) { - if (key === null || key === undefined) { - return new ModuleError('Key cannot be null or undefined', { - code: 'LEVEL_INVALID_KEY' - }) + _chainedBatch () { + return new DefaultChainedBatch(this) } -} -AbstractLevel.prototype._checkValue = function (value) { - if (value === null || value === undefined) { - return new ModuleError('Value cannot be null or undefined', { - code: 'LEVEL_INVALID_VALUE' - }) + _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' + }) + } } } diff --git a/test/self.js b/test/self.js index 5765e63..98c34f3 100644 --- a/test/self.js +++ b/test/self.js @@ -73,11 +73,7 @@ require('./clear-test').args(test, testCommon) require('./clear-test').events(test, testCommon) function implement (ctor, methods) { - function Test () { - ctor.apply(this, arguments) - } - - Object.setPrototypeOf(Test.prototype, ctor.prototype) + class Test extends ctor {} for (const k in methods) { Test.prototype[k] = methods[k] @@ -662,7 +658,8 @@ test('test AbstractChainedBatch expects a db', function (t) { const Test = implement(AbstractChainedBatch) try { - Test() + // eslint-disable-next-line no-new + new Test() } catch (err) { t.is(err.message, 'The first argument must be an abstract-level database, received undefined') }