Skip to content

Commit

Permalink
feat: adds .replace and .getAll methods to config (#227)
Browse files Browse the repository at this point in the history
The config .get/.set methods are overloaded to allow the user to retrieve or replace the entire config.  This is an error prone approach that could end with the user invalidating their config when they did not mean to.

The change here adds `.replace` and `.getAll` methods to allow the user to be specific about their intentions and hopefully makes the API slightly less of a foot-gun.
  • Loading branch information
achingbrain authored Apr 27, 2020
1 parent 02f1ed7 commit 0122537
Show file tree
Hide file tree
Showing 17 changed files with 153 additions and 124 deletions.
65 changes: 49 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# IPFS Repo JavaScript Implementation
# IPFS Repo JavaScript Implementation <!-- omit in toc -->

[![](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/)
Expand All @@ -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 <!-- omit in toc -->

[Alex Potsides](https://github.com/achingbrain)

## Table of Contents
## Table of Contents <!-- omit in toc -->

- [Background](#background)
- [Install](#install)
Expand All @@ -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<boolean> repo.exists ()`](#promiseboolean-repoexists-)
- [Repos](#repos)
- [`Promise repo.put (key, value:Buffer)`](#promise-repoput-key-valuebuffer)
- [`Promise<Buffer> repo.get (key)`](#promisebuffer-repoget-key)
- [`Promise<Boolean> repo.isInitialized ()`](#promiseboolean-repoisinitialized-)
- [`Promise repo.blocks.put (block:Block)`](#promise-repoblocksput-blockblock)
- [`Promise repo.blocks.putMany (blocks)`](#promise-repoblocksputmany-blocks)
- [`Promise<Buffer> 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<Object> repo.config.getAll()`](#promiseobject-repoconfiggetall)
- [`Promise<boolean> repo.config.exists()`](#promiseboolean-repoconfigexists)
- [Version](#version)
- [`Promise<Number> repo.version.get ()`](#promisenumber-repoversionget-)
- [`Promise repo.version.set (version:Number)`](#promise-repoversionset-versionnumber)
- [API Addr](#api-addr)
- [`Promise<String> repo.apiAddr.get ()`](#promisestring-repoapiaddrget-)
- [`Promise repo.apiAddr.set (value)`](#promise-repoapiaddrset-value)
- [Status](#status)
- [`Promise<Object> repo.stat ()`](#promiseobject-repostat-)
- [Lock](#lock)
- [`Promise lock.lock (dir)`](#promise-locklock-dir)
- [`Promise closer.close ()`](#promise-closerclose-)
- [`Promise<boolean> lock.locked (dir)`](#promiseboolean-locklocked-dir)
- [Notes](#notes)
- [Migrations](#migrations)
- [Contribute](#contribute)
- [License](#license)

Expand Down Expand Up @@ -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.

Expand All @@ -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.

Expand All @@ -243,25 +274,25 @@ const value = await repo.config.get('a.b.c')
console.log('config.a.b.c = ', value)
```

##### `Promise<Object> repo.config.get()`
#### `Promise<Object> repo.config.getAll()`

Get the entire config value.

#### `Promise<boolean> repo.config.exists()`

Whether the config sub-repo exists.

#### `repo.version`
### Version

##### `Promise<Number> repo.version.get ()`
#### `Promise<Number> 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<String> repo.apiAddr.get ()`

Expand All @@ -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<Object> repo.stat ()`
### Status

#### `Promise<Object> repo.stat ()`

Gets the repo status.

Expand Down Expand Up @@ -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`

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
53 changes: 48 additions & 5 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<Object>}
*/
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<Object>}
*/
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`)
Expand All @@ -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
Expand All @@ -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))
},

/**
Expand All @@ -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) {
Expand Down
4 changes: 1 addition & 3 deletions test/api-addr-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {
Expand Down
57 changes: 12 additions & 45 deletions test/blockstore-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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()
})
})

Expand Down Expand Up @@ -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 () => {
Expand All @@ -187,25 +173,16 @@ 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 () => {
const data = Buffer.from(`TEST${Date.now()}`)
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 () => {
Expand Down Expand Up @@ -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 () => {
Expand All @@ -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')
})
})
})
Expand Down
4 changes: 1 addition & 3 deletions test/blockstore-utils-test.js
Original file line number Diff line number Diff line change
@@ -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')
Expand Down
Loading

0 comments on commit 0122537

Please sign in to comment.