Skip to content

Commit 0122537

Browse files
authored
feat: adds .replace and .getAll methods to config (#227)
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 02f1ed7 commit 0122537

17 files changed

+153
-124
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.8.1",
4747
"chai": "^4.2.0",
48+
"chai-as-promised": "^7.1.1",
4849
"dirty-chai": "^2.0.1",
4950
"just-range": "^2.1.0",
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 && _get(config, key) === undefined) {
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/api-addr-test.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
'use strict'
33

44
const { Buffer } = require('buffer')
5-
const chai = require('chai')
6-
chai.use(require('dirty-chai'))
7-
const expect = chai.expect
5+
const { expect } = require('./utils/chai')
86
const apiAddr = require('../src/api-addr')
97

108
module.exports = () => {

test/blockstore-test.js

Lines changed: 12 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33
'use strict'
44

55
const { Buffer } = require('buffer')
6-
const chai = require('chai')
7-
chai.use(require('dirty-chai'))
8-
const expect = chai.expect
9-
const assert = chai.assert
6+
const { expect } = require('./utils/chai')
107
const Block = require('ipld-block')
118
const CID = require('cids')
129
const range = require('just-range')
@@ -124,13 +121,8 @@ module.exports = (repo) => {
124121
expect(commitInvoked).to.be.true()
125122
})
126123

127-
it('returns an error on invalid block', async () => {
128-
try {
129-
await repo.blocks.put('hello')
130-
assert.fail()
131-
} catch (err) {
132-
expect(err).to.exist()
133-
}
124+
it('returns an error on invalid block', () => {
125+
return expect(repo.blocks.put('hello')).to.eventually.be.rejected()
134126
})
135127
})
136128

@@ -158,14 +150,8 @@ module.exports = (repo) => {
158150
}))
159151
})
160152

161-
it('returns an error on invalid block', async () => {
162-
try {
163-
await repo.blocks.get('woot')
164-
} catch (err) {
165-
expect(err).to.exist()
166-
return
167-
}
168-
assert.fail()
153+
it('returns an error on invalid block', () => {
154+
return expect(repo.blocks.get('woot')).to.eventually.be.rejected()
169155
})
170156

171157
it('should get block stored under v0 CID with a v1 CID', async () => {
@@ -187,25 +173,16 @@ module.exports = (repo) => {
187173
expect(block.data).to.eql(data)
188174
})
189175

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

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

204-
try {
205-
await repo.blocks.get(cid)
206-
} catch (err) {
207-
expect(err.code).to.equal('ERR_NOT_FOUND')
208-
}
185+
await expect(repo.blocks.get(cid)).to.eventually.be.rejected().with.property('code', 'ERR_NOT_FOUND')
209186
})
210187

211188
it('throws unknown error encountered when getting a block', async () => {
@@ -279,13 +256,8 @@ module.exports = (repo) => {
279256
expect(exists).to.eql(true)
280257
})
281258

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

291263
it('returns false when requesting non-dag-pb CID that is not in the store', async () => {
@@ -305,13 +277,8 @@ module.exports = (repo) => {
305277
expect(exists).to.equal(false)
306278
})
307279

308-
it('throws when passed an invalid cid', async () => {
309-
try {
310-
await repo.blocks.delete('foo')
311-
throw new Error('Should have thrown')
312-
} catch (err) {
313-
expect(err.code).to.equal('ERR_INVALID_CID')
314-
}
280+
it('throws when passed an invalid cid', () => {
281+
return expect(repo.blocks.delete('foo')).to.eventually.be.rejected().with.property('code', 'ERR_INVALID_CID')
315282
})
316283
})
317284
})

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)