Skip to content

Commit 1f4328f

Browse files
authored
feat: adds .replace and .getAll methods to config (#229)
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.
1 parent ac32e3c commit 1f4328f

File tree

11 files changed

+145
-100
lines changed

11 files changed

+145
-100
lines changed

README.md

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# IPFS Repo JavaScript Implementation
1+
# IPFS Repo JavaScript Implementation <!-- omit in toc -->
22

33
[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io)
44
[![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/)
@@ -14,11 +14,11 @@
1414
1515
This is the implementation of the [IPFS repo spec](https://github.com/ipfs/specs/blob/master/REPO.md) in JavaScript.
1616

17-
## Lead Maintainer
17+
## Lead Maintainer <!-- omit in toc -->
1818

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

21-
## Table of Contents
21+
## Table of Contents <!-- omit in toc -->
2222

2323
- [Background](#background)
2424
- [Install](#install)
@@ -28,7 +28,40 @@ This is the implementation of the [IPFS repo spec](https://github.com/ipfs/specs
2828
- [Use in a browser Using a script tag](#use-in-a-browser-using-a-script-tag)
2929
- [Usage](#usage)
3030
- [API](#api)
31+
- [Setup](#setup)
32+
- [`new Repo(path[, options])`](#new-repopath-options)
33+
- [`Promise repo.init ()`](#promise-repoinit-)
34+
- [`Promise repo.open ()`](#promise-repoopen-)
35+
- [`Promise repo.close ()`](#promise-repoclose-)
36+
- [`Promise<boolean> repo.exists ()`](#promiseboolean-repoexists-)
37+
- [Repos](#repos)
38+
- [`Promise repo.put (key, value:Buffer)`](#promise-repoput-key-valuebuffer)
39+
- [`Promise<Buffer> repo.get (key)`](#promisebuffer-repoget-key)
40+
- [`Promise<Boolean> repo.isInitialized ()`](#promiseboolean-repoisinitialized-)
41+
- [`Promise repo.blocks.put (block:Block)`](#promise-repoblocksput-blockblock)
42+
- [`Promise repo.blocks.putMany (blocks)`](#promise-repoblocksputmany-blocks)
43+
- [`Promise<Buffer> repo.blocks.get (cid)`](#promisebuffer-repoblocksget-cid)
44+
- [`repo.datastore`](#repodatastore)
45+
- [Config](#config)
46+
- [`Promise repo.config.set(key:string, value)`](#promise-repoconfigsetkeystring-value)
47+
- [`Promise repo.config.replace(value)`](#promise-repoconfigreplacevalue)
48+
- [`Promise<?> repo.config.get(key:string)`](#promise-repoconfiggetkeystring)
49+
- [`Promise<Object> repo.config.getAll()`](#promiseobject-repoconfiggetall)
50+
- [`Promise<boolean> repo.config.exists()`](#promiseboolean-repoconfigexists)
51+
- [Version](#version)
52+
- [`Promise<Number> repo.version.get ()`](#promisenumber-repoversionget-)
53+
- [`Promise repo.version.set (version:Number)`](#promise-repoversionset-versionnumber)
54+
- [API Addr](#api-addr)
55+
- [`Promise<String> repo.apiAddr.get ()`](#promisestring-repoapiaddrget-)
56+
- [`Promise repo.apiAddr.set (value)`](#promise-repoapiaddrset-value)
57+
- [Status](#status)
58+
- [`Promise<Object> repo.stat ()`](#promiseobject-repostat-)
59+
- [Lock](#lock)
60+
- [`Promise lock.lock (dir)`](#promise-locklock-dir)
61+
- [`Promise closer.close ()`](#promise-closerclose-)
62+
- [`Promise<boolean> lock.locked (dir)`](#promiseboolean-locklocked-dir)
3163
- [Notes](#notes)
64+
- [Migrations](#migrations)
3265
- [Contribute](#contribute)
3366
- [License](#license)
3467

@@ -210,13 +243,11 @@ Datastore:
210243
This contains a full implementation of [the `interface-datastore` API](https://github.com/ipfs/interface-datastore#api).
211244

212245

213-
### Utils
214-
215-
#### `repo.config`
246+
### Config
216247

217248
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.
218249

219-
##### `Promise repo.config.set(key:string, value)`
250+
#### `Promise repo.config.set(key:string, value)`
220251

221252
Set a config value. `value` can be any object that is serializable to JSON.
222253

@@ -228,11 +259,11 @@ const config = await repo.config.get()
228259
assert.equal(config.a.b.c, 'c value')
229260
```
230261

231-
##### `Promise repo.config.set(value)`
262+
#### `Promise repo.config.replace(value)`
232263

233264
Set the whole config value. `value` can be any object that is serializable to JSON.
234265

235-
##### `Promise<?> repo.config.get(key:string)`
266+
#### `Promise<?> repo.config.get(key:string)`
236267

237268
Get a config value. Returned promise resolves to the same type that was set before.
238269

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

246-
##### `Promise<Object> repo.config.get()`
277+
#### `Promise<Object> repo.config.getAll()`
247278

248279
Get the entire config value.
249280

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

252283
Whether the config sub-repo exists.
253284

254-
#### `repo.version`
285+
### Version
255286

256-
##### `Promise<Number> repo.version.get ()`
287+
#### `Promise<Number> repo.version.get ()`
257288

258289
Gets the repo version (an integer).
259290

260-
##### `Promise repo.version.set (version:Number)`
291+
#### `Promise repo.version.set (version:Number)`
261292

262293
Sets the repo version
263294

264-
#### `repo.apiAddr`
295+
### API Addr
265296

266297
#### `Promise<String> repo.apiAddr.get ()`
267298

@@ -273,7 +304,9 @@ Sets the API address.
273304

274305
* `value` should be a [Multiaddr](https://github.com/multiformats/js-multiaddr) or a String representing a valid one.
275306

276-
### `Promise<Object> repo.stat ()`
307+
### Status
308+
309+
#### `Promise<Object> repo.stat ()`
277310

278311
Gets the repo status.
279312

@@ -304,7 +337,7 @@ Sets the lock if one does not already exist. If a lock already exists, should th
304337

305338
Returns `closer`, where `closer` has a `close` method for removing the lock.
306339

307-
##### `Promise closer.close ()`
340+
#### `Promise closer.close ()`
308341

309342
Closes the lock created by `lock.open`
310343

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"devDependencies": {
4646
"aegir": "^21.4.5",
4747
"chai": "^4.2.0",
48+
"chai-as-promised": "^7.1.1",
4849
"dirty-chai": "^2.0.1",
4950
"lodash": "^4.17.11",
5051
"memdown": "^5.1.0",

src/config.js

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,33 @@ module.exports = (store) => {
1717
/**
1818
* Get the current configuration from the repo.
1919
*
20+
* @param {Object} options - options
21+
* @param {AbortSignal} options.signal - abort this config read
22+
* @returns {Promise<Object>}
23+
*/
24+
async getAll (options = {}) { // eslint-disable-line require-await
25+
return configStore.get(undefined, options)
26+
},
27+
28+
/**
29+
* Get the value for the passed configuration key from the repo.
30+
*
2031
* @param {String} key - the config key to get
32+
* @param {Object} options - options
33+
* @param {AbortSignal} options.signal - abort this config read
2134
* @returns {Promise<Object>}
2235
*/
23-
async get (key) {
36+
async get (key, options = {}) {
2437
if (!key) {
2538
key = undefined
2639
}
2740

2841
const encodedValue = await store.get(configKey)
42+
43+
if (options.signal && options.signal.aborted) {
44+
return
45+
}
46+
2947
const config = JSON.parse(encodedValue.toString())
3048
if (key !== undefined && !_has(config, key)) {
3149
throw new errors.NotFoundError(`Key ${key} does not exist in config`)
@@ -40,9 +58,11 @@ module.exports = (store) => {
4058
*
4159
* @param {String} key - the config key to be written
4260
* @param {Object} value - the config value to be written
61+
* @param {Object} options - options
62+
* @param {AbortSignal} options.signal - abort this config write
4363
* @returns {void}
4464
*/
45-
async set (key, value) { // eslint-disable-line require-await
65+
async set (key, value, options = {}) { // eslint-disable-line require-await
4666
if (arguments.length === 1) {
4767
value = key
4868
key = undefined
@@ -54,10 +74,29 @@ module.exports = (store) => {
5474
throw errcode(new Error('Invalid value type: ' + typeof value), 'ERR_INVALID_VALUE')
5575
}
5676

57-
return setQueue.add(() => _doSet({
77+
return setQueue.add(() => _maybeDoSet({
5878
key: key,
5979
value: value
60-
}))
80+
}, options.signal))
81+
},
82+
83+
/**
84+
* Set the current configuration for this repo.
85+
*
86+
* @param {Object} value - the config value to be written
87+
* @param {Object} options - options
88+
* @param {AbortSignal} options.signal - abort this config write
89+
* @returns {void}
90+
*/
91+
async replace (value, options = {}) { // eslint-disable-line require-await
92+
if (!value || Buffer.isBuffer(value)) {
93+
throw errcode(new Error('Invalid value type: ' + typeof value), 'ERR_INVALID_VALUE')
94+
}
95+
96+
return setQueue.add(() => _maybeDoSet({
97+
key: undefined,
98+
value: value
99+
}, options.signal))
61100
},
62101

63102
/**
@@ -72,7 +111,11 @@ module.exports = (store) => {
72111

73112
return configStore
74113

75-
async function _doSet (m) {
114+
async function _maybeDoSet (m, signal) {
115+
if (signal && signal.aborted) {
116+
return
117+
}
118+
76119
const key = m.key
77120
const value = m.value
78121
if (key) {

test/blockstore-test.js

Lines changed: 11 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
const chai = require('chai')
66
chai.use(require('dirty-chai'))
77
const expect = chai.expect
8-
const assert = chai.assert
98
const Block = require('ipfs-block')
109
const CID = require('cids')
1110
const _ = require('lodash')
@@ -121,13 +120,8 @@ module.exports = (repo) => {
121120
expect(commitInvoked).to.be.true()
122121
})
123122

124-
it('returns an error on invalid block', async () => {
125-
try {
126-
await repo.blocks.put('hello')
127-
assert.fail()
128-
} catch (err) {
129-
expect(err).to.exist()
130-
}
123+
it('returns an error on invalid block', () => {
124+
return expect(repo.blocks.put('hello')).to.eventually.be.rejected()
131125
})
132126
})
133127

@@ -155,14 +149,8 @@ module.exports = (repo) => {
155149
}))
156150
})
157151

158-
it('returns an error on invalid block', async () => {
159-
try {
160-
await repo.blocks.get('woot')
161-
} catch (err) {
162-
expect(err).to.exist()
163-
return
164-
}
165-
assert.fail()
152+
it('returns an error on invalid block', () => {
153+
return expect(repo.blocks.get('woot')).to.eventually.be.rejected()
166154
})
167155

168156
it('should get block stored under v0 CID with a v1 CID', async () => {
@@ -184,25 +172,16 @@ module.exports = (repo) => {
184172
expect(block.data).to.eql(data)
185173
})
186174

187-
it('throws when passed an invalid cid', async () => {
188-
try {
189-
await repo.blocks.get('foo')
190-
throw new Error('Should have thrown')
191-
} catch (err) {
192-
expect(err.code).to.equal('ERR_INVALID_CID')
193-
}
175+
it('throws when passed an invalid cid', () => {
176+
return expect(repo.blocks.get('foo')).to.eventually.be.rejected().with.property('code', 'ERR_INVALID_CID')
194177
})
195178

196179
it('throws ERR_NOT_FOUND when requesting non-dag-pb CID that is not in the store', async () => {
197180
const data = Buffer.from(`TEST${Date.now()}`)
198181
const hash = await multihashing(data, 'sha2-256')
199182
const cid = new CID(1, 'dag-cbor', hash)
200183

201-
try {
202-
await repo.blocks.get(cid)
203-
} catch (err) {
204-
expect(err.code).to.equal('ERR_NOT_FOUND')
205-
}
184+
await expect(repo.blocks.get(cid)).to.eventually.be.rejected().with.property('code', 'ERR_NOT_FOUND')
206185
})
207186

208187
it('throws unknown error encountered when getting a block', async () => {
@@ -276,13 +255,8 @@ module.exports = (repo) => {
276255
expect(exists).to.eql(true)
277256
})
278257

279-
it('throws when passed an invalid cid', async () => {
280-
try {
281-
await repo.blocks.has('foo')
282-
throw new Error('Should have thrown')
283-
} catch (err) {
284-
expect(err.code).to.equal('ERR_INVALID_CID')
285-
}
258+
it('throws when passed an invalid cid', () => {
259+
return expect(repo.blocks.has('foo')).to.eventually.be.rejected().with.property('code', 'ERR_INVALID_CID')
286260
})
287261

288262
it('returns false when requesting non-dag-pb CID that is not in the store', async () => {
@@ -302,13 +276,8 @@ module.exports = (repo) => {
302276
expect(exists).to.equal(false)
303277
})
304278

305-
it('throws when passed an invalid cid', async () => {
306-
try {
307-
await repo.blocks.delete('foo')
308-
throw new Error('Should have thrown')
309-
} catch (err) {
310-
expect(err.code).to.equal('ERR_INVALID_CID')
311-
}
279+
it('throws when passed an invalid cid', () => {
280+
return expect(repo.blocks.delete('foo')).to.eventually.be.rejected().with.property('code', 'ERR_INVALID_CID')
312281
})
313282
})
314283
})

test/blockstore-utils-test.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
/* eslint-env mocha */
22
'use strict'
33

4-
const chai = require('chai')
5-
chai.use(require('dirty-chai'))
6-
const { expect } = chai
4+
const { expect } = require('./utils/chai')
75
const { Key } = require('interface-datastore')
86
const CID = require('cids')
97
const Repo = require('../src')

0 commit comments

Comments
 (0)