From 51cd55e34aa5139dd9dfdb8966df5283e3c5a324 Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Fri, 22 Jan 2021 18:42:59 +0000 Subject: [PATCH] feat: types (#53) Adds types support --- .github/workflows/main.yml | 67 +++++++++++ .npmignore | 47 -------- .travis.yml | 40 ------- package.json | 18 ++- src/index.js | 113 ++++++++++++++----- test/browser.js | 9 +- test/fixtures/test-level-iterator-destroy.js | 5 +- test/index.spec.js | 3 + test/node.js | 26 +++-- tsconfig.json | 10 ++ 10 files changed, 205 insertions(+), 133 deletions(-) create mode 100644 .github/workflows/main.yml delete mode 100644 .npmignore delete mode 100644 .travis.yml create mode 100644 tsconfig.json diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..9e81b21 --- /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: npm install + - run: npx aegir lint + - 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 + 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: npm install + - run: npx nyc --reporter=lcov aegir test -t node -- --bail + - uses: codecov/codecov-action@v1 + test-chrome: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - 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: 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: 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: npm install + - run: npx xvfb-maybe aegir test -t electron-renderer --bail \ No newline at end of file diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 3fe67d4..0000000 --- a/.npmignore +++ /dev/null @@ -1,47 +0,0 @@ -docs -package-lock.json -yarn.lock -**/node_modules/ -**/*.log -test/repo-tests* -test/test-repo/datastore/* - -# Logs -logs -*.log - -coverage - -# 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 - -lib -dist -test/test-repo/datastore -init-default -datastore-test -.vscode -test -.aegir.js -.github -.travis.yml 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/package.json b/package.json index 7cd48b4..ec2b8bf 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,17 @@ "description": "Datastore implementation with level(up|down) backend", "leadMaintainer": "Pedro Teixeira ", "main": "src/index.js", + "types": "dist/src/index.d.ts", + "files": [ + "src", + "dist" + ], "scripts": { "test": "aegir test", "test:node": "aegir test -t node", "test:browser": "aegir test -t browser", "test:webworker": "aegir test -t webworker", - "build": "aegir build", + "prepare": "aegir build --no-bundle", "lint": "aegir lint", "release": "aegir release", "release-minor": "aegir release --type minor", @@ -37,18 +42,21 @@ }, "homepage": "https://github.com/ipfs/js-datastore-level#readme", "dependencies": { - "datastore-core": "^2.0.0", - "interface-datastore": "^2.0.0", + "datastore-core": "^3.0.0", + "interface-datastore": "^3.0.3", "level": "^5.0.1" }, "devDependencies": { - "aegir": "^28.1.0", + "aegir": "^30.3.0", "chai": "^4.2.0", - "cids": "^1.0.2", + "cids": "^1.1.5", "dirty-chai": "^2.0.1", "level-mem": "^5.0.1", "rimraf": "^3.0.0" }, + "eslintConfig": { + "extends": "ipfs" + }, "contributors": [ "David Dias ", "achingbrain ", diff --git a/src/index.js b/src/index.js index 26a20f6..d375a34 100644 --- a/src/index.js +++ b/src/index.js @@ -7,10 +7,27 @@ const { } } = require('interface-datastore') +/** + * @typedef {import('interface-datastore').Datastore} Datastore + * @typedef {import('interface-datastore').Pair} Pair + * @typedef {import('interface-datastore').Batch} Batch + * @typedef {import('interface-datastore').Query} Query + * @typedef {import('interface-datastore').Options} QueryOptions + */ + /** * A datastore backed by leveldb. + * + * @implements {Datastore} */ class LevelDatastore extends Adapter { + /** + * @param {any} path + * @param {Object} [opts] + * @param {any} [opts.db] - level db reference + * @param {boolean} [opts.createIfMissing] + * @param {boolean} [opts.errorIfExists] + */ constructor (path, opts) { super() @@ -20,13 +37,19 @@ class LevelDatastore extends Adapter { database = opts.db delete opts.db } else { + // @ts-ignore database = require('level') } this.db = this._initDb(database, path, opts) } - _initDb (database, path, opts) { + /** + * @param {(arg0: any, arg1: any) => any} database + * @param {string} path + * @param {any} opts + */ + _initDb (database, path, opts = {}) { return database(path, { ...opts, valueEncoding: 'binary', @@ -42,6 +65,10 @@ class LevelDatastore extends Adapter { } } + /** + * @param {Key} key + * @param {Uint8Array} value + */ async put (key, value) { try { await this.db.put(key.toString(), value) @@ -50,6 +77,10 @@ class LevelDatastore extends Adapter { } } + /** + * @param {Key} key + * @returns {Promise} + */ async get (key) { let data try { @@ -61,6 +92,10 @@ class LevelDatastore extends Adapter { return data } + /** + * @param {Key} key + * @returns {Promise} + */ async has (key) { try { await this.db.get(key.toString()) @@ -71,6 +106,10 @@ class LevelDatastore extends Adapter { return true } + /** + * @param {Key} key + * @returns {Promise} + */ async delete (key) { try { await this.db.del(key.toString()) @@ -83,7 +122,11 @@ class LevelDatastore extends Adapter { return this.db.close() } + /** + * @returns {Batch} + */ batch () { + /** @type {{ type: string; key: string; value?: Uint8Array; }[]} */ const ops = [] return { put: (key, value) => { @@ -105,6 +148,10 @@ class LevelDatastore extends Adapter { } } + /** + * @param {Query} q + * @returns {AsyncIterable} + */ query (q) { let values = true if (q.keysOnly != null) { @@ -121,8 +168,10 @@ class LevelDatastore extends Adapter { if (q.prefix != null) { const prefix = q.prefix.toString() // Match keys greater than or equal to `prefix` and + // @ts-ignore opts.gte = prefix // less than `prefix` + \xFF (hex escape sequence) + // @ts-ignore opts.lt = prefix + '\xFF' } @@ -131,11 +180,10 @@ class LevelDatastore extends Adapter { ) it = map(it, ({ key, value }) => { - const res = { key: new Key(key, false) } if (values) { - res.value = value + return { key, value } } - return res + return /** @type {Pair} */({ key }) }) if (Array.isArray(q.filters)) { @@ -145,42 +193,53 @@ class LevelDatastore extends Adapter { if (Array.isArray(q.orders)) { it = q.orders.reduce((it, f) => sortAll(it, f), it) } - - if (q.offset != null) { + const { offset, limit } = q + if (offset) { let i = 0 - it = filter(it, () => i++ >= q.offset) + it = filter(it, () => i++ >= offset) } - if (q.limit != null) { - it = take(it, q.limit) + if (limit) { + it = take(it, limit) } return it } } +/** + * @typedef {Object} LevelIterator + * @property {(cb: (err: Error, key: string | Uint8Array | null, value: any)=> void)=>void} next + * @property {(cb: (err: Error) => void) => void } end + */ + +/** + * @param {LevelIterator} li - Level iterator + * @returns {AsyncIterable} + */ function levelIteratorToIterator (li) { return { - next: () => new Promise((resolve, reject) => { - li.next((err, key, value) => { - if (err) return reject(err) - if (key == null) { - return li.end(err => { + [Symbol.asyncIterator] () { + return { + next: () => new Promise((resolve, reject) => { + li.next((err, key, value) => { if (err) return reject(err) - resolve({ done: true }) + if (key == null) { + return li.end(err => { + if (err) return reject(err) + resolve({ done: true, value: undefined }) + }) + } + resolve({ done: false, value: { key: new Key(key, false), value } }) }) - } - resolve({ done: false, value: { key, value } }) - }) - }), - return: () => new Promise((resolve, reject) => { - li.end(err => { - if (err) return reject(err) - resolve({ done: true }) - }) - }), - [Symbol.asyncIterator] () { - return this + }), + return: () => new Promise((resolve, reject) => { + li.end(err => { + if (err) return reject(err) + resolve({ done: true, value: undefined }) + }) + }) + } } } } diff --git a/test/browser.js b/test/browser.js index b4b3428..ce76bce 100644 --- a/test/browser.js +++ b/test/browser.js @@ -3,23 +3,27 @@ const { MountDatastore } = require('datastore-core') const { Key } = require('interface-datastore') +// @ts-ignore const leveljs = require('level') const LevelStore = require('../src') describe('LevelDatastore', () => { describe('interface-datastore (leveljs)', () => { + // @ts-ignore require('interface-datastore/src/tests')({ setup: () => new LevelStore('hello', { db: leveljs }), teardown: () => new Promise((resolve, reject) => { + // @ts-ignore leveljs.destroy('hello', err => { if (err) return reject(err) - resolve() + resolve(true) }) }) }) }) describe('interface-datastore (mount(leveljs, leveljs, leveljs))', () => { + // @ts-ignore require('interface-datastore/src/tests')({ setup () { return new MountDatastore([{ @@ -36,9 +40,10 @@ describe('LevelDatastore', () => { teardown () { return Promise.all(['one', 'two', 'three'].map(dir => { return new Promise((resolve, reject) => { + // @ts-ignore leveljs.destroy(dir, err => { if (err) return reject(err) - resolve() + resolve(true) }) }) })) diff --git a/test/fixtures/test-level-iterator-destroy.js b/test/fixtures/test-level-iterator-destroy.js index a71a3d5..b558fd6 100644 --- a/test/fixtures/test-level-iterator-destroy.js +++ b/test/fixtures/test-level-iterator-destroy.js @@ -1,12 +1,13 @@ 'use strict' -const { utils } = require('interface-datastore') +const { utils, Key } = require('interface-datastore') const LevelStore = require('../../src') async function testLevelIteratorDestroy () { + // @ts-ignore const store = new LevelStore(utils.tmpdir(), { db: require('level') }) await store.open() - await store.put(`/test/key${Date.now()}`, Buffer.from(`TESTDATA${Date.now()}`)) + await store.put(new Key(`/test/key${Date.now()}`), Buffer.from(`TESTDATA${Date.now()}`)) for await (const d of store.query({})) { console.log(d) // eslint-disable-line no-console } diff --git a/test/index.spec.js b/test/index.spec.js index d4ab1ee..cb891ba 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -4,7 +4,9 @@ const chai = require('chai') chai.use(require('dirty-chai')) const expect = chai.expect +// @ts-ignore const levelmem = require('level-mem') +// @ts-ignore const level = require('level') const LevelStore = require('../src') const { utils } = require('interface-datastore') @@ -42,6 +44,7 @@ describe('LevelDatastore', () => { ;[levelmem, level].forEach(database => { describe(`interface-datastore ${database.name}`, () => { + // @ts-ignore require('interface-datastore/src/tests')({ setup: () => new LevelStore(utils.tmpdir(), { db: database }), teardown () {} diff --git a/test/node.js b/test/node.js index 8bc6ca6..09b5687 100644 --- a/test/node.js +++ b/test/node.js @@ -6,19 +6,24 @@ chai.use(require('dirty-chai')) const expect = chai.expect const path = require('path') const { Key, utils } = require('interface-datastore') +// @ts-ignore const rimraf = require('rimraf') const { MountDatastore } = require('datastore-core') const CID = require('cids') const { promisify } = require('util') const childProcess = require('child_process') +// @ts-ignore +const level = require('level') +// @ts-ignore +const tests = require('interface-datastore/src/tests') const LevelStore = require('../src') describe('LevelDatastore', () => { describe('interface-datastore (leveldown)', () => { const dir = utils.tmpdir() - require('interface-datastore/src/tests')({ - setup: () => new LevelStore(dir, { db: require('level') }), + tests({ + setup: () => new LevelStore(dir, { db: level }), teardown: () => promisify(rimraf)(dir) }) }) @@ -30,22 +35,22 @@ describe('LevelDatastore', () => { utils.tmpdir() ] - require('interface-datastore/src/tests')({ + tests({ setup () { return new MountDatastore([{ prefix: new Key('/a'), datastore: new LevelStore(dirs[0], { - db: require('level') + db: level }) }, { prefix: new Key('/q'), datastore: new LevelStore(dirs[1], { - db: require('level') + db: level }) }, { prefix: new Key('/z'), datastore: new LevelStore(dirs[2], { - db: require('level') + db: level }) }]) }, @@ -57,7 +62,7 @@ describe('LevelDatastore', () => { it.skip('interop with go', async () => { const store = new LevelStore(path.join(__dirname, 'test-repo', 'datastore'), { - db: require('level') + db: level }) const cids = [] @@ -86,11 +91,12 @@ describe('LevelDatastore', () => { // // https://github.com/Level/leveldown/blob/d3453fbde4d2a8aa04d9091101c25c999649069b/binding.cc#L545 it('should not leave iterators open and leak memory', (done) => { - const cp = childProcess.fork(`${__dirname}/fixtures/test-level-iterator-destroy`, { stdio: 'pipe' }) + const cp = childProcess.fork(path.join(__dirname, '/fixtures/test-level-iterator-destroy'), { stdio: 'pipe' }) let out = '' - cp.stdout.on('data', d => { out += d }) - cp.stderr.on('data', d => { out += d }) + const { stdout, stderr } = cp + stdout && stdout.on('data', d => { out += d }) + stderr && stderr.on('data', d => { out += d }) cp.on('exit', code => { expect(code).to.equal(0) 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" + ] +}