From 2afd9be3abf747528473c46550671f92acc5792e Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Mon, 30 Nov 2020 14:49:16 +0000 Subject: [PATCH 01/21] feat: ts types, github ci and clean up - add ts types with jsdocs and aegir - remove travis and add github action - update deps and repo clean up (readme, package.json, etc.. ) --- .github/workflows/main.yml | 67 +++++++++ .npmignore | 41 ----- .travis.yml | 40 ----- README.md | 298 +------------------------------------ package.json | 23 ++- src/adapter.js | 99 +++++++----- src/errors.js | 18 ++- src/index.js | 12 +- src/key.js | 95 ++++++++---- src/memory.js | 25 +++- src/tests.js | 7 +- src/types.ts | 161 ++++++++++++++++++++ src/utils.js | 75 ++++++++-- test/key.spec.js | 3 +- test/utils.spec.js | 5 +- tsconfig.json | 10 ++ 16 files changed, 500 insertions(+), 479 deletions(-) create mode 100644 .github/workflows/main.yml delete mode 100644 .npmignore delete mode 100644 .travis.yml create mode 100644 src/types.ts create mode 100644 tsconfig.json diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..b94c65e --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,67 @@ +name: ci +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: yarn lint + - run: yarn build + - uses: gozala/typescript-error-reporter-action@v1.0.4 + - run: yarn aegir dep-check -- -i aegir + - uses: ipfs/aegir/actions/bundle-size@master + name: size + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + test-node: + needs: check + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + node: [12, 14] + fail-fast: true + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node }} + - run: yarn + - run: npx nyc --reporter=lcov npm run test:node -- --bail + - uses: codecov/codecov-action@v1 + test-chrome: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: yarn aegir test -t browser -t webworker + test-firefox: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: yarn aegir test -t browser -t webworker -- --browsers FirefoxHeadless + test-electron-main: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: npx xvfb-maybe yarn aegir test -t electron-main --bail + test-electron-renderer: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: yarn + - run: npx xvfb-maybe yarn aegir test -t electron-renderer --bail \ No newline at end of file diff --git a/.npmignore b/.npmignore deleted file mode 100644 index d5b8420..0000000 --- a/.npmignore +++ /dev/null @@ -1,41 +0,0 @@ -yarn.lock -package-lock.json - -**/node_modules/ -**/*.log -test/repo-tests* - -# Logs -logs -*.log - -coverage -.nyc_output - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# node-waf configuration -.lock-wscript - -build - -# Dependency directory -# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git -node_modules - -.travis.yml -.github -docs -test \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d0cf281..0000000 --- a/.travis.yml +++ /dev/null @@ -1,40 +0,0 @@ -language: node_js -cache: npm -stages: - - check - - test - - cov - -node_js: - - '10' - -os: - - linux - - osx - - windows - -script: npx nyc -s npm run test:node -- --bail -after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov - -jobs: - include: - - stage: check - script: - - npx aegir commitlint --travis - - npx aegir dep-check - - npm run lint - - - stage: test - name: chrome - addons: - chrome: stable - script: npx aegir test -t browser -t webworker - - - stage: test - name: firefox - addons: - firefox: latest - script: npx aegir test -t browser -t webworker -- --browsers FirefoxHeadless - -notifications: - email: false diff --git a/README.md b/README.md index bf9bb93..c9bb7d1 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,8 @@ [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) [![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/) [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) -[![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) -[![Build Status](https://flat.badgen.net/travis/ipfs/interface-datastore)](https://travis-ci.com/ipfs/interface-datastore) -[![Code Coverage](https://codecov.io/gh/ipfs/interface-datastore/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/interface-datastore) -[![Dependency Status](https://david-dm.org/ipfs/interface-datastore.svg?style=flat-square)](https://david-dm.org/ipfs/interface-datastore) -[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard) -![](https://img.shields.io/badge/npm-%3E%3D3.0.0-orange.svg?style=flat-square) -![](https://img.shields.io/badge/Node.js-%3E%3D8.0.0-orange.svg?style=flat-square) +[![codecov](https://img.shields.io/codecov/c/github/ipfs/interface-datastore.svg?style=flat-square)](https://codecov.io/gh/ipfs/interface-datastore) +[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/ipfs/interface-datastore/ci?label=ci&style=flat-square)](https://github.com/ipfs/interface-datastore/actions?query=branch%3Amaster+workflow%3Aci+) > Implementation of the [datastore](https://github.com/ipfs/go-datastore) interface in JavaScript @@ -25,41 +20,10 @@ - [Usage](#usage) - [Wrapping Stores](#wrapping-stores) - [Test suite](#test-suite) + - [Aborting requests](#aborting-requests) + - [Concurrency](#concurrency) - [Keys](#keys) - [API](#api) - - [`has(key, [options])` -> `Promise`](#haskey-options---promiseboolean) - - [Arguments](#arguments) - - [Example](#example) - - [`put(key, value, [options])` -> `Promise`](#putkey-value-options---promise) - - [Arguments](#arguments-1) - - [Example](#example-1) - - [`putMany(source, [options])` -> `AsyncIterator<{ key: Key, value: Uint8Array }>`](#putmanysource-options---asynciterator-key-key-value-uint8array-) - - [Arguments](#arguments-2) - - [Example](#example-2) - - [`get(key, [options])` -> `Promise`](#getkey-options---promiseuint8array) - - [Arguments](#arguments-3) - - [Example](#example-3) - - [`getMany(source, [options])` -> `AsyncIterator`](#getmanysource-options---asynciteratoruint8array) - - [Arguments](#arguments-4) - - [Example](#example-4) - - [`delete(key, [options])` -> `Promise`](#deletekey-options---promise) - - [Arguments](#arguments-5) - - [Example](#example-5) - - [`deleteMany(source, [options])` -> `AsyncIterator`](#deletemanysource-options---asynciteratorkey) - - [Arguments](#arguments-6) - - [Example](#example-6) - - [`query(query, [options])` -> `AsyncIterable`](#queryquery-options---asynciterableuint8array) - - [Arguments](#arguments-7) - - [Example](#example-7) - - [`batch()`](#batch) - - [Example](#example-8) - - [`put(key, value)`](#putkey-value) - - [`delete(key)`](#deletekey) - - [`commit([options])` -> `Promise`](#commitoptions---promisevoid) - - [Arguments](#arguments-8) - - [Example](#example-9) - - [`open()` -> `Promise`](#open---promise) - - [`close()` -> `Promise`](#close---promise) - [Contribute](#contribute) - [License](#license) @@ -121,12 +85,6 @@ See the [MemoryDatastore](./src/memory.js) for an example of how it is used. $ npm install interface-datastore ``` -The type definitions for this package are available on http://definitelytyped.org/. To install just use: - -```sh -$ npm install -D @types/interface-datastore -``` - ## Usage ### Wrapping Stores @@ -156,11 +114,11 @@ describe('mystore', () => { }) ``` -### Aborting requests +### Aborting requests Most API methods accept an [AbortSignal][] as part of an options object. Implementations may listen for an `abort` event emitted by this object, or test the `signal.aborted` property. When received implementations should tear down any long-lived requests or resources created. -### Concurrency +### Concurrency The streaming `(put|get|delete)Many` methods are intended to be used with modules such as [it-parallel-batch](https://www.npmjs.com/package/it-parallel-batch) to allow calling code to control levels of parallelisation. The batching method ensures results are returned in the correct order, but interface implementations should be thread safe. @@ -198,249 +156,7 @@ Also, every namespace can be parameterized to embed relevant object information. - `new Key('/Comedy/MontyPython/Sketch:CheeseShop/Character:Mousebender')` ## API - -Implementations of this interface should make the following methods available: - -### `has(key, [options])` -> `Promise` - -Check for the existence of a given key - -#### Arguments - -| Name | Type | Description | -| ---- | ---- | ----------- | -| key | [Key][] | The key to check the existance of | -| options | [Object][] | An options object, all properties are optional | -| options.signal | [AbortSignal][] | A way to signal that the caller is no longer interested in the outcome of this operation | - -#### Example - -```js -const exists = await store.has(new Key('awesome')) - -if (exists) { - console.log('it is there') -} else { - console.log('it is not there') -} -``` - -### `put(key, value, [options])` -> `Promise` - -Store a value with the given key. - -#### Arguments - -| Name | Type | Description | -| ---- | ---- | ----------- | -| key | [Key][] | The key to store the value under | -| value | [Uint8Array][] | Value to store | -| options | [Object][] | An options object, all properties are optional | -| options.signal | [AbortSignal][] | A way to signal that the caller is no longer interested in the outcome of this operation | - -#### Example - -```js -await store.put([{ key: new Key('awesome'), value: new Uint8Array([0, 1, 2, 3]) }]) -console.log('put content') -``` - -### `putMany(source, [options])` -> `AsyncIterator<{ key: Key, value: Uint8Array }>` - -Store many key-value pairs. - -#### Arguments - -| Name | Type | Description | -| ---- | ---- | ----------- | -| source | [AsyncIterator][]<{ key: [Key][], value: [Uint8Array][] }> | The key to store the value under | -| value | [Uint8Array][] | Value to store | -| options | [Object][] | An options object, all properties are optional | -| options.signal | [AbortSignal][] | A way to signal that the caller is no longer interested in the outcome of this operation | - -#### Example - -```js -const source = [{ key: new Key('awesome'), value: new Uint8Array([0, 1, 2, 3]) }] - -for await (const { key, value } of store.putMany(source)) { - console.info(`put content for key ${key}`) -} -``` - -### `get(key, [options])` -> `Promise` - -#### Arguments - -| Name | Type | Description | -| ---- | ---- | ----------- | -| key | [Key][] | The key retrieve the value for | -| options | [Object][] | An options object, all properties are optional | -| options.signal | [AbortSignal][] | A way to signal that the caller is no longer interested in the outcome of this operation | - -#### Example - -Retrieve the value stored under the given key. - -```js -const value = await store.get(new Key('awesome')) -console.log('got content: %s', value.toString('utf8')) -// => got content: datastore -``` - -### `getMany(source, [options])` -> `AsyncIterator` - -#### Arguments - -| Name | Type | Description | -| ---- | ---- | ----------- | -| source | [AsyncIterator][]<[Key][]> | One or more keys to retrieve values for | -| options | [Object][] | An options object, all properties are optional | -| options.signal | [AbortSignal][] | A way to signal that the caller is no longer interested in the outcome of this operation | - -#### Example - -Retrieve a stream of values stored under the given keys. - -```js -for await (const value of store.getMany([new Key('awesome')])) { - console.log('got content:', new TextDecoder('utf8').decode(value)) - // => got content: datastore -} -``` - -### `delete(key, [options])` -> `Promise` - -Delete the content stored under the given key. - -#### Arguments - -| Name | Type | Description | -| ---- | ---- | ----------- | -| key | [Key][] | The key to remove the value for | -| options | [Object][] | An options object, all properties are optional | -| options.signal | [AbortSignal][] | A way to signal that the caller is no longer interested in the outcome of this operation | - -#### Example - -```js -await store.delete(new Key('awesome')) -console.log('deleted awesome content :(') -``` - -### `deleteMany(source, [options])` -> `AsyncIterator` - -Delete the content stored under the given keys. - -#### Arguments - -| Name | Type | Description | -| ---- | ---- | ----------- | -| source | [AsyncIterator][]<[Key][]> | One or more keys to remove values for | -| options | [Object][] | An options object, all properties are optional | -| options.signal | [AbortSignal][] | A way to signal that the caller is no longer interested in the outcome of this operation | - -#### Example - -```js -const source = [new Key('awesome')] - -for await (const key of store.deleteMany(source)) { - console.log(`deleted content with key ${key}`) -} -``` - -### `query(query, [options])` -> `AsyncIterable` - -Search the store for some values. Returns an [AsyncIterable][] with each item being a [Uint8Array][]. - -#### Arguments - -| Name | Type | Description | -| ---- | ---- | ----------- | -| query | [Object][] | A query object, all properties are optional | -| query.prefix | [String][] | Only return values where the key starts with this prefix | -| query.filters | [Array][]<[Function][]([Uint8Array][]) -> [Boolean][]> | Filter the results according to the these functions | -| query.orders | [Array][]<[Function][]([Array][]<[Uint8Array][]>) -> [Array][]<[Uint8Array][]>> | Order the results according to these functions | -| query.limit | [Number][] | Only return this many records | -| query.offset | [Number][] | Skip this many records at the beginning | -| options | [Object][] | An options object, all properties are optional | -| options.signal | [AbortSignal][] | A way to signal that the caller is no longer interested in the outcome of this operation | - -#### Example - -```js -// retrieve __all__ values from the store -let list = [] -for await (const value of store.query({})) { - list.push(value) -} -console.log('ALL THE VALUES', list) -``` - -### `batch()` - -This will return an object with which you can chain multiple operations together, with them only being executed on calling `commit`. - -#### Example - -```js -const b = store.batch() - -for (let i = 0; i < 100; i++) { - b.put(new Key(`hello${i}`), new TextEncoder('utf8').encode(`hello world ${i}`)) -} - -await b.commit() -console.log('put 100 values') -``` - -#### `put(key, value)` - -Queue a put operation to the store. - -| Name | Type | Description | -| ---- | ---- | ----------- | -| key | [Key][] | The key to store the value under | -| value | [Uint8Array][] | Value to store | - -#### `delete(key)` - -Queue a delete operation to the store. - -| Name | Type | Description | -| ---- | ---- | ----------- | -| key | [Key][] | The key to remove the value for | - -#### `commit([options])` -> `Promise` - -Write all queued operations to the underyling store. The batch object should not be used after calling this. - -#### Arguments - -| Name | Type | Description | -| ---- | ---- | ----------- | -| options | [Object][] | An options object, all properties are optional | -| options.signal | [AbortSignal][] | A way to signal that the caller is no longer interested in the outcome of this operation | - -#### Example - -```js -const batch = store.batch() - -batch.put(new Key('to-put'), new TextEncoder('utf8').encode('hello world')) -batch.del(new Key('to-remove')) - -await batch.commit() -``` - -### `open()` -> `Promise` - -Opens the datastore, this is only needed if the store was closed before, otherwise this is taken care of by the constructor. - -### `close()` -> `Promise` - -Close the datastore, this should always be called to ensure resources are cleaned up. +https://ipfs.github.io/interface-datastore/ ## Contribute diff --git a/package.json b/package.json index f826bb6..5f69fc1 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,19 @@ "description": "datastore interface", "leadMaintainer": "Alex Potsides ", "main": "src/index.js", + "types": "dist/src/index.d.ts", + "typesVersions": { + "*": { + "src/*": [ + "dist/src/*", + "dist/src/*/index" + ] + } + }, + "files": [ + "src", + "dist" + ], "scripts": { "lint": "aegir lint", "build": "aegir build", @@ -33,19 +46,19 @@ }, "homepage": "https://github.com/ipfs/interface-datastore#readme", "devDependencies": { - "aegir": "^28.1.0", - "chai": "^4.1.2", - "dirty-chai": "^2.0.1" + "aegir": "^29.2.0" }, "dependencies": { - "class-is": "^1.1.0", "err-code": "^2.0.1", - "ipfs-utils": "^4.0.1", + "ipfs-utils": "^5.0.1", "iso-random-stream": "^1.1.1", "it-all": "^1.0.2", "it-drain": "^1.0.1", "nanoid": "^3.0.2" }, + "eslintConfig": { + "extends": "ipfs" + }, "contributors": [ "achingbrain ", "David Dias ", diff --git a/src/adapter.js b/src/adapter.js index 94c83e7..11edfca 100644 --- a/src/adapter.js +++ b/src/adapter.js @@ -3,13 +3,30 @@ const { filter, sortAll, take, map } = require('./utils') const drain = require('it-drain') -class InterfaceDatastoreAdapter { - async open () { // eslint-disable-line require-await - +/** + * @typedef {import('./key')} Key + * @typedef {import('./types').Pair} Pair + * @typedef {import('./types').IDatastore} IDatastore + * @typedef {import('./types').Options} Options + * @typedef {import('./types').Query} Query + * @typedef {import('./types').Batch} Batch + */ + +/** + * @template O + * @typedef {import('./types').AnyIterable} AnyIterable + */ + +/** + * @implements {IDatastore} + */ +class Adapter { + open () { + return Promise.resolve() } - async close () { // eslint-disable-line require-await - + close () { + return Promise.resolve() } /** @@ -17,19 +34,19 @@ class InterfaceDatastoreAdapter { * * @param {Key} key * @param {Uint8Array} val - * @param {Object} options + * @param {Options} options * @returns {Promise} */ - async put (key, val, options = {}) { // eslint-disable-line require-await - + put (key, val, options) { // eslint-disable-line require-await + return Promise.resolve() } /** * Store the given key/value pairs * - * @param {AsyncIterator<{ key: Key, value: Uint8Array }>} source + * @param {AnyIterable} source * @param {Object} options - * @returns {AsyncIterator<{ key: Key, value: Uint8Array }>} + * @returns {AsyncGenerator} */ async * putMany (source, options = {}) { for await (const { key, value } of source) { @@ -45,16 +62,12 @@ class InterfaceDatastoreAdapter { * @param {Object} options * @returns {Promise} */ - async get (key, options = {}) { // eslint-disable-line require-await - + get (key, options = {}) { + return Promise.resolve(new Uint8Array()) } /** - * Retrieve values for the passed keys - * - * @param {AsyncIterator} source - * @param {Object} options - * @returns {AsyncIterator} + * @param {AnyIterable} source */ async * getMany (source, options = {}) { for await (const key of source) { @@ -67,9 +80,19 @@ class InterfaceDatastoreAdapter { * * @param {Key} key * @returns {Promise} + * @example + * ```js + * const exists = await store.has(new Key('awesome')) + * + * if (exists) { + * console.log('it is there') + * } else { + * console.log('it is not there') + * } + * ``` */ - async has (key) { // eslint-disable-line require-await - + has (key) { // eslint-disable-line require-await + return Promise.resolve(false) } /** @@ -79,16 +102,16 @@ class InterfaceDatastoreAdapter { * @param {Object} options * @returns {Promise} */ - async delete (key, options = {}) { // eslint-disable-line require-await - + delete (key, options = {}) { // eslint-disable-line require-await + return Promise.resolve() } /** * Remove values for the passed keys * - * @param {AsyncIterator} source - * @param {Object} options - * @returns {AsyncIterator} + * @param {AnyIterable} source + * @param {Options} options + * @returns {AsyncGenerator} */ async * deleteMany (source, options = {}) { for await (const key of source) { @@ -100,7 +123,7 @@ class InterfaceDatastoreAdapter { /** * Create a new batch object. * - * @returns {Object} + * @returns {Batch} */ batch () { let puts = [] @@ -125,26 +148,24 @@ class InterfaceDatastoreAdapter { /** * Yield all datastore values * - * @param {Object} q - * @param {Object} options - * @returns {AsyncIterable<{ key: Key, value: Uint8Array }>} + * @param {Query} q + * @param {Options} options + * @returns {AsyncGenerator} */ async * _all (q, options) { // eslint-disable-line require-await } /** - * Query the store. - * - * @param {Object} q - * @param {Object} options - * @returns {AsyncIterable} + * @param {Query} q + * @param {Options} options + * @returns {AsyncGenerator} */ - async * query (q, options) { // eslint-disable-line require-await + query (q, options) { let it = this._all(q, options) if (q.prefix != null) { - it = filter(it, e => e.key.toString().startsWith(q.prefix)) + it = filter(it, e => e.key.toString().startsWith(/** @type {string} */(q.prefix))) } if (Array.isArray(q.filters)) { @@ -157,7 +178,7 @@ class InterfaceDatastoreAdapter { if (q.offset != null) { let i = 0 - it = filter(it, () => i++ >= q.offset) + it = filter(it, () => i++ >= /** @type {number} */(q.offset)) } if (q.limit != null) { @@ -165,11 +186,11 @@ class InterfaceDatastoreAdapter { } if (q.keysOnly === true) { - it = map(it, e => ({ key: e.key })) + return map(it, e => ({ key: e.key })) } - yield * it + return it } } -module.exports = InterfaceDatastoreAdapter +module.exports = Adapter diff --git a/src/errors.js b/src/errors.js index 7e4941d..b98ff3f 100644 --- a/src/errors.js +++ b/src/errors.js @@ -2,27 +2,35 @@ const errcode = require('err-code') -module.exports.dbOpenFailedError = (err) => { +const dbOpenFailedError = (err) => { err = err || new Error('Cannot open database') return errcode(err, 'ERR_DB_OPEN_FAILED') } -module.exports.dbDeleteFailedError = (err) => { +const dbDeleteFailedError = (err) => { err = err || new Error('Delete failed') return errcode(err, 'ERR_DB_DELETE_FAILED') } -module.exports.dbWriteFailedError = (err) => { +const dbWriteFailedError = (err) => { err = err || new Error('Write failed') return errcode(err, 'ERR_DB_WRITE_FAILED') } -module.exports.notFoundError = (err) => { +const notFoundError = (err) => { err = err || new Error('Not Found') return errcode(err, 'ERR_NOT_FOUND') } -module.exports.abortedError = (err) => { +const abortedError = (err) => { err = err || new Error('Aborted') return errcode(err, 'ERR_ABORTED') } + +module.exports = { + dbOpenFailedError, + dbDeleteFailedError, + dbWriteFailedError, + notFoundError, + abortedError +} diff --git a/src/index.js b/src/index.js index 2226059..742da51 100644 --- a/src/index.js +++ b/src/index.js @@ -6,8 +6,10 @@ const utils = require('./utils') const Errors = require('./errors') const Adapter = require('./adapter') -exports.Key = Key -exports.MemoryDatastore = MemoryDatastore -exports.utils = utils -exports.Errors = Errors -exports.Adapter = Adapter +module.exports = { + Key, + MemoryDatastore, + utils, + Errors, + Adapter +} diff --git a/src/key.js b/src/key.js index 181fc7d..a3cf740 100644 --- a/src/key.js +++ b/src/key.js @@ -1,10 +1,10 @@ 'use strict' const { nanoid } = require('nanoid') -const withIs = require('class-is') const { utf8Encoder, utf8Decoder } = require('./utils') const TextDecoder = require('ipfs-utils/src/text-decoder') +const symbol = Symbol.for('@ipfs/interface-datastore/key') const pathSepS = '/' const pathSepB = utf8Encoder.encode(pathSepS) const pathSep = pathSepB[0] @@ -26,7 +26,12 @@ const pathSep = pathSepB[0] * */ class Key { + /** + * @param {string | Uint8Array} s + * @param {boolean} [clean] + */ constructor (s, clean) { + Object.defineProperty(this, symbol, { value: true }) if (typeof s === 'string') { this._buf = utf8Encoder.encode(s) } else if (s instanceof Uint8Array) { @@ -51,7 +56,7 @@ class Key { /** * Convert to the string representation * - * @param {string} [encoding='utf8'] + * @param {string} [encoding='utf8'] - The encoding to use. Should default to 'utf8'. * @returns {string} */ toString (encoding = 'utf8') { @@ -72,6 +77,8 @@ class Key { } /** + * Return string representation of the key + * * @returns {string} */ get [Symbol.toStringTag] () { @@ -81,16 +88,17 @@ class Key { /** * Constructs a key out of a namespace array. * - * @param {Array} list + * @param {Array} list - The array of namespaces * @returns {Key} * * @example + * ```js * Key.withNamespaces(['one', 'two']) * // => Key('/one/two') - * + * ``` */ static withNamespaces (list) { - return new _Key(list.join(pathSepS)) + return new Key(list.join(pathSepS)) } /** @@ -99,12 +107,13 @@ class Key { * @returns {Key} * * @example + * ```js * Key.random() * // => Key('/f98719ea086343f7b71f32ea9d9d521d') - * + * ``` */ static random () { - return new _Key(nanoid().replace(/-/g, '')) + return new Key(nanoid().replace(/-/g, '')) } /** @@ -133,7 +142,7 @@ class Key { /** * Check if the given key is sorted lower than ourself. * - * @param {Key} key + * @param {Key} key - The other Key to check against * @returns {boolean} */ less (key) { @@ -164,8 +173,10 @@ class Key { * @returns {Key} * * @example + * ```js * new Key('/Comedy/MontyPython/Actor:JohnCleese').reverse() * // => Key('/Actor:JohnCleese/MontyPython/Comedy') + * ``` */ reverse () { return Key.withNamespaces(this.list().slice().reverse()) @@ -185,9 +196,10 @@ class Key { * @returns {string} * * @example + * ```js * new Key('/Comedy/MontyPython/Actor:JohnCleese').baseNamespace() * // => 'Actor:JohnCleese' - * + * ``` */ baseNamespace () { const ns = this.namespaces() @@ -200,9 +212,10 @@ class Key { * @returns {Array} * * @example + * ```js * new Key('/Comedy/MontyPython/Actor:JohnCleese').list() * // => ['Comedy', 'MontyPythong', 'Actor:JohnCleese'] - * + * ``` */ list () { return this.toString().split(pathSepS).slice(1) @@ -214,9 +227,10 @@ class Key { * @returns {string} * * @example + * ```js * new Key('/Comedy/MontyPython/Actor:JohnCleese').type() * // => 'Actor' - * + * ``` */ type () { return namespaceType(this.baseNamespace()) @@ -228,8 +242,10 @@ class Key { * @returns {string} * * @example + * ```js * new Key('/Comedy/MontyPython/Actor:JohnCleese').name() * // => 'JohnCleese' + * ``` */ name () { return namespaceValue(this.baseNamespace()) @@ -238,15 +254,17 @@ class Key { /** * Returns an "instance" of this type key (appends value to namespace). * - * @param {string} s + * @param {string} s - The string to append. * @returns {Key} * * @example + * ```js * new Key('/Comedy/MontyPython/Actor').instance('JohnClesse') * // => Key('/Comedy/MontyPython/Actor:JohnCleese') + * ``` */ instance (s) { - return new _Key(this.toString() + ':' + s) + return new Key(this.toString() + ':' + s) } /** @@ -255,9 +273,10 @@ class Key { * @returns {Key} * * @example + * ```js * new Key('/Comedy/MontyPython/Actor:JohnCleese').path() * // => Key('/Comedy/MontyPython/Actor') - * + * ``` */ path () { let p = this.parent().toString() @@ -265,7 +284,7 @@ class Key { p += pathSepS } p += this.type() - return new _Key(p) + return new Key(p) } /** @@ -274,29 +293,31 @@ class Key { * @returns {Key} * * @example + * ```js * new Key("/Comedy/MontyPython/Actor:JohnCleese").parent() * // => Key("/Comedy/MontyPython") - * + * ``` */ parent () { const list = this.list() if (list.length === 1) { - return new _Key(pathSepS) + return new Key(pathSepS) } - return new _Key(list.slice(0, -1).join(pathSepS)) + return new Key(list.slice(0, -1).join(pathSepS)) } /** * Returns the `child` Key of this Key. * - * @param {Key} key + * @param {Key} key - The child Key to add * @returns {Key} * * @example + * ```js * new Key('/Comedy/MontyPython').child(new Key('Actor:JohnCleese')) * // => Key('/Comedy/MontyPython/Actor:JohnCleese') - * + * ``` */ child (key) { if (this.toString() === pathSepS) { @@ -305,19 +326,20 @@ class Key { return this } - return new _Key(this.toString() + key.toString(), false) + return new Key(this.toString() + key.toString(), false) } /** * Returns whether this key is a prefix of `other` * - * @param {Key} other + * @param {Key} other - The other key to test against * @returns {boolean} * * @example + * ```js * new Key('/Comedy').isAncestorOf('/Comedy/MontyPython') * // => true - * + * ``` */ isAncestorOf (other) { if (other.toString() === this.toString()) { @@ -330,13 +352,14 @@ class Key { /** * Returns whether this key is a contains another as prefix. * - * @param {Key} other + * @param {Key} other - The other Key to test against * @returns {boolean} * * @example + * ```js * new Key('/Comedy/MontyPython').isDecendantOf('/Comedy') * // => true - * + * ``` */ isDecendantOf (other) { if (other.toString() === this.toString()) { @@ -347,7 +370,7 @@ class Key { } /** - * Returns wether this key has only one namespace. + * Checks if this key has only one namespace. * * @returns {boolean} * @@ -359,12 +382,22 @@ class Key { /** * Concats one or more Keys into one new Key. * - * @param {Array} keys + * @param {Array} keys - The array of keys to concatenate * @returns {Key} */ concat (...keys) { return Key.withNamespaces([...this.namespaces(), ...flatten(keys.map(key => key.namespaces()))]) } + + /** + * Check if value is a Key instance + * + * @param {any} value - Value to check + * @returns {boolean} + */ + static isKey (value) { + return value instanceof Key || Boolean(value && value[symbol]) + } } /** @@ -395,13 +428,11 @@ function namespaceValue (ns) { /** * Flatten array of arrays (only one level) * - * @param {Array} arr - * @returns {*} + * @param {Array} arr + * @returns {Array} */ function flatten (arr) { return [].concat(...arr) } -const _Key = withIs(Key, { className: 'Key', symbolName: '@ipfs/interface-datastore/key' }) - -module.exports = _Key +module.exports = Key diff --git a/src/memory.js b/src/memory.js index eb659d3..392a88f 100644 --- a/src/memory.js +++ b/src/memory.js @@ -6,6 +6,16 @@ const Adapter = require('./adapter') // Errors const Errors = require('./errors') +/** + * @typedef {import('./types').Pair} Pair + * @typedef {import('./types').IDatastore} IDatastore + * @typedef {import('./types').Options} Options + */ + +/** + * @class MemoryDatastore + * @implements {IDatastore} + */ class MemoryDatastore extends Adapter { constructor () { super() @@ -13,25 +23,38 @@ class MemoryDatastore extends Adapter { this.data = {} } + /** + * @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()] } - * _all () { + async * _all () { yield * Object.entries(this.data) .map(([key, value]) => ({ key: new Key(key), value })) } diff --git a/src/tests.js b/src/tests.js index dab6fc0..7c94f9a 100644 --- a/src/tests.js +++ b/src/tests.js @@ -3,9 +3,7 @@ 'use strict' const randomBytes = require('iso-random-stream/src/random') -const chai = require('chai') -chai.use(require('dirty-chai')) -const expect = chai.expect +const { expect } = require('aegir/utils/chai') const all = require('it-all') const drain = require('it-drain') const { utf8Encoder } = require('../src/utils') @@ -294,6 +292,7 @@ module.exports = (test) => { }) } + /** @type {Array<[string, any, any[]|number]>} */ const tests = [ ['empty', {}, [hello, world, hello2]], ['prefix', { prefix: '/z' }, [world, hello2]], @@ -358,7 +357,7 @@ module.exports = (test) => { const hello3 = { key: new Key('/z/4hello3'), value: utf8Encoder.encode('4') } let firstIteration = true - for await (const { key, value } of store.query({})) { // eslint-disable-line no-unused-vars + for await (const {} of store.query({})) { // eslint-disable-line no-empty-pattern if (firstIteration) { expect(await store.has(hello2.key)).to.be.true() await store.delete(hello2.key) diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..b4f7287 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,161 @@ +import Key from './key' + +export type AnyIterable = Iterable | AsyncIterable +export type PromiseOrValue = Promise | T +export interface Pair { + key: Key, + value: Uint8Array +} +/** + * Options for async operations. + */ +export interface Options { + signal?: AbortSignal; +} + +export interface Batch { + put(key: Key, value: Uint8Array): void; + delete(key: Key): void; + commit(options?: Options): Promise; +} + +export interface IDatastore { + open(): Promise + close(): Promise + /** + * Store the passed value under the passed key + * + * @example + * + * ```js + * await store.put([{ key: new Key('awesome'), value: new Uint8Array([0, 1, 2, 3]) }]) + * ``` + */ + put(key: Key, val: Uint8Array, options?: Options): Promise; + /** + * Retrieve the value stored under the given key + * + * @example + * ```js + * const value = await store.get(new Key('awesome')) + * console.log('got content: %s', value.toString('utf8')) + * // => got content: datastore + * ``` + */ + get(key: Key, options?: Options): Promise; + /** + * Check for the existence of a value for the passed key + * + * @example + * ```js + *const exists = await store.has(new Key('awesome')) + * + *if (exists) { + * console.log('it is there') + *} else { + * console.log('it is not there') + *} + *``` + */ + has(key: Key, options?: Options): Promise; + /** + * Remove the record for the passed key + * + * @example + * + * ```js + * await store.delete(new Key('awesome')) + * console.log('deleted awesome content :(') + * ``` + */ + delete(key: Key, options?: Options): Promise; + /** + * Store the given key/value pairs + * + * @example + * ```js + * const source = [{ key: new Key('awesome'), value: new Uint8Array([0, 1, 2, 3]) }] + * + * for await (const { key, value } of store.putMany(source)) { + * console.info(`put content for key ${key}`) + * } + * ``` + */ + putMany( + source: AnyIterable, + options?: Options, + ): AsyncGenerator; + /** + * Retrieve values for the passed keys + * + * @example + * ```js + * for await (const value of store.getMany([new Key('awesome')])) { + * console.log('got content:', new TextDecoder('utf8').decode(value)) + * // => got content: datastore + * } + * ``` + */ + getMany(source: AnyIterable, options?: Options): AsyncGenerator; + /** + * Remove values for the passed keys + * + * @example + * + * ```js + * const source = [new Key('awesome')] + * + * for await (const key of store.deleteMany(source)) { + * console.log(`deleted content with key ${key}`) + * } + * ``` + */ + deleteMany(source: AnyIterable, options?: Options): AsyncGenerator; + /** + * This will return an object with which you can chain multiple operations together, with them only being executed on calling `commit`. + * + * @example + * ```js + * const b = store.batch() + * + * for (let i = 0; i < 100; i++) { + * b.put(new Key(`hello${i}`), new TextEncoder('utf8').encode(`hello world ${i}`)) + * } + * + * await b.commit() + * console.log('put 100 values') + * ``` + */ + batch(): Batch; + /** + * Query the store. + * + * @example + * ```js + * // retrieve __all__ values from the store + * let list = [] + * for await (const value of store.query({})) { + * list.push(value) + * } + * console.log('ALL THE VALUES', list) + * ``` + */ + query(q: Query, options?: Options): AsyncGenerator; + /** + * Yield all datastore values + * + * @private + * @param q + * @param options + */ + _all(q: Query, options: Options): AsyncGenerator +} + +export interface Query { + prefix?: string; + filters?: Array<(item: Pair) => boolean>; + orders?: Array<(items: Array) => Array>; + limit?: number; + offset?: number; + keysOnly?: boolean; +} diff --git a/src/utils.js b/src/utils.js index 90c85fa..29eca2f 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,13 +1,33 @@ 'use strict' const tempdir = require('ipfs-utils/src/temp-dir') -const TextEncoder = require('ipfs-utils/src/text-encoder') -const TextDecoder = require('ipfs-utils/src/text-decoder') +const _TextEncoder = require('ipfs-utils/src/text-encoder') +const _TextDecoder = require('ipfs-utils/src/text-decoder') -exports.utf8Encoder = new TextEncoder('utf8') -exports.utf8Decoder = new TextDecoder('utf8') +/** + * @template T + * @typedef {import("./types").PromiseOrValue} PromiseOrValue + */ -exports.filter = (iterable, filterer) => { +/** + * @template T + * @typedef {import("./types").AnyIterable} AnyIterable + */ + +/** @type {TextEncoder} */ +const utf8Encoder = new _TextEncoder('utf8') +/** @type {TextDecoder} */ +const utf8Decoder = new _TextDecoder('utf8') + +/** + * Filter + * + * @template T + * @param {AnyIterable} iterable + * @param {(item: T) => PromiseOrValue} filterer + * @returns {AsyncGenerator} + */ +const filter = (iterable, filterer) => { return (async function * () { for await (const value of iterable) { const keep = await filterer(value) @@ -19,7 +39,15 @@ exports.filter = (iterable, filterer) => { // Not just sort, because the sorter is given all the values and should return // them all sorted -exports.sortAll = (iterable, sorter) => { +/** + * Sort All + * + * @template T + * @param {AnyIterable} iterable + * @param {(items: T[]) => PromiseOrValue} sorter + * @returns {AsyncGenerator} + */ +const sortAll = (iterable, sorter) => { return (async function * () { let values = [] for await (const value of iterable) values.push(value) @@ -28,7 +56,14 @@ exports.sortAll = (iterable, sorter) => { })() } -exports.take = (iterable, n) => { +/** + * + * @template T + * @param {AsyncIterable | Iterable} iterable + * @param {number} n + * @returns {AsyncGenerator} + */ +const take = (iterable, n) => { return (async function * () { if (n <= 0) return let i = 0 @@ -40,7 +75,14 @@ exports.take = (iterable, n) => { })() } -exports.map = (iterable, mapper) => { +/** + * + * @template T,O + * @param {AsyncIterable | Iterable} iterable + * @param {(item: T) => O} mapper + * @returns {AsyncGenerator} + */ +const map = (iterable, mapper) => { return (async function * () { for await (const value of iterable) { yield mapper(value) @@ -48,9 +90,22 @@ exports.map = (iterable, mapper) => { })() } -exports.replaceStartWith = function (s, r) { +/** + * @param {string} s + * @param {string} r + */ +const replaceStartWith = (s, r) => { const matcher = new RegExp('^' + r) return s.replace(matcher, '') } -exports.tmpdir = tempdir +module.exports = { + map, + take, + sortAll, + filter, + utf8Encoder, + utf8Decoder, + tmpdir: tempdir, + replaceStartWith +} diff --git a/test/key.spec.js b/test/key.spec.js index 2bd2c89..4767844 100644 --- a/test/key.spec.js +++ b/test/key.spec.js @@ -1,8 +1,7 @@ /* eslint-env mocha */ 'use strict' -const expect = require('chai').expect - +const { expect } = require('aegir/utils/chai') const Key = require('../src').Key const pathSep = '/' diff --git a/test/utils.spec.js b/test/utils.spec.js index f0449db..27f145d 100644 --- a/test/utils.spec.js +++ b/test/utils.spec.js @@ -1,10 +1,7 @@ /* eslint-env mocha */ 'use strict' -const chai = require('chai') -chai.use(require('dirty-chai')) -const expect = chai.expect - +const { expect } = require('aegir/utils/chai') const utils = require('../src').utils describe('utils', () => { diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e605b61 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "./node_modules/aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": [ + "test", // remove this line if you don't want to type-check tests + "src" + ] +} From 42aebd5f56e4577e6743f0c3861ea0a558e142b7 Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Mon, 30 Nov 2020 17:04:16 +0000 Subject: [PATCH 02/21] fix: fix some types --- src/adapter.js | 17 +++++++++-------- src/errors.js | 25 ++++++++++++++++++++----- src/types.ts | 2 +- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/adapter.js b/src/adapter.js index 11edfca..c5db9d1 100644 --- a/src/adapter.js +++ b/src/adapter.js @@ -34,7 +34,7 @@ class Adapter { * * @param {Key} key * @param {Uint8Array} val - * @param {Options} options + * @param {Options} [options] * @returns {Promise} */ put (key, val, options) { // eslint-disable-line require-await @@ -45,7 +45,7 @@ class Adapter { * Store the given key/value pairs * * @param {AnyIterable} source - * @param {Object} options + * @param {Object} [options] * @returns {AsyncGenerator} */ async * putMany (source, options = {}) { @@ -59,7 +59,7 @@ class Adapter { * Retrieve the value for the passed key * * @param {Key} key - * @param {Object} options + * @param {Object} [options] * @returns {Promise} */ get (key, options = {}) { @@ -79,6 +79,7 @@ class Adapter { * Check for the existence of a value for the passed key * * @param {Key} key + * @param {Options} [options] * @returns {Promise} * @example * ```js @@ -91,7 +92,7 @@ class Adapter { * } * ``` */ - has (key) { // eslint-disable-line require-await + has (key, options) { // eslint-disable-line require-await return Promise.resolve(false) } @@ -99,7 +100,7 @@ class Adapter { * Remove the record for the passed key * * @param {Key} key - * @param {Object} options + * @param {Object} [options] * @returns {Promise} */ delete (key, options = {}) { // eslint-disable-line require-await @@ -110,7 +111,7 @@ class Adapter { * Remove values for the passed keys * * @param {AnyIterable} source - * @param {Options} options + * @param {Options} [options] * @returns {AsyncGenerator} */ async * deleteMany (source, options = {}) { @@ -149,7 +150,7 @@ class Adapter { * Yield all datastore values * * @param {Query} q - * @param {Options} options + * @param {Options} [options] * @returns {AsyncGenerator} */ async * _all (q, options) { // eslint-disable-line require-await @@ -158,7 +159,7 @@ class Adapter { /** * @param {Query} q - * @param {Options} options + * @param {Options} [options] * @returns {AsyncGenerator} */ query (q, options) { diff --git a/src/errors.js b/src/errors.js index b98ff3f..3fd3469 100644 --- a/src/errors.js +++ b/src/errors.js @@ -1,27 +1,42 @@ 'use strict' const errcode = require('err-code') - +/** + * + * @param {Error} [err] + */ const dbOpenFailedError = (err) => { err = err || new Error('Cannot open database') return errcode(err, 'ERR_DB_OPEN_FAILED') } - +/** + * + * @param {Error} [err] + */ const dbDeleteFailedError = (err) => { err = err || new Error('Delete failed') return errcode(err, 'ERR_DB_DELETE_FAILED') } - +/** + * + * @param {Error} [err] + */ const dbWriteFailedError = (err) => { err = err || new Error('Write failed') return errcode(err, 'ERR_DB_WRITE_FAILED') } - +/** + * + * @param {Error} [err] + */ const notFoundError = (err) => { err = err || new Error('Not Found') return errcode(err, 'ERR_NOT_FOUND') } - +/** + * + * @param {Error} [err] + */ const abortedError = (err) => { err = err || new Error('Aborted') return errcode(err, 'ERR_ABORTED') diff --git a/src/types.ts b/src/types.ts index b4f7287..df9b9ce 100644 --- a/src/types.ts +++ b/src/types.ts @@ -148,7 +148,7 @@ export interface IDatastore { * @param q * @param options */ - _all(q: Query, options: Options): AsyncGenerator + _all(q: Query, options?: Options): AsyncGenerator } export interface Query { From 2aa294fd254b3a436d16eb0292ef26fe190e21bd Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Mon, 30 Nov 2020 17:13:10 +0000 Subject: [PATCH 03/21] Update src/key.js Co-authored-by: Vasco Santos --- src/key.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/key.js b/src/key.js index a3cf740..60f2ba2 100644 --- a/src/key.js +++ b/src/key.js @@ -56,7 +56,7 @@ class Key { /** * Convert to the string representation * - * @param {string} [encoding='utf8'] - The encoding to use. Should default to 'utf8'. + * @param {string} [encoding='utf8'] - The encoding to use. * @returns {string} */ toString (encoding = 'utf8') { From 8e8216d702df84020123d503e082a4e91b609411 Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Wed, 2 Dec 2020 14:26:45 +0000 Subject: [PATCH 04/21] Update src/adapter.js Co-authored-by: Irakli Gozalishvili --- src/adapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adapter.js b/src/adapter.js index 11edfca..2a29923 100644 --- a/src/adapter.js +++ b/src/adapter.js @@ -37,7 +37,7 @@ class Adapter { * @param {Options} options * @returns {Promise} */ - put (key, val, options) { // eslint-disable-line require-await + put (key, val, options) { return Promise.resolve() } From 56d12c6c0e7b3fbcc7cb5fa9169dfdf2dd3e70d2 Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Wed, 2 Dec 2020 14:26:59 +0000 Subject: [PATCH 05/21] Update src/adapter.js Co-authored-by: Irakli Gozalishvili --- src/adapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adapter.js b/src/adapter.js index 2a29923..edf0aa7 100644 --- a/src/adapter.js +++ b/src/adapter.js @@ -102,7 +102,7 @@ class Adapter { * @param {Object} options * @returns {Promise} */ - delete (key, options = {}) { // eslint-disable-line require-await + delete (key, options = {}) { return Promise.resolve() } From e6f03da84d1cfd7ef67f9ab50b0b871a7b1f0e07 Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Wed, 2 Dec 2020 14:27:23 +0000 Subject: [PATCH 06/21] Update src/key.js Co-authored-by: Irakli Gozalishvili --- src/key.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/key.js b/src/key.js index 60f2ba2..a2914a6 100644 --- a/src/key.js +++ b/src/key.js @@ -31,7 +31,6 @@ class Key { * @param {boolean} [clean] */ constructor (s, clean) { - Object.defineProperty(this, symbol, { value: true }) if (typeof s === 'string') { this._buf = utf8Encoder.encode(s) } else if (s instanceof Uint8Array) { From fe1aa1ba571fda2ce223a2e66f80237dc55bce31 Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Wed, 2 Dec 2020 14:27:34 +0000 Subject: [PATCH 07/21] Update src/key.js Co-authored-by: Irakli Gozalishvili --- src/key.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/key.js b/src/key.js index a2914a6..d7356a3 100644 --- a/src/key.js +++ b/src/key.js @@ -75,6 +75,9 @@ class Key { return this._buf } + get [symbol] () { + return true + } /** * Return string representation of the key * From 7a4d1d1dda17f84a15b5b057a95b2e48e668db3b Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Wed, 2 Dec 2020 14:27:56 +0000 Subject: [PATCH 08/21] Update src/key.js Co-authored-by: Irakli Gozalishvili --- src/key.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/key.js b/src/key.js index d7356a3..e8be42f 100644 --- a/src/key.js +++ b/src/key.js @@ -430,7 +430,9 @@ function namespaceValue (ns) { /** * Flatten array of arrays (only one level) * - * @param {Array} arr + * @template T + * @param {Array} arr + * @returns {T[]} * @returns {Array} */ function flatten (arr) { From 248cddb7d14ee9f29e92fdbe24916578577f4f6d Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Thu, 3 Dec 2020 14:38:23 +0000 Subject: [PATCH 09/21] fix: feedback --- .github/workflows/main.yml | 14 ++-- src/adapter.js | 135 +++++++++++++++++-------------------- src/key.js | 4 +- src/memory.js | 14 ++-- src/types.ts | 39 +++++++---- src/utils.js | 12 ++-- 6 files changed, 110 insertions(+), 108 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b94c65e..499a3f3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,9 +14,9 @@ jobs: - uses: actions/checkout@v2 - run: yarn - run: yarn lint - - run: yarn build - uses: gozala/typescript-error-reporter-action@v1.0.4 - - run: yarn aegir dep-check -- -i aegir + - run: yarn build + - run: yarn aegir dep-check - uses: ipfs/aegir/actions/bundle-size@master name: size with: @@ -35,7 +35,7 @@ jobs: with: node-version: ${{ matrix.node }} - run: yarn - - run: npx nyc --reporter=lcov npm run test:node -- --bail + - run: npx nyc --reporter=lcov aegir test -t node -- --bail - uses: codecov/codecov-action@v1 test-chrome: needs: check @@ -43,25 +43,25 @@ jobs: steps: - uses: actions/checkout@v2 - run: yarn - - run: yarn aegir test -t browser -t webworker + - run: npx aegir test -t browser -t webworker --bail test-firefox: needs: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - run: yarn - - run: yarn aegir test -t browser -t webworker -- --browsers FirefoxHeadless + - run: npx aegir test -t browser -t webworker --bail -- --browsers FirefoxHeadless test-electron-main: needs: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - run: yarn - - run: npx xvfb-maybe yarn aegir test -t electron-main --bail + - run: npx xvfb-maybe aegir test -t electron-main --bail test-electron-renderer: needs: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - run: yarn - - run: npx xvfb-maybe yarn aegir test -t electron-renderer --bail \ No newline at end of file + - run: npx xvfb-maybe aegir test -t electron-renderer --bail \ No newline at end of file diff --git a/src/adapter.js b/src/adapter.js index 20c7026..cc39170 100644 --- a/src/adapter.js +++ b/src/adapter.js @@ -6,7 +6,7 @@ const drain = require('it-drain') /** * @typedef {import('./key')} Key * @typedef {import('./types').Pair} Pair - * @typedef {import('./types').IDatastore} IDatastore + * @typedef {import('./types').Datastore} Datastore * @typedef {import('./types').Options} Options * @typedef {import('./types').Query} Query * @typedef {import('./types').Batch} Batch @@ -14,105 +14,101 @@ const drain = require('it-drain') /** * @template O - * @typedef {import('./types').AnyIterable} AnyIterable + * @typedef {import('./types').AwaitIterable} AwaitIterable */ /** - * @implements {IDatastore} + * @implements {Datastore} */ -class Adapter { +class DatastoreBase { + /** + * @returns {Promise} + */ open () { - return Promise.resolve() + return Promise.reject(new Error('.open is not implemented')) } + /** + * @returns {Promise} + */ close () { - return Promise.resolve() + return Promise.reject(new Error('.close is not implemented')) } /** - * Store the passed value under the passed key - * * @param {Key} key * @param {Uint8Array} val * @param {Options} [options] * @returns {Promise} */ put (key, val, options) { - return Promise.resolve() + return Promise.reject(new Error('.put is not implemented')) } /** - * Store the given key/value pairs - * - * @param {AnyIterable} source - * @param {Object} [options] - * @returns {AsyncGenerator} + * @param {Key} key + * @param {Options} [options] + * @returns {Promise} */ - async * putMany (source, options = {}) { - for await (const { key, value } of source) { - await this.put(key, value, options) - yield { key, value } - } + get (key, options) { + return Promise.reject(new Error('.get is not implemented')) } /** - * Retrieve the value for the passed key - * * @param {Key} key - * @param {Object} [options] - * @returns {Promise} + * @param {Options} [options] + * @returns {Promise} */ - get (key, options = {}) { - return Promise.resolve(new Uint8Array()) + has (key, options) { + return Promise.reject(new Error('.has is not implemented')) } /** - * @param {AnyIterable} source + * @param {Key} key + * @param {Options} [options] + * @returns {Promise} */ - async * getMany (source, options = {}) { - for await (const key of source) { - yield this.get(key, options) - } + delete (key, options) { + return Promise.reject(new Error('.delete is not implemented')) } /** - * Check for the existence of a value for the passed key - * - * @param {Key} key + * @param {Query} q * @param {Options} [options] - * @returns {Promise} - * @example - * ```js - * const exists = await store.has(new Key('awesome')) - * - * if (exists) { - * console.log('it is there') - * } else { - * console.log('it is not there') - * } - * ``` + * @returns {AsyncIterable} */ - has (key, options) { // eslint-disable-line require-await - return Promise.resolve(false) + // eslint-disable-next-line require-yield + async * _all (q, options) { + throw new Error('._all is not implemented') } /** - * Remove the record for the passed key - * - * @param {Key} key - * @param {Object} [options] - * @returns {Promise} + * @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} */ - delete (key, options = {}) { - return Promise.resolve() + async * getMany (source, options = {}) { + for await (const key of source) { + yield this.get(key, options) + } } /** - * Remove values for the passed keys - * - * @param {AnyIterable} source + * @param {AwaitIterable} source * @param {Options} [options] - * @returns {AsyncGenerator} + * @returns {AsyncIterable} */ async * deleteMany (source, options = {}) { for await (const key of source) { @@ -122,8 +118,6 @@ class Adapter { } /** - * Create a new batch object. - * * @returns {Batch} */ batch () { @@ -134,6 +128,7 @@ class Adapter { put (key, value) { puts.push({ key, value }) }, + delete (key) { dels.push(key) }, @@ -146,27 +141,17 @@ class Adapter { } } - /** - * Yield all datastore values - * - * @param {Query} q - * @param {Options} [options] - * @returns {AsyncGenerator} - */ - async * _all (q, options) { // eslint-disable-line require-await - - } - /** * @param {Query} q * @param {Options} [options] - * @returns {AsyncGenerator} */ 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))) + it = filter(it, (e) => + e.key.toString().startsWith(/** @type {string} */ (q.prefix)) + ) } if (Array.isArray(q.filters)) { @@ -179,7 +164,7 @@ class Adapter { if (q.offset != null) { let i = 0 - it = filter(it, () => i++ >= /** @type {number} */(q.offset)) + it = filter(it, () => i++ >= /** @type {number} */ (q.offset)) } if (q.limit != null) { @@ -187,11 +172,11 @@ class Adapter { } if (q.keysOnly === true) { - return map(it, e => ({ key: e.key })) + return map(it, (e) => ({ key: e.key })) } return it } } -module.exports = Adapter +module.exports = DatastoreBase diff --git a/src/key.js b/src/key.js index e8be42f..c1d3f6b 100644 --- a/src/key.js +++ b/src/key.js @@ -78,6 +78,7 @@ class Key { get [symbol] () { return true } + /** * Return string representation of the key * @@ -433,10 +434,9 @@ function namespaceValue (ns) { * @template T * @param {Array} arr * @returns {T[]} - * @returns {Array} */ function flatten (arr) { - return [].concat(...arr) + return /** @type {T[]} */([]).concat(...arr) } module.exports = Key diff --git a/src/memory.js b/src/memory.js index 392a88f..3277c31 100644 --- a/src/memory.js +++ b/src/memory.js @@ -2,19 +2,17 @@ const Key = require('./key') const Adapter = require('./adapter') - -// Errors const Errors = require('./errors') /** * @typedef {import('./types').Pair} Pair - * @typedef {import('./types').IDatastore} IDatastore + * @typedef {import('./types').Datastore} Datastore * @typedef {import('./types').Options} Options */ /** * @class MemoryDatastore - * @implements {IDatastore} + * @implements {Datastore} */ class MemoryDatastore extends Adapter { constructor () { @@ -23,6 +21,14 @@ class MemoryDatastore extends Adapter { this.data = {} } + open () { + return Promise.resolve() + } + + close () { + return Promise.resolve() + } + /** * @param {Key} key * @param {Uint8Array} val diff --git a/src/types.ts b/src/types.ts index df9b9ce..870ed6a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,10 +1,10 @@ import Key from './key' -export type AnyIterable = Iterable | AsyncIterable -export type PromiseOrValue = Promise | T +export type AwaitIterable = Iterable | AsyncIterable; +export type Await = Promise | T; export interface Pair { - key: Key, - value: Uint8Array + key: Key; + value: Uint8Array; } /** * Options for async operations. @@ -19,9 +19,14 @@ export interface Batch { commit(options?: Options): Promise; } -export interface IDatastore { - open(): Promise - close(): Promise +export interface DatastoreFactory extends Datastore { + prototype: Datastore; + new (): Datastore; +} + +export interface Datastore { + open(): Promise; + close(): Promise; /** * Store the passed value under the passed key * @@ -82,9 +87,9 @@ export interface IDatastore { * ``` */ putMany( - source: AnyIterable, - options?: Options, - ): AsyncGenerator; + source: AwaitIterable, + options?: Options + ): AsyncIterable; /** * Retrieve values for the passed keys * @@ -96,7 +101,10 @@ export interface IDatastore { * } * ``` */ - getMany(source: AnyIterable, options?: Options): AsyncGenerator; + getMany( + source: AwaitIterable, + options?: Options + ): AsyncIterable; /** * Remove values for the passed keys * @@ -110,7 +118,10 @@ export interface IDatastore { * } * ``` */ - deleteMany(source: AnyIterable, options?: Options): AsyncGenerator; + deleteMany( + source: AwaitIterable, + options?: Options + ): AsyncIterable; /** * This will return an object with which you can chain multiple operations together, with them only being executed on calling `commit`. * @@ -140,7 +151,7 @@ export interface IDatastore { * console.log('ALL THE VALUES', list) * ``` */ - query(q: Query, options?: Options): AsyncGenerator; + query(q: Query, options?: Options): AsyncIterable; /** * Yield all datastore values * @@ -148,7 +159,7 @@ export interface IDatastore { * @param q * @param options */ - _all(q: Query, options?: Options): AsyncGenerator + _all(q: Query, options?: Options): AsyncIterable; } export interface Query { diff --git a/src/utils.js b/src/utils.js index 29eca2f..6b82254 100644 --- a/src/utils.js +++ b/src/utils.js @@ -6,12 +6,12 @@ const _TextDecoder = require('ipfs-utils/src/text-decoder') /** * @template T - * @typedef {import("./types").PromiseOrValue} PromiseOrValue + * @typedef {import("./types").Await} PromiseOrValue */ /** * @template T - * @typedef {import("./types").AnyIterable} AnyIterable + * @typedef {import("./types").AwaitIterable} AnyIterable */ /** @type {TextEncoder} */ @@ -25,7 +25,7 @@ const utf8Decoder = new _TextDecoder('utf8') * @template T * @param {AnyIterable} iterable * @param {(item: T) => PromiseOrValue} filterer - * @returns {AsyncGenerator} + * @returns {AsyncIterable} */ const filter = (iterable, filterer) => { return (async function * () { @@ -45,7 +45,7 @@ const filter = (iterable, filterer) => { * @template T * @param {AnyIterable} iterable * @param {(items: T[]) => PromiseOrValue} sorter - * @returns {AsyncGenerator} + * @returns {AsyncIterable} */ const sortAll = (iterable, sorter) => { return (async function * () { @@ -61,7 +61,7 @@ const sortAll = (iterable, sorter) => { * @template T * @param {AsyncIterable | Iterable} iterable * @param {number} n - * @returns {AsyncGenerator} + * @returns {AsyncIterable} */ const take = (iterable, n) => { return (async function * () { @@ -80,7 +80,7 @@ const take = (iterable, n) => { * @template T,O * @param {AsyncIterable | Iterable} iterable * @param {(item: T) => O} mapper - * @returns {AsyncGenerator} + * @returns {AsyncIterable} */ const map = (iterable, mapper) => { return (async function * () { From 63c1a82768a3491d5439e68f7a870c23859b4df5 Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Thu, 3 Dec 2020 16:15:55 +0000 Subject: [PATCH 10/21] chore: change build to prepare --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5f69fc1..8aaba99 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,8 @@ "dist" ], "scripts": { + "prepare": "aegir build", "lint": "aegir lint", - "build": "aegir build", "test": "aegir test", "test:node": "aegir test --target node", "test:browser": "aegir test --target browser", From 5b8f8aef1594faedf8eed5063f6cfe11f586749f Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Thu, 3 Dec 2020 16:27:02 +0000 Subject: [PATCH 11/21] chore: change prepare to prepack --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8aaba99..af5114c 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "dist" ], "scripts": { - "prepare": "aegir build", + "prepack": "aegir build", "lint": "aegir lint", "test": "aegir test", "test:node": "aegir test --target node", From 75e315b6993d1e3d7134999260eaf4fdfad0e1d2 Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Thu, 3 Dec 2020 16:30:54 +0000 Subject: [PATCH 12/21] chore: change prepare --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index af5114c..8aaba99 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "dist" ], "scripts": { - "prepack": "aegir build", + "prepare": "aegir build", "lint": "aegir lint", "test": "aegir test", "test:node": "aegir test --target node", From 29f094ac81e140116c043779eb20a09c02a1c835 Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Wed, 9 Dec 2020 11:45:01 +0000 Subject: [PATCH 13/21] chore: fix ci --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 499a3f3..10fd8a3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,7 +15,7 @@ jobs: - run: yarn - run: yarn lint - uses: gozala/typescript-error-reporter-action@v1.0.4 - - run: yarn build + - run: npx aegir build - run: yarn aegir dep-check - uses: ipfs/aegir/actions/bundle-size@master name: size From f8fe99ec949a694434564b0494bc9f6b57351df4 Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Fri, 11 Dec 2020 11:00:00 +0000 Subject: [PATCH 14/21] fix: types --- src/adapter.js | 2 +- src/types.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/adapter.js b/src/adapter.js index cc39170..f22a976 100644 --- a/src/adapter.js +++ b/src/adapter.js @@ -172,7 +172,7 @@ class DatastoreBase { } if (q.keysOnly === true) { - return map(it, (e) => ({ key: e.key })) + return map(it, (e) => /** @type {Pair} */({ key: e.key })) } return it diff --git a/src/types.ts b/src/types.ts index 870ed6a..93d9ecd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -20,7 +20,6 @@ export interface Batch { } export interface DatastoreFactory extends Datastore { - prototype: Datastore; new (): Datastore; } @@ -151,7 +150,7 @@ export interface Datastore { * console.log('ALL THE VALUES', list) * ``` */ - query(q: Query, options?: Options): AsyncIterable; + query(q: Query, options?: Options): AsyncIterable; /** * Yield all datastore values * From f197aa4a719a388ba91c65ea49ee3cdc5be4dc84 Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Fri, 11 Dec 2020 11:00:55 +0000 Subject: [PATCH 15/21] fix: ci --- .github/workflows/main.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 10fd8a3..b08e965 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,11 +12,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - run: yarn - - run: yarn lint - - uses: gozala/typescript-error-reporter-action@v1.0.4 + - run: npm install + - run: npx aegir lint + - uses: gozala/typescript-error-reporter-action@v1 - run: npx aegir build - - run: yarn aegir dep-check + - run: npx aegir dep-check - uses: ipfs/aegir/actions/bundle-size@master name: size with: @@ -34,7 +34,7 @@ jobs: - uses: actions/setup-node@v1 with: node-version: ${{ matrix.node }} - - run: yarn + - run: npm install - run: npx nyc --reporter=lcov aegir test -t node -- --bail - uses: codecov/codecov-action@v1 test-chrome: @@ -42,26 +42,26 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - run: yarn + - run: npm install - run: npx aegir test -t browser -t webworker --bail test-firefox: needs: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - run: yarn + - run: npm install - run: npx aegir test -t browser -t webworker --bail -- --browsers FirefoxHeadless test-electron-main: needs: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - run: yarn + - run: npm install - run: npx xvfb-maybe aegir test -t electron-main --bail test-electron-renderer: needs: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - run: yarn + - run: npm install - run: npx xvfb-maybe aegir test -t electron-renderer --bail \ No newline at end of file From 98607c04dcfe47b2c00b196f00687c2f764df989 Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Fri, 11 Dec 2020 11:07:27 +0000 Subject: [PATCH 16/21] ci --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b08e965..9e81b21 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v2 - run: npm install - run: npx aegir lint - - uses: gozala/typescript-error-reporter-action@v1 + - uses: gozala/typescript-error-reporter-action@v1.0.8 - run: npx aegir build - run: npx aegir dep-check - uses: ipfs/aegir/actions/bundle-size@master From 5ea6e72e47eca2414ef2d81010dc567c57a00382 Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Fri, 11 Dec 2020 11:39:18 +0000 Subject: [PATCH 17/21] prepack --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8aaba99..af5114c 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "dist" ], "scripts": { - "prepare": "aegir build", + "prepack": "aegir build", "lint": "aegir lint", "test": "aegir test", "test:node": "aegir test --target node", From e425f2074e97c48c49fca17e6d6b62443dc98326 Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Fri, 11 Dec 2020 11:44:33 +0000 Subject: [PATCH 18/21] prepare --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index af5114c..8aaba99 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "dist" ], "scripts": { - "prepack": "aegir build", + "prepare": "aegir build", "lint": "aegir lint", "test": "aegir test", "test:node": "aegir test --target node", From eab84b025c03b6a2fff805af3a238cefd57545f2 Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Mon, 11 Jan 2021 16:12:18 +0000 Subject: [PATCH 19/21] fix: update aegir and feedback --- package.json | 10 +- src/adapter.js | 26 ++-- src/key.js | 2 +- src/memory.js | 1 + src/tests.js | 47 +++++++- src/types.ts | 294 ++++++++++++++++++++++----------------------- src/utils.js | 10 +- test/key.spec.js | 11 ++ test/utils.spec.js | 14 ++- 9 files changed, 236 insertions(+), 179 deletions(-) diff --git a/package.json b/package.json index 8aaba99..07533d1 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "dist" ], "scripts": { - "prepare": "aegir build", + "prepare": "aegir build --no-bundle", "lint": "aegir lint", "test": "aegir test", "test:node": "aegir test --target node", @@ -46,11 +46,12 @@ }, "homepage": "https://github.com/ipfs/interface-datastore#readme", "devDependencies": { - "aegir": "^29.2.0" + "@types/err-code": "^2.0.0", + "aegir": "^30.3.0" }, "dependencies": { "err-code": "^2.0.1", - "ipfs-utils": "^5.0.1", + "ipfs-utils": "ipfs/js-ipfs-utils#feat/types", "iso-random-stream": "^1.1.1", "it-all": "^1.0.2", "it-drain": "^1.0.1", @@ -76,6 +77,5 @@ "Hugo Dias ", "tcme ", "Adam Uhlir " - ], - "bundleDependencies": [] + ] } diff --git a/src/adapter.js b/src/adapter.js index f22a976..a36e095 100644 --- a/src/adapter.js +++ b/src/adapter.js @@ -20,7 +20,7 @@ const drain = require('it-drain') /** * @implements {Datastore} */ -class DatastoreBase { +class Adapter { /** * @returns {Promise} */ @@ -72,16 +72,6 @@ class DatastoreBase { return Promise.reject(new Error('.delete is not implemented')) } - /** - * @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') - } - /** * @param {AwaitIterable} source * @param {Options} [options] @@ -121,7 +111,9 @@ class DatastoreBase { * @returns {Batch} */ batch () { + /** @type {Pair[]} */ let puts = [] + /** @type {Key[]} */ let dels = [] return { @@ -141,6 +133,16 @@ class DatastoreBase { } } + /** + * @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') + } + /** * @param {Query} q * @param {Options} [options] @@ -179,4 +181,4 @@ class DatastoreBase { } } -module.exports = DatastoreBase +module.exports = Adapter diff --git a/src/key.js b/src/key.js index c1d3f6b..9ebb86f 100644 --- a/src/key.js +++ b/src/key.js @@ -396,7 +396,7 @@ class Key { * Check if value is a Key instance * * @param {any} value - Value to check - * @returns {boolean} + * @returns {value is Key} */ static isKey (value) { return value instanceof Key || Boolean(value && value[symbol]) diff --git a/src/memory.js b/src/memory.js index 3277c31..81a8c71 100644 --- a/src/memory.js +++ b/src/memory.js @@ -18,6 +18,7 @@ class MemoryDatastore extends Adapter { constructor () { super() + /** @type {Record} */ this.data = {} } diff --git a/src/tests.js b/src/tests.js index 7c94f9a..d882105 100644 --- a/src/tests.js +++ b/src/tests.js @@ -1,22 +1,34 @@ /* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ 'use strict' +// @ts-ignore const randomBytes = require('iso-random-stream/src/random') const { expect } = require('aegir/utils/chai') const all = require('it-all') const drain = require('it-drain') const { utf8Encoder } = require('../src/utils') -const Key = require('../src').Key +const { Key } = require('../src') +/** + * @typedef {import('./types').Datastore} Datastore + * @typedef {import('./types').Pair} Pair + */ + +/** + * @param {{ teardown: () => void; setup: () => Datastore; }} test + */ module.exports = (test) => { + /** + * @param {Datastore} store + */ const cleanup = async store => { await store.close() await test.teardown() } describe('put', () => { + /** @type {Datastore} */ let store beforeEach(async () => { @@ -45,6 +57,7 @@ module.exports = (test) => { }) describe('putMany', () => { + /** @type {Datastore} */ let store beforeEach(async () => { @@ -75,6 +88,7 @@ module.exports = (test) => { }) describe('get', () => { + /** @type {Datastore} */ let store beforeEach(async () => { @@ -106,6 +120,7 @@ module.exports = (test) => { }) describe('getMany', () => { + /** @type {Datastore} */ let store beforeEach(async () => { @@ -140,6 +155,7 @@ module.exports = (test) => { }) describe('delete', () => { + /** @type {Datastore} */ let store beforeEach(async () => { @@ -159,6 +175,7 @@ module.exports = (test) => { }) it('parallel', async () => { + /** @type {[Key, Uint8Array][]} */ const data = [] for (let i = 0; i < 100; i++) { data.push([new Key(`/a/key${i}`), utf8Encoder.encode(`data${i}`)]) @@ -177,6 +194,7 @@ module.exports = (test) => { }) describe('deleteMany', () => { + /** @type {Datastore} */ let store beforeEach(async () => { @@ -212,6 +230,7 @@ module.exports = (test) => { }) describe('batch', () => { + /** @type {Datastore} */ let store beforeEach(async () => { @@ -250,9 +269,13 @@ module.exports = (test) => { await b.commit() + /** + * @param {AsyncIterable} iterable + */ const total = async iterable => { let count = 0 - for await (const _ of iterable) count++ // eslint-disable-line + // eslint-disable-next-line no-unused-vars + for await (const _ of iterable) count++ return count } @@ -263,14 +286,24 @@ module.exports = (test) => { }) describe('query', () => { + /** @type {Datastore} */ let store const hello = { key: new Key('/q/1hello'), value: utf8Encoder.encode('1') } const world = { key: new Key('/z/2world'), value: utf8Encoder.encode('2') } const hello2 = { key: new Key('/z/3hello2'), value: utf8Encoder.encode('3') } + /** + * @param {Pair} entry + */ const filter1 = entry => !entry.key.toString().endsWith('hello') + /** + * @param {Pair} entry + */ const filter2 = entry => entry.key.toString().endsWith('hello2') + /** + * @param {Pair[]} res + */ const order1 = res => { return res.sort((a, b) => { if (a.value.toString() < b.value.toString()) { @@ -280,6 +313,9 @@ module.exports = (test) => { }) } + /** + * @param {Pair[]} res + */ const order2 = res => { return res.sort((a, b) => { if (a.value.toString() < b.value.toString()) { @@ -326,6 +362,10 @@ module.exports = (test) => { if (Array.isArray(expected)) { if (query.orders == null) { expect(res).to.have.length(expected.length) + /** + * @param {Pair} a + * @param {Pair} b + */ const s = (a, b) => { if (a.key.toString() < b.key.toString()) { return 1 @@ -387,6 +427,7 @@ module.exports = (test) => { }) describe('lifecycle', () => { + /** @type {Datastore} */ let store before(async () => { diff --git a/src/types.ts b/src/types.ts index 93d9ecd..550630f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,171 +1,163 @@ -import Key from './key' +import type Key from './key' -export type AwaitIterable = Iterable | AsyncIterable; -export type Await = Promise | T; +export type AwaitIterable = Iterable | AsyncIterable +export type Await = Promise | T export interface Pair { - key: Key; - value: Uint8Array; + key: Key + value: Uint8Array } /** * Options for async operations. */ export interface Options { - signal?: AbortSignal; + signal?: AbortSignal } export interface Batch { - put(key: Key, value: Uint8Array): void; - delete(key: Key): void; - commit(options?: Options): Promise; + put: (key: Key, value: Uint8Array) => void + delete: (key: Key) => void + commit: (options?: Options) => Promise } export interface DatastoreFactory extends Datastore { - new (): Datastore; + new (): Datastore } export interface Datastore { - open(): Promise; - close(): Promise; - /** - * Store the passed value under the passed key - * - * @example - * - * ```js - * await store.put([{ key: new Key('awesome'), value: new Uint8Array([0, 1, 2, 3]) }]) - * ``` - */ - put(key: Key, val: Uint8Array, options?: Options): Promise; - /** - * Retrieve the value stored under the given key - * - * @example - * ```js - * const value = await store.get(new Key('awesome')) - * console.log('got content: %s', value.toString('utf8')) - * // => got content: datastore - * ``` - */ - get(key: Key, options?: Options): Promise; - /** - * Check for the existence of a value for the passed key - * - * @example - * ```js - *const exists = await store.has(new Key('awesome')) - * - *if (exists) { - * console.log('it is there') - *} else { - * console.log('it is not there') - *} - *``` - */ - has(key: Key, options?: Options): Promise; - /** - * Remove the record for the passed key - * - * @example - * - * ```js - * await store.delete(new Key('awesome')) - * console.log('deleted awesome content :(') - * ``` - */ - delete(key: Key, options?: Options): Promise; - /** - * Store the given key/value pairs - * - * @example - * ```js - * const source = [{ key: new Key('awesome'), value: new Uint8Array([0, 1, 2, 3]) }] - * - * for await (const { key, value } of store.putMany(source)) { - * console.info(`put content for key ${key}`) - * } - * ``` - */ - putMany( - source: AwaitIterable, - options?: Options - ): AsyncIterable; - /** - * Retrieve values for the passed keys - * - * @example - * ```js - * for await (const value of store.getMany([new Key('awesome')])) { - * console.log('got content:', new TextDecoder('utf8').decode(value)) - * // => got content: datastore - * } - * ``` - */ - getMany( - source: AwaitIterable, - options?: Options - ): AsyncIterable; - /** - * Remove values for the passed keys - * - * @example - * - * ```js - * const source = [new Key('awesome')] - * - * for await (const key of store.deleteMany(source)) { - * console.log(`deleted content with key ${key}`) - * } - * ``` - */ - deleteMany( - source: AwaitIterable, - options?: Options - ): AsyncIterable; - /** - * This will return an object with which you can chain multiple operations together, with them only being executed on calling `commit`. - * - * @example - * ```js - * const b = store.batch() - * - * for (let i = 0; i < 100; i++) { - * b.put(new Key(`hello${i}`), new TextEncoder('utf8').encode(`hello world ${i}`)) - * } - * - * await b.commit() - * console.log('put 100 values') - * ``` - */ - batch(): Batch; - /** - * Query the store. - * - * @example - * ```js - * // retrieve __all__ values from the store - * let list = [] - * for await (const value of store.query({})) { - * list.push(value) - * } - * console.log('ALL THE VALUES', list) - * ``` - */ - query(q: Query, options?: Options): AsyncIterable; - /** - * Yield all datastore values - * - * @private - * @param q - * @param options - */ - _all(q: Query, options?: Options): AsyncIterable; + open: () => Promise + close: () => Promise + /** + * Store the passed value under the passed key + * + * @example + * + * ```js + * await store.put([{ key: new Key('awesome'), value: new Uint8Array([0, 1, 2, 3]) }]) + * ``` + */ + put: (key: Key, val: Uint8Array, options?: Options) => Promise + /** + * Retrieve the value stored under the given key + * + * @example + * ```js + * const value = await store.get(new Key('awesome')) + * console.log('got content: %s', value.toString('utf8')) + * // => got content: datastore + * ``` + */ + get: (key: Key, options?: Options) => Promise + /** + * Check for the existence of a value for the passed key + * + * @example + * ```js + *const exists = await store.has(new Key('awesome')) + * + *if (exists) { + * console.log('it is there') + *} else { + * console.log('it is not there') + *} + *``` + */ + has: (key: Key, options?: Options) => Promise + /** + * Remove the record for the passed key + * + * @example + * + * ```js + * await store.delete(new Key('awesome')) + * console.log('deleted awesome content :(') + * ``` + */ + delete: (key: Key, options?: Options) => Promise + /** + * Store the given key/value pairs + * + * @example + * ```js + * const source = [{ key: new Key('awesome'), value: new Uint8Array([0, 1, 2, 3]) }] + * + * for await (const { key, value } of store.putMany(source)) { + * console.info(`put content for key ${key}`) + * } + * ``` + */ + putMany: ( + source: AwaitIterable, + options?: Options + ) => AsyncIterable + /** + * Retrieve values for the passed keys + * + * @example + * ```js + * for await (const value of store.getMany([new Key('awesome')])) { + * console.log('got content:', new TextDecoder('utf8').decode(value)) + * // => got content: datastore + * } + * ``` + */ + getMany: ( + source: AwaitIterable, + options?: Options + ) => AsyncIterable + /** + * Remove values for the passed keys + * + * @example + * + * ```js + * const source = [new Key('awesome')] + * + * for await (const key of store.deleteMany(source)) { + * console.log(`deleted content with key ${key}`) + * } + * ``` + */ + deleteMany: ( + source: AwaitIterable, + options?: Options + ) => AsyncIterable + /** + * This will return an object with which you can chain multiple operations together, with them only being executed on calling `commit`. + * + * @example + * ```js + * const b = store.batch() + * + * for (let i = 0; i < 100; i++) { + * b.put(new Key(`hello${i}`), new TextEncoder('utf8').encode(`hello world ${i}`)) + * } + * + * await b.commit() + * console.log('put 100 values') + * ``` + */ + batch: () => Batch + /** + * Query the store. + * + * @example + * ```js + * // retrieve __all__ values from the store + * let list = [] + * for await (const value of store.query({})) { + * list.push(value) + * } + * console.log('ALL THE VALUES', list) + * ``` + */ + query: (q: Query, options?: Options) => AsyncIterable } export interface Query { - prefix?: string; - filters?: Array<(item: Pair) => boolean>; - orders?: Array<(items: Array) => Array>; - limit?: number; - offset?: number; - keysOnly?: boolean; + prefix?: string + filters?: Array<(item: Pair) => boolean> + orders?: Array<(items: Pair[]) => Pair[]> + limit?: number + offset?: number + keysOnly?: boolean } diff --git a/src/utils.js b/src/utils.js index 6b82254..cb3445c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,8 +1,8 @@ 'use strict' const tempdir = require('ipfs-utils/src/temp-dir') -const _TextEncoder = require('ipfs-utils/src/text-encoder') -const _TextDecoder = require('ipfs-utils/src/text-decoder') +const TextEncoder = require('ipfs-utils/src/text-encoder') +const TextDecoder = require('ipfs-utils/src/text-decoder') /** * @template T @@ -14,10 +14,8 @@ const _TextDecoder = require('ipfs-utils/src/text-decoder') * @typedef {import("./types").AwaitIterable} AnyIterable */ -/** @type {TextEncoder} */ -const utf8Encoder = new _TextEncoder('utf8') -/** @type {TextDecoder} */ -const utf8Decoder = new _TextDecoder('utf8') +const utf8Encoder = new TextEncoder() +const utf8Decoder = new TextDecoder('utf8') /** * Filter diff --git a/test/key.spec.js b/test/key.spec.js index 4767844..59d78fe 100644 --- a/test/key.spec.js +++ b/test/key.spec.js @@ -7,6 +7,9 @@ const Key = require('../src').Key const pathSep = '/' describe('Key', () => { + /** + * @param {string} s + */ const clean = (s) => { let fixed = s if (fixed.startsWith(pathSep + pathSep)) { @@ -20,6 +23,9 @@ describe('Key', () => { } describe('basic', () => { + /** + * @param {string} s + */ const validKey = (s) => it(s, () => { const fixed = clean(pathSep + s) const namespaces = fixed.split(pathSep).slice(1) @@ -109,6 +115,7 @@ describe('Key', () => { }) it('random', () => { + /** @type {Record} */ const keys = {} const k = 100 for (let i = 0; i < k; i++) { @@ -121,6 +128,10 @@ describe('Key', () => { }) it('less', () => { + /** + * @param {string | Uint8Array} a + * @param {string | Uint8Array} b + */ const checkLess = (a, b) => { const ak = new Key(a) const bk = new Key(b) diff --git a/test/utils.spec.js b/test/utils.spec.js index 27f145d..6f3bfe9 100644 --- a/test/utils.spec.js +++ b/test/utils.spec.js @@ -7,6 +7,9 @@ const utils = require('../src').utils 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 utils.filter(data, filterer)) { @@ -17,6 +20,9 @@ describe('utils', () => { 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 utils.filter(data, filterer)) { @@ -27,6 +33,9 @@ describe('utils', () => { it('sortAll', async () => { const data = [1, 2, 3, 4] + /** + * @param {number[]} vals + */ const sorter = vals => vals.reverse() const res = [] for await (const val of utils.sortAll(data, sorter)) { @@ -37,7 +46,7 @@ describe('utils', () => { it('sortAll - fail', async () => { const data = [1, 2, 3, 4] - const sorter = vals => { throw new Error('fail') } + const sorter = () => { throw new Error('fail') } const res = [] try { @@ -72,6 +81,9 @@ describe('utils', () => { 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 utils.map(data, mapper)) { From e449528d5b98edf6b62e770033d59686928fe67e Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Tue, 12 Jan 2021 14:43:30 +0000 Subject: [PATCH 20/21] fix: remove types versions and tweak orders --- package.json | 8 -------- src/types.ts | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/package.json b/package.json index 07533d1..af522c8 100644 --- a/package.json +++ b/package.json @@ -5,14 +5,6 @@ "leadMaintainer": "Alex Potsides ", "main": "src/index.js", "types": "dist/src/index.d.ts", - "typesVersions": { - "*": { - "src/*": [ - "dist/src/*", - "dist/src/*/index" - ] - } - }, "files": [ "src", "dist" diff --git a/src/types.ts b/src/types.ts index 550630f..d1b73bd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -156,7 +156,7 @@ export interface Datastore { export interface Query { prefix?: string filters?: Array<(item: Pair) => boolean> - orders?: Array<(items: Pair[]) => Pair[]> + orders?: Array<(items: Pair[]) => Await> limit?: number offset?: number keysOnly?: boolean From 90b6ab64270d4771bce7e46fdce94beedec25554 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Fri, 15 Jan 2021 16:07:34 +0000 Subject: [PATCH 21/21] chore: remove gh url --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index af522c8..3e363dc 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ }, "dependencies": { "err-code": "^2.0.1", - "ipfs-utils": "ipfs/js-ipfs-utils#feat/types", + "ipfs-utils": "^6.0.0", "iso-random-stream": "^1.1.1", "it-all": "^1.0.2", "it-drain": "^1.0.1",