diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9e81b21..3b0afed 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,7 +27,7 @@ jobs: strategy: matrix: os: [windows-latest, ubuntu-latest, macos-latest] - node: [12, 14] + node: [14, 16] fail-fast: true steps: - uses: actions/checkout@v2 @@ -57,11 +57,13 @@ jobs: steps: - uses: actions/checkout@v2 - run: npm install - - run: npx xvfb-maybe aegir test -t electron-main --bail + - run: npm run pretest + - run: npx xvfb-maybe aegir test -t electron-main --bail -f dist/cjs/node-test/*js test-electron-renderer: needs: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - run: npm install - - run: npx xvfb-maybe aegir test -t electron-renderer --bail \ No newline at end of file + - run: npm run pretest + - run: npx xvfb-maybe aegir test -t electron-renderer --bail -f dist/cjs/browser-test/*js diff --git a/README.md b/README.md index 4ccc64c..2176124 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![codecov](https://img.shields.io/codecov/c/github/ipfs/js-datastore-core.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-datastore-core) [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/ipfs/js-datastore-core/ci?label=ci&style=flat-square)](https://github.com/ipfs/js-datastore-core/actions?query=branch%3Amaster+workflow%3Aci+) -> Wrapping implementations for [interface-datastore](https://github.com/ipfs/interface-datastore). +> Implementations for [interface-datastore](https://github.com/ipfs/js-ipfs-interfaces/packages/interface-datastore). ## Lead Maintainer @@ -17,6 +17,7 @@ - [Implementations](#implementations) - [Install](#install) - [Usage](#usage) + - [BaseDatastore](#basedatastore) - [Wrapping Stores](#wrapping-stores) - [Contribute](#contribute) - [License](#license) @@ -38,12 +39,41 @@ $ npm install datastore-core ## Usage + +### BaseDatastore + +An base store is made available to make implementing your own datastore easier: + +```javascript +const { BaseDatastore } from 'datastore-core') + +class MyDatastore extends BaseDatastore { + 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. + ### Wrapping Stores ```js -const MemoryStore = require('interface-datastore').MemoryDatastore -const MountStore = require('datastore-core').MountDatastore -const Key = require('interface-datastore').Key +import { Key } from 'interface-datastore' +import { + MemoryStore, + MountStore +} from 'datastore-core' const store = new MountStore({prefix: new Key('/a'), datastore: new MemoryStore()}) ``` diff --git a/package.json b/package.json index 664dfcb..bfb1950 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,49 @@ "description": "Wrapper implementation for interface-datastore", "leadMaintainer": "Alex Potsides ", "main": "src/index.js", + "type": "module", "types": "dist/src/index.d.ts", + "exports": { + "./": { + "import": "./src/index.js" + }, + "./base": { + "import": "./src/base.js" + }, + "./errors": { + "import": "./src/errors.js" + }, + "./keytransform": { + "import": "./src/keytransform.js" + }, + "./memory": { + "import": "./src/memory.js" + }, + "./mount": { + "import": "./src/mount.js" + }, + "./namespace": { + "import": "./src/namespace.js" + }, + "./shard": { + "import": "./src/shard.js" + }, + "./sharding": { + "import": "./src/sharding.js" + }, + "./tiered": { + "import": "./src/tiered.js" + } + }, "scripts": { - "prepare": "aegir build --no-bundle", + "clean": "rimraf dist types", + "prepare": "aegir build", + "pretest": "aegir build --no-bundle --esm-tests", "test": "aegir test", "test:node": "aegir test -t node", "test:browser": "aegir test -t browser", "test:webworker": "aegir test -t webworker", - "lint": "aegir lint", + "lint": "aegir ts -p check && aegir lint", "release": "aegir release", "release-minor": "aegir release --type minor", "release-major": "aegir release --type major", @@ -41,13 +76,15 @@ "devDependencies": { "@types/debug": "^4.1.5", "aegir": "^35.0.2", - "interface-datastore-tests": "^1.0.0", + "interface-datastore-tests": "^2.0.3", "it-all": "^1.0.4", + "rimraf": "^3.0.2", "util": "^0.12.4" }, "dependencies": { "debug": "^4.1.1", - "interface-datastore": "^5.1.1", + "err-code": "^3.0.1", + "interface-datastore": "^6.0.2", "it-drain": "^1.0.4", "it-filter": "^1.0.2", "it-map": "^1.0.5", @@ -61,7 +98,10 @@ "node": ">=12.0.0" }, "eslintConfig": { - "extends": "ipfs" + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } }, "contributors": [ "achingbrain ", diff --git a/src/base.js b/src/base.js new file mode 100644 index 0000000..9c4517c --- /dev/null +++ b/src/base.js @@ -0,0 +1,226 @@ +import { sortAll } from './utils.js' +import drain from 'it-drain' +import filter from 'it-filter' +import take from 'it-take' + +/** + * @typedef {import('interface-store').Options} Options + * @typedef {import('interface-datastore').Key} Key + * @typedef {import('interface-datastore').Pair} Pair + * @typedef {import('interface-datastore').Datastore} Datastore + * @typedef {import('interface-datastore').Query} Query + * @typedef {import('interface-datastore').KeyQuery} KeyQuery + * @typedef {import('interface-datastore').Batch} Batch + */ + +/** + * @template O + * @typedef {import('interface-store').AwaitIterable} AwaitIterable + */ + +/** + * @implements {Datastore} + */ +export class BaseDatastore { + /** + * @returns {Promise} + */ + open () { + return Promise.reject(new Error('.open is not implemented')) + } + + /** + * @returns {Promise} + */ + close () { + return Promise.reject(new Error('.close is not implemented')) + } + + /** + * @param {Key} key + * @param {Uint8Array} val + * @param {Options} [options] + * @returns {Promise} + */ + put (key, val, options) { + return Promise.reject(new Error('.put is not implemented')) + } + + /** + * @param {Key} key + * @param {Options} [options] + * @returns {Promise} + */ + get (key, options) { + return Promise.reject(new Error('.get is not implemented')) + } + + /** + * @param {Key} key + * @param {Options} [options] + * @returns {Promise} + */ + has (key, options) { + return Promise.reject(new Error('.has is not implemented')) + } + + /** + * @param {Key} key + * @param {Options} [options] + * @returns {Promise} + */ + delete (key, options) { + return Promise.reject(new Error('.delete is not implemented')) + } + + /** + * @param {AwaitIterable} source + * @param {Options} [options] + * @returns {AsyncIterable} + */ + async * putMany (source, options = {}) { + for await (const { key, value } of source) { + await this.put(key, value, options) + yield { key, value } + } + } + + /** + * @param {AwaitIterable} source + * @param {Options} [options] + * @returns {AsyncIterable} + */ + async * getMany (source, options = {}) { + for await (const key of source) { + yield this.get(key, options) + } + } + + /** + * @param {AwaitIterable} source + * @param {Options} [options] + * @returns {AsyncIterable} + */ + async * deleteMany (source, options = {}) { + for await (const key of source) { + await this.delete(key, options) + yield key + } + } + + /** + * @returns {Batch} + */ + batch () { + /** @type {Pair[]} */ + let puts = [] + /** @type {Key[]} */ + 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 = [] + } + } + } + + /** + * Extending classes should override `query` or implement this method + * + * @param {Query} q + * @param {Options} [options] + * @returns {AsyncIterable} + */ + // eslint-disable-next-line require-yield + async * _all (q, options) { + throw new Error('._all is not implemented') + } + + /** + * Extending classes should override `queryKeys` or implement this method + * + * @param {KeyQuery} q + * @param {Options} [options] + * @returns {AsyncIterable} + */ + // eslint-disable-next-line require-yield + async * _allKeys (q, options) { + throw new Error('._allKeys is not implemented') + } + + /** + * @param {Query} q + * @param {Options} [options] + */ + query (q, options) { + let it = this._all(q, options) + + if (q.prefix != null) { + it = filter(it, (e) => + e.key.toString().startsWith(/** @type {string} */ (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++ >= /** @type {number} */ (q.offset)) + } + + if (q.limit != null) { + it = take(it, q.limit) + } + + return it + } + + /** + * @param {KeyQuery} q + * @param {Options} [options] + */ + queryKeys (q, options) { + let it = this._allKeys(q, options) + + if (q.prefix != null) { + it = filter(it, (key) => + key.toString().startsWith(/** @type {string} */ (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++ >= /** @type {number} */ (q.offset)) + } + + if (q.limit != null) { + it = take(it, q.limit) + } + + return it + } +} diff --git a/src/errors.js b/src/errors.js new file mode 100644 index 0000000..a9494af --- /dev/null +++ b/src/errors.js @@ -0,0 +1,41 @@ +import errCode from 'err-code' + +/** + * @param {Error} [err] + */ +export function dbOpenFailedError (err) { + err = err || new Error('Cannot open database') + return errCode(err, 'ERR_DB_OPEN_FAILED') +} + +/** + * @param {Error} [err] + */ +export function dbDeleteFailedError (err) { + err = err || new Error('Delete failed') + return errCode(err, 'ERR_DB_DELETE_FAILED') +} + +/** + * @param {Error} [err] + */ +export function dbWriteFailedError (err) { + err = err || new Error('Write failed') + return errCode(err, 'ERR_DB_WRITE_FAILED') +} + +/** + * @param {Error} [err] + */ +export function notFoundError (err) { + err = err || new Error('Not Found') + return errCode(err, 'ERR_NOT_FOUND') +} + +/** + * @param {Error} [err] + */ +export function abortedError (err) { + err = err || new Error('Aborted') + return errCode(err, 'ERR_ABORTED') +} diff --git a/src/index.js b/src/index.js index f764ea4..9d27251 100644 --- a/src/index.js +++ b/src/index.js @@ -1,22 +1,13 @@ -'use strict' - -const KeytransformDatastore = require('./keytransform') -const ShardingDatastore = require('./sharding') -const MountDatastore = require('./mount') -const TieredDatastore = require('./tiered') -const NamespaceDatastore = require('./namespace') -const shard = require('./shard') +export { BaseDatastore } from './base.js' +export { MemoryDatastore } from './memory.js' +export { KeyTransformDatastore } from './keytransform.js' +export { ShardingDatastore } from './sharding.js' +export { MountDatastore } from './mount.js' +export { TieredDatastore } from './tiered.js' +export { NamespaceDatastore } from './namespace.js' +export * as shard from './shard.js' /** * @typedef {import("./types").Shard } Shard * @typedef {import("./types").KeyTransform } KeyTransform */ - -module.exports = { - KeytransformDatastore, - ShardingDatastore, - MountDatastore, - TieredDatastore, - NamespaceDatastore, - shard -} diff --git a/src/keytransform.js b/src/keytransform.js index a5fb88f..b22cd36 100644 --- a/src/keytransform.js +++ b/src/keytransform.js @@ -1,8 +1,6 @@ -'use strict' - -const { Adapter } = require('interface-datastore') -const map = require('it-map') -const { pipe } = require('it-pipe') +import { BaseDatastore } from './base.js' +import map from 'it-map' +import { pipe } from 'it-pipe' /** * @typedef {import('interface-datastore').Datastore} Datastore @@ -27,7 +25,7 @@ const { pipe } = require('it-pipe') * * @implements {Datastore} */ -class KeyTransformDatastore extends Adapter { +export class KeyTransformDatastore extends BaseDatastore { /** * @param {Datastore} child * @param {KeyTransform} transform @@ -193,5 +191,3 @@ class KeyTransformDatastore extends Adapter { return this.child.close() } } - -module.exports = KeyTransformDatastore diff --git a/src/memory.js b/src/memory.js new file mode 100644 index 0000000..2a0dc39 --- /dev/null +++ b/src/memory.js @@ -0,0 +1,71 @@ +import { BaseDatastore } from './base.js' +import { Key } from 'interface-datastore/key' +import * as Errors from './errors.js' + +/** + * @typedef {import('interface-datastore').Pair} Pair + * @typedef {import('interface-datastore').Datastore} Datastore + * @typedef {import('interface-store').Options} Options + */ + +/** + * @class MemoryDatastore + * @implements {Datastore} + */ +export class MemoryDatastore extends BaseDatastore { + constructor () { + super() + + /** @type {Record} */ + this.data = {} + } + + open () { + return Promise.resolve() + } + + close () { + return Promise.resolve() + } + + /** + * @param {Key} key + * @param {Uint8Array} val + */ + async put (key, val) { // eslint-disable-line require-await + this.data[key.toString()] = val + } + + /** + * @param {Key} key + */ + async get (key) { + const exists = await this.has(key) + if (!exists) throw Errors.notFoundError() + return this.data[key.toString()] + } + + /** + * @param {Key} key + */ + async has (key) { // eslint-disable-line require-await + return this.data[key.toString()] !== undefined + } + + /** + * @param {Key} key + */ + async delete (key) { // eslint-disable-line require-await + delete this.data[key.toString()] + } + + async * _all () { + yield * Object.entries(this.data) + .map(([key, value]) => ({ key: new Key(key), value })) + } + + async * _allKeys () { + yield * Object.entries(this.data) + .map(([key]) => new Key(key)) + } +} diff --git a/src/mount.js b/src/mount.js index 1d5972d..d6eaf17 100644 --- a/src/mount.js +++ b/src/mount.js @@ -1,17 +1,14 @@ -/* @flow */ -'use strict' - -const { - Adapter, Key, Errors, utils: { - sortAll, - replaceStartWith - } -} = require('interface-datastore') -const filter = require('it-filter') -const take = require('it-take') -const merge = require('it-merge') - -const Keytransform = require('./keytransform') +import filter from 'it-filter' +import take from 'it-take' +import merge from 'it-merge' +import { BaseDatastore } from './base.js' +import { KeyTransformDatastore } from './keytransform.js' +import * as Errors from './errors.js' +import { + sortAll, + replaceStartWith +} from './utils.js' +import { Key } from 'interface-datastore/key' /** * @typedef {import('interface-datastore').Datastore} Datastore @@ -28,7 +25,7 @@ const Keytransform = require('./keytransform') * * @implements {Datastore} */ -class MountDatastore extends Adapter { +export class MountDatastore extends BaseDatastore { /** * @param {Array<{prefix: Key, datastore: Datastore}>} mounts */ @@ -166,7 +163,7 @@ class MountDatastore extends Adapter { */ query (q, options) { const qs = this.mounts.map(m => { - const ks = new Keytransform(m.datastore, { + const ks = new KeyTransformDatastore(m.datastore, { convert: (key) => { throw new Error('should never be called') }, @@ -204,7 +201,7 @@ class MountDatastore extends Adapter { */ queryKeys (q, options) { const qs = this.mounts.map(m => { - const ks = new Keytransform(m.datastore, { + const ks = new KeyTransformDatastore(m.datastore, { convert: (key) => { throw new Error('should never be called') }, @@ -236,5 +233,3 @@ class MountDatastore extends Adapter { return it } } - -module.exports = MountDatastore diff --git a/src/namespace.js b/src/namespace.js index 3a65565..3fe13ef 100644 --- a/src/namespace.js +++ b/src/namespace.js @@ -1,7 +1,5 @@ -'use strict' - -const Key = require('interface-datastore').Key -const KeytransformDatastore = require('./keytransform') +import { Key } from 'interface-datastore' +import { KeyTransformDatastore } from './keytransform.js' /** * @typedef {import('interface-datastore').Datastore} Datastore * @typedef {import('interface-datastore').Query} Query @@ -20,7 +18,7 @@ const KeytransformDatastore = require('./keytransform') * `/hello/world`. * */ -class NamespaceDatastore extends KeytransformDatastore { +export class NamespaceDatastore extends KeyTransformDatastore { /** * @param {Datastore} child * @param {Key} prefix @@ -72,5 +70,3 @@ class NamespaceDatastore extends KeytransformDatastore { return super.queryKeys(q, options) } } - -module.exports = NamespaceDatastore diff --git a/src/shard-readme.js b/src/shard-readme.js index 1a68ef1..557255a 100644 --- a/src/shard-readme.js +++ b/src/shard-readme.js @@ -1,6 +1,4 @@ -'use strict' - -module.exports = `This is a repository of IPLD objects. Each IPLD object is in a single file, +export default `This is a repository of IPLD objects. Each IPLD object is in a single file, named .data. Where is the "base32" encoding of the CID (as specified in https://github.com/multiformats/multibase) without the 'B' prefix. diff --git a/src/shard.js b/src/shard.js index 8d73fad..cc55e55 100644 --- a/src/shard.js +++ b/src/shard.js @@ -1,21 +1,21 @@ -'use strict' - -const { Key } = require('interface-datastore') -const readme = require('./shard-readme') +import { Key } from 'interface-datastore/key' +// @ts-expect-error readme is unused +// eslint-disable-next-line no-unused-vars +import readme from './shard-readme.js' /** * @typedef {import('interface-datastore').Datastore} Datastore * @typedef {import('./types').Shard} Shard */ -const PREFIX = '/repo/flatfs/shard/' -const SHARDING_FN = 'SHARDING' -const README_FN = '_README' +export const PREFIX = '/repo/flatfs/shard/' +export const SHARDING_FN = 'SHARDING' +export const README_FN = '_README' /** * @implements {Shard} */ -class ShardBase { +export class ShardBase { /** * @param {any} param */ @@ -39,7 +39,7 @@ class ShardBase { /** * @implements {Shard} */ -class Prefix extends ShardBase { +export class Prefix extends ShardBase { /** * @param {number} prefixLen */ @@ -57,7 +57,7 @@ class Prefix extends ShardBase { } } -class Suffix extends ShardBase { +export class Suffix extends ShardBase { /** * @param {number} suffixLen */ @@ -76,7 +76,7 @@ class Suffix extends ShardBase { } } -class NextToLast extends ShardBase { +export class NextToLast extends ShardBase { /** * @param {number} suffixLen */ @@ -102,7 +102,7 @@ class NextToLast extends ShardBase { * @param {string} str * @returns {Shard} */ -function parseShardFun (str) { +export function parseShardFun (str) { str = str.trim() if (str.length === 0) { @@ -144,7 +144,7 @@ function parseShardFun (str) { * @param {string | Uint8Array} path * @param {Datastore} store */ -const readShardFun = async (path, store) => { +export const readShardFun = async (path, store) => { const key = new Key(path).child(new Key(SHARDING_FN)) // @ts-ignore const get = typeof store.getRaw === 'function' ? store.getRaw.bind(store) : store.get.bind(store) @@ -152,14 +152,4 @@ const readShardFun = async (path, store) => { return parseShardFun(new TextDecoder().decode(res || '').trim()) } -module.exports = { - readme, - parseShardFun, - readShardFun, - Prefix, - Suffix, - NextToLast, - README_FN, - SHARDING_FN, - PREFIX -} +export { default as readme } from './shard-readme.js' diff --git a/src/sharding.js b/src/sharding.js index d506394..07e44bd 100644 --- a/src/sharding.js +++ b/src/sharding.js @@ -1,11 +1,16 @@ -'use strict' - -const { Adapter, Key, Errors } = require('interface-datastore') -const sh = require('./shard') -const KeytransformStore = require('./keytransform') - -const shardKey = new Key(sh.SHARDING_FN) -const shardReadmeKey = new Key(sh.README_FN) +import { Key } from 'interface-datastore' +import { + readShardFun, + SHARDING_FN, + README_FN, + readme +} from './shard.js' +import { BaseDatastore } from './base.js' +import { KeyTransformDatastore } from './keytransform.js' +import * as Errors from './errors.js' + +const shardKey = new Key(SHARDING_FN) +const shardReadmeKey = new Key(README_FN) /** * @typedef {import('interface-datastore').Datastore} Datastore * @typedef {import('interface-datastore').Options} Options @@ -36,7 +41,7 @@ const shardReadmeKey = new Key(sh.README_FN) * Wraps another datastore such that all values are stored * sharded according to the given sharding function. */ -class ShardingDatastore extends Adapter { +export class ShardingDatastore extends BaseDatastore { /** * @param {Datastore} store * @param {Shard} shard @@ -44,7 +49,7 @@ class ShardingDatastore extends Adapter { constructor (store, shard) { super() - this.child = new KeytransformStore(store, { + this.child = new KeyTransformDatastore(store, { convert: this._convertKey.bind(this), invert: this._invertKey.bind(this) }) @@ -89,7 +94,7 @@ class ShardingDatastore extends Adapter { static async createOrOpen (store, shard) { try { await ShardingDatastore.create(store, shard) - } catch (err) { + } catch (/** @type {any} */ err) { if (err && err.message !== 'datastore exists') throw err } return ShardingDatastore.open(store) @@ -100,7 +105,7 @@ class ShardingDatastore extends Adapter { * @param {Datastore} store */ static async open (store) { - const shard = await sh.readShardFun('/', store) + const shard = await readShardFun('/', store) return new ShardingDatastore(store, shard) } @@ -118,14 +123,14 @@ class ShardingDatastore extends Adapter { const put = typeof store.putRaw === 'function' ? store.putRaw.bind(store) : store.put.bind(store) await Promise.all([ put(shardKey, new TextEncoder().encode(shard.toString() + '\n')), - put(shardReadmeKey, new TextEncoder().encode(sh.readme)) + put(shardReadmeKey, new TextEncoder().encode(readme)) ]) return shard } // test shards - const diskShard = await sh.readShardFun('/', store) + const diskShard = await readShardFun('/', store) const a = (diskShard || '').toString() const b = shard.toString() if (a !== b) { @@ -313,5 +318,3 @@ class ShardingDatastore extends Adapter { return this.child.close() } } - -module.exports = ShardingDatastore diff --git a/src/tiered.js b/src/tiered.js index 3bbb7f5..52fe8df 100644 --- a/src/tiered.js +++ b/src/tiered.js @@ -1,9 +1,10 @@ -'use strict' +import { BaseDatastore } from './base.js' +import * as Errors from './errors.js' +import debug from 'debug' +import pushable from 'it-pushable' +import drain from 'it-drain' -const { Adapter, Errors } = require('interface-datastore') -const log = require('debug')('datastore:core:tiered') -const pushable = require('it-pushable') -const drain = require('it-drain') +const log = debug('datastore:core:tiered') /** * @typedef {import('interface-datastore').Datastore} Datastore @@ -27,7 +28,7 @@ const drain = require('it-drain') * last one first. * */ -class TieredDatastore extends Adapter { +export class TieredDatastore extends BaseDatastore { /** * @param {Datastore[]} stores */ @@ -208,5 +209,3 @@ class TieredDatastore extends Adapter { return this.stores[this.stores.length - 1].queryKeys(q, options) } } - -module.exports = TieredDatastore diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..ccdf572 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,27 @@ + +import all from 'it-all' + +/** + * Collect all values from the iterable and sort them using + * the passed sorter function + * + * @template T + * @param {AsyncIterable | Iterable} iterable + * @param {(a: T, b: T) => -1 | 0 | 1} sorter + * @returns {AsyncIterable} + */ +export const sortAll = (iterable, sorter) => { + return (async function * () { + const values = await all(iterable) + yield * values.sort(sorter) + })() +} + +/** + * @param {string} s + * @param {string} r + */ +export const replaceStartWith = (s, r) => { + const matcher = new RegExp('^' + r) + return s.replace(matcher, '') +} diff --git a/test/keytransform.spec.js b/test/keytransform.spec.js index 8fbe14c..178a661 100644 --- a/test/keytransform.spec.js +++ b/test/keytransform.spec.js @@ -1,11 +1,10 @@ /* eslint-env mocha */ -'use strict' -const { expect } = require('aegir/utils/chai') -const { Key, MemoryDatastore } = require('interface-datastore') -const all = require('it-all') - -const KeytransformStore = require('../src/').KeytransformDatastore +import { expect } from 'aegir/utils/chai.js' +import all from 'it-all' +import { Key } from 'interface-datastore/key' +import { MemoryDatastore } from '../src/memory.js' +import { KeyTransformDatastore } from '../src/keytransform.js' describe('KeyTransformDatastore', () => { it('basic', async () => { @@ -29,7 +28,7 @@ describe('KeyTransformDatastore', () => { } } - const kStore = new KeytransformStore(mStore, transform) + const kStore = new KeyTransformDatastore(mStore, transform) const keys = [ 'foo', diff --git a/test/memory.spec.js b/test/memory.spec.js new file mode 100644 index 0000000..8011d6b --- /dev/null +++ b/test/memory.spec.js @@ -0,0 +1,15 @@ +/* eslint-env mocha */ + +import { MemoryDatastore } from '../src/memory.js' +import { interfaceDatastoreTests } from 'interface-datastore-tests' + +describe('Memory', () => { + describe('interface-datastore', () => { + interfaceDatastoreTests({ + setup () { + return new MemoryDatastore() + }, + teardown () {} + }) + }) +}) diff --git a/test/mount.spec.js b/test/mount.spec.js index 578466f..2a175fc 100644 --- a/test/mount.spec.js +++ b/test/mount.spec.js @@ -1,16 +1,17 @@ /* eslint-env mocha */ /* eslint max-nested-callbacks: ["error", 8] */ -'use strict' -const { expect, assert } = require('aegir/utils/chai') -const all = require('it-all') -const { Key, MemoryDatastore } = require('interface-datastore') -const MountStore = require('../src').MountDatastore -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') +import { expect, assert } from 'aegir/utils/chai.js' +import all from 'it-all' +import { Key } from 'interface-datastore/key' +import { MemoryDatastore } from '../src/memory.js' +import { MountDatastore } from '../src/mount.js' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { interfaceDatastoreTests } from 'interface-datastore-tests' -describe('MountStore', () => { +describe('MountDatastore', () => { it('put - no mount', async () => { - const m = new MountStore([]) + const m = new MountDatastore([]) try { await m.put(new Key('hello'), uint8ArrayFromString('foo')) assert(false, 'Failed to throw error on no mount') @@ -20,7 +21,7 @@ describe('MountStore', () => { }) it('put - wrong mount', async () => { - const m = new MountStore([{ + const m = new MountDatastore([{ datastore: new MemoryDatastore(), prefix: new Key('cool') }]) @@ -34,7 +35,7 @@ describe('MountStore', () => { it('put', async () => { const mds = new MemoryDatastore() - const m = new MountStore([{ + const m = new MountDatastore([{ datastore: mds, prefix: new Key('cool') }]) @@ -47,7 +48,7 @@ describe('MountStore', () => { it('get', async () => { const mds = new MemoryDatastore() - const m = new MountStore([{ + const m = new MountDatastore([{ datastore: mds, prefix: new Key('cool') }]) @@ -60,7 +61,7 @@ describe('MountStore', () => { it('has', async () => { const mds = new MemoryDatastore() - const m = new MountStore([{ + const m = new MountDatastore([{ datastore: mds, prefix: new Key('cool') }]) @@ -73,7 +74,7 @@ describe('MountStore', () => { it('delete', async () => { const mds = new MemoryDatastore() - const m = new MountStore([{ + const m = new MountDatastore([{ datastore: mds, prefix: new Key('cool') }]) @@ -89,7 +90,7 @@ describe('MountStore', () => { it('query simple', async () => { const mds = new MemoryDatastore() - const m = new MountStore([{ + const m = new MountDatastore([{ datastore: mds, prefix: new Key('cool') }]) @@ -101,10 +102,9 @@ describe('MountStore', () => { }) describe('interface-datastore', () => { - // @ts-ignore - require('interface-datastore-tests')({ + interfaceDatastoreTests({ setup () { - return new MountStore([{ + return new MountDatastore([{ prefix: new Key('/a'), datastore: new MemoryDatastore() }, { diff --git a/test/namespace.spec.js b/test/namespace.spec.js index d69461b..6872325 100644 --- a/test/namespace.spec.js +++ b/test/namespace.spec.js @@ -1,11 +1,12 @@ /* eslint-env mocha */ -'use strict' -const { expect } = require('aegir/utils/chai') -const { Key, MemoryDatastore } = require('interface-datastore') -const NamespaceStore = require('../src/').NamespaceDatastore -const all = require('it-all') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') +import { expect } from 'aegir/utils/chai.js' +import all from 'it-all' +import { Key } from 'interface-datastore/key' +import { MemoryDatastore } from '../src/memory.js' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { NamespaceDatastore } from '../src/namespace.js' +import { interfaceDatastoreTests } from 'interface-datastore-tests' describe('KeyTransformDatastore', () => { const prefixes = [ @@ -14,7 +15,7 @@ describe('KeyTransformDatastore', () => { ] prefixes.forEach((prefix) => it(`basic '${prefix}'`, async () => { const mStore = new MemoryDatastore() - const store = new NamespaceStore(mStore, new Key(prefix)) + const store = new NamespaceDatastore(mStore, new Key(prefix)) const keys = [ 'foo', @@ -47,10 +48,9 @@ describe('KeyTransformDatastore', () => { prefixes.forEach((prefix) => { describe(`interface-datastore: '${prefix}'`, () => { - // @ts-ignore - require('interface-datastore-tests')({ + interfaceDatastoreTests({ setup () { - return new NamespaceStore(new MemoryDatastore(), new Key(prefix)) + return new NamespaceDatastore(new MemoryDatastore(), new Key(prefix)) }, async teardown () { } }) diff --git a/test/shard.spec.js b/test/shard.spec.js index 231ffa5..37880e7 100644 --- a/test/shard.spec.js +++ b/test/shard.spec.js @@ -1,24 +1,28 @@ /* eslint-env mocha */ -'use strict' -const { expect } = require('aegir/utils/chai') -const shard = require('../src').shard +import { expect } from 'aegir/utils/chai.js' +import { + Prefix, + Suffix, + NextToLast, + parseShardFun +} from '../src/shard.js' describe('shard', () => { it('prefix', () => { expect( - new shard.Prefix(2).fun('hello') + new Prefix(2).fun('hello') ).to.eql( 'he' ) expect( - new shard.Prefix(2).fun('h') + new Prefix(2).fun('h') ).to.eql( 'h_' ) expect( - new shard.Prefix(2).toString() + new Prefix(2).toString() ).to.eql( '/repo/flatfs/shard/v1/prefix/2' ) @@ -26,18 +30,18 @@ describe('shard', () => { it('suffix', () => { expect( - new shard.Suffix(2).fun('hello') + new Suffix(2).fun('hello') ).to.eql( 'lo' ) expect( - new shard.Suffix(2).fun('h') + new Suffix(2).fun('h') ).to.eql( '_h' ) expect( - new shard.Suffix(2).toString() + new Suffix(2).toString() ).to.eql( '/repo/flatfs/shard/v1/suffix/2' ) @@ -45,18 +49,18 @@ describe('shard', () => { it('next-to-last', () => { expect( - new shard.NextToLast(2).fun('hello') + new NextToLast(2).fun('hello') ).to.eql( 'll' ) expect( - new shard.NextToLast(3).fun('he') + new NextToLast(3).fun('he') ).to.eql( '__h' ) expect( - new shard.NextToLast(2).toString() + new NextToLast(2).toString() ).to.eql( '/repo/flatfs/shard/v1/next-to-last/2' ) @@ -75,7 +79,7 @@ describe('parsesShardFun', () => { errors.forEach((input) => { expect( - () => shard.parseShardFun(input) + () => parseShardFun(input) ).to.throw() }) }) @@ -90,7 +94,7 @@ describe('parsesShardFun', () => { success.forEach((name) => { const n = Math.floor(Math.random() * 100) expect( - shard.parseShardFun( + parseShardFun( `/repo/flatfs/shard/v1/${name}/${n}` ).name ).to.eql(name) diff --git a/test/sharding.spec.js b/test/sharding.spec.js index ceedd38..cc0657b 100644 --- a/test/sharding.spec.js +++ b/test/sharding.spec.js @@ -1,32 +1,39 @@ /* eslint-env mocha */ -'use strict' -const { expect } = require('aegir/utils/chai') -const { Key, MemoryDatastore } = require('interface-datastore') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') +import { expect } from 'aegir/utils/chai.js' +import { Key } from 'interface-datastore/key' +import { MemoryDatastore } from '../src/memory.js' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { + NextToLast, + SHARDING_FN, + README_FN, + readme +} from '../src/shard.js' +import { + ShardingDatastore +} from '../src/sharding.js' +import { interfaceDatastoreTests } from 'interface-datastore-tests' -const ShardingStore = require('../src').ShardingDatastore -const sh = require('../src').shard - -describe('ShardingStore', () => { +describe('ShardingDatastore', () => { it('create', async () => { const ms = new MemoryDatastore() - const shard = new sh.NextToLast(2) - const store = new ShardingStore(ms, shard) + const shard = new NextToLast(2) + const store = new ShardingDatastore(ms, shard) await store.open() const res = await Promise.all([ - ms.get(new Key(sh.SHARDING_FN)), - ms.get(new Key(sh.README_FN)) + ms.get(new Key(SHARDING_FN)), + ms.get(new Key(README_FN)) ]) expect(uint8ArrayToString(res[0])).to.eql(shard.toString() + '\n') - expect(uint8ArrayToString(res[1])).to.eql(sh.readme) + expect(uint8ArrayToString(res[1])).to.eql(readme) }) it('open - empty', () => { const ms = new MemoryDatastore() // @ts-expect-error - const store = new ShardingStore(ms) + const store = new ShardingDatastore(ms) return expect(store.open()) .to.eventually.be.rejected() .with.property('code', 'ERR_DB_OPEN_FAILED') @@ -34,16 +41,16 @@ describe('ShardingStore', () => { it('open - existing', () => { const ms = new MemoryDatastore() - const shard = new sh.NextToLast(2) - const store = new ShardingStore(ms, shard) + const shard = new NextToLast(2) + const store = new ShardingDatastore(ms, shard) return expect(store.open()).to.eventually.be.fulfilled() }) it('basics', async () => { const ms = new MemoryDatastore() - const shard = new sh.NextToLast(2) - const store = new ShardingStore(ms, shard) + const shard = new NextToLast(2) + const store = new ShardingDatastore(ms, shard) await store.open() await store.put(new Key('hello'), uint8ArrayFromString('test')) const res = await ms.get(new Key('ll').child(new Key('hello'))) @@ -51,11 +58,10 @@ describe('ShardingStore', () => { }) describe('interface-datastore', () => { - // @ts-ignore - require('interface-datastore-tests')({ + interfaceDatastoreTests({ setup () { - const shard = new sh.NextToLast(2) - return new ShardingStore(new MemoryDatastore(), shard) + const shard = new NextToLast(2) + return new ShardingDatastore(new MemoryDatastore(), shard) }, teardown () { } }) diff --git a/test/tiered.spec.js b/test/tiered.spec.js index 47460fa..1ed30ef 100644 --- a/test/tiered.spec.js +++ b/test/tiered.spec.js @@ -1,12 +1,14 @@ /* eslint-env mocha */ -'use strict' -const { expect } = require('aegir/utils/chai') -const { Key, MemoryDatastore } = require('interface-datastore') -const { TieredDatastore } = require('../src') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') +import { expect } from 'aegir/utils/chai.js' +import { Key } from 'interface-datastore/key' +import { MemoryDatastore } from '../src/memory.js' +import { TieredDatastore } from '../src/tiered.js' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { interfaceDatastoreTests } from 'interface-datastore-tests' + /** - * @typedef {import('interface-datastore/dist/src/types').Datastore} Datastore + * @typedef {import('interface-datastore').Datastore} Datastore */ describe('Tiered', () => { @@ -58,8 +60,7 @@ describe('Tiered', () => { }) describe('inteface-datastore-single', () => { - // @ts-ignore - require('interface-datastore-tests')({ + interfaceDatastoreTests({ setup () { return new TieredDatastore([ new MemoryDatastore(), diff --git a/test/utils.spec.js b/test/utils.spec.js new file mode 100644 index 0000000..b55b564 --- /dev/null +++ b/test/utils.spec.js @@ -0,0 +1,121 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import * as utils from '../src/utils.js' +import filter from 'it-filter' +import take from 'it-take' +import map from 'it-map' + +describe('utils', () => { + it('filter - sync', async () => { + const data = [1, 2, 3, 4] + /** + * @param {number} val + */ + const filterer = val => val % 2 === 0 + const res = [] + for await (const val of filter(data, filterer)) { + res.push(val) + } + expect(res).to.be.eql([2, 4]) + }) + + it('filter - async', async () => { + const data = [1, 2, 3, 4] + /** + * @param {number} val + */ + const filterer = val => val % 2 === 0 + const res = [] + for await (const val of filter(data, filterer)) { + res.push(val) + } + expect(res).to.be.eql([2, 4]) + }) + + it('sortAll', async () => { + const data = [1, 2, 3, 4] + /** + * @param {number} a + * @param {number} b + */ + const sorter = (a, b) => { + if (a < b) { + return 1 + } + + if (a > b) { + return -1 + } + + return 0 + } + const res = [] + for await (const val of utils.sortAll(data, sorter)) { + res.push(val) + } + expect(res).to.be.eql([4, 3, 2, 1]) + }) + + it('sortAll - fail', async () => { + const data = [1, 2, 3, 4] + const sorter = () => { throw new Error('fail') } + const res = [] + + try { + for await (const val of utils.sortAll(data, sorter)) { + res.push(val) + } + } catch (/** @type {any} */ err) { + expect(err.message).to.be.eql('fail') + return + } + + throw new Error('expected error to be thrown') + }) + + it('should take n values from iterator', async () => { + const data = [1, 2, 3, 4] + const n = 3 + const res = [] + for await (const val of take(data, n)) { + res.push(val) + } + expect(res).to.be.eql([1, 2, 3]) + }) + + it('should take nothing from iterator', async () => { + const data = [1, 2, 3, 4] + const n = 0 + for await (const _ of take(data, n)) { // eslint-disable-line + throw new Error('took a value') + } + }) + + it('should map iterator values', async () => { + const data = [1, 2, 3, 4] + /** + * @param {number} n + */ + const mapper = n => n * 2 + const res = [] + for await (const val of map(data, mapper)) { + res.push(val) + } + expect(res).to.be.eql([2, 4, 6, 8]) + }) + + it('replaceStartWith', () => { + expect( + utils.replaceStartWith('helloworld', 'hello') + ).to.eql( + 'world' + ) + + expect( + utils.replaceStartWith('helloworld', 'world') + ).to.eql( + 'helloworld' + ) + }) +})