UNPKG

@usecannon/ganache

Version:

A library and cli to create a local blockchain for fast Ethereum development.

392 lines (292 loc) 10.6 kB
'use strict' const supports = require('level-supports') const isBuffer = require('is-buffer') const catering = require('catering') const AbstractIterator = require('./abstract-iterator') const AbstractChainedBatch = require('./abstract-chained-batch') const getCallback = require('./lib/common').getCallback const getOptions = require('./lib/common').getOptions const hasOwnProperty = Object.prototype.hasOwnProperty const rangeOptions = ['lt', 'lte', 'gt', 'gte'] function AbstractLevelDOWN (manifest) { this.status = 'new' // TODO (next major): make this mandatory this.supports = supports(manifest, { status: true }) } AbstractLevelDOWN.prototype.open = function (options, callback) { const oldStatus = this.status if (typeof options === 'function') callback = options if (typeof callback !== 'function') { throw new Error('open() requires a callback argument') } if (typeof options !== 'object' || options === null) options = {} options.createIfMissing = options.createIfMissing !== false options.errorIfExists = !!options.errorIfExists this.status = 'opening' this._open(options, (err) => { if (err) { this.status = oldStatus return callback(err) } this.status = 'open' callback() }) } AbstractLevelDOWN.prototype._open = function (options, callback) { this._nextTick(callback) } AbstractLevelDOWN.prototype.close = function (callback) { const oldStatus = this.status if (typeof callback !== 'function') { throw new Error('close() requires a callback argument') } this.status = 'closing' this._close((err) => { if (err) { this.status = oldStatus return callback(err) } this.status = 'closed' callback() }) } AbstractLevelDOWN.prototype._close = function (callback) { this._nextTick(callback) } AbstractLevelDOWN.prototype.get = function (key, options, callback) { if (typeof options === 'function') callback = options if (typeof callback !== 'function') { throw new Error('get() requires a callback argument') } const err = this._checkKey(key) if (err) return this._nextTick(callback, err) key = this._serializeKey(key) if (typeof options !== 'object' || options === null) options = {} options.asBuffer = options.asBuffer !== false this._get(key, options, callback) } AbstractLevelDOWN.prototype._get = function (key, options, callback) { this._nextTick(function () { callback(new Error('NotFound')) }) } AbstractLevelDOWN.prototype.getMany = function (keys, options, callback) { callback = getCallback(options, callback) callback = catering.fromCallback(callback) options = getOptions(options) if (maybeError(this, callback)) { return callback.promise } if (!Array.isArray(keys)) { this._nextTick(callback, new Error('getMany() requires an array argument')) return callback.promise } if (keys.length === 0) { this._nextTick(callback, null, []) return callback.promise } if (typeof options.asBuffer !== 'boolean') { options = { ...options, asBuffer: true } } const serialized = 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.promise } serialized[i] = this._serializeKey(key) } this._getMany(serialized, options, callback) return callback.promise } AbstractLevelDOWN.prototype._getMany = function (keys, options, callback) { this._nextTick(callback, null, new Array(keys.length).fill(undefined)) } AbstractLevelDOWN.prototype.put = function (key, value, options, callback) { if (typeof options === 'function') callback = options if (typeof callback !== 'function') { throw new Error('put() requires a callback argument') } const err = this._checkKey(key) || this._checkValue(value) if (err) return this._nextTick(callback, err) key = this._serializeKey(key) value = this._serializeValue(value) if (typeof options !== 'object' || options === null) options = {} this._put(key, value, options, callback) } AbstractLevelDOWN.prototype._put = function (key, value, options, callback) { this._nextTick(callback) } AbstractLevelDOWN.prototype.del = function (key, options, callback) { if (typeof options === 'function') callback = options if (typeof callback !== 'function') { throw new Error('del() requires a callback argument') } const err = this._checkKey(key) if (err) return this._nextTick(callback, err) key = this._serializeKey(key) if (typeof options !== 'object' || options === null) options = {} this._del(key, options, callback) } AbstractLevelDOWN.prototype._del = function (key, options, callback) { this._nextTick(callback) } AbstractLevelDOWN.prototype.batch = function (array, options, callback) { if (!arguments.length) return this._chainedBatch() if (typeof options === 'function') callback = options if (typeof array === 'function') callback = array if (typeof callback !== 'function') { throw new Error('batch(array) requires a callback argument') } if (!Array.isArray(array)) { return this._nextTick(callback, new Error('batch(array) requires an array argument')) } if (array.length === 0) { return this._nextTick(callback) } if (typeof options !== 'object' || options === null) options = {} const serialized = new Array(array.length) for (let i = 0; i < array.length; i++) { if (typeof array[i] !== 'object' || array[i] === null) { return this._nextTick(callback, new Error('batch(array) element must be an object and not `null`')) } const e = Object.assign({}, array[i]) if (e.type !== 'put' && e.type !== 'del') { return this._nextTick(callback, new Error("`type` must be 'put' or 'del'")) } const err = this._checkKey(e.key) if (err) return this._nextTick(callback, err) e.key = this._serializeKey(e.key) if (e.type === 'put') { const valueErr = this._checkValue(e.value) if (valueErr) return this._nextTick(callback, valueErr) e.value = this._serializeValue(e.value) } serialized[i] = e } this._batch(serialized, options, callback) } AbstractLevelDOWN.prototype._batch = function (array, options, callback) { this._nextTick(callback) } AbstractLevelDOWN.prototype.clear = function (options, callback) { if (typeof options === 'function') { callback = options } else if (typeof callback !== 'function') { throw new Error('clear() requires a callback argument') } options = cleanRangeOptions(this, options) options.reverse = !!options.reverse options.limit = 'limit' in options ? options.limit : -1 this._clear(options, callback) } AbstractLevelDOWN.prototype._clear = function (options, callback) { // Avoid setupIteratorOptions, would serialize range options a second time. options.keys = true options.values = false options.keyAsBuffer = true options.valueAsBuffer = true const iterator = this._iterator(options) const emptyOptions = {} const next = (err) => { if (err) { return iterator.end(function () { callback(err) }) } iterator.next((err, key) => { if (err) return next(err) if (key === undefined) return iterator.end(callback) // This could be optimized by using a batch, but the default _clear // is not meant to be fast. Implementations have more room to optimize // if they override _clear. Note: using _del bypasses key serialization. this._del(key, emptyOptions, next) }) } next() } AbstractLevelDOWN.prototype._setupIteratorOptions = function (options) { options = cleanRangeOptions(this, options) options.reverse = !!options.reverse options.keys = options.keys !== false options.values = options.values !== false options.limit = 'limit' in options ? options.limit : -1 options.keyAsBuffer = options.keyAsBuffer !== false options.valueAsBuffer = options.valueAsBuffer !== false return options } function cleanRangeOptions (db, options) { const result = {} for (const k in options) { if (!hasOwnProperty.call(options, k)) continue if (k === 'start' || k === 'end') { throw new Error('Legacy range options ("start" and "end") have been removed') } let opt = options[k] if (isRangeOption(k)) { // Note that we don't reject nullish and empty options here. While // those types are invalid as keys, they are valid as range options. opt = db._serializeKey(opt) } result[k] = opt } return result } function isRangeOption (k) { return rangeOptions.indexOf(k) !== -1 } AbstractLevelDOWN.prototype.iterator = function (options) { if (typeof options !== 'object' || options === null) options = {} options = this._setupIteratorOptions(options) return this._iterator(options) } AbstractLevelDOWN.prototype._iterator = function (options) { return new AbstractIterator(this) } AbstractLevelDOWN.prototype._chainedBatch = function () { return new AbstractChainedBatch(this) } AbstractLevelDOWN.prototype._serializeKey = function (key) { return key } AbstractLevelDOWN.prototype._serializeValue = function (value) { return value } AbstractLevelDOWN.prototype._checkKey = function (key) { if (key === null || key === undefined) { return new Error('key cannot be `null` or `undefined`') } else if (isBuffer(key) && key.length === 0) { // TODO: replace with typed array check return new Error('key cannot be an empty Buffer') } else if (key === '') { return new Error('key cannot be an empty String') } else if (Array.isArray(key) && key.length === 0) { return new Error('key cannot be an empty Array') } } AbstractLevelDOWN.prototype._checkValue = function (value) { if (value === null || value === undefined) { return new Error('value cannot be `null` or `undefined`') } } // TODO: docs and tests AbstractLevelDOWN.prototype.isOperational = function () { return this.status === 'open' || this._isOperational() } // Implementation may accept operations in other states too AbstractLevelDOWN.prototype._isOperational = function () { return false } // Expose browser-compatible nextTick for dependents // TODO: rename _nextTick to _queueMicrotask // TODO: after we drop node 10, also use queueMicrotask in node AbstractLevelDOWN.prototype._nextTick = require('./next-tick') module.exports = AbstractLevelDOWN function maybeError (db, callback) { if (!db.isOperational()) { db._nextTick(callback, new Error('Database is not open')) return true } return false }