diff --git a/package.json b/package.json index abca6c3..cd8f0e5 100644 --- a/package.json +++ b/package.json @@ -49,43 +49,43 @@ "exports": { ".": { "types": "./dist/src/index.d.ts", - "import": "./src/index.js" + "import": "./dist/src/index.js" }, "./base": { - "types": "./src/base.d.ts", - "import": "./src/base.js" + "types": "./dist/src/base.d.ts", + "import": "./dist/src/base.js" }, "./errors": { - "types": "./src/errors.d.ts", - "import": "./src/errors.js" + "types": "./dist/src/errors.d.ts", + "import": "./dist/src/errors.js" }, "./keytransform": { - "types": "./src/keytransform.d.ts", - "import": "./src/keytransform.js" + "types": "./dist/src/keytransform.d.ts", + "import": "./dist/src/keytransform.js" }, "./memory": { - "types": "./src/memory.d.ts", - "import": "./src/memory.js" + "types": "./dist/src/memory.d.ts", + "import": "./dist/src/memory.js" }, "./mount": { - "types": "./src/mount.d.ts", - "import": "./src/mount.js" + "types": "./dist/src/mount.d.ts", + "import": "./dist/src/mount.js" }, "./namespace": { - "types": "./src/namespace.d.ts", - "import": "./src/namespace.js" + "types": "./dist/src/namespace.d.ts", + "import": "./dist/src/namespace.js" }, "./shard": { - "types": "./src/shard.d.ts", - "import": "./src/shard.js" + "types": "./dist/src/shard.d.ts", + "import": "./dist/src/shard.js" }, "./sharding": { - "types": "./src/sharding.d.ts", - "import": "./src/sharding.js" + "types": "./dist/src/sharding.d.ts", + "import": "./dist/src/sharding.js" }, "./tiered": { - "types": "./src/tiered.d.ts", - "import": "./src/tiered.js" + "types": "./dist/src/tiered.d.ts", + "import": "./dist/src/tiered.js" } }, "eslintConfig": { @@ -196,7 +196,7 @@ "dependencies": { "@libp2p/logger": "^2.0.0", "err-code": "^3.0.1", - "interface-datastore": "^7.0.0", + "interface-store": "^4.0.0", "it-all": "^2.0.0", "it-drain": "^2.0.0", "it-filter": "^2.0.0", @@ -204,11 +204,13 @@ "it-merge": "^2.0.0", "it-pipe": "^2.0.3", "it-pushable": "^3.0.0", + "it-sort": "^2.0.1", "it-take": "^2.0.0", "uint8arrays": "^4.0.2" }, "devDependencies": { - "aegir": "^37.4.5", - "interface-datastore-tests": "^3.0.0" + "aegir": "^38.1.7", + "interface-datastore": "^8.0.0", + "interface-datastore-tests": "^4.0.0" } } diff --git a/src/base.js b/src/base.js deleted file mode 100644 index 9c4517c..0000000 --- a/src/base.js +++ /dev/null @@ -1,226 +0,0 @@ -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/base.ts b/src/base.ts new file mode 100644 index 0000000..132443b --- /dev/null +++ b/src/base.ts @@ -0,0 +1,149 @@ +import sort from 'it-sort' +import drain from 'it-drain' +import filter from 'it-filter' +import take from 'it-take' +import type { Batch, Datastore, Key, KeyQuery, Options, Pair, Query } from 'interface-datastore' +import type { AwaitIterable } from 'interface-store' + +export class BaseDatastore implements Datastore { + async open (): Promise { + + } + + async close (): Promise { + + } + + async put (key: Key, val: Uint8Array, options?: Options): Promise { + await Promise.reject(new Error('.put is not implemented')) + } + + async get (key: Key, options?: Options): Promise { + return await Promise.reject(new Error('.get is not implemented')) + } + + async has (key: Key, options?: Options): Promise { + return await Promise.reject(new Error('.has is not implemented')) + } + + async delete (key: Key, options?: Options): Promise { + await Promise.reject(new Error('.delete is not implemented')) + } + + async * putMany (source: AwaitIterable, options: Options = {}): AsyncIterable { + for await (const { key, value } of source) { + await this.put(key, value, options) + yield { key, value } + } + } + + async * getMany (source: AwaitIterable, options: Options = {}): AsyncIterable { + for await (const key of source) { + yield this.get(key, options) + } + } + + async * deleteMany (source: AwaitIterable, options: Options = {}): AsyncIterable { + for await (const key of source) { + await this.delete(key, options) + yield key + } + } + + batch (): Batch { + let puts: Pair[] = [] + let dels: Key[] = [] + + 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 + */ + // eslint-disable-next-line require-yield + async * _all (q: Query, options?: Options): AsyncIterable { + throw new Error('._all is not implemented') + } + + /** + * Extending classes should override `queryKeys` or implement this method + */ + // eslint-disable-next-line require-yield + async * _allKeys (q: KeyQuery, options?: Options): AsyncIterable { + throw new Error('._allKeys is not implemented') + } + + query (q: Query, options?: Options): AsyncIterable { + let it = this._all(q, options) + + if (q.prefix != null) { + const prefix = q.prefix + it = filter(it, (e) => e.key.toString().startsWith(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) => sort(it, f), it) + } + + if (q.offset != null) { + let i = 0 + const offset = q.offset + it = filter(it, () => i++ >= offset) + } + + if (q.limit != null) { + it = take(it, q.limit) + } + + return it + } + + queryKeys (q: KeyQuery, options?: Options): AsyncIterable { + let it = this._allKeys(q, options) + + if (q.prefix != null) { + const prefix = q.prefix + it = filter(it, (key) => + key.toString().startsWith(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) => sort(it, f), it) + } + + if (q.offset != null) { + const offset = q.offset + let i = 0 + it = filter(it, () => i++ >= offset) + } + + if (q.limit != null) { + it = take(it, q.limit) + } + + return it + } +} diff --git a/src/errors.js b/src/errors.js deleted file mode 100644 index a9494af..0000000 --- a/src/errors.js +++ /dev/null @@ -1,41 +0,0 @@ -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/errors.ts b/src/errors.ts new file mode 100644 index 0000000..57a0277 --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,26 @@ +import errCode from 'err-code' + +export function dbOpenFailedError (err?: Error): Error { + err = err ?? new Error('Cannot open database') + return errCode(err, 'ERR_DB_OPEN_FAILED') +} + +export function dbDeleteFailedError (err?: Error): Error { + err = err ?? new Error('Delete failed') + return errCode(err, 'ERR_DB_DELETE_FAILED') +} + +export function dbWriteFailedError (err?: Error): Error { + err = err ?? new Error('Write failed') + return errCode(err, 'ERR_DB_WRITE_FAILED') +} + +export function notFoundError (err?: Error): Error { + err = err ?? new Error('Not Found') + return errCode(err, 'ERR_NOT_FOUND') +} + +export function abortedError (err?: Error): Error { + err = err ?? new Error('Aborted') + return errCode(err, 'ERR_ABORTED') +} diff --git a/src/index.js b/src/index.ts similarity index 61% rename from src/index.js rename to src/index.ts index de0b1b2..04f205c 100644 --- a/src/index.js +++ b/src/index.ts @@ -1,3 +1,5 @@ +import type { Key } from 'interface-datastore' + import * as Errors from './errors.js' import * as shard from './shard.js' @@ -12,7 +14,15 @@ export { NamespaceDatastore } from './namespace.js' export { Errors } export { shard } -/** - * @typedef {import("./types").Shard } Shard - * @typedef {import("./types").KeyTransform } KeyTransform - */ +export interface Shard { + name: string + param: number + readonly _padding: string + fun: (s: string) => string + toString: () => string +} + +export interface KeyTransform { + convert: (key: Key) => Key + invert: (key: Key) => Key +} diff --git a/src/keytransform.js b/src/keytransform.ts similarity index 56% rename from src/keytransform.js rename to src/keytransform.ts index 00d8977..efe967c 100644 --- a/src/keytransform.js +++ b/src/keytransform.ts @@ -1,85 +1,43 @@ import { BaseDatastore } from './base.js' import map from 'it-map' import { pipe } from 'it-pipe' - -/** - * @typedef {import('interface-datastore').Datastore} Datastore - * @typedef {import('interface-datastore').Options} Options - * @typedef {import('interface-datastore').Batch} Batch - * @typedef {import('interface-datastore').Query} Query - * @typedef {import('interface-datastore').KeyQuery} KeyQuery - * @typedef {import('interface-datastore').Key} Key - * @typedef {import('interface-datastore').Pair} Pair - * @typedef {import('./types').KeyTransform} KeyTransform - */ - -/** - * @template TEntry - * @typedef {import('interface-store').AwaitIterable} AwaitIterable - */ +import type { KeyTransform } from './index.js' +import type { Batch, Datastore, Key, KeyQuery, Options, Pair, Query } from 'interface-datastore' +import type { AwaitIterable } from 'interface-store' /** * A datastore shim, that wraps around a given datastore, changing * the way keys look to the user, for example namespacing * keys, reversing them, etc. - * - * @implements {Datastore} */ export class KeyTransformDatastore extends BaseDatastore { - /** - * @param {Datastore} child - * @param {KeyTransform} transform - */ - constructor (child, transform) { + private readonly child: Datastore + public transform: KeyTransform + + constructor (child: Datastore, transform: KeyTransform) { super() this.child = child this.transform = transform } - open () { - return this.child.open() - } - - /** - * @param {Key} key - * @param {Uint8Array} val - * @param {Options} [options] - */ - put (key, val, options) { - return this.child.put(this.transform.convert(key), val, options) + async put (key: Key, val: Uint8Array, options?: Options): Promise { + await this.child.put(this.transform.convert(key), val, options) } - /** - * @param {Key} key - * @param {Options} [options] - */ - get (key, options) { - return this.child.get(this.transform.convert(key), options) + async get (key: Key, options?: Options): Promise { + return await this.child.get(this.transform.convert(key), options) } - /** - * @param {Key} key - * @param {Options} [options] - */ - has (key, options) { - return this.child.has(this.transform.convert(key), options) + async has (key: Key, options?: Options): Promise { + return await this.child.has(this.transform.convert(key), options) } - /** - * @param {Key} key - * @param {Options} [options] - */ - delete (key, options) { - return this.child.delete(this.transform.convert(key), options) + async delete (key: Key, options?: Options): Promise { + await this.child.delete(this.transform.convert(key), options) } - /** - * @param {AwaitIterable} source - * @param {Options} [options] - * @returns {AsyncIterable} - */ - async * putMany (source, options = {}) { + async * putMany (source: AwaitIterable, options: Options = {}): AsyncIterable { const transform = this.transform const child = this.child @@ -103,12 +61,7 @@ export class KeyTransformDatastore extends BaseDatastore { ) } - /** - * @param {AwaitIterable} source - * @param {Options} [options] - * @returns {AsyncIterable} - */ - async * getMany (source, options = {}) { + async * getMany (source: AwaitIterable, options: Options = {}): AsyncIterable { const transform = this.transform const child = this.child @@ -123,12 +76,7 @@ export class KeyTransformDatastore extends BaseDatastore { ) } - /** - * @param {AwaitIterable} source - * @param {Options} [options] - * @returns {AsyncIterable} - */ - async * deleteMany (source, options = {}) { + async * deleteMany (source: AwaitIterable, options: Options = {}): AsyncIterable { const transform = this.transform const child = this.child @@ -146,10 +94,7 @@ export class KeyTransformDatastore extends BaseDatastore { ) } - /** - * @returns {Batch} - */ - batch () { + batch (): Batch { const b = this.child.batch() return { put: (key, value) => { @@ -158,23 +103,18 @@ export class KeyTransformDatastore extends BaseDatastore { delete: (key) => { b.delete(this.transform.convert(key)) }, - commit: (options) => { - return b.commit(options) + commit: async (options) => { + await b.commit(options) } } } - /** - * @param {Query} q - * @param {Options} [options] - */ - query (q, options) { - /** @type {Query} */ - const query = { + query (q: Query, options?: Options): AsyncIterable { + const query: Query = { ...q } - query.filters = (query.filters || []).map(filter => { + query.filters = (query.filters ?? []).map(filter => { return ({ key, value }) => filter({ key: this.transform.convert(key), value }) }) @@ -186,7 +126,7 @@ export class KeyTransformDatastore extends BaseDatastore { }) } - if (query.orders) { + if (query.orders != null) { query.orders = query.orders.map(order => { return (a, b) => order( { key: this.transform.invert(a.key), value: a.value }, @@ -203,16 +143,12 @@ export class KeyTransformDatastore extends BaseDatastore { }) } - /** - * @param {KeyQuery} q - * @param {Options} [options] - */ - queryKeys (q, options) { + queryKeys (q: KeyQuery, options?: Options): AsyncIterable { const query = { ...q } - query.filters = (query.filters || []).map(filter => { + query.filters = (query.filters ?? []).map(filter => { return (key) => filter(this.transform.convert(key)) }) @@ -224,7 +160,7 @@ export class KeyTransformDatastore extends BaseDatastore { }) } - if (query.orders) { + if (query.orders != null) { query.orders = query.orders.map(order => { return (a, b) => order( this.transform.invert(a), @@ -237,8 +173,4 @@ export class KeyTransformDatastore extends BaseDatastore { return this.transform.invert(key) }) } - - close () { - return this.child.close() - } } diff --git a/src/memory.js b/src/memory.js deleted file mode 100644 index 2a0dc39..0000000 --- a/src/memory.js +++ /dev/null @@ -1,71 +0,0 @@ -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/memory.ts b/src/memory.ts new file mode 100644 index 0000000..191a7d5 --- /dev/null +++ b/src/memory.ts @@ -0,0 +1,48 @@ +import { BaseDatastore } from './base.js' +import { Key } from 'interface-datastore/key' +import * as Errors from './errors.js' +import type { Pair } from 'interface-datastore' + +export class MemoryDatastore extends BaseDatastore { + private readonly data: Map + + constructor () { + super() + + this.data = new Map() + } + + async put (key: Key, val: Uint8Array): Promise { // eslint-disable-line require-await + this.data.set(key.toString(), val) + } + + async get (key: Key): Promise { + const result = this.data.get(key.toString()) + + if (result == null) { + throw Errors.notFoundError() + } + + return result + } + + async has (key: Key): Promise { // eslint-disable-line require-await + return this.data.has(key.toString()) + } + + async delete (key: Key): Promise { // eslint-disable-line require-await + this.data.delete(key.toString()) + } + + async * _all (): AsyncIterable { + for (const [key, value] of this.data.entries()) { + yield { key: new Key(key), value } + } + } + + async * _allKeys (): AsyncIterable { + for (const key of this.data.keys()) { + yield new Key(key) + } + } +} diff --git a/src/mount.js b/src/mount.ts similarity index 50% rename from src/mount.js rename to src/mount.ts index 0260125..6e498a3 100644 --- a/src/mount.js +++ b/src/mount.ts @@ -3,49 +3,27 @@ import take from 'it-take' import merge from 'it-merge' import { BaseDatastore } from './base.js' import * as Errors from './errors.js' -import { - sortAll -} from './utils.js' - -/** - * @typedef {import('interface-datastore').Datastore} Datastore - * @typedef {import('interface-datastore').Key} Key - * @typedef {import('interface-datastore').Pair} Pair - * @typedef {import('interface-datastore').Options} Options - * @typedef {import('interface-datastore').Batch} Batch - * @typedef {import('interface-datastore').Query} Query - * @typedef {import('interface-datastore').KeyQuery} KeyQuery - * @typedef {import('./types').KeyTransform} KeyTransform - */ +import sort from 'it-sort' +import type { Batch, Datastore, Key, KeyQuery, Pair, Query } from 'interface-datastore' +import type { Options } from 'interface-store' /** * A datastore that can combine multiple stores inside various * key prefixes - * - * @implements {Datastore} */ export class MountDatastore extends BaseDatastore { - /** - * @param {Array<{prefix: Key, datastore: Datastore}>} mounts - */ - constructor (mounts) { + private readonly mounts: Array<{ prefix: Key, datastore: Datastore }> + + constructor (mounts: Array<{ prefix: Key, datastore: Datastore }>) { super() this.mounts = mounts.slice() } - async open () { - await Promise.all(this.mounts.map((m) => m.datastore.open())) - } - /** * Lookup the matching datastore for the given key - * - * @private - * @param {Key} key - * @returns {{datastore: Datastore, mountpoint: Key} | undefined} */ - _lookup (key) { + private _lookup (key: Key): { datastore: Datastore, mountpoint: Key } | undefined { for (const mount of this.mounts) { if (mount.prefix.toString() === key.toString() || mount.prefix.isAncestorOf(key)) { return { @@ -56,73 +34,48 @@ export class MountDatastore extends BaseDatastore { } } - /** - * @param {Key} key - * @param {Uint8Array} value - * @param {Options} [options] - */ - put (key, value, options) { + async put (key: Key, value: Uint8Array, options?: Options): Promise { const match = this._lookup(key) if (match == null) { throw Errors.dbWriteFailedError(new Error('No datastore mounted for this key')) } - return match.datastore.put(key, value, options) + await match.datastore.put(key, value, options) } /** * @param {Key} key * @param {Options} [options] */ - get (key, options) { + async get (key: Key, options: Options = {}): Promise { const match = this._lookup(key) if (match == null) { throw Errors.notFoundError(new Error('No datastore mounted for this key')) } - return match.datastore.get(key, options) + return await match.datastore.get(key, options) } - /** - * @param {Key} key - * @param {Options} [options] - */ - has (key, options) { + async has (key: Key, options?: Options): Promise { const match = this._lookup(key) if (match == null) { - return Promise.resolve(false) + return await Promise.resolve(false) } - return match.datastore.has(key, options) + return await match.datastore.has(key, options) } - /** - * @param {Key} key - * @param {Options} [options] - */ - delete (key, options) { + async delete (key: Key, options?: Options): Promise { const match = this._lookup(key) if (match == null) { throw Errors.dbDeleteFailedError(new Error('No datastore mounted for this key')) } - return match.datastore.delete(key, options) + await match.datastore.delete(key, options) } - async close () { - await Promise.all(this.mounts.map((m) => { - return m.datastore.close() - })) - } + batch (): Batch { + const batchMounts: Record = {} - /** - * @returns {Batch} - */ - batch () { - /** @type {Record} */ - const batchMounts = {} - /** - * @param {Key} key - */ - const lookup = (key) => { + const lookup = (key: Key): { batch: Batch } => { const match = this._lookup(key) if (match == null) { throw new Error('No datastore mounted for this key') @@ -148,16 +101,12 @@ export class MountDatastore extends BaseDatastore { match.batch.delete(key) }, commit: async (options) => { - await Promise.all(Object.keys(batchMounts).map(p => batchMounts[p].commit(options))) + await Promise.all(Object.keys(batchMounts).map(async p => { await batchMounts[p].commit(options) })) } } } - /** - * @param {Query} q - * @param {Options} [options] - */ - query (q, options) { + query (q: Query, options?: Options): AsyncIterable { const qs = this.mounts.map(m => { return m.datastore.query({ prefix: q.prefix, @@ -165,24 +114,20 @@ export class MountDatastore extends BaseDatastore { }, options) }) - /** @type AsyncIterable */ let it = merge(...qs) - if (q.filters) q.filters.forEach(f => { it = filter(it, f) }) - if (q.orders) q.orders.forEach(o => { it = sortAll(it, o) }) + if (q.filters != null) q.filters.forEach(f => { it = filter(it, f) }) + if (q.orders != null) q.orders.forEach(o => { it = sort(it, o) }) if (q.offset != null) { let i = 0 - it = filter(it, () => i++ >= /** @type {number} */ (q.offset)) + const offset = q.offset + it = filter(it, () => i++ >= offset) } if (q.limit != null) it = take(it, q.limit) return it } - /** - * @param {KeyQuery} q - * @param {Options} [options] - */ - queryKeys (q, options) { + queryKeys (q: KeyQuery, options?: Options): AsyncIterable { const qs = this.mounts.map(m => { return m.datastore.queryKeys({ prefix: q.prefix, @@ -192,11 +137,12 @@ export class MountDatastore extends BaseDatastore { /** @type AsyncIterable */ let it = merge(...qs) - if (q.filters) q.filters.forEach(f => { it = filter(it, f) }) - if (q.orders) q.orders.forEach(o => { it = sortAll(it, o) }) + if (q.filters != null) q.filters.forEach(f => { it = filter(it, f) }) + if (q.orders != null) q.orders.forEach(o => { it = sort(it, o) }) if (q.offset != null) { let i = 0 - it = filter(it, () => i++ >= /** @type {number} */ (q.offset)) + const offset = q.offset + it = filter(it, () => i++ >= offset) } if (q.limit != null) it = take(it, q.limit) diff --git a/src/namespace.js b/src/namespace.ts similarity index 64% rename from src/namespace.js rename to src/namespace.ts index 679e783..08f0dcd 100644 --- a/src/namespace.js +++ b/src/namespace.ts @@ -1,13 +1,6 @@ import { Key } from 'interface-datastore' +import type { Datastore } from 'interface-datastore' import { KeyTransformDatastore } from './keytransform.js' -/** - * @typedef {import('interface-datastore').Datastore} Datastore - * @typedef {import('interface-datastore').Query} Query - * @typedef {import('interface-datastore').KeyQuery} KeyQuery - * @typedef {import('interface-datastore').Options} Options - * @typedef {import('interface-datastore').Batch} Batch - * @typedef {import('./types').KeyTransform} KeyTransform - */ /** * Wraps a given datastore into a keytransform which @@ -18,11 +11,7 @@ import { KeyTransformDatastore } from './keytransform.js' * `/hello/world`. */ export class NamespaceDatastore extends KeyTransformDatastore { - /** - * @param {Datastore} child - * @param {Key} prefix - */ - constructor (child, prefix) { + constructor (child: Datastore, prefix: Key) { super(child, { convert (key) { return prefix.child(key) diff --git a/src/shard-readme.js b/src/shard-readme.js deleted file mode 100644 index 557255a..0000000 --- a/src/shard-readme.js +++ /dev/null @@ -1,23 +0,0 @@ -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. -All the object files are placed in a tree of directories, based on a -function of the CID. This is a form of sharding similar to -the objects directory in git repositories. Previously, we used -prefixes, we now use the next-to-last two charters. - func NextToLast(base32cid string) { - nextToLastLen := 2 - offset := len(base32cid) - nextToLastLen - 1 - return str[offset : offset+nextToLastLen] - } -For example, an object with a base58 CIDv1 of - zb2rhYSxw4ZjuzgCnWSt19Q94ERaeFhu9uSqRgjSdx9bsgM6f -has a base32 CIDv1 of - BAFKREIA22FLID5AJ2KU7URG47MDLROZIH6YF2KALU2PWEFPVI37YLKRSCA -and will be placed at - SC/AFKREIA22FLID5AJ2KU7URG47MDLROZIH6YF2KALU2PWEFPVI37YLKRSCA.data -with 'SC' being the last-to-next two characters and the 'B' at the -beginning of the CIDv1 string is the multibase prefix that is not -stored in the filename. -` diff --git a/src/shard.js b/src/shard.ts similarity index 59% rename from src/shard.js rename to src/shard.ts index cc55e55..4005f1c 100644 --- a/src/shard.js +++ b/src/shard.ts @@ -1,95 +1,64 @@ +import type { Datastore } from 'interface-datastore' 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 - */ +import type { Shard } from './index.js' export const PREFIX = '/repo/flatfs/shard/' export const SHARDING_FN = 'SHARDING' -export const README_FN = '_README' -/** - * @implements {Shard} - */ -export class ShardBase { - /** - * @param {any} param - */ - constructor (param) { +export class ShardBase implements Shard { + public param: number + public name: string + public _padding: string + + constructor (param: number) { this.param = param this.name = 'base' this._padding = '' } - /** - * @param {string} s - */ - fun (s) { + fun (s: string): string { return 'implement me' } - toString () { + toString (): string { return `${PREFIX}v1/${this.name}/${this.param}` } } -/** - * @implements {Shard} - */ + export class Prefix extends ShardBase { - /** - * @param {number} prefixLen - */ - constructor (prefixLen) { + constructor (prefixLen: number) { super(prefixLen) this._padding = ''.padStart(prefixLen, '_') this.name = 'prefix' } - /** - * @param {string} noslash - */ - fun (noslash) { + fun (noslash: string): string { return (noslash + this._padding).slice(0, this.param) } } export class Suffix extends ShardBase { - /** - * @param {number} suffixLen - */ - constructor (suffixLen) { + constructor (suffixLen: number) { super(suffixLen) + this._padding = ''.padStart(suffixLen, '_') this.name = 'suffix' } - /** - * @param {string} noslash - */ - fun (noslash) { + fun (noslash: string): string { const s = this._padding + noslash return s.slice(s.length - this.param) } } export class NextToLast extends ShardBase { - /** - * @param {number} suffixLen - */ - constructor (suffixLen) { + constructor (suffixLen: number) { super(suffixLen) this._padding = ''.padStart(suffixLen + 1, '_') this.name = 'next-to-last' } - /** - * @param {string} noslash - */ - fun (noslash) { + fun (noslash: string): string { const s = this._padding + noslash const offset = s.length - this.param - 1 return s.slice(offset, offset + this.param) @@ -97,12 +66,9 @@ export class NextToLast extends ShardBase { } /** - * Convert a given string to the matching sharding function. - * - * @param {string} str - * @returns {Shard} + * Convert a given string to the matching sharding function */ -export function parseShardFun (str) { +export function parseShardFun (str: string): Shard { str = str.trim() if (str.length === 0) { @@ -122,7 +88,7 @@ export function parseShardFun (str) { const name = parts[1] - if (!parts[2]) { + if (parts[2] == null || parts[2] === '') { throw new Error('missing param') } @@ -140,16 +106,10 @@ export function parseShardFun (str) { } } -/** - * @param {string | Uint8Array} path - * @param {Datastore} store - */ -export const readShardFun = async (path, store) => { +export const readShardFun = async (path: string | Uint8Array, store: Datastore): Promise => { const key = new Key(path).child(new Key(SHARDING_FN)) - // @ts-ignore + // @ts-expect-error const get = typeof store.getRaw === 'function' ? store.getRaw.bind(store) : store.get.bind(store) const res = await get(key) - return parseShardFun(new TextDecoder().decode(res || '').trim()) + return parseShardFun(new TextDecoder().decode(res ?? '').trim()) } - -export { default as readme } from './shard-readme.js' diff --git a/src/sharding.js b/src/sharding.js deleted file mode 100644 index c92648b..0000000 --- a/src/sharding.js +++ /dev/null @@ -1,247 +0,0 @@ -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 - * @typedef {import('interface-datastore').Batch} Batch - * @typedef {import('interface-datastore').Query} Query - * @typedef {import('interface-datastore').QueryFilter} QueryFilter - * @typedef {import('interface-datastore').QueryOrder} QueryOrder - * @typedef {import('interface-datastore').KeyQuery} KeyQuery - * @typedef {import('interface-datastore').KeyQueryFilter} KeyQueryFilter - * @typedef {import('interface-datastore').KeyQueryOrder} KeyQueryOrder - * @typedef {import('interface-datastore').Pair} Pair - * @typedef {import('./types').Shard} Shard - * - */ -/** - * @template TValue - * @typedef {import('interface-store').Await } Await - */ - -/** - * @template TEntry - * @typedef {import('interface-store').AwaitIterable} AwaitIterable - */ - -/** - * Backend independent abstraction of go-ds-flatfs. - * - * Wraps another datastore such that all values are stored - * sharded according to the given sharding function. - */ -export class ShardingDatastore extends BaseDatastore { - /** - * @param {Datastore} store - * @param {Shard} shard - */ - constructor (store, shard) { - super() - - this.child = new KeyTransformDatastore(store, { - convert: this._convertKey.bind(this), - invert: this._invertKey.bind(this) - }) - this.shard = shard - } - - async open () { - await this.child.open() - - this.shard = await ShardingDatastore.create(this.child, this.shard) - } - - /** - * @param {Key} key - */ - _convertKey (key) { - const s = key.toString() - if (s === shardKey.toString() || s === shardReadmeKey.toString()) { - return key - } - - const parent = new Key(this.shard.fun(s)) - return parent.child(key) - } - - /** - * @param {Key} key - */ - _invertKey (key) { - const s = key.toString() - if (s === shardKey.toString() || s === shardReadmeKey.toString()) { - return key - } - return Key.withNamespaces(key.list().slice(1)) - } - - /** - * @deprecated - * @param {Datastore} store - * @param {Shard} shard - */ - static async createOrOpen (store, shard) { - try { - await ShardingDatastore.create(store, shard) - } catch (/** @type {any} */ err) { - if (err && err.message !== 'datastore exists') throw err - } - return ShardingDatastore.open(store) - } - - /** - * @deprecated - * @param {Datastore} store - */ - static async open (store) { - const shard = await readShardFun('/', store) - return new ShardingDatastore(store, shard) - } - - /** - * @param {Datastore} store - * @param {Shard} shard - */ - static async create (store, shard) { - const hasShard = await store.has(shardKey) - if (!hasShard && !shard) { - throw Errors.dbOpenFailedError(Error('Shard is required when datastore doesn\'t have a shard key already.')) - } - if (!hasShard) { - // @ts-ignore i have no idea what putRaw is or saw any implementation - 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(readme)) - ]) - - return shard - } - - // test shards - const diskShard = await readShardFun('/', store) - const a = (diskShard || '').toString() - const b = shard.toString() - if (a !== b) { - throw new Error(`specified fun ${b} does not match repo shard fun ${a}`) - } - return diskShard - } - - /** - * @param {Key} key - * @param {Uint8Array} val - * @param {Options} [options] - */ - put (key, val, options) { - return this.child.put(key, val, options) - } - - /** - * @param {Key} key - * @param {Options} [options] - */ - get (key, options) { - return this.child.get(key, options) - } - - /** - * @param {Key} key - * @param {Options} [options] - */ - has (key, options) { - return this.child.has(key, options) - } - - /** - * @param {Key} key - * @param {Options} [options] - */ - delete (key, options) { - return this.child.delete(key, options) - } - - /** - * @param {AwaitIterable} source - * @param {Options} [options] - * @returns {AsyncIterable} - */ - async * putMany (source, options = {}) { - yield * this.child.putMany(source, options) - } - - /** - * @param {AwaitIterable} source - * @param {Options} [options] - * @returns {AsyncIterable} - */ - async * getMany (source, options = {}) { - yield * this.child.getMany(source, options) - } - - /** - * @param {AwaitIterable} source - * @param {Options} [options] - * @returns {AsyncIterable} - */ - async * deleteMany (source, options = {}) { - yield * this.child.deleteMany(source, options) - } - - batch () { - return this.child.batch() - } - - /** - * @param {Query} q - * @param {Options} [options] - */ - query (q, options) { - /** @type {Query} */ - const tq = { - ...q, - filters: [ - /** @type {QueryFilter} */ - ({ key }) => key.toString() !== shardKey.toString(), - /** @type {QueryFilter} */ - ({ key }) => key.toString() !== shardReadmeKey.toString() - ].concat(q.filters || []) - } - - return this.child.query(tq, options) - } - - /** - * @param {KeyQuery} q - * @param {Options} [options] - */ - queryKeys (q, options) { - /** @type {KeyQuery} */ - const tq = { - ...q, - filters: [ - /** @type {KeyQueryFilter} */ - key => key.toString() !== shardKey.toString(), - /** @type {KeyQueryFilter} */ - key => key.toString() !== shardReadmeKey.toString() - ].concat(q.filters || []) - } - - return this.child.queryKeys(tq, options) - } - - close () { - return this.child.close() - } -} diff --git a/src/sharding.ts b/src/sharding.ts new file mode 100644 index 0000000..64c597e --- /dev/null +++ b/src/sharding.ts @@ -0,0 +1,139 @@ +import { Batch, Key, KeyQuery, KeyQueryFilter, Options, Pair, Query, QueryFilter } from 'interface-datastore' +import { + readShardFun, + SHARDING_FN +} from './shard.js' +import { BaseDatastore } from './base.js' +import { KeyTransformDatastore } from './keytransform.js' +import * as Errors from './errors.js' +import type { Shard } from './index.js' +import type { Datastore } from 'interface-datastore' +import type { AwaitIterable } from 'interface-store' + +const shardKey = new Key(SHARDING_FN) + +/** + * Backend independent abstraction of go-ds-flatfs. + * + * Wraps another datastore such that all values are stored + * sharded according to the given sharding function. + */ +export class ShardingDatastore extends BaseDatastore { + private readonly child: KeyTransformDatastore + private shard: Shard + + constructor (store: Datastore, shard: Shard) { + super() + + this.child = new KeyTransformDatastore(store, { + convert: this._convertKey.bind(this), + invert: this._invertKey.bind(this) + }) + this.shard = shard + } + + async open (): Promise { + this.shard = await ShardingDatastore.create(this.child, this.shard) + } + + _convertKey (key: Key): Key { + const s = key.toString() + if (s === shardKey.toString()) { + return key + } + + const parent = new Key(this.shard.fun(s)) + return parent.child(key) + } + + _invertKey (key: Key): Key { + const s = key.toString() + if (s === shardKey.toString()) { + return key + } + return Key.withNamespaces(key.list().slice(1)) + } + + static async create (store: Datastore, shard: Shard): Promise { + const hasShard = await store.has(shardKey) + if (!hasShard && shard == null) { + throw Errors.dbOpenFailedError(Error('Shard is required when datastore doesn\'t have a shard key already.')) + } + if (!hasShard) { + // @ts-expect-error i have no idea what putRaw is or saw any implementation + 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')) + ]) + + return shard + } + + // test shards + const diskShard = await readShardFun('/', store) + const a = diskShard.toString() + const b = shard.toString() + if (a !== b) { + throw new Error(`specified fun ${b} does not match repo shard fun ${a}`) + } + return diskShard + } + + async put (key: Key, val: Uint8Array, options?: Options): Promise { + await this.child.put(key, val, options) + } + + async get (key: Key, options?: Options): Promise { + return await this.child.get(key, options) + } + + async has (key: Key, options?: Options): Promise { + return await this.child.has(key, options) + } + + async delete (key: Key, options?: Options): Promise { + await this.child.delete(key, options) + } + + async * putMany (source: AwaitIterable, options: Options = {}): AsyncIterable { + yield * this.child.putMany(source, options) + } + + async * getMany (source: AwaitIterable, options: Options = {}): AsyncIterable { + yield * this.child.getMany(source, options) + } + + async * deleteMany (source: AwaitIterable, options: Options = {}): AsyncIterable { + yield * this.child.deleteMany(source, options) + } + + batch (): Batch { + return this.child.batch() + } + + query (q: Query, options?: Options): AsyncIterable { + const omitShard: QueryFilter = ({ key }) => key.toString() !== shardKey.toString() + + const tq: Query = { + ...q, + filters: [ + omitShard + ].concat(q.filters ?? []) + } + + return this.child.query(tq, options) + } + + queryKeys (q: KeyQuery, options?: Options): AsyncIterable { + const omitShard: KeyQueryFilter = (key) => key.toString() !== shardKey.toString() + + const tq: KeyQuery = { + ...q, + filters: [ + omitShard + ].concat(q.filters ?? []) + } + + return this.child.queryKeys(tq, options) + } +} diff --git a/src/tiered.js b/src/tiered.ts similarity index 51% rename from src/tiered.js rename to src/tiered.ts index 1ab0ed0..cbf27ca 100644 --- a/src/tiered.js +++ b/src/tiered.ts @@ -3,24 +3,11 @@ import * as Errors from './errors.js' import { logger } from '@libp2p/logger' import { pushable } from 'it-pushable' import drain from 'it-drain' +import type { Batch, Datastore, Key, KeyQuery, Options, Pair, Query } from 'interface-datastore' +import type { AwaitIterable } from 'interface-store' const log = logger('datastore:core:tiered') -/** - * @typedef {import('interface-datastore').Datastore} Datastore - * @typedef {import('interface-datastore').Options} Options - * @typedef {import('interface-datastore').Batch} Batch - * @typedef {import('interface-datastore').Query} Query - * @typedef {import('interface-datastore').KeyQuery} KeyQuery - * @typedef {import('interface-datastore').Key} Key - * @typedef {import('interface-datastore').Pair} Pair - */ - -/** - * @template TEntry - * @typedef {import('interface-store').AwaitIterable} AwaitIterable - */ - /** * A datastore that can combine multiple stores. Puts and deletes * will write through to all datastores. Has and get will @@ -29,45 +16,27 @@ const log = logger('datastore:core:tiered') * */ export class TieredDatastore extends BaseDatastore { - /** - * @param {Datastore[]} stores - */ - constructor (stores) { + private readonly stores: Datastore[] + + constructor (stores: Datastore[]) { super() this.stores = stores.slice() } - async open () { + async put (key: Key, value: Uint8Array, options?: Options): Promise { try { - await Promise.all(this.stores.map((store) => store.open())) - } catch (/** @type {any} */ err) { - throw Errors.dbOpenFailedError(err) - } - } - - /** - * @param {Key} key - * @param {Uint8Array} value - * @param {Options} [options] - */ - async put (key, value, options) { - try { - await Promise.all(this.stores.map(store => store.put(key, value, options))) - } catch (/** @type {any} */ err) { + await Promise.all(this.stores.map(async store => { await store.put(key, value, options) })) + } catch (err: any) { throw Errors.dbWriteFailedError(err) } } - /** - * @param {Key} key - * @param {Options} [options] - */ - async get (key, options) { + async get (key: Key, options?: Options): Promise { for (const store of this.stores) { try { const res = await store.get(key, options) - if (res) return res + if (res != null) return res } catch (err) { log.error(err) } @@ -75,11 +44,7 @@ export class TieredDatastore extends BaseDatastore { throw Errors.notFoundError() } - /** - * @param {Key} key - * @param {Options} [options] - */ - async has (key, options) { + async has (key: Key, options?: Options): Promise { for (const s of this.stores) { if (await s.has(key, options)) { return true @@ -89,27 +54,18 @@ export class TieredDatastore extends BaseDatastore { return false } - /** - * @param {Key} key - * @param {Options} [options] - */ - async delete (key, options) { + async delete (key: Key, options?: Options): Promise { try { - await Promise.all(this.stores.map(store => store.delete(key, options))) - } catch (/** @type {any} */ err) { + await Promise.all(this.stores.map(async store => { await store.delete(key, options) })) + } catch (err: any) { throw Errors.dbDeleteFailedError(err) } } - /** - * @param {AwaitIterable} source - * @param {Options} [options] - * @returns {AsyncIterable} - */ - async * putMany (source, options = {}) { - let error + async * putMany (source: AwaitIterable, options: Options = {}): AsyncIterable { + let error: Error | undefined const pushables = this.stores.map(store => { - const source = pushable({ + const source = pushable({ objectMode: true }) @@ -124,7 +80,7 @@ export class TieredDatastore extends BaseDatastore { try { for await (const pair of source) { - if (error) { + if (error != null) { throw error } @@ -137,15 +93,10 @@ export class TieredDatastore extends BaseDatastore { } } - /** - * @param {AwaitIterable} source - * @param {Options} [options] - * @returns {AsyncIterable} - */ - async * deleteMany (source, options = {}) { - let error + async * deleteMany (source: AwaitIterable, options: Options = {}): AsyncIterable { + let error: Error | undefined const pushables = this.stores.map(store => { - const source = pushable({ + const source = pushable({ objectMode: true }) @@ -160,7 +111,7 @@ export class TieredDatastore extends BaseDatastore { try { for await (const key of source) { - if (error) { + if (error != null) { throw error } @@ -173,22 +124,15 @@ export class TieredDatastore extends BaseDatastore { } } - async close () { - await Promise.all(this.stores.map(store => store.close())) - } - - /** - * @returns {Batch} - */ - batch () { + batch (): Batch { const batches = this.stores.map(store => store.batch()) return { put: (key, value) => { - batches.forEach(b => b.put(key, value)) + batches.forEach(b => { b.put(key, value) }) }, delete: (key) => { - batches.forEach(b => b.delete(key)) + batches.forEach(b => { b.delete(key) }) }, commit: async (options) => { for (const batch of batches) { @@ -198,19 +142,11 @@ export class TieredDatastore extends BaseDatastore { } } - /** - * @param {Query} q - * @param {Options} [options] - */ - query (q, options) { + query (q: Query, options?: Options): AsyncIterable { return this.stores[this.stores.length - 1].query(q, options) } - /** - * @param {KeyQuery} q - * @param {Options} [options] - */ - queryKeys (q, options) { + queryKeys (q: KeyQuery, options?: Options): AsyncIterable { return this.stores[this.stores.length - 1].queryKeys(q, options) } } diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index 87fb613..0000000 --- a/src/types.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Key } from 'interface-datastore' - -export interface Shard { - name: string - param: number - readonly _padding: string - fun: (s: string) => string - toString: () => string -} - -export interface KeyTransform { - convert: (key: Key) => Key - invert: (key: Key) => Key -} - -export type AwaitIterable = Iterable | AsyncIterable -export type Await = Promise | T diff --git a/src/utils.js b/src/utils.js deleted file mode 100644 index ccdf572..0000000 --- a/src/utils.js +++ /dev/null @@ -1,27 +0,0 @@ - -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/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..540bea0 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,5 @@ + +export const replaceStartWith = (s: string, r: string): string => { + const matcher = new RegExp('^' + r) + return s.replace(matcher, '') +} diff --git a/test/keytransform.spec.js b/test/keytransform.spec.ts similarity index 76% rename from test/keytransform.spec.js rename to test/keytransform.spec.ts index 961dfcd..265266b 100644 --- a/test/keytransform.spec.js +++ b/test/keytransform.spec.ts @@ -10,16 +10,10 @@ describe('KeyTransformDatastore', () => { it('basic', async () => { const mStore = new MemoryDatastore() const transform = { - /** - * @param {Key} key - */ - convert (key) { + convert (key: Key): Key { return new Key('/abc').child(key) }, - /** - * @param {Key} key - */ - invert (key) { + invert (key: Key): Key { const l = key.list() if (l[0] !== 'abc') { throw new Error('missing prefix, convert failed?') @@ -38,9 +32,9 @@ describe('KeyTransformDatastore', () => { 'foo/bar/bazb', 'foo/bar/baz/barb' ].map((s) => new Key(s)) - await Promise.all(keys.map((key) => kStore.put(key, key.uint8Array()))) - const kResults = Promise.all(keys.map((key) => kStore.get(key))) - const mResults = Promise.all(keys.map((key) => mStore.get(new Key('abc').child(key)))) + await Promise.all(keys.map(async (key) => { await kStore.put(key, key.uint8Array()) })) + const kResults = Promise.all(keys.map(async (key) => await kStore.get(key))) + const mResults = Promise.all(keys.map(async (key) => await mStore.get(new Key('abc').child(key)))) const results = await Promise.all([kResults, mResults]) expect(results[0]).to.eql(results[1]) @@ -54,6 +48,5 @@ describe('KeyTransformDatastore', () => { expect(transform.invert(kA)).to.eql(kB) expect(kA).to.eql(transform.convert(kB)) }) - await kStore.close() }) }) diff --git a/test/memory.spec.js b/test/memory.spec.ts similarity index 100% rename from test/memory.spec.js rename to test/memory.spec.ts diff --git a/test/mount.spec.js b/test/mount.spec.ts similarity index 96% rename from test/mount.spec.js rename to test/mount.spec.ts index ee1f2ad..a30e101 100644 --- a/test/mount.spec.js +++ b/test/mount.spec.ts @@ -9,12 +9,9 @@ import { MountDatastore } from '../src/mount.js' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { interfaceDatastoreTests } from 'interface-datastore-tests' import { KeyTransformDatastore } from '../src/keytransform.js' +import type { Datastore } from 'interface-datastore' -/** - * @param {import('interface-datastore').Datastore} datastore - * @param {Key} prefix - */ -const stripPrefixDatastore = (datastore, prefix) => { +const stripPrefixDatastore = (datastore: Datastore, prefix: Key): Datastore => { return new KeyTransformDatastore( datastore, { convert: (key) => { diff --git a/test/namespace.spec.js b/test/namespace.spec.ts similarity index 83% rename from test/namespace.spec.js rename to test/namespace.spec.ts index 25980bd..a81c677 100644 --- a/test/namespace.spec.js +++ b/test/namespace.spec.ts @@ -26,9 +26,9 @@ describe('NamespaceDatastore', () => { 'foo/bar/baz/barb' ].map((s) => new Key(s)) - await Promise.all(keys.map(key => store.put(key, uint8ArrayFromString(key.toString())))) - const nResults = Promise.all(keys.map((key) => store.get(key))) - const mResults = Promise.all(keys.map((key) => mStore.get(new Key(prefix).child(key)))) + await Promise.all(keys.map(async key => { await store.put(key, uint8ArrayFromString(key.toString())) })) + const nResults = Promise.all(keys.map(async (key) => await store.get(key))) + const mResults = Promise.all(keys.map(async (key) => await mStore.get(new Key(prefix).child(key)))) const results = await Promise.all([nResults, mResults]) const mRes = await all(mStore.query({})) const nRes = await all(store.query({})) @@ -41,7 +41,6 @@ describe('NamespaceDatastore', () => { expect(store.transform.invert(kA)).to.eql(kB) expect(kA).to.eql(store.transform.convert(kB)) }) - await store.close() expect(results[0]).to.eql(results[1]) })) diff --git a/test/shard.spec.js b/test/shard.spec.ts similarity index 100% rename from test/shard.spec.js rename to test/shard.spec.ts diff --git a/test/sharding.spec.js b/test/sharding.spec.ts similarity index 92% rename from test/sharding.spec.js rename to test/sharding.spec.ts index cdafd6c..4266019 100644 --- a/test/sharding.spec.js +++ b/test/sharding.spec.ts @@ -7,9 +7,7 @@ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { NextToLast, - SHARDING_FN, - README_FN, - readme + SHARDING_FN } from '../src/shard.js' import { ShardingDatastore @@ -23,11 +21,9 @@ describe('ShardingDatastore', () => { const store = new ShardingDatastore(ms, shard) await store.open() const res = await Promise.all([ - ms.get(new Key(SHARDING_FN)), - ms.get(new Key(README_FN)) + store.get(new Key(SHARDING_FN)) ]) expect(uint8ArrayToString(res[0])).to.eql(shard.toString() + '\n') - expect(uint8ArrayToString(res[1])).to.eql(readme) }) it('open - empty', () => { diff --git a/test/tiered.spec.js b/test/tiered.spec.ts similarity index 94% rename from test/tiered.spec.js rename to test/tiered.spec.ts index 47b492a..9dade7d 100644 --- a/test/tiered.spec.js +++ b/test/tiered.spec.ts @@ -6,6 +6,7 @@ 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' +import type { Datastore } from 'interface-datastore' /** * @typedef {import('interface-datastore').Datastore} Datastore @@ -13,10 +14,8 @@ import { interfaceDatastoreTests } from 'interface-datastore-tests' describe('Tiered', () => { describe('all stores', () => { - /** @type {Datastore[]} */ - const ms = [] - /** @type {TieredDatastore} */ - let store + const ms: Datastore[] = [] + let store: TieredDatastore beforeEach(() => { ms.push(new MemoryDatastore()) ms.push(new MemoryDatastore()) diff --git a/test/utils.spec.js b/test/utils.spec.js deleted file mode 100644 index a2bceef..0000000 --- a/test/utils.spec.js +++ /dev/null @@ -1,121 +0,0 @@ -/* eslint-env mocha */ - -import { expect } from 'aegir/chai' -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' - ) - }) -}) diff --git a/test/utils.spec.ts b/test/utils.spec.ts new file mode 100644 index 0000000..5cb851f --- /dev/null +++ b/test/utils.spec.ts @@ -0,0 +1,20 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/chai' +import * as utils from '../src/utils.js' + +describe('utils', () => { + it('replaceStartWith', () => { + expect( + utils.replaceStartWith('helloworld', 'hello') + ).to.eql( + 'world' + ) + + expect( + utils.replaceStartWith('helloworld', 'world') + ).to.eql( + 'helloworld' + ) + }) +}) diff --git a/tsconfig.json b/tsconfig.json index 2742a08..13a3599 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,10 @@ { - "extends": "aegir/src/config/tsconfig.aegir.json", - "compilerOptions": { - "outDir": "dist", - "emitDeclarationOnly": true - }, - "include": [ - "test", - "src" - ] + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "src", + "test" + ] }