diff --git a/README.md b/README.md index 284c303f..cd20cdcd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# IPFS Repo JavaScript Implementation +# IPFS Repo JavaScript Implementation [![](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/) @@ -14,11 +14,11 @@ This is the implementation of the [IPFS repo spec](https://github.com/ipfs/specs/blob/master/REPO.md) in JavaScript. -## Lead Maintainer +## Lead Maintainer [Alex Potsides](https://github.com/achingbrain) -## Table of Contents +## Table of Contents - [Background](#background) - [Install](#install) @@ -28,7 +28,40 @@ This is the implementation of the [IPFS repo spec](https://github.com/ipfs/specs - [Use in a browser Using a script tag](#use-in-a-browser-using-a-script-tag) - [Usage](#usage) - [API](#api) + - [Setup](#setup) + - [`new Repo(path[, options])`](#new-repopath-options) + - [`Promise repo.init ()`](#promise-repoinit-) + - [`Promise repo.open ()`](#promise-repoopen-) + - [`Promise repo.close ()`](#promise-repoclose-) + - [`Promise repo.exists ()`](#promiseboolean-repoexists-) + - [Repos](#repos) + - [`Promise repo.put (key, value:Buffer)`](#promise-repoput-key-valuebuffer) + - [`Promise repo.get (key)`](#promisebuffer-repoget-key) + - [`Promise repo.isInitialized ()`](#promiseboolean-repoisinitialized-) + - [`Promise repo.blocks.put (block:Block)`](#promise-repoblocksput-blockblock) + - [`Promise repo.blocks.putMany (blocks)`](#promise-repoblocksputmany-blocks) + - [`Promise repo.blocks.get (cid)`](#promisebuffer-repoblocksget-cid) + - [`repo.datastore`](#repodatastore) + - [Config](#config) + - [`Promise repo.config.set(key:string, value)`](#promise-repoconfigsetkeystring-value) + - [`Promise repo.config.replace(value)`](#promise-repoconfigreplacevalue) + - [`Promise repo.config.get(key:string)`](#promise-repoconfiggetkeystring) + - [`Promise repo.config.getAll()`](#promiseobject-repoconfiggetall) + - [`Promise repo.config.exists()`](#promiseboolean-repoconfigexists) + - [Version](#version) + - [`Promise repo.version.get ()`](#promisenumber-repoversionget-) + - [`Promise repo.version.set (version:Number)`](#promise-repoversionset-versionnumber) + - [API Addr](#api-addr) + - [`Promise repo.apiAddr.get ()`](#promisestring-repoapiaddrget-) + - [`Promise repo.apiAddr.set (value)`](#promise-repoapiaddrset-value) + - [Status](#status) + - [`Promise repo.stat ()`](#promiseobject-repostat-) + - [Lock](#lock) + - [`Promise lock.lock (dir)`](#promise-locklock-dir) + - [`Promise closer.close ()`](#promise-closerclose-) + - [`Promise lock.locked (dir)`](#promiseboolean-locklocked-dir) - [Notes](#notes) + - [Migrations](#migrations) - [Contribute](#contribute) - [License](#license) @@ -210,13 +243,11 @@ Datastore: This contains a full implementation of [the `interface-datastore` API](https://github.com/ipfs/interface-datastore#api). -### Utils - -#### `repo.config` +### Config Instead of using `repo.set('config')` this exposes an API that allows you to set and get a decoded config object, as well as, in a safe manner, change any of the config values individually. -##### `Promise repo.config.set(key:string, value)` +#### `Promise repo.config.set(key:string, value)` Set a config value. `value` can be any object that is serializable to JSON. @@ -228,11 +259,11 @@ const config = await repo.config.get() assert.equal(config.a.b.c, 'c value') ``` -##### `Promise repo.config.set(value)` +#### `Promise repo.config.replace(value)` Set the whole config value. `value` can be any object that is serializable to JSON. -##### `Promise repo.config.get(key:string)` +#### `Promise repo.config.get(key:string)` Get a config value. Returned promise resolves to the same type that was set before. @@ -243,7 +274,7 @@ const value = await repo.config.get('a.b.c') console.log('config.a.b.c = ', value) ``` -##### `Promise repo.config.get()` +#### `Promise repo.config.getAll()` Get the entire config value. @@ -251,17 +282,17 @@ Get the entire config value. Whether the config sub-repo exists. -#### `repo.version` +### Version -##### `Promise repo.version.get ()` +#### `Promise repo.version.get ()` Gets the repo version (an integer). -##### `Promise repo.version.set (version:Number)` +#### `Promise repo.version.set (version:Number)` Sets the repo version -#### `repo.apiAddr` +### API Addr #### `Promise repo.apiAddr.get ()` @@ -273,7 +304,9 @@ Sets the API address. * `value` should be a [Multiaddr](https://github.com/multiformats/js-multiaddr) or a String representing a valid one. -### `Promise repo.stat ()` +### Status + +#### `Promise repo.stat ()` Gets the repo status. @@ -304,7 +337,7 @@ Sets the lock if one does not already exist. If a lock already exists, should th Returns `closer`, where `closer` has a `close` method for removing the lock. -##### `Promise closer.close ()` +#### `Promise closer.close ()` Closes the lock created by `lock.open` diff --git a/package.json b/package.json index 47b9de85..b4494434 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "devDependencies": { "aegir": "^21.8.1", "chai": "^4.2.0", + "chai-as-promised": "^7.1.1", "dirty-chai": "^2.0.1", "just-range": "^2.1.0", "memdown": "^5.1.0", diff --git a/src/config.js b/src/config.js index 4ad5ec27..35b17dc7 100644 --- a/src/config.js +++ b/src/config.js @@ -17,15 +17,33 @@ module.exports = (store) => { /** * Get the current configuration from the repo. * + * @param {Object} options - options + * @param {AbortSignal} options.signal - abort this config read + * @returns {Promise} + */ + async getAll (options = {}) { // eslint-disable-line require-await + return configStore.get(undefined, options) + }, + + /** + * Get the value for the passed configuration key from the repo. + * * @param {String} key - the config key to get + * @param {Object} options - options + * @param {AbortSignal} options.signal - abort this config read * @returns {Promise} */ - async get (key) { + async get (key, options = {}) { if (!key) { key = undefined } const encodedValue = await store.get(configKey) + + if (options.signal && options.signal.aborted) { + return + } + const config = JSON.parse(encodedValue.toString()) if (key !== undefined && _get(config, key) === undefined) { throw new errors.NotFoundError(`Key ${key} does not exist in config`) @@ -40,9 +58,11 @@ module.exports = (store) => { * * @param {String} key - the config key to be written * @param {Object} value - the config value to be written + * @param {Object} options - options + * @param {AbortSignal} options.signal - abort this config write * @returns {void} */ - async set (key, value) { // eslint-disable-line require-await + async set (key, value, options = {}) { // eslint-disable-line require-await if (arguments.length === 1) { value = key key = undefined @@ -54,10 +74,29 @@ module.exports = (store) => { throw errcode(new Error('Invalid value type: ' + typeof value), 'ERR_INVALID_VALUE') } - return setQueue.add(() => _doSet({ + return setQueue.add(() => _maybeDoSet({ key: key, value: value - })) + }, options.signal)) + }, + + /** + * Set the current configuration for this repo. + * + * @param {Object} value - the config value to be written + * @param {Object} options - options + * @param {AbortSignal} options.signal - abort this config write + * @returns {void} + */ + async replace (value, options = {}) { // eslint-disable-line require-await + if (!value || Buffer.isBuffer(value)) { + throw errcode(new Error('Invalid value type: ' + typeof value), 'ERR_INVALID_VALUE') + } + + return setQueue.add(() => _maybeDoSet({ + key: undefined, + value: value + }, options.signal)) }, /** @@ -72,7 +111,11 @@ module.exports = (store) => { return configStore - async function _doSet (m) { + async function _maybeDoSet (m, signal) { + if (signal && signal.aborted) { + return + } + const key = m.key const value = m.value if (key) { diff --git a/test/api-addr-test.js b/test/api-addr-test.js index 4ca856a9..4f0657b4 100644 --- a/test/api-addr-test.js +++ b/test/api-addr-test.js @@ -2,9 +2,7 @@ 'use strict' const { Buffer } = require('buffer') -const chai = require('chai') -chai.use(require('dirty-chai')) -const expect = chai.expect +const { expect } = require('./utils/chai') const apiAddr = require('../src/api-addr') module.exports = () => { diff --git a/test/blockstore-test.js b/test/blockstore-test.js index 7e6096b1..efaeb217 100644 --- a/test/blockstore-test.js +++ b/test/blockstore-test.js @@ -3,10 +3,7 @@ 'use strict' const { Buffer } = require('buffer') -const chai = require('chai') -chai.use(require('dirty-chai')) -const expect = chai.expect -const assert = chai.assert +const { expect } = require('./utils/chai') const Block = require('ipld-block') const CID = require('cids') const range = require('just-range') @@ -124,13 +121,8 @@ module.exports = (repo) => { expect(commitInvoked).to.be.true() }) - it('returns an error on invalid block', async () => { - try { - await repo.blocks.put('hello') - assert.fail() - } catch (err) { - expect(err).to.exist() - } + it('returns an error on invalid block', () => { + return expect(repo.blocks.put('hello')).to.eventually.be.rejected() }) }) @@ -158,14 +150,8 @@ module.exports = (repo) => { })) }) - it('returns an error on invalid block', async () => { - try { - await repo.blocks.get('woot') - } catch (err) { - expect(err).to.exist() - return - } - assert.fail() + it('returns an error on invalid block', () => { + return expect(repo.blocks.get('woot')).to.eventually.be.rejected() }) it('should get block stored under v0 CID with a v1 CID', async () => { @@ -187,13 +173,8 @@ module.exports = (repo) => { expect(block.data).to.eql(data) }) - it('throws when passed an invalid cid', async () => { - try { - await repo.blocks.get('foo') - throw new Error('Should have thrown') - } catch (err) { - expect(err.code).to.equal('ERR_INVALID_CID') - } + it('throws when passed an invalid cid', () => { + return expect(repo.blocks.get('foo')).to.eventually.be.rejected().with.property('code', 'ERR_INVALID_CID') }) it('throws ERR_NOT_FOUND when requesting non-dag-pb CID that is not in the store', async () => { @@ -201,11 +182,7 @@ module.exports = (repo) => { const hash = await multihashing(data, 'sha2-256') const cid = new CID(1, 'dag-cbor', hash) - try { - await repo.blocks.get(cid) - } catch (err) { - expect(err.code).to.equal('ERR_NOT_FOUND') - } + await expect(repo.blocks.get(cid)).to.eventually.be.rejected().with.property('code', 'ERR_NOT_FOUND') }) it('throws unknown error encountered when getting a block', async () => { @@ -279,13 +256,8 @@ module.exports = (repo) => { expect(exists).to.eql(true) }) - it('throws when passed an invalid cid', async () => { - try { - await repo.blocks.has('foo') - throw new Error('Should have thrown') - } catch (err) { - expect(err.code).to.equal('ERR_INVALID_CID') - } + it('throws when passed an invalid cid', () => { + return expect(repo.blocks.has('foo')).to.eventually.be.rejected().with.property('code', 'ERR_INVALID_CID') }) it('returns false when requesting non-dag-pb CID that is not in the store', async () => { @@ -305,13 +277,8 @@ module.exports = (repo) => { expect(exists).to.equal(false) }) - it('throws when passed an invalid cid', async () => { - try { - await repo.blocks.delete('foo') - throw new Error('Should have thrown') - } catch (err) { - expect(err.code).to.equal('ERR_INVALID_CID') - } + it('throws when passed an invalid cid', () => { + return expect(repo.blocks.delete('foo')).to.eventually.be.rejected().with.property('code', 'ERR_INVALID_CID') }) }) }) diff --git a/test/blockstore-utils-test.js b/test/blockstore-utils-test.js index 3bea8544..630069e9 100644 --- a/test/blockstore-utils-test.js +++ b/test/blockstore-utils-test.js @@ -1,9 +1,7 @@ /* eslint-env mocha */ 'use strict' -const chai = require('chai') -chai.use(require('dirty-chai')) -const { expect } = chai +const { expect } = require('./utils/chai') const { Key } = require('interface-datastore') const CID = require('cids') const Repo = require('../src') diff --git a/test/config-test.js b/test/config-test.js index 285773ad..369eee9c 100644 --- a/test/config-test.js +++ b/test/config-test.js @@ -2,39 +2,38 @@ 'use strict' const { Buffer } = require('buffer') -const chai = require('chai') -chai.use(require('dirty-chai')) -const expect = chai.expect +const { expect } = require('./utils/chai') module.exports = (repo) => { describe('config', () => { describe('.set', () => { - it('should throw when invalid key is passed', async () => { - try { - await repo.config.set(5, 'value') - throw new Error('Should have thrown') - } catch (err) { - expect(err.code).to.equal('ERR_INVALID_KEY') - } + it('should throw when invalid key is passed', () => { + return expect(repo.config.set(5, 'value')).to.eventually.be.rejected().with.property('code', 'ERR_INVALID_KEY') }) - it('should throw when invalid value is passed', async () => { - try { - await repo.config.set('foo', Buffer.from([0, 1, 2])) - throw new Error('Should have thrown') - } catch (err) { - expect(err.code).to.equal('ERR_INVALID_VALUE') - } + it('should throw when invalid value is passed', () => { + return expect(repo.config.set('foo', Buffer.from([0, 1, 2]))).to.eventually.be.rejected().with.property('code', 'ERR_INVALID_VALUE') }) }) describe('.get', () => { - it('should throw NotFoundError when key does not exist', async () => { - try { - await repo.config.get('someRandomKey') - throw new Error('Should have thrown') - } catch (err) { - expect(err.code).to.equal('ERR_NOT_FOUND') - } + it('should throw NotFoundError when key does not exist', () => { + return expect(repo.config.get('someRandomKey')).to.eventually.be.rejected().with.property('code', 'ERR_NOT_FOUND') + }) + }) + describe('.getAll', () => { + it('should return the whole conifg', async () => { + const thing = await repo.config.getAll() + + expect(thing).to.deep.equal(await repo.config.get()) + }) + }) + describe('.replace', () => { + it('should replace the whole conifg', async () => { + expect({}).to.not.deep.equal(await repo.config.get()) + + await repo.config.replace({}) + + expect({}).to.deep.equal(await repo.config.get()) }) }) }) diff --git a/test/datastore-test.js b/test/datastore-test.js index a5f5b311..dc42019d 100644 --- a/test/datastore-test.js +++ b/test/datastore-test.js @@ -3,9 +3,7 @@ 'use strict' const { Buffer } = require('buffer') -const chai = require('chai') -chai.use(require('dirty-chai')) -const expect = chai.expect +const { expect } = require('./utils/chai') const range = require('just-range') const Key = require('interface-datastore').Key diff --git a/test/interop-test.js b/test/interop-test.js index 393044fb..ac4d50e8 100644 --- a/test/interop-test.js +++ b/test/interop-test.js @@ -1,9 +1,7 @@ /* eslint-env mocha */ 'use strict' -const chai = require('chai') -chai.use(require('dirty-chai')) -const expect = chai.expect +const { expect } = require('./utils/chai') const mh = require('multihashes') const CID = require('cids') const Key = require('interface-datastore').Key diff --git a/test/is-initialized.js b/test/is-initialized.js index ab5cc878..3d91458e 100644 --- a/test/is-initialized.js +++ b/test/is-initialized.js @@ -2,10 +2,8 @@ /* eslint-env mocha */ 'use strict' -const chai = require('chai') +const { expect } = require('./utils/chai') const tempDir = require('ipfs-utils/src/temp-dir') -chai.use(require('dirty-chai')) -const expect = chai.expect const IPFSRepo = require('../src') describe('isInitialized', () => { diff --git a/test/keystore-test.js b/test/keystore-test.js index 0c3469c6..3867c90d 100644 --- a/test/keystore-test.js +++ b/test/keystore-test.js @@ -2,9 +2,7 @@ /* eslint-env mocha */ 'use strict' -const chai = require('chai') -chai.use(require('dirty-chai')) -const expect = chai.expect +const { expect } = require('./utils/chai') module.exports = (repo) => { describe('keystore', () => { diff --git a/test/lock-test.js b/test/lock-test.js index 50e2d194..b8c4c4f7 100644 --- a/test/lock-test.js +++ b/test/lock-test.js @@ -1,9 +1,7 @@ /* eslint-env mocha */ 'use strict' -const chai = require('chai') -chai.use(require('dirty-chai')) -const expect = chai.expect +const { expect } = require('./utils/chai') const IPFSRepo = require('../') const lockMemory = require('../src/lock-memory') const { LockExistsError } = require('./../src/errors') diff --git a/test/migrations-test.js b/test/migrations-test.js index 3a00e780..7ca83924 100644 --- a/test/migrations-test.js +++ b/test/migrations-test.js @@ -2,10 +2,7 @@ /* eslint-env mocha */ 'use strict' -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) +const { expect } = require('./utils/chai') const sinon = require('sinon') const migrator = require('ipfs-repo-migrations') diff --git a/test/options-test.js b/test/options-test.js index c5501ea0..41a454e7 100644 --- a/test/options-test.js +++ b/test/options-test.js @@ -1,9 +1,7 @@ /* eslint-env mocha */ 'use strict' -const chai = require('chai') -chai.use(require('dirty-chai')) -const expect = chai.expect +const { expect } = require('./utils/chai') const tempDir = require('ipfs-utils/src/temp-dir') const { isNode } = require('ipfs-utils/src/env') const rimraf = require('rimraf') diff --git a/test/repo-test.js b/test/repo-test.js index e80f160b..966622c5 100644 --- a/test/repo-test.js +++ b/test/repo-test.js @@ -1,9 +1,7 @@ /* eslint-env mocha */ 'use strict' -const chai = require('chai') -chai.use(require('dirty-chai')) -const expect = chai.expect +const { expect } = require('./utils/chai') const tempDir = require('ipfs-utils/src/temp-dir') const IPFSRepo = require('../') const Errors = require('../src/errors') diff --git a/test/stat-test.js b/test/stat-test.js index 08209bff..a62fe48b 100644 --- a/test/stat-test.js +++ b/test/stat-test.js @@ -1,9 +1,7 @@ /* eslint-env mocha */ 'use strict' -const chai = require('chai') -chai.use(require('dirty-chai')) -const expect = chai.expect +const { expect } = require('./utils/chai') const Block = require('ipld-block') const CID = require('cids') diff --git a/test/utils/chai.js b/test/utils/chai.js new file mode 100644 index 00000000..2e30a3e2 --- /dev/null +++ b/test/utils/chai.js @@ -0,0 +1,9 @@ +'use strict' + +const chai = require('chai') +chai.use(require('dirty-chai')) +chai.use(require('chai-as-promised')) + +module.exports = { + expect: chai.expect +}