abstract-leveldown
Advanced tools
Comparing version 7.1.0 to 7.2.0
@@ -5,4 +5,8 @@ 'use strict' | ||
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 | ||
@@ -94,2 +98,47 @@ const rangeOptions = ['lt', 'lte', 'gt', 'gte'] | ||
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) { | ||
@@ -320,2 +369,12 @@ if (typeof options === 'function') callback = options | ||
// 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 | ||
@@ -327,1 +386,10 @@ // TODO: rename _nextTick to _queueMicrotask | ||
module.exports = AbstractLevelDOWN | ||
function maybeError (db, callback) { | ||
if (!db.isOperational()) { | ||
db._nextTick(callback, new Error('Database is not open')) | ||
return true | ||
} | ||
return false | ||
} |
# Changelog | ||
## [7.2.0] - 2021-09-28 | ||
### Added | ||
- Add `db.getMany(keys)` ([#381](https://github.com/Level/abstract-leveldown/issues/381)) ([`e4445a7`](https://github.com/Level/abstract-leveldown/commit/e4445a7)) (Vincent Weevers). | ||
## [7.1.0] - 2021-09-21 | ||
@@ -850,2 +856,4 @@ | ||
[7.2.0]: https://github.com/Level/abstract-leveldown/releases/tag/v7.2.0 | ||
[7.1.0]: https://github.com/Level/abstract-leveldown/releases/tag/v7.1.0 | ||
@@ -852,0 +860,0 @@ |
{ | ||
"name": "abstract-leveldown", | ||
"version": "7.1.0", | ||
"version": "7.2.0", | ||
"description": "An abstract prototype matching the LevelDOWN API", | ||
@@ -24,2 +24,3 @@ "license": "MIT", | ||
"index.js", | ||
"lib", | ||
"next-tick-browser.js", | ||
@@ -34,5 +35,6 @@ "next-tick.js", | ||
"buffer": "^6.0.3", | ||
"catering": "^2.0.0", | ||
"is-buffer": "^2.0.5", | ||
"level-concat-iterator": "^3.0.0", | ||
"level-supports": "^2.0.0", | ||
"level-supports": "^2.0.1", | ||
"queue-microtask": "^1.2.3" | ||
@@ -39,0 +41,0 @@ }, |
@@ -28,2 +28,3 @@ # abstract-leveldown | ||
- [`db.get(key[, options], callback)`](#dbgetkey-options-callback) | ||
- [`db.getMany(keys[, options][, callback])`](#dbgetmanykeys-options-callback) | ||
- [`db.put(key, value[, options], callback)`](#dbputkey-value-options-callback) | ||
@@ -55,2 +56,3 @@ - [`db.del(key[, options], callback)`](#dbdelkey-options-callback) | ||
- [`db._get(key, options, callback)`](#db_getkey-options-callback) | ||
- [`db._getMany(keys, options, callback)`](#db_getmanykeys-options-callback) | ||
- [`db._put(key, value, options, callback)`](#db_putkey-value-options-callback) | ||
@@ -223,4 +225,14 @@ - [`db._del(key, options, callback)`](#db_delkey-options-callback) | ||
The `callback` function will be called with an `Error` if the operation failed for any reason. If successful the first argument will be `null` and the second argument will be the value. | ||
The `callback` function will be called with an `Error` if the operation failed for any reason, including if the key was not found. If successful the first argument will be `null` and the second argument will be the value. | ||
### `db.getMany(keys[, options][, callback])` | ||
Get multiple values from the store by an array of `keys`. The optional `options` object may contain: | ||
- `asBuffer` _(boolean, default: `true`)_: Whether to return the `value` as a Buffer. If `false`, the returned type depends on the implementation. | ||
The `callback` function will be called with an `Error` if the operation failed for any reason. If successful the first argument will be `null` and the second argument will be an array of values with the same order as `keys`. If a key was not found, the relevant value will be `undefined`. | ||
If no callback is provided, a promise is returned. | ||
### `db.put(key, value[, options], callback)` | ||
@@ -441,2 +453,10 @@ | ||
### `db._getMany(keys, options, callback)` | ||
**This new method is optional for the time being. To enable its tests, set the [`getMany` option of the test suite](#excluding-tests) to `true`.** | ||
Get multiple values by an array of `keys`. The `options` object will always have the following properties: `asBuffer`. If an error occurs, call the `callback` function with an `Error`. Otherwise call `callback` with `null` as the first argument and an array of values as the second. If a key does not exist, set the relevant value to `undefined`. | ||
The default `_getMany()` invokes `callback` on a next tick with an array of values that is equal in length to `keys` and is filled with `undefined`. It must be overridden to support `getMany()` but this is currently an opt-in feature. If the implementation does support `getMany()` then `db.supports.getMany` must be set to true via the [constructor](#db--abstractleveldownmanifest). | ||
### `db._put(key, value, options, callback)` | ||
@@ -588,2 +608,3 @@ | ||
- `clear`: defaults to `false` until a next major release. Set to `true` if your implementation either implements `_clear()` itself or is suitable to use the default implementation of `_clear()` (which requires binary key support). | ||
- `getMany`: defaults to `false` until a next major release. Set to `true` if your implementation implements `_getMany()`. | ||
- `snapshots`: set to `false` if any of the following is true: | ||
@@ -590,0 +611,0 @@ - Reads don't operate on a [snapshot](#iterator) |
@@ -34,2 +34,3 @@ 'use strict' | ||
clear: !!options.clear, | ||
getMany: !!options.getMany, | ||
@@ -46,3 +47,2 @@ // Support running test suite on a levelup db. All options below this line | ||
// Not yet used, only here for symmetry with levelup's test suite. | ||
deferredOpen: !!options.deferredOpen, | ||
@@ -49,0 +49,0 @@ streams: !!options.streams |
@@ -42,18 +42,2 @@ 'use strict' | ||
}) | ||
testCommon.serialize && test('test custom _serialize*', function (t) { | ||
t.plan(3) | ||
const db = testCommon.factory() | ||
db._serializeKey = function (data) { return data } | ||
db._del = function (key, options, callback) { | ||
t.deepEqual(key, { foo: 'bar' }) | ||
this._nextTick(callback) | ||
} | ||
db.open(function () { | ||
db.del({ foo: 'bar' }, function (err) { | ||
t.error(err) | ||
db.close(t.error.bind(t)) | ||
}) | ||
}) | ||
}) | ||
} | ||
@@ -60,0 +44,0 @@ |
'use strict' | ||
const isBuffer = require('is-buffer') | ||
const verifyNotFoundError = require('./util').verifyNotFoundError | ||
@@ -43,18 +44,2 @@ const isTypedArray = require('./util').isTypedArray | ||
}) | ||
testCommon.serialize && test('test custom _serialize*', function (t) { | ||
t.plan(3) | ||
const db = testCommon.factory() | ||
db._serializeKey = function (data) { return data } | ||
db._get = function (key, options, callback) { | ||
t.deepEqual(key, { foo: 'bar' }) | ||
this._nextTick(callback) | ||
} | ||
db.open(function () { | ||
db.get({ foo: 'bar' }, function (err) { | ||
t.error(err) | ||
db.close(t.error.bind(t)) | ||
}) | ||
}) | ||
}) | ||
} | ||
@@ -72,3 +57,3 @@ | ||
if (!testCommon.encodings) { | ||
t.ok(typeof value !== 'string', 'should not be string by default') | ||
t.isNot(typeof value, 'string', 'should not be string by default') | ||
@@ -78,3 +63,3 @@ if (isTypedArray(value)) { | ||
} else { | ||
t.ok(typeof Buffer !== 'undefined' && value instanceof Buffer) | ||
t.ok(isBuffer(value)) | ||
try { | ||
@@ -90,3 +75,3 @@ result = value.toString() | ||
t.equal(result, 'bar') | ||
t.is(result, 'bar') | ||
@@ -104,3 +89,3 @@ db.get('foo', {}, function (err, value) { // same but with {} | ||
} else { | ||
t.ok(typeof Buffer !== 'undefined' && value instanceof Buffer) | ||
t.ok(isBuffer(value)) | ||
try { | ||
@@ -116,3 +101,3 @@ result = value.toString() | ||
t.equal(result, 'bar') | ||
t.is(result, 'bar') | ||
@@ -122,3 +107,3 @@ db.get('foo', { asBuffer: false }, function (err, value) { | ||
t.ok(typeof value === 'string', 'should be string if not buffer') | ||
t.equal(value, 'bar') | ||
t.is(value, 'bar') | ||
t.end() | ||
@@ -131,16 +116,14 @@ }) | ||
test('test simultaniously get()', function (t) { | ||
test('test simultaneous get()', function (t) { | ||
db.put('hello', 'world', function (err) { | ||
t.error(err) | ||
let r = 0 | ||
let completed = 0 | ||
const done = function () { | ||
if (++r === 20) { t.end() } | ||
if (++completed === 20) t.end() | ||
} | ||
let i = 0 | ||
let j = 0 | ||
for (; i < 10; ++i) { | ||
for (let i = 0; i < 10; ++i) { | ||
db.get('hello', function (err, value) { | ||
t.error(err) | ||
t.equal(value.toString(), 'world') | ||
t.is(value.toString(), 'world') | ||
done() | ||
@@ -150,3 +133,3 @@ }) | ||
for (; j < 10; ++j) { | ||
for (let i = 0; i < 10; ++i) { | ||
db.get('not found', function (err, value) { | ||
@@ -163,18 +146,14 @@ t.ok(err, 'should error') | ||
test('test get() not found error is asynchronous', function (t) { | ||
t.plan(5) | ||
t.plan(4) | ||
db.put('hello', 'world', function (err) { | ||
t.error(err) | ||
let async = false | ||
let async = false | ||
db.get('not found', function (err, value) { | ||
t.ok(err, 'should error') | ||
t.ok(verifyNotFoundError(err), 'should have correct error message') | ||
t.ok(typeof value === 'undefined', 'value is undefined') | ||
t.ok(async, 'callback is asynchronous') | ||
}) | ||
db.get('not found', function (err, value) { | ||
t.ok(err, 'should error') | ||
t.ok(verifyNotFoundError(err), 'should have correct error message') | ||
t.ok(typeof value === 'undefined', 'value is undefined') | ||
t.ok(async, 'callback is asynchronous') | ||
}) | ||
async = true | ||
}) | ||
async = true | ||
}) | ||
@@ -181,0 +160,0 @@ } |
@@ -29,2 +29,6 @@ 'use strict' | ||
if (testCommon.getMany) { | ||
require('./get-many-test').all(test, testCommon) | ||
} | ||
require('./batch-test').all(test, testCommon) | ||
@@ -31,0 +35,0 @@ require('./chained-batch-test').all(test, testCommon) |
@@ -16,4 +16,4 @@ 'use strict' | ||
const iterator = db.iterator() | ||
// For levelup compat: may return iterator of an underlying db, that's okay. | ||
t.ok(iterator.db === db || iterator.db) | ||
// For levelup & deferred-leveldown compat: may return iterator of an underlying db, that's okay. | ||
t.ok(iterator.db === db || iterator.db === (db.db || db._db || db)) | ||
iterator.end(t.end.bind(t)) | ||
@@ -20,0 +20,0 @@ }) |
'use strict' | ||
const verifyNotFoundError = require('./util').verifyNotFoundError | ||
const assertAsync = require('./util').assertAsync | ||
const testBuffer = Buffer.from('testbuffer') | ||
@@ -8,3 +9,3 @@ | ||
function makeGetDelErrorTests (test, type, key, expectedError) { | ||
function makeGetDelErrorTests (test, testCommon, type, key, expectedError) { | ||
test('test get() with ' + type + ' causes error', function (t) { | ||
@@ -37,2 +38,19 @@ let async = false | ||
}) | ||
testCommon.getMany && test('test getMany() with ' + type + ' causes error', assertAsync.ctx(function (t) { | ||
// Add 1 assertion for every assertAsync() | ||
t.plan(2 * 4) | ||
db.getMany([key], assertAsync(function (err) { | ||
t.ok(err, 'has error') | ||
t.ok(err instanceof Error) | ||
t.ok(err.message.match(expectedError), 'correct error message') | ||
})) | ||
db.getMany(['valid', key], assertAsync(function (err) { | ||
t.ok(err, 'has error') | ||
t.ok(err instanceof Error) | ||
t.ok(err.message.match(expectedError), 'correct error message') | ||
})) | ||
})) | ||
} | ||
@@ -101,4 +119,4 @@ | ||
function makeErrorKeyTest (test, type, key, expectedError) { | ||
makeGetDelErrorTests(test, type, key, expectedError) | ||
function makeErrorKeyTest (test, testCommon, type, key, expectedError) { | ||
makeGetDelErrorTests(test, testCommon, type, key, expectedError) | ||
makePutErrorTest(test, type, key, 'foo', expectedError) | ||
@@ -116,7 +134,7 @@ } | ||
exports.errorKeys = function (test, testCommon) { | ||
makeErrorKeyTest(test, 'null key', null, /key cannot be `null` or `undefined`/) | ||
makeErrorKeyTest(test, 'undefined key', undefined, /key cannot be `null` or `undefined`/) | ||
makeErrorKeyTest(test, 'empty String key', '', /key cannot be an empty String/) | ||
makeErrorKeyTest(test, 'empty Buffer key', Buffer.alloc(0), /key cannot be an empty \w*Buffer/) | ||
makeErrorKeyTest(test, 'empty Array key', [], /key cannot be an empty Array/) | ||
makeErrorKeyTest(test, testCommon, 'null key', null, /key cannot be `null` or `undefined`/) | ||
makeErrorKeyTest(test, testCommon, 'undefined key', undefined, /key cannot be `null` or `undefined`/) | ||
makeErrorKeyTest(test, testCommon, 'empty String key', '', /key cannot be an empty String/) | ||
makeErrorKeyTest(test, testCommon, 'empty Buffer key', Buffer.alloc(0), /key cannot be an empty \w*Buffer/) | ||
makeErrorKeyTest(test, testCommon, 'empty Array key', [], /key cannot be an empty Array/) | ||
} | ||
@@ -123,0 +141,0 @@ |
@@ -51,32 +51,2 @@ 'use strict' | ||
}) | ||
testCommon.serialize && test('test _serialize object', function (t) { | ||
t.plan(3) | ||
const db = testCommon.factory() | ||
db._put = function (key, value, opts, callback) { | ||
t.ok(key) | ||
t.ok(value) | ||
this._nextTick(callback) | ||
} | ||
db.put({}, {}, function (err, val) { | ||
t.error(err) | ||
}) | ||
}) | ||
testCommon.serialize && test('test custom _serialize*', function (t) { | ||
t.plan(4) | ||
const db = testCommon.factory() | ||
db._serializeKey = db._serializeValue = function (data) { return data } | ||
db._put = function (key, value, options, callback) { | ||
t.deepEqual(key, { foo: 'bar' }) | ||
t.deepEqual(value, { beep: 'boop' }) | ||
this._nextTick(callback) | ||
} | ||
db.open(function () { | ||
db.put({ foo: 'bar' }, { beep: 'boop' }, function (err) { | ||
t.error(err) | ||
db.close(t.error.bind(t)) | ||
}) | ||
}) | ||
}) | ||
} | ||
@@ -83,0 +53,0 @@ |
@@ -39,2 +39,5 @@ 'use strict' | ||
require('./get-many-test').setUp(test, testCommon) | ||
require('./get-many-test').args(test, testCommon) | ||
require('./put-test').setUp(test, testCommon) | ||
@@ -237,2 +240,33 @@ require('./put-test').args(test, testCommon) | ||
test('test getMany() extensibility', function (t) { | ||
const spy = sinon.spy() | ||
const expectedCb = function () {} | ||
const expectedOptions = { asBuffer: true } | ||
const expectedKey = 'a key' | ||
const Test = implement(AbstractLevelDOWN, { _getMany: spy }) | ||
const test = new Test('foobar') | ||
test.status = 'open' | ||
test.getMany([expectedKey], expectedCb) | ||
t.equal(spy.callCount, 1, 'got _getMany() call') | ||
t.equal(spy.getCall(0).thisValue, test, '`this` on _getMany() was correct') | ||
t.equal(spy.getCall(0).args.length, 3, 'got three arguments') | ||
t.deepEqual(spy.getCall(0).args[0], [expectedKey], 'got expected keys argument') | ||
t.deepEqual(spy.getCall(0).args[1], expectedOptions, 'got default options argument') | ||
t.equal(spy.getCall(0).args[2], expectedCb, 'got expected cb argument') | ||
test.getMany([expectedKey], { options: 1 }, expectedCb) | ||
expectedOptions.options = 1 | ||
t.equal(spy.callCount, 2, 'got _getMany() call') | ||
t.equal(spy.getCall(1).thisValue, test, '`this` on _getMany() was correct') | ||
t.equal(spy.getCall(1).args.length, 3, 'got three arguments') | ||
t.deepEqual(spy.getCall(1).args[0], [expectedKey], 'got expected key argument') | ||
t.deepEqual(spy.getCall(1).args[1], expectedOptions, 'got expected options argument') | ||
t.equal(spy.getCall(1).args[2], expectedCb, 'got expected cb argument') | ||
t.end() | ||
}) | ||
test('test del() extensibility', function (t) { | ||
@@ -624,2 +658,40 @@ const spy = sinon.spy() | ||
test('test serialization extensibility (get)', function (t) { | ||
t.plan(2) | ||
const spy = sinon.spy() | ||
const Test = implement(AbstractLevelDOWN, { | ||
_get: spy, | ||
_serializeKey: function (key) { | ||
return key.toUpperCase() | ||
} | ||
}) | ||
const test = new Test() | ||
test.get('foo', function () {}) | ||
t.is(spy.callCount, 1, 'got _get() call') | ||
t.is(spy.getCall(0).args[0], 'FOO', 'got expected key argument') | ||
}) | ||
test('test serialization extensibility (getMany)', function (t) { | ||
t.plan(2) | ||
const spy = sinon.spy() | ||
const Test = implement(AbstractLevelDOWN, { | ||
_getMany: spy, | ||
_serializeKey: function (key) { | ||
return key.toUpperCase() | ||
} | ||
}) | ||
const test = new Test() | ||
test.status = 'open' | ||
test.getMany(['foo', 'bar'], function () {}) | ||
t.is(spy.callCount, 1, 'got _getMany() call') | ||
t.same(spy.getCall(0).args[0], ['FOO', 'BAR'], 'got expected keys argument') | ||
}) | ||
test('test serialization extensibility (put)', function (t) { | ||
@@ -915,2 +987,4 @@ t.plan(5) | ||
}) | ||
t.equal(test.status, 'opening') | ||
}) | ||
@@ -917,0 +991,0 @@ |
'use strict' | ||
const nfre = /NotFound/i | ||
const spies = [] | ||
@@ -13,1 +14,60 @@ exports.verifyNotFoundError = function verifyNotFoundError (err) { | ||
} | ||
/** | ||
* Wrap a callback to check that it's called asynchronously. Must be | ||
* combined with a `ctx()`, `with()` or `end()` call. | ||
* | ||
* @param {function} cb Callback to check. | ||
* @param {string} name Optional callback name to use in assertion messages. | ||
* @returns {function} Wrapped callback. | ||
*/ | ||
exports.assertAsync = function (cb, name) { | ||
const spy = { | ||
called: false, | ||
name: name || cb.name || 'anonymous' | ||
} | ||
spies.push(spy) | ||
return function (...args) { | ||
spy.called = true | ||
return cb.apply(this, args) | ||
} | ||
} | ||
/** | ||
* Verify that callbacks wrapped with `assertAsync()` were not yet called. | ||
* @param {import('tape').Test} t Tape test object. | ||
*/ | ||
exports.assertAsync.end = function (t) { | ||
for (const { called, name } of spies.splice(0, spies.length)) { | ||
t.is(called, false, `callback (${name}) is asynchronous`) | ||
} | ||
} | ||
/** | ||
* Wrap a test function to verify `assertAsync()` spies at the end. | ||
* @param {import('tape').TestCase} test Test function to be passed to `tape()`. | ||
* @returns {import('tape').TestCase} Wrapped test function. | ||
*/ | ||
exports.assertAsync.ctx = function (test) { | ||
return function (...args) { | ||
const ret = test.call(this, ...args) | ||
exports.assertAsync.end(args[0]) | ||
return ret | ||
} | ||
} | ||
/** | ||
* Wrap an arbitrary callback to verify `assertAsync()` spies at the end. | ||
* @param {import('tape').Test} t Tape test object. | ||
* @param {function} cb Callback to wrap. | ||
* @returns {function} Wrapped callback. | ||
*/ | ||
exports.assertAsync.with = function (t, cb) { | ||
return function (...args) { | ||
const ret = cb.call(this, ...args) | ||
exports.assertAsync.end(t) | ||
return ret | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
242559
39
4135
708
6
+ Addedcatering@^2.0.0
Updatedlevel-supports@^2.0.1