Skip to content
This repository was archived by the owner on Feb 12, 2024. It is now read-only.

Commit 582be49

Browse files
committed
feat: store pins in datastore instead of a DAG
Adds a `.pins` datastore to `ipfs-repo` and uses that to store pins as cbor binary keyed by base64 stringified multihashes (n.b. not CIDs). Each pin has several fields: ```javascript { cid: // buffer, the full CID pinned type: // string, 'recursive' or 'direct' comments: // string, human-readable comments for the pin } ``` BREAKING CHANGES: * pins are now stored in a datastore, a repo migration will be necessary * ipfs.pins.add now returns an async generator * ipfs.pins.rm now returns an async generator Depends on: - [ ] ipfs/js-ipfs-repo#221
1 parent e9eca18 commit 582be49

File tree

37 files changed

+855
-1438
lines changed

37 files changed

+855
-1438
lines changed

examples/custom-ipfs-repo/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"dependencies": {
1313
"datastore-fs": "^0.9.1",
1414
"ipfs": "^0.41.0",
15-
"ipfs-repo": "^0.30.1",
15+
"ipfs-repo": "github:ipfs/js-ipfs-repo#store-pins-in-datastore",
1616
"it-all": "^1.0.1"
1717
},
1818
"devDependencies": {

packages/interface-ipfs-core/SPEC/PIN.md

+11-10
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88

99
> Adds an IPFS object to the pinset and also stores it to the IPFS repo. pinset is the set of hashes currently pinned (not gc'able).
1010
11-
##### `ipfs.pin.add(hash, [options])`
11+
##### `ipfs.pin.add(source, [options])`
1212

1313
Where:
1414

15-
- `hash` is an IPFS multihash.
15+
- `source` is a [CID], an array of CIDs or an (async) iterable that yields CIDs
1616
- `options` is an object that can contain the following keys
1717
- `recursive` (`boolean`) - Recursively pin the object linked. Type: bool. Default: `true`
1818
- `timeout` (`number`|`string`) - Throw an error if the request does not complete within the specified milliseconds timeout. If `timeout` is a string, the value is parsed as a [human readable duration](https://www.npmjs.com/package/parse-duration). There is no timeout by default.
@@ -21,9 +21,9 @@ Where:
2121

2222
| Type | Description |
2323
| -------- | -------- |
24-
| `Promise<{ cid: CID }>` | An array of objects that represent the files that were pinned |
24+
| `AsyncIterable<CID>` | An async iterable that yields objects containing the CIDs that were pinned |
2525

26-
an array of objects is returned, each of the form:
26+
Each yielded object has the form:
2727

2828
```JavaScript
2929
{
@@ -77,26 +77,27 @@ A great source of [examples][] can be found in the tests for this API.
7777

7878
> Remove a hash from the pinset
7979
80-
##### `ipfs.pin.rm(hash, [options])`
80+
##### `ipfs.pin.rm(source, [options])`
8181

8282
Where:
83-
- `hash` is a multihash.
83+
- `source` is a [CID], an array of CIDs or an (async) iterable that yields CIDs
8484
- `options` is an object that can contain the following keys
8585
- 'recursive' - Recursively unpin the object linked. Type: bool. Default: `true`
8686

8787
**Returns**
8888

8989
| Type | Description |
9090
| -------- | -------- |
91-
| `Promise<{ cid: CID }>` | An array of unpinned objects |
91+
| `AsyncIterable<{ cid: CID }>` | An async iterable that yields objects containing the CIDs that were unpinned |
9292

9393
**Example:**
9494

9595
```JavaScript
96-
const pinset = await ipfs.pin.rm('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u')
97-
console.log(pinset)
96+
for await (const unpinned of ipfs.pin.rm(new CID('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u'))) {
97+
console.log(unpinned)
98+
}
9899
// prints the hashes that were unpinned
99-
// [ { cid: CID('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u') } ]
100+
// { cid: CID('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u') }
100101
```
101102

102103
A great source of [examples][] can be found in the tests for this API.

packages/interface-ipfs-core/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"is-ipfs": "^0.6.1",
4646
"it-all": "^1.0.1",
4747
"it-concat": "^1.0.0",
48+
"it-drain": "^1.0.0",
4849
"it-last": "^1.0.1",
4950
"it-pushable": "^1.3.1",
5051
"multiaddr": "^7.2.1",

packages/interface-ipfs-core/src/block/rm.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
const { getDescribe, getIt, expect } = require('../utils/mocha')
55
const hat = require('hat')
66
const all = require('it-all')
7+
const last = require('it-last')
8+
const drain = require('it-drain')
79

810
/** @typedef { import("ipfsd-ctl/src/factory") } Factory */
911
/**
@@ -142,13 +144,12 @@ module.exports = (common, options) => {
142144
format: 'raw',
143145
hashAlg: 'sha2-256'
144146
})
145-
await ipfs.pin.add(cid.toString())
147+
await drain(ipfs.pin.add(cid))
146148

147-
const result = await all(ipfs.block.rm(cid))
149+
const result = await last(ipfs.block.rm(cid))
148150

149-
expect(result).to.be.an('array').and.to.have.lengthOf(1)
150-
expect(result[0]).to.have.property('error')
151-
expect(result[0].error.message).to.include('pinned')
151+
expect(result).to.have.property('error').that.is.an('Error')
152+
.with.property('message').that.includes('pinned')
152153
})
153154
})
154155
}

packages/interface-ipfs-core/src/pin/add.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ module.exports = (common, options) => {
2828
after(() => common.clean())
2929

3030
it('should add a pin', async () => {
31-
const pinset = await ipfs.pin.add(fixtures.files[0].cid, { recursive: false })
32-
expect(pinset.map(p => p.cid.toString())).to.include(fixtures.files[0].cid)
31+
const pinset = await all(ipfs.pin.add(fixtures.files[0].cid, { recursive: false }))
32+
expect(pinset.map(p => p.cid)).to.deep.include(fixtures.files[0].cid)
3333
})
3434
})
3535
}

packages/interface-ipfs-core/src/pin/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ const { createSuite } = require('../utils/suite')
44
const tests = {
55
ls: require('./ls'),
66
rm: require('./rm'),
7-
add: require('./add')
7+
add: require('./add'),
8+
core: require('./pins')
89
}
910

1011
module.exports = createSuite(tests)

packages/interface-ipfs-core/src/pin/ls.js

+29-23
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
const { fixtures } = require('./utils')
55
const { getDescribe, getIt, expect } = require('../utils/mocha')
66
const all = require('it-all')
7+
const drain = require('it-drain')
78

89
/** @typedef { import("ipfsd-ctl/src/factory") } Factory */
910
/**
@@ -23,22 +24,21 @@ module.exports = (common, options) => {
2324
ipfs = (await common.spawn()).api
2425
// two files wrapped in directories, only root CID pinned recursively
2526
const dir = fixtures.directory.files.map((file) => ({ path: file.path, content: file.data }))
26-
await all(ipfs.add(dir, { pin: false, cidVersion: 0 }))
27-
await ipfs.pin.add(fixtures.directory.cid, { recursive: true })
27+
await drain(ipfs.add(dir, { pin: false, cidVersion: 0 }))
28+
await drain(ipfs.pin.add(fixtures.directory.cid, { recursive: true }))
2829
// a file (CID pinned recursively)
29-
await all(ipfs.add(fixtures.files[0].data, { pin: false, cidVersion: 0 }))
30-
await ipfs.pin.add(fixtures.files[0].cid, { recursive: true })
30+
await drain(ipfs.add(fixtures.files[0].data, { pin: false, cidVersion: 0 }))
31+
await drain(ipfs.pin.add(fixtures.files[0].cid, { recursive: true }))
3132
// a single CID (pinned directly)
32-
await all(ipfs.add(fixtures.files[1].data, { pin: false, cidVersion: 0 }))
33-
await ipfs.pin.add(fixtures.files[1].cid, { recursive: false })
33+
await drain(ipfs.add(fixtures.files[1].data, { pin: false, cidVersion: 0 }))
34+
await drain(ipfs.pin.add(fixtures.files[1].cid, { recursive: false }))
3435
})
3536

3637
after(() => common.clean())
3738

3839
// 1st, because ipfs.add pins automatically
3940
it('should list all recursive pins', async () => {
40-
const pinset = (await all(ipfs.pin.ls({ type: 'recursive' })))
41-
.map(p => ({ ...p, cid: p.cid.toString() }))
41+
const pinset = await all(ipfs.pin.ls({ type: 'recursive' }))
4242

4343
expect(pinset).to.deep.include({
4444
type: 'recursive',
@@ -51,8 +51,7 @@ module.exports = (common, options) => {
5151
})
5252

5353
it('should list all indirect pins', async () => {
54-
const pinset = (await all(ipfs.pin.ls({ type: 'indirect' })))
55-
.map(p => ({ ...p, cid: p.cid.toString() }))
54+
const pinset = await all(ipfs.pin.ls({ type: 'indirect' }))
5655

5756
expect(pinset).to.not.deep.include({
5857
type: 'recursive',
@@ -77,8 +76,7 @@ module.exports = (common, options) => {
7776
})
7877

7978
it('should list all types of pins', async () => {
80-
const pinset = (await all(ipfs.pin.ls()))
81-
.map(p => ({ ...p, cid: p.cid.toString() }))
79+
const pinset = await all(ipfs.pin.ls())
8280

8381
expect(pinset).to.not.be.empty()
8482
// check the three "roots"
@@ -107,15 +105,19 @@ module.exports = (common, options) => {
107105
it('should list all direct pins', async () => {
108106
const pinset = await all(ipfs.pin.ls({ type: 'direct' }))
109107
expect(pinset).to.have.lengthOf(1)
110-
expect(pinset[0].type).to.equal('direct')
111-
expect(pinset[0].cid.toString()).to.equal(fixtures.files[1].cid)
108+
expect(pinset).to.deep.include({
109+
type: 'direct',
110+
cid: fixtures.files[1].cid
111+
})
112112
})
113113

114114
it('should list pins for a specific hash', async () => {
115115
const pinset = await all(ipfs.pin.ls(fixtures.files[0].cid))
116116
expect(pinset).to.have.lengthOf(1)
117-
expect(pinset[0].type).to.equal('recursive')
118-
expect(pinset[0].cid.toString()).to.equal(fixtures.files[0].cid)
117+
expect(pinset).to.deep.include({
118+
type: 'recursive',
119+
cid: fixtures.files[0].cid
120+
})
119121
})
120122

121123
it('should throw an error on missing direct pins for existing path', () => {
@@ -136,22 +138,26 @@ module.exports = (common, options) => {
136138
it('should list indirect pins for a specific path', async () => {
137139
const pinset = await all(ipfs.pin.ls(`/ipfs/${fixtures.directory.cid}/files/ipfs.txt`, { type: 'indirect' }))
138140
expect(pinset).to.have.lengthOf(1)
139-
expect(pinset[0].type).to.equal(`indirect through ${fixtures.directory.cid}`)
140-
expect(pinset[0].cid.toString()).to.equal(fixtures.directory.files[1].cid)
141+
expect(pinset).to.deep.include({
142+
type: `indirect through ${fixtures.directory.cid}`,
143+
cid: fixtures.directory.files[1].cid
144+
})
141145
})
142146

143147
it('should list recursive pins for a specific hash', async () => {
144148
const pinset = await all(ipfs.pin.ls(fixtures.files[0].cid, { type: 'recursive' }))
145149
expect(pinset).to.have.lengthOf(1)
146-
expect(pinset[0].type).to.equal('recursive')
147-
expect(pinset[0].cid.toString()).to.equal(fixtures.files[0].cid)
150+
expect(pinset).to.deep.include({
151+
type: 'recursive',
152+
cid: fixtures.files[0].cid
153+
})
148154
})
149155

150156
it('should list pins for multiple CIDs', async () => {
151157
const pinset = await all(ipfs.pin.ls([fixtures.files[0].cid, fixtures.files[1].cid]))
152-
const cids = pinset.map(p => p.cid.toString())
153-
expect(cids).to.include(fixtures.files[0].cid)
154-
expect(cids).to.include(fixtures.files[1].cid)
158+
const cids = pinset.map(p => p.cid)
159+
expect(cids).to.deep.include(fixtures.files[0].cid)
160+
expect(cids).to.deep.include(fixtures.files[1].cid)
155161
})
156162
})
157163
}

0 commit comments

Comments
 (0)