Skip to content
This repository was archived by the owner on Aug 11, 2021. It is now read-only.

Commit 743e679

Browse files
committed
feat: implementation of the new get() function
BREAKING CHANGE: `get()` is replacing the `getMany()` function. The API docs for it: > Retrieve several IPLD Nodes at once. - `cids` (`Iterable<CID>`): the CIDs of the IPLD Nodes that should be retrieved. Returns an async iterator with the IPLD Nodes that correspond to the given `cids`. Throws an error if a IPLD Node can’t be retrieved.
1 parent 8b737b1 commit 743e679

File tree

9 files changed

+277
-334
lines changed

9 files changed

+277
-334
lines changed

src/index.js

Lines changed: 70 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -146,29 +146,56 @@ class IPLDResolver {
146146
/**
147147
* Get multiple nodes back from an array of CIDs.
148148
*
149-
* @param {Array<CID>} cids
150-
* @param {function(Error, Array)} callback
151-
* @returns {void}
149+
* @param {Iterable.<CID>} cids - The CIDs of the IPLD Nodes that should be retrieved.
150+
* @returns {Iterable.<Promise.<Object>>} - Returns an async iterator with the IPLD Nodes that correspond to the given `cids`.
152151
*/
153-
getMany (cids, callback) {
154-
if (!Array.isArray(cids)) {
155-
return callback(new Error('Argument must be an array of CIDs'))
152+
get (cids) {
153+
if (!typical.isIterable(cids) || typical.isString(cids) ||
154+
Buffer.isBuffer(cids)) {
155+
throw new Error('`cids` must be an iterable of CIDs')
156156
}
157-
this.bs.getMany(cids, (err, blocks) => {
158-
if (err) {
159-
return callback(err)
157+
158+
let blocks
159+
const next = () => {
160+
// End of iteration if there aren't any blocks left to return
161+
if (cids.length === 0 ||
162+
(blocks !== undefined && blocks.length === 0)
163+
) {
164+
return Promise.resolve({ done: true })
160165
}
161-
map(blocks, (block, mapCallback) => {
162-
// TODO vmx 2018-12-07: Make this one async/await once
163-
// `util.serialize()` is a Promise
164-
this._getFormat(block.cid.codec).then((format) => {
165-
format.util.deserialize(block.data, mapCallback)
166-
}).catch((err) => {
167-
mapCallback(err)
168-
})
169-
},
170-
callback)
171-
})
166+
167+
return new Promise(async (resolve, reject) => {
168+
// Lazy load block.
169+
// Currntly the BlockService return all nodes as an array. In the
170+
// future this will also be an iterator
171+
if (blocks === undefined) {
172+
const cidsArray = Array.from(cids)
173+
this.bs.getMany(cidsArray, async (err, returnedBlocks) => {
174+
if (err) {
175+
return reject(err)
176+
}
177+
blocks = returnedBlocks
178+
const block = blocks.shift()
179+
try {
180+
const node = await this._deserialize(block)
181+
return resolve({ done: false, value: node })
182+
} catch (err) {
183+
return reject(err)
184+
}
185+
})
186+
} else {
187+
const block = blocks.shift()
188+
try {
189+
const node = await this._deserialize(block)
190+
return resolve({ done: false, value: node })
191+
} catch (err) {
192+
return reject(err)
193+
}
194+
}
195+
})
196+
}
197+
198+
return fancyIterator(next)
172199
}
173200

174201
/**
@@ -410,6 +437,29 @@ class IPLDResolver {
410437
})
411438
}
412439

440+
/**
441+
* Deserialize a given block
442+
*
443+
* @param {Object} block - The block to deserialize
444+
* @return {Object} = Returns the deserialized node
445+
*/
446+
async _deserialize (block) {
447+
return new Promise((resolve, reject) => {
448+
this._getFormat(block.cid.codec).then((format) => {
449+
// TODO vmx 2018-12-11: Make this one async/await once
450+
// `util.serialize()` is a Promise
451+
format.util.deserialize(block.data, (err, deserialized) => {
452+
if (err) {
453+
return reject(err)
454+
}
455+
return resolve(deserialized)
456+
})
457+
}).catch((err) => {
458+
return reject(err)
459+
})
460+
})
461+
}
462+
413463
/**
414464
* Return a CID instance if it is a link.
415465
*

test/basics.js

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,6 @@ module.exports = (repo) => {
5050
'No resolver found for codec "blake2b-8"')
5151
})
5252

53-
// TODO vmx 2018-11-29 Change this test to use `get()`.
54-
// it('_get - errors on unknown resolver', (done) => {
55-
// const bs = new BlockService(repo)
56-
// const r = new IPLDResolver({ blockService: bs })
57-
// // choosing a format that is not supported
58-
// const cid = new CID(1, 'base1', multihash.encode(Buffer.from('abcd', 'hex'), 'sha1'))
59-
// r.get(cid, (err, result) => {
60-
// expect(err).to.exist()
61-
// expect(err.message).to.eql('No resolver found for codec "base1"')
62-
// done()
63-
// })
64-
// }
65-
6653
it('put - errors on unknown resolver', async () => {
6754
const bs = new BlockService(repo)
6855
const r = new IPLDResolver({ blockService: bs })

test/ipld-all.js

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
*/
99

1010
const chai = require('chai')
11+
const chaiAsProised = require('chai-as-promised')
1112
const dirtyChai = require('dirty-chai')
1213
const expect = chai.expect
14+
chai.use(chaiAsProised)
1315
chai.use(dirtyChai)
1416
const dagPB = require('ipld-dag-pb')
1517
const dagCBOR = require('ipld-dag-cbor')
@@ -104,49 +106,50 @@ describe('IPLD Resolver for dag-cbor + dag-pb', () => {
104106
})
105107
})
106108

107-
describe('getMany', () => {
108-
it('should return nodes correctly', (done) => {
109-
resolver.getMany([cidCbor, cidPb], (err, result) => {
110-
expect(err).to.not.exist()
111-
expect(result.length).to.equal(2)
112-
expect(result).to.deep.equal([nodeCbor, nodePb])
113-
done()
114-
})
109+
describe('get', () => {
110+
it('should return nodes correctly', async () => {
111+
const result = resolver.get([cidCbor, cidPb])
112+
const node1 = await result.first()
113+
expect(node1).to.eql(nodeCbor)
114+
115+
const node2 = await result.first()
116+
expect(node2).to.eql(nodePb)
115117
})
116118

117-
it('should return nodes in input order', (done) => {
118-
resolver.getMany([cidPb, cidCbor], (err, result) => {
119-
expect(err).to.not.exist()
120-
expect(result.length).to.equal(2)
121-
expect(result).to.deep.equal([nodePb, nodeCbor])
122-
done()
123-
})
119+
it('should return nodes in input order', async () => {
120+
const result = resolver.get([cidPb, cidCbor])
121+
const node1 = await result.first()
122+
expect(node1).to.eql(nodePb)
123+
124+
const node2 = await result.first()
125+
expect(node2).to.eql(nodeCbor)
124126
})
125127

126-
it('should return error on invalid CID', (done) => {
127-
resolver.getMany([cidCbor, 'invalidcid'], (err, result) => {
128-
expect(err.message).to.equal('Not a valid cid')
129-
expect(result).to.be.undefined()
130-
done()
131-
})
128+
it('should return error on invalid CID', async () => {
129+
const result = resolver.get([cidCbor, 'invalidcid'])
130+
// TODO vmx 2018-12-11: This should really fail on the second node
131+
// we get, as the first one is valid. This is only possible once
132+
// the `getmany()` call of the BlockService takes and returns an
133+
// iterator and not an array.
134+
await expect(result.next()).to.be.rejectedWith(
135+
'Not a valid cid')
132136
})
133137

134-
it('should return error on non-existent CID', (done) => {
138+
it('should return error on non-existent CID', async () => {
135139
const nonExistentCid = new CID(
136140
'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP')
137-
resolver.getMany([cidCbor, nonExistentCid], (err, result) => {
138-
expect(err.message).to.equal('Not Found')
139-
expect(result).to.be.undefined()
140-
done()
141-
})
141+
const result = resolver.get([cidCbor, nonExistentCid])
142+
// TODO vmx 2018-12-11: This should really fail on the second node
143+
// we get, as the first one is valid. This is only possible once
144+
// the `getmany()` call of the BlockService takes and returns an
145+
// iterator and not an array.
146+
await expect(result.next()).to.be.rejectedWith(
147+
'Not Found')
142148
})
143149

144-
it('should return error on invalid input', (done) => {
145-
resolver.getMany('astring', (err, result) => {
146-
expect(err.message).to.equal('Argument must be an array of CIDs')
147-
expect(result).to.be.undefined()
148-
done()
149-
})
150+
it('should return error on invalid input', () => {
151+
expect(() => resolver.get('astring')).to.throw(
152+
'`cids` must be an iterable of CIDs')
150153
})
151154
})
152155
})

test/ipld-bitcoin.js

Lines changed: 28 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -102,18 +102,6 @@ module.exports = (repo) => {
102102
resolver._put(nc.cid, nc.node, cb)
103103
}, done)
104104
})
105-
106-
// TODO vmx 2018-11-30 Change this test to use `get()`.
107-
// it('resolver._get', (done) => {
108-
// resolver.put(node1, { cid: cid1 }, (err) => {
109-
// expect(err).to.not.exist()
110-
// resolver.get(cid1, (err, result) => {
111-
// expect(err).to.not.exist()
112-
// expect(node1.version).to.eql(result.value.version)
113-
// done()
114-
// })
115-
// })
116-
// })
117105
})
118106

119107
describe('public api', () => {
@@ -139,19 +127,6 @@ module.exports = (repo) => {
139127
expect(mh.name).to.equal('sha3-512')
140128
})
141129

142-
// TODO vmx 2018-11-30: Implement getting the whole object properly
143-
// it('root path (same as get)', (done) => {
144-
// resolver.get(cid1, '/', (err, result) => {
145-
// expect(err).to.not.exist()
146-
//
147-
// ipldBitcoin.util.cid(result.value, (err, cid) => {
148-
// expect(err).to.not.exist()
149-
// expect(cid).to.eql(cid1)
150-
// done()
151-
// })
152-
// })
153-
// })
154-
155130
it('resolves value within 1st node scope', async () => {
156131
const result = resolver.resolve(cid1, 'version')
157132
const node = await result.first()
@@ -187,27 +162,34 @@ module.exports = (repo) => {
187162
expect(node3.value).to.eql(1)
188163
})
189164

190-
// // TODO vmx 2018-11-30: remove this `get()` call with the new `get()`
191-
// it('resolver.remove', (done) => {
192-
// resolver.put(node1, { cid: cid1 }, (err) => {
193-
// expect(err).to.not.exist()
194-
// resolver.get(cid1, (err, result) => {
195-
// expect(err).to.not.exist()
196-
// expect(result.value.version).to.eql(1)
197-
// remove()
198-
// })
199-
// })
200-
//
201-
// function remove () {
202-
// resolver.remove(cid1, (err) => {
203-
// expect(err).to.not.exist()
204-
// resolver.get(cid1, (err) => {
205-
// expect(err).to.exist()
206-
// done()
207-
// })
208-
// })
209-
// }
210-
// })
165+
it('resolver.get round-trip', async () => {
166+
const resultPut = resolver.put([node1], multicodec.BITCOIN_BLOCK)
167+
const cid = await resultPut.first()
168+
const resultGet = resolver.get([cid])
169+
const node = await resultGet.first()
170+
expect(node).to.deep.equal(node1)
171+
})
172+
173+
it('resolver.remove', async () => {
174+
const resultPut = resolver.put([node1], multicodec.BITCOIN_BLOCK)
175+
const cid = await resultPut.first()
176+
const resultGet = resolver.get([cid])
177+
const sameAsNode1 = await resultGet.first()
178+
expect(sameAsNode1).to.deep.equal(node1)
179+
return remove()
180+
181+
function remove () {
182+
return new Promise((resolve, reject) => {
183+
resolver.remove(cid, (err) => {
184+
expect(err).to.not.exist()
185+
const resultGet = resolver.get([cid])
186+
expect(resultGet.next()).to.eventually.be.rejected()
187+
.then(() => resolve())
188+
.catch((err) => reject(err))
189+
})
190+
})
191+
}
192+
})
211193
})
212194
})
213195
}

test/ipld-dag-cbor.js

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -89,18 +89,6 @@ module.exports = (repo) => {
8989
resolver._put(nc.cid, nc.node, cb)
9090
}, done)
9191
})
92-
93-
// TODO vmx 2018-11-30 Change this test to use `get()`.
94-
// it('resolver._get', (done) => {
95-
// resolver.put(node1, { cid: cid1 }, (err) => {
96-
// expect(err).to.not.exist()
97-
// resolver._get(cid1, (err, node) => {
98-
// expect(err).to.not.exist()
99-
// expect(node1).to.eql(node)
100-
// done()
101-
// })
102-
// })
103-
// })
10492
})
10593

10694
describe('public api', () => {
@@ -127,19 +115,6 @@ module.exports = (repo) => {
127115
expect(mh.name).to.equal('sha3-512')
128116
})
129117

130-
// TODO vmx 2018-11-30: Implement getting the whole object properly
131-
// it('resolver.get root path', (done) => {
132-
// resolver.get(cid1, '/', (err, result) => {
133-
// expect(err).to.not.exist()
134-
//
135-
// dagCBOR.util.cid(result.value, (err, cid) => {
136-
// expect(err).to.not.exist()
137-
// expect(cid).to.eql(cid1)
138-
// done()
139-
// })
140-
// })
141-
// })
142-
143118
it('resolves value within 1st node scope', async () => {
144119
const result = resolver.resolve(cid1, 'someData')
145120
const node = await result.first()
@@ -193,6 +168,14 @@ module.exports = (repo) => {
193168
'path not available at root')
194169
})
195170

171+
it('resolver.get round-trip', async () => {
172+
const resultPut = resolver.put([node1], multicodec.DAG_CBOR)
173+
const cid = await resultPut.first()
174+
const resultGet = resolver.get([cid])
175+
const node = await resultGet.first()
176+
expect(node).to.deep.equal(node1)
177+
})
178+
196179
it('resolver.tree', (done) => {
197180
pull(
198181
resolver.treeStream(cid3),

0 commit comments

Comments
 (0)