diff --git a/.aegir.cjs b/.aegir.cjs deleted file mode 100644 index 53e8c55..0000000 --- a/.aegir.cjs +++ /dev/null @@ -1,32 +0,0 @@ -'use strict' - -const path = require('path') - -/** @type {import('aegir').Options["build"]["config"]} */ -const esbuild = { - inject: [path.join(__dirname, './scripts/node-globals.js')], - plugins: [ - { - name: 'node built ins', - setup (build) { - build.onResolve({ filter: /^immediate$/ }, () => { - return { path: path.join(__dirname, './scripts/immediate.js') } - }) - } - } - ] -} - -/** @type {import('aegir').PartialOptions} */ -module.exports = { - build: { - bundlesizeMax: '67KB' - }, - test: { - browser: { - config: { - buildConfig: esbuild - } - } - } -} diff --git a/.aegir.js b/.aegir.js new file mode 100644 index 0000000..ed4824f --- /dev/null +++ b/.aegir.js @@ -0,0 +1,6 @@ +/** @type {import('aegir').PartialOptions} */ +export default { + build: { + bundlesizeMax: '67KB' + } +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 290ad02..0bc3b42 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,3 +6,6 @@ updates: interval: daily time: "10:00" open-pull-requests-limit: 10 + commit-message: + prefix: "deps" + prefix-development: "deps(dev)" diff --git a/README.md b/README.md index 799c58d..f8a4efd 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,14 @@ -# js-datastore-level +# datastore-level -[![](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://github.com/ipfs/js-datastore-level/actions/workflows/js-test-and-release.yml/badge.svg?branch=master)](https://github.com/ipfs/js-datastore-level/actions/workflows/js-test-and-release.yml) -[![Codecov](https://codecov.io/gh/ipfs/js-datastore-level/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/js-datastore-level) -[![Dependency Status](https://david-dm.org/ipfs/js-datastore-level.svg?style=flat-square)](https://david-dm.org/ipfs/js-datastore-level) -[![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) +[![ipfs.io](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io) +[![IRC](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) +[![Discord](https://img.shields.io/discord/806902334369824788?style=flat-square)](https://discord.gg/ipfs) +[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-datastore-level.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-datastore-level) +[![CI](https://img.shields.io/github/workflow/status/ipfs/js-datastore-level/test%20&%20maybe%20release/master?style=flat-square)](https://github.com/ipfs/js-datastore-level/actions/workflows/js-test-and-release.yml) +> Datastore implementation with level(up|down) backend -> Datastore implementation with [levelup](https://github.com/level/levelup) backend. - -## Table of Contents +## Table of contents - [Install](#install) - [Usage](#usage) @@ -22,11 +16,12 @@ - [Database names](#database-names) - [Contribute](#contribute) - [License](#license) +- [Contribute](#contribute-1) ## Install -``` -$ npm install datastore-level +```console +$ npm i datastore-level ``` ## Usage @@ -37,15 +32,18 @@ import { LevelDatastore } from 'datastore-level' // Default using level as backend for node or the browser const store = new LevelDatastore('path/to/store') -// another leveldown compliant backend like memdown -const memStore = new LevelDatastore('my/mem/store', { - db: require('level-mem') -}) +// another leveldown compliant backend like memory-level +const memStore = new LevelDatastore( + new MemoryLevel({ + keyEncoding: 'utf8', + valueEncoding: 'view' + }) +) ``` ### Browser Shimming Leveldown -`LevelStore` uses the `level` module to automatically use `level.js` if a modern bundler is used which can detect bundle targets based on the `pkg.browser` property in your `package.json`. +`LevelStore` uses the `level` module to automatically use `level` if a modern bundler is used which can detect bundle targets based on the `pkg.browser` property in your `package.json`. If you are using a bundler that does not support `pkg.browser`, you will need to handle the shimming yourself, as was the case with versions of `LevelStore` 0.7.0 and earlier. @@ -55,8 +53,8 @@ If you are using a bundler that does not support `pkg.browser`, you will need to ```javascript import leveljs from 'level-js' -import browserStore = new LevelDatastore('my/db/name', { - db: (path) => leveljs(path, { +import browserStore = new LevelDatastore( + new Level('my/db/name', { prefix: 'IDBWrapper-' }) }) @@ -74,4 +72,15 @@ This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/c ## License -[MIT](LICENSE) +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribute + +Feel free to join in. All welcome. Open an [issue](https://github.com/ipfs/js-ipfs-unixfs-importer/issues)! + +This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). + +[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md) diff --git a/package.json b/package.json index 50d6bf2..14b7319 100644 --- a/package.json +++ b/package.json @@ -25,27 +25,33 @@ "node": ">=16.0.0", "npm": ">=7.0.0" }, - "main": "src/index.js", "type": "module", - "types": "types/src/index.d.ts", + "types": "./dist/src/index.d.ts", "typesVersions": { "*": { "*": [ - "types/*", - "types/src/*" + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" ], - "types/*": [ - "types/*", - "types/src/*" + "src/*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" ] } }, "files": [ - "*", + "src", + "dist", + "!dist/test", "!**/*.tsbuildinfo" ], "exports": { ".": { + "types": "./dist/src/index.d.ts", "import": "./src/index.js" } }, @@ -82,15 +88,15 @@ "release": "patch" }, { - "type": "chore", + "type": "docs", "release": "patch" }, { - "type": "docs", + "type": "test", "release": "patch" }, { - "type": "test", + "type": "deps", "release": "patch" }, { @@ -120,7 +126,11 @@ }, { "type": "docs", - "section": "Trivial Changes" + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" }, { "type": "test", @@ -131,52 +141,44 @@ } ], "@semantic-release/changelog", - [ - "@semantic-release/npm", - { - "pkgRoot": "dist" - } - ], + "@semantic-release/npm", "@semantic-release/github", "@semantic-release/git" ] }, "scripts": { - "clean": "rimraf dist types", - "prepare": "aegir build --no-bundle && cp -R types dist", - "lint": "aegir ts -p check && aegir lint", - "build": "aegir build --esm-tests", - "release": "semantic-release", + "clean": "aegir clean", + "lint": "aegir lint", + "build": "aegir build", + "release": "aegir release", "test": "aegir test", - "test:node": "aegir test -t node", - "test:chrome": "aegir test -t browser", + "test:node": "aegir test -t node --cov", + "test:chrome": "aegir test -t browser --cov", "test:chrome-webworker": "aegir test -t webworker", "test:firefox": "aegir test -t browser -- --browser firefox", "test:firefox-webworker": "aegir test -t webworker -- --browser firefox", - "test:electron-main": "aegir test -t electron-main -f dist/cjs/node-test/*js", - "test:electron-renderer": "aegir test -t electron-renderer -f dist/cjs/node-test/*js", - "dep-check": "aegir dep-check -i rimraf" + "test:electron-main": "aegir test -t electron-main", + "dep-check": "aegir dep-check" }, "dependencies": { - "datastore-core": "^7.0.0", - "interface-datastore": "^6.0.2", + "abstract-level": "^1.0.3", + "datastore-core": "^8.0.1", + "interface-datastore": "^7.0.0", "it-filter": "^1.0.2", "it-map": "^1.0.5", "it-sort": "^1.0.0", "it-take": "^1.0.1", - "level": "^7.0.0" + "level": "^8.0.0" }, "devDependencies": { "@ipld/dag-cbor": "^7.0.0", "@types/rimraf": "^3.0.2", - "aegir": "^36.1.3", - "assert": "^2.0.0", - "buffer": "^6.0.3", - "interface-datastore-tests": "^2.0.3", + "aegir": "^37.5.1", + "interface-datastore-tests": "^3.0.0", "ipfs-utils": "^9.0.4", "level-mem": "^6.0.1", + "memory-level": "^1.0.0", "multiformats": "^9.2.0", - "rimraf": "^3.0.0", - "util": "^0.12.3" + "rimraf": "^3.0.2" } } diff --git a/scripts/immediate.js b/scripts/immediate.js deleted file mode 100644 index 2b6f76f..0000000 --- a/scripts/immediate.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = setTimeout diff --git a/scripts/node-globals.js b/scripts/node-globals.js deleted file mode 100644 index 2936df1..0000000 --- a/scripts/node-globals.js +++ /dev/null @@ -1,3 +0,0 @@ -// @ts-nocheck -export const { Buffer } = require('buffer') -export const setImmediate = global.setImmediate diff --git a/src/index.js b/src/index.js index 836941d..b298da0 100644 --- a/src/index.js +++ b/src/index.js @@ -4,8 +4,7 @@ import filter from 'it-filter' import map from 'it-map' import take from 'it-take' import sort from 'it-sort' -// @ts-ignore no types -import Level from 'level' +import { Level } from 'level' /** * @typedef {import('interface-datastore').Datastore} Datastore @@ -14,70 +13,33 @@ import Level from 'level' * @typedef {import('interface-datastore').Query} Query * @typedef {import('interface-datastore').KeyQuery} KeyQuery * @typedef {import('interface-datastore').Options} QueryOptions + * @typedef {import('abstract-level').AbstractLevel} LevelDb */ /** - * A datastore backed by leveldb. - * - * @implements {Datastore} + * A datastore backed by leveldb */ export class LevelDatastore extends BaseDatastore { /** - * @param {any} path - * @param {Object} [opts] - * @param {any} [opts.db] - level db reference - * @param {boolean} [opts.createIfMissing] - * @param {boolean} [opts.errorIfExists] - * @param {string} [opts.prefix] - level-js option - * @param {number} [opts.version] - level-js option - * @param {number} [opts.cacheSize] - leveldown option - * @param {number} [opts.writeBufferSize] - leveldown option - * @param {number} [opts.blockSize] - leveldown option - * @param {number} [opts.maxOpenFiles] - leveldown option - * @param {number} [opts.blockRestartInterval] - leveldown option - * @param {number} [opts.maxFileSize] - leveldown option + * @param {string | LevelDb} path + * @param {import('level').DatabaseOptions} [opts] */ - constructor (path, opts) { + constructor (path, opts = {}) { super() - this.path = path - this.opts = opts - if (opts && opts.db) { - this.database = opts.db - delete opts.db - } else { - // @ts-ignore - this.database = Level - } - } - - _initDb () { - return new Promise((resolve, reject) => { - this.db = this.database( - this.path, - { - ...this.opts, - valueEncoding: 'binary', - compression: false // same default as go - }, - /** @param {Error} [err] */ - (err) => { - if (err) { - return reject(err) - } - resolve(this.db) - } - ) - }) + /** @type {LevelDb} */ + this.db = typeof path === 'string' + ? new Level(path, { + ...opts, + keyEncoding: 'utf8', + valueEncoding: 'view' + }) + : path } async open () { try { - if (this.db) { - await this.db.open() - } else { - this.db = await this._initDb() - } + await this.db.open() } catch (/** @type {any} */ err) { throw Errors.dbOpenFailedError(err) } @@ -144,7 +106,7 @@ export class LevelDatastore extends BaseDatastore { * @returns {Batch} */ batch () { - /** @type {{ type: string; key: string; value?: Uint8Array; }[]} */ + /** @type {Array<{ type: 'put', key: string, value: Uint8Array; } | { type: 'del', key: string }>} */ const ops = [] return { put: (key, value) => { @@ -233,9 +195,10 @@ export class LevelDatastore extends BaseDatastore { * @returns {AsyncIterable} */ _query (opts) { + /** @type {import('level').IteratorOptions} */ const iteratorOpts = { keys: true, - keyAsBuffer: true, + keyEncoding: 'buffer', values: opts.values } @@ -243,10 +206,8 @@ export class LevelDatastore extends BaseDatastore { if (opts.prefix != null) { const prefix = opts.prefix.toString() // Match keys greater than or equal to `prefix` and - // @ts-ignore iteratorOpts.gte = prefix // less than `prefix` + \xFF (hex escape sequence) - // @ts-ignore iteratorOpts.lt = prefix + '\xFF' } @@ -255,38 +216,13 @@ export class LevelDatastore extends BaseDatastore { } /** - * @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 + * @param {import('level').Iterator} li - Level iterator * @returns {AsyncIterable} */ -function levelIteratorToIterator (li) { - return { - [Symbol.asyncIterator] () { - return { - next: () => new Promise((resolve, reject) => { - li.next((err, key, value) => { - if (err) return reject(err) - 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 } }) - }) - }), - return: () => new Promise((resolve, reject) => { - li.end(err => { - if (err) return reject(err) - resolve({ done: true, value: undefined }) - }) - }) - } - } +async function * levelIteratorToIterator (li) { + for await (const [key, value] of li) { + yield { key: new Key(key, false), value } } + + await li.close() } diff --git a/test/browser.js b/test/browser.js index 73bf40a..eb10f61 100644 --- a/test/browser.js +++ b/test/browser.js @@ -2,22 +2,14 @@ import { MountDatastore } from 'datastore-core' import { Key } from 'interface-datastore/key' -// @ts-ignore -import leveljs from 'level' import { LevelDatastore } from '../src/index.js' import { interfaceDatastoreTests } from 'interface-datastore-tests' describe('LevelDatastore', () => { describe('interface-datastore (leveljs)', () => { interfaceDatastoreTests({ - setup: () => new LevelDatastore('hello', { db: leveljs }), - teardown: () => new Promise((resolve, reject) => { - // @ts-ignore - leveljs.destroy('hello', err => { - if (err) return reject(err) - resolve(true) - }) - }) + setup: () => new LevelDatastore('hello-' + Math.random()), + teardown: () => {} }) }) @@ -26,26 +18,16 @@ describe('LevelDatastore', () => { setup () { return new MountDatastore([{ prefix: new Key('/a'), - datastore: new LevelDatastore('one', { db: leveljs }) + datastore: new LevelDatastore('one-' + Math.random()) }, { prefix: new Key('/q'), - datastore: new LevelDatastore('two', { db: leveljs }) + datastore: new LevelDatastore('two-' + Math.random()) }, { prefix: new Key('/z'), - datastore: new LevelDatastore('three', { db: leveljs }) + datastore: new LevelDatastore('three-' + Math.random()) }]) }, - 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(true) - }) - }) - })) - } + teardown () {} }) }) }) diff --git a/test/fixtures/test-level-iterator-destroy.js b/test/fixtures/test-level-iterator-destroy.js index ef3892e..7b1f125 100644 --- a/test/fixtures/test-level-iterator-destroy.js +++ b/test/fixtures/test-level-iterator-destroy.js @@ -1,12 +1,9 @@ import { Key } from 'interface-datastore/key' import { LevelDatastore } from '../../src/index.js' import tempdir from 'ipfs-utils/src/temp-dir.js' -// @ts-ignore no types -import Level from 'level' async function testLevelIteratorDestroy () { - // @ts-ignore - const store = new LevelDatastore(tempdir(), { db: Level }) + const store = new LevelDatastore(tempdir()) await store.open() await store.put(new Key(`/test/key${Date.now()}`), new TextEncoder().encode(`TESTDATA${Date.now()}`)) for await (const d of store.query({})) { diff --git a/test/index.spec.js b/test/index.spec.js index f9a627b..c0f4b85 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -1,10 +1,8 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' -// @ts-ignore -import levelmem from 'level-mem' -// @ts-ignore -import level from 'level' +import { expect } from 'aegir/chai' +import { MemoryLevel } from 'memory-level' +import { Level } from 'level' import { LevelDatastore } from '../src/index.js' import tempdir from 'ipfs-utils/src/temp-dir.js' import { interfaceDatastoreTests } from 'interface-datastore-tests' @@ -15,37 +13,39 @@ describe('LevelDatastore', () => { const levelStore = new LevelDatastore('init-default') await levelStore.open() - expect(levelStore.db.options).to.include({ - createIfMissing: true, - errorIfExists: false - }) - expect(levelStore.db.db.codec.opts).to.include({ - valueEncoding: 'binary' - }) + expect(levelStore.db).to.be.an.instanceOf(Level) }) it('should be able to override the database', async () => { - const levelStore = new LevelDatastore('init-default', { - db: levelmem, - createIfMissing: true, - errorIfExists: true - }) + const levelStore = new LevelDatastore( + new MemoryLevel({ + keyEncoding: 'utf8', + valueEncoding: 'view' + }) + ) await levelStore.open() - expect(levelStore.db.options).to.include({ - createIfMissing: true, - errorIfExists: true - }) + expect(levelStore.db).to.be.an.instanceOf(MemoryLevel) }) }) - ;[levelmem, level].forEach(database => { - describe(`interface-datastore ${database.name}`, () => { - interfaceDatastoreTests({ - setup: () => new LevelDatastore(tempdir(), { db: database }), - teardown () {} - }) + describe('interface-datastore MemoryLevel', () => { + interfaceDatastoreTests({ + setup: () => new LevelDatastore( + new MemoryLevel({ + keyEncoding: 'utf8', + valueEncoding: 'view' + }) + ), + teardown () {} + }) + }) + + describe('interface-datastore Level', () => { + interfaceDatastoreTests({ + setup: () => new LevelDatastore(tempdir()), + teardown () {} }) }) }) diff --git a/test/node.js b/test/node.js index a531c3e..5d43961 100644 --- a/test/node.js +++ b/test/node.js @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { expect } from 'aegir/utils/chai.js' +import { expect } from 'aegir/chai' import path from 'path' import { Key } from 'interface-datastore/key' import rimraf from 'rimraf' @@ -10,9 +10,6 @@ import * as Digest from 'multiformats/hashes/digest' import * as dagCbor from '@ipld/dag-cbor' import { promisify } from 'util' import childProcess from 'child_process' -// @ts-ignore -import level from 'level' -// @ts-ignore import { interfaceDatastoreTests } from 'interface-datastore-tests' import { LevelDatastore } from '../src/index.js' import tempdir from 'ipfs-utils/src/temp-dir.js' @@ -21,7 +18,7 @@ describe('LevelDatastore', () => { describe('interface-datastore (leveldown)', () => { const dir = tempdir() interfaceDatastoreTests({ - setup: () => new LevelDatastore(dir, { db: level }), + setup: () => new LevelDatastore(dir), teardown: () => promisify(rimraf)(dir) }) }) @@ -37,19 +34,13 @@ describe('LevelDatastore', () => { setup () { return new MountDatastore([{ prefix: new Key('/a'), - datastore: new LevelDatastore(dirs[0], { - db: level - }) + datastore: new LevelDatastore(dirs[0]) }, { prefix: new Key('/q'), - datastore: new LevelDatastore(dirs[1], { - db: level - }) + datastore: new LevelDatastore(dirs[1]) }, { prefix: new Key('/z'), - datastore: new LevelDatastore(dirs[2], { - db: level - }) + datastore: new LevelDatastore(dirs[2]) }]) }, teardown () { @@ -59,9 +50,7 @@ describe('LevelDatastore', () => { }) it.skip('interop with go', async () => { - const store = new LevelDatastore(path.join(__dirname, 'test-repo', 'datastore'), { - db: level - }) + const store = new LevelDatastore(path.join(__dirname, 'test-repo', 'datastore')) const cids = [] diff --git a/tsconfig.json b/tsconfig.json index c743414..2742a08 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,8 @@ { "extends": "aegir/src/config/tsconfig.aegir.json", "compilerOptions": { - "outDir": "types" + "outDir": "dist", + "emitDeclarationOnly": true }, "include": [ "test",