diff --git a/README.md b/README.md index 3eaf52e..3cb3600 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ ## Table of Contents - [Implementations](#implementations) +- [Adapter](#adapter) - [Install](#install) - [Usage](#usage) - [Wrapping Stores](#wrapping-stores) @@ -54,7 +55,7 @@ - [Example](#example-8) - [`put(key, value)`](#putkey-value) - [`delete(key)`](#deletekey) - - [`commit([options])` -> `AsyncIterator`](#commitoptions---asynciterator) + - [`commit([options])` -> `Promise`](#commitoptions---promisevoid) - [Arguments](#arguments-8) - [Example](#example-9) - [`open()` -> `Promise`](#open---promise) @@ -88,6 +89,32 @@ const fs = new FsStore('path/to/store') const flatfs = await ShardingStore.createOrOpen(fs, new NextToLast(2)) ``` +## Adapter + +An adapter is made available to make implementing your own datastore easier: + +```javascript +const { Adapter } = require('interface-datastore') + +class MyDatastore extends Adapter { + constructor () { + super() + } + + async put (key, val) { + // your implementation here + } + + async get (key) { + // your implementation here + } + + // etc... +} +``` + +See the [MemoryDatastore](./src/memory.js) for an example of how it is used. + ## Install ```sh @@ -385,7 +412,7 @@ Queue a delete operation to the store. | ---- | ---- | ----------- | | key | [Key][] | The key to remove the value for | -#### `commit([options])` -> `AsyncIterator` +#### `commit([options])` -> `Promise` Write all queued operations to the underyling store. The batch object should not be used after calling this. @@ -404,13 +431,7 @@ const batch = store.batch() batch.put(new Key('to-put'), Buffer.from('hello world')) batch.del(new Key('to-remove')) -for await (const res of batch.commit()) { - if (res.key) { - console.info('put', res.key) - } else { - console.info('del', res) - } -} +await batch.commit() ``` ### `open()` -> `Promise` diff --git a/src/adapter.js b/src/adapter.js new file mode 100644 index 0000000..42be9e5 --- /dev/null +++ b/src/adapter.js @@ -0,0 +1,175 @@ +'use strict' + +const { filter, sortAll, take, map } = require('./utils') +const drain = require('it-drain') + +class InterfaceDatastoreAdapter { + async open () { // eslint-disable-line require-await + + } + + async close () { // eslint-disable-line require-await + + } + + /** + * Store the passed value under the passed key + * + * @param {Key} key + * @param {Buffer} val + * @param {Object} options + * @returns {Promise} + */ + async put (key, val, options = {}) { // eslint-disable-line require-await + + } + + /** + * Store the given key/value pairs + * + * @param {AsyncIterator<{ key: Key, value: Buffer }>} source + * @param {Object} options + * @returns {AsyncIterator<{ key: Key, value: Buffer }>} + */ + async * putMany (source, options = {}) { + for await (const { key, value } of source) { + await this.put(key, value, options) + yield { key, value } + } + } + + /** + * Retrieve the value for the passed key + * + * @param {Key} key + * @param {Object} options + * @returns {Promise} + */ + async get (key, options = {}) { // eslint-disable-line require-await + + } + + /** + * Retrieve values for the passed keys + * + * @param {AsyncIterator} source + * @param {Object} options + * @returns {AsyncIterator} + */ + async * getMany (source, options = {}) { + for await (const key of source) { + yield this.get(key, options) + } + } + + /** + * Check for the existence of a value for the passed key + * + * @param {Key} key + * @returns {Promise} + */ + async has (key) { // eslint-disable-line require-await + + } + + /** + * Remove the record for the passed key + * + * @param {Key} key + * @param {Object} options + * @returns {Promise} + */ + async delete (key, options = {}) { // eslint-disable-line require-await + + } + + /** + * Remove values for the passed keys + * + * @param {AsyncIterator} source + * @param {Object} options + * @returns {AsyncIterator} + */ + async * deleteMany (source, options = {}) { + for await (const key of source) { + await this.delete(key, options) + yield key + } + } + + /** + * Create a new batch object. + * + * @returns {Object} + */ + batch () { + let puts = [] + let dels = [] + + return { + put (key, value) { + puts.push({ key, value }) + }, + delete (key) { + dels.push(key) + }, + commit: async (options) => { + await drain(this.putMany(puts, options)) + puts = [] + await drain(this.deleteMany(dels, options)) + dels = [] + } + } + } + + /** + * Yield all datastore values + * + * @param {Object} q + * @param {Object} options + * @returns {AsyncIterable<{ key: Key, value: Buffer }>} + */ + async * _all (q, options) { // eslint-disable-line require-await + + } + + /** + * Query the store. + * + * @param {Object} q + * @param {Object} options + * @returns {AsyncIterable} + */ + async * query (q, options) { // eslint-disable-line require-await + let it = this._all(q, options) + + if (q.prefix != null) { + it = filter(it, e => e.key.toString().startsWith(q.prefix)) + } + + if (Array.isArray(q.filters)) { + it = q.filters.reduce((it, f) => filter(it, f), it) + } + + if (Array.isArray(q.orders)) { + it = q.orders.reduce((it, f) => sortAll(it, f), it) + } + + if (q.offset != null) { + let i = 0 + it = filter(it, () => i++ >= q.offset) + } + + if (q.limit != null) { + it = take(it, q.limit) + } + + if (q.keysOnly === true) { + it = map(it, e => ({ key: e.key })) + } + + yield * it + } +} + +module.exports = InterfaceDatastoreAdapter diff --git a/src/index.js b/src/index.js index 4d798ae..2226059 100644 --- a/src/index.js +++ b/src/index.js @@ -4,8 +4,10 @@ const Key = require('./key') const MemoryDatastore = require('./memory') const utils = require('./utils') const Errors = require('./errors') +const Adapter = require('./adapter') exports.Key = Key exports.MemoryDatastore = MemoryDatastore exports.utils = utils exports.Errors = Errors +exports.Adapter = Adapter diff --git a/src/memory.js b/src/memory.js index d574d56..eb659d3 100644 --- a/src/memory.js +++ b/src/memory.js @@ -1,53 +1,28 @@ 'use strict' -const { filter, sortAll, take, map } = require('./utils') const Key = require('./key') +const Adapter = require('./adapter') // Errors const Errors = require('./errors') -function throwIfAborted (signal) { - if (signal && signal.aborted) { - throw Error.abortedError() - } -} - -class MemoryDatastore { +class MemoryDatastore extends Adapter { constructor () { + super() + this.data = {} } - async open () {} - async put (key, val) { // eslint-disable-line require-await this.data[key.toString()] = val } - async * putMany (source, options = {}) { - throwIfAborted(options.signal) - - for await (const { key, value } of source) { - throwIfAborted(options.signal) - await this.put(key, value) - yield { key, value } - } - } - async get (key) { const exists = await this.has(key) if (!exists) throw Errors.notFoundError() return this.data[key.toString()] } - async * getMany (source, options = {}) { - throwIfAborted(options.signal) - - for await (const key of source) { - throwIfAborted(options.signal) - yield this.get(key) - } - } - async has (key) { // eslint-disable-line require-await return this.data[key.toString()] !== undefined } @@ -56,72 +31,10 @@ class MemoryDatastore { delete this.data[key.toString()] } - async * deleteMany (source, options = {}) { - throwIfAborted(options.signal) - - for await (const key of source) { - throwIfAborted(options.signal) - await this.delete(key) - yield key - } + * _all () { + yield * Object.entries(this.data) + .map(([key, value]) => ({ key: new Key(key), value })) } - - batch () { - let puts = [] - let dels = [] - - const self = this - - return { - put (key, value) { - puts.push({ key, value }) - }, - delete (key) { - dels.push(key) - }, - async * commit (options) { // eslint-disable-line require-await - yield * self.putMany(puts, options) - puts = [] - yield * self.deleteMany(dels, options) - dels = [] - } - } - } - - query (q) { - let it = Object.entries(this.data) - - it = map(it, entry => ({ key: new Key(entry[0]), value: entry[1] })) - - if (q.prefix != null) { - it = filter(it, e => e.key.toString().startsWith(q.prefix)) - } - - if (Array.isArray(q.filters)) { - it = q.filters.reduce((it, f) => filter(it, f), it) - } - - if (Array.isArray(q.orders)) { - it = q.orders.reduce((it, f) => sortAll(it, f), it) - } - - if (q.offset != null) { - let i = 0 - it = filter(it, () => i++ >= q.offset) - } - - if (q.limit != null) { - it = take(it, q.limit) - } - - if (q.keysOnly === true) { - it = map(it, e => ({ key: e.key })) - } - - return it - } - - async close () {} } module.exports = MemoryDatastore diff --git a/src/tests.js b/src/tests.js index 156891c..da3897a 100644 --- a/src/tests.js +++ b/src/tests.js @@ -232,7 +232,7 @@ module.exports = (test) => { b.put(new Key('/q/two'), Buffer.from('2')) b.put(new Key('/q/three'), Buffer.from('3')) b.delete(new Key('/z/old')) - await drain(b.commit()) + await b.commit() const keys = ['/a/one', '/q/two', '/q/three', '/z/old'] const res = await Promise.all(keys.map(k => store.has(new Key(k)))) @@ -250,7 +250,7 @@ module.exports = (test) => { b.put(new Key(`/z/hello${i}`), randomBytes(128)) } - await drain(b.commit()) + await b.commit() const total = async iterable => { let count = 0 @@ -316,7 +316,7 @@ module.exports = (test) => { b.put(world.key, world.value) b.put(hello2.key, hello2.value) - return drain(b.commit()) + return b.commit() }) after(() => cleanup(store))