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

Commit 90b808b

Browse files
committed
feat: implementation of the new tree() function
BREAKING CHANGE: This replaces the `treeStream()` function. The API docs for it: > Returns all the paths that can be resolved into. - `cid` (`CID`, required): the CID to get the paths from. - `path` (`IPLD Path`, default: ''): the path to start to retrieve the other paths from. - `options`: - `recursive` (`bool`, default: false): whether to get the paths recursively or not. `false` resolves only the paths of the given CID. Returns an async iterator of all the paths (as Strings) you could resolve into.
1 parent 96acce7 commit 90b808b

File tree

5 files changed

+173
-195
lines changed

5 files changed

+173
-195
lines changed

package.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,6 @@
6262
"ipld-raw": "^2.0.1",
6363
"merge-options": "^1.0.1",
6464
"multicodec": "~0.5.0",
65-
"pull-defer": "~0.2.3",
66-
"pull-stream": "^3.6.9",
67-
"pull-traverse": "^1.0.3",
6865
"typical": "^3.0.0"
6966
},
7067
"contributors": [

src/index.js

Lines changed: 124 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
'use strict'
22

33
const Block = require('ipfs-block')
4-
const pull = require('pull-stream')
54
const CID = require('cids')
6-
const pullDeferSource = require('pull-defer').source
7-
const pullTraverse = require('pull-traverse')
8-
const map = require('async/map')
95
const waterfall = require('async/waterfall')
106
const mergeOptions = require('merge-options')
117
const ipldDagCbor = require('ipld-dag-cbor')
@@ -285,122 +281,6 @@ class IPLDResolver {
285281
return fancyIterator(putIterator)
286282
}
287283

288-
treeStream (cid, path, options) {
289-
if (typeof path === 'object') {
290-
options = path
291-
path = undefined
292-
}
293-
294-
options = options || {}
295-
296-
let p
297-
298-
if (!options.recursive) {
299-
p = pullDeferSource()
300-
301-
waterfall([
302-
async () => {
303-
return this._getFormat(cid.codec)
304-
},
305-
(format, cb) => this.bs.get(cid, (err, block) => {
306-
if (err) return cb(err)
307-
cb(null, format, block)
308-
}),
309-
(format, block, cb) => format.resolver.tree(block.data, cb)
310-
], (err, paths) => {
311-
if (err) {
312-
p.abort(err)
313-
return p
314-
}
315-
p.resolve(pull.values(paths))
316-
})
317-
}
318-
319-
// recursive
320-
if (options.recursive) {
321-
p = pull(
322-
pullTraverse.widthFirst({
323-
basePath: null,
324-
cid: cid
325-
}, (el) => {
326-
// pass the paths through the pushable pull stream
327-
// continue traversing the graph by returning
328-
// the next cids with deferred
329-
330-
if (typeof el === 'string') {
331-
return pull.empty()
332-
}
333-
334-
const deferred = pullDeferSource()
335-
const cid = el.cid
336-
337-
waterfall([
338-
async () => {
339-
return this._getFormat(cid.codec)
340-
},
341-
(format, cb) => this.bs.get(cid, (err, block) => {
342-
if (err) return cb(err)
343-
cb(null, format, block)
344-
}),
345-
(format, block, cb) => format.resolver.tree(block.data, (err, paths) => {
346-
if (err) {
347-
return cb(err)
348-
}
349-
map(paths, (p, cb) => {
350-
format.resolver.isLink(block.data, p, (err, link) => {
351-
if (err) {
352-
return cb(err)
353-
}
354-
cb(null, { path: p, link: link })
355-
})
356-
}, cb)
357-
})
358-
], (err, paths) => {
359-
if (err) {
360-
deferred.abort(err)
361-
return deferred
362-
}
363-
364-
deferred.resolve(pull.values(paths.map((p) => {
365-
const base = el.basePath ? el.basePath + '/' + p.path : p.path
366-
if (p.link) {
367-
return {
368-
basePath: base,
369-
cid: IPLDResolver._maybeCID(p.link)
370-
}
371-
}
372-
return base
373-
})))
374-
})
375-
return deferred
376-
}),
377-
pull.map((e) => {
378-
if (typeof e === 'string') {
379-
return e
380-
}
381-
return e.basePath
382-
}),
383-
pull.filter(Boolean)
384-
)
385-
}
386-
387-
// filter out by path
388-
if (path) {
389-
return pull(
390-
p,
391-
pull.map((el) => {
392-
if (el.indexOf(path) === 0) {
393-
el = el.slice(path.length + 1)
394-
return el
395-
}
396-
}),
397-
pull.filter(Boolean)
398-
)
399-
}
400-
401-
return p
402-
}
403-
404284
/**
405285
* Remove IPLD Nodes by the given CIDs.
406286
*
@@ -437,6 +317,130 @@ class IPLDResolver {
437317
return fancyIterator(removeIterator)
438318
}
439319

320+
/**
321+
* Returns all the paths that can be resolved into.
322+
*
323+
* @param {Object} cid - The ID to get the paths from
324+
* @param {string} [offsetPath=''] - the path to start to retrieve the other paths from.
325+
* @param {Object} [userOptions]
326+
* @param {number} [userOptions.recursive=false] - whether to get the paths recursively or not. `false` resolves only the paths of the given CID.
327+
* @returns {Iterable.<Promise.<String>>} - Returns an async iterator with paths that can be resolved into
328+
*/
329+
tree (cid, offsetPath, userOptions) {
330+
if (typeof offsetPath === 'object') {
331+
userOptions = offsetPath
332+
offsetPath = undefined
333+
}
334+
offsetPath = offsetPath || ''
335+
336+
const defaultOptions = {
337+
recursive: false
338+
}
339+
const options = mergeOptions(defaultOptions, userOptions)
340+
341+
// Get available paths from a block
342+
const getPaths = (cid) => {
343+
return new Promise(async (resolve, reject) => {
344+
let format
345+
try {
346+
format = await this._getFormat(cid.codec)
347+
} catch (error) {
348+
return reject(error)
349+
}
350+
this.bs.get(cid, (err, block) => {
351+
if (err) {
352+
return reject(err)
353+
}
354+
format.resolver.tree(block.data, (err, paths) => {
355+
if (err) {
356+
return reject(err)
357+
}
358+
return resolve({ paths, block })
359+
})
360+
})
361+
})
362+
}
363+
364+
// If a path is a link then follow it and return its CID
365+
const maybeRecurse = (block, treePath) => {
366+
return new Promise(async (resolve, reject) => {
367+
// A treepath we might want to follow recursively
368+
const format = await this._getFormat(block.cid.codec)
369+
format.resolver.isLink(block.data, treePath, (err, link) => {
370+
if (err) {
371+
return reject(err)
372+
}
373+
// Something to follow recusively, hence push it into the queue
374+
if (link) {
375+
const cid = IPLDResolver._maybeCID(link)
376+
resolve(cid)
377+
} else {
378+
resolve(null)
379+
}
380+
})
381+
})
382+
}
383+
384+
// The list of paths that will get returned
385+
let treePaths = []
386+
// The current block, needed to call `isLink()` on every interation
387+
let block
388+
// The list of items we want to follow recursively. The items are
389+
// an object consisting of the CID and the currently already resolved
390+
// path
391+
const queue = [{ cid, basePath: '' }]
392+
// The path that was already traversed
393+
let basePath
394+
395+
const next = async () => {
396+
// End of iteration if there aren't any paths left to return or
397+
// if we don't want to traverse recursively and have already
398+
// returne the first level
399+
if (treePaths.length === 0 && queue.length === 0) {
400+
return { done: true }
401+
}
402+
403+
return new Promise(async (resolve, reject) => {
404+
// There aren't any paths left, get them from the given CID
405+
if (treePaths.length === 0 && queue.length > 0) {
406+
({ cid, basePath } = queue.shift())
407+
408+
let paths
409+
try {
410+
({ block, paths } = await getPaths(cid))
411+
} catch (error) {
412+
return reject(error)
413+
}
414+
treePaths.push(...paths)
415+
}
416+
const treePath = treePaths.shift()
417+
let fullPath = basePath + treePath
418+
419+
// Only follow links if recursion is intended
420+
if (options.recursive) {
421+
cid = await maybeRecurse(block, treePath)
422+
if (cid !== null) {
423+
queue.push({ cid, basePath: fullPath + '/' })
424+
}
425+
}
426+
427+
// Return it if it matches the given offset path, but is not the
428+
// offset path itself
429+
if (fullPath.startsWith(offsetPath) &&
430+
fullPath.length > offsetPath.length) {
431+
if (offsetPath.length > 0) {
432+
fullPath = fullPath.slice(offsetPath.length + 1)
433+
}
434+
return resolve({ done: false, value: fullPath })
435+
} else { // Else move on to the next iteration before returning
436+
return resolve(next())
437+
}
438+
})
439+
}
440+
441+
return fancyIterator({ next })
442+
}
443+
440444
/* */
441445
/* internals */
442446
/* */

src/util.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,16 @@ exports.ends = (iterator) => {
2020
return iterator
2121
}
2222

23+
exports.all = async (iterator) => {
24+
const values = []
25+
for await (const value of iterator) {
26+
values.push(value)
27+
}
28+
return values
29+
}
30+
2331
exports.fancyIterator = (iterator) => {
2432
iterator[Symbol.asyncIterator] = function () { return this }
33+
iterator.all = () => exports.all(iterator)
2534
return exports.ends(iterator)
2635
}

test/basics.js

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ const BlockService = require('ipfs-block-service')
1111
const CID = require('cids')
1212
const multihash = require('multihashes')
1313
const multicodec = require('multicodec')
14-
const pull = require('pull-stream')
1514
const inMemory = require('ipld-in-memory')
1615

1716
const IPLDResolver = require('../src')
@@ -81,7 +80,7 @@ module.exports = (repo) => {
8180
})
8281
})
8382

84-
it('treeStream - errors on unknown resolver', (done) => {
83+
it('tree - errors on unknown resolver', async () => {
8584
const bs = new BlockService(repo)
8685
const r = new IPLDResolver({ blockService: bs })
8786
// choosing a format that is not supported
@@ -90,14 +89,9 @@ module.exports = (repo) => {
9089
'blake2b-8',
9190
multihash.encode(Buffer.from('abcd', 'hex'), 'sha1')
9291
)
93-
pull(
94-
r.treeStream(cid, '/', {}),
95-
pull.collect(function (err) {
96-
expect(err).to.exist()
97-
expect(err.message).to.eql('No resolver found for codec "blake2b-8"')
98-
done()
99-
})
100-
)
92+
const result = r.tree(cid)
93+
await expect(result.next()).to.be.rejectedWith(
94+
'No resolver found for codec "blake2b-8"')
10195
})
10296
})
10397
}

0 commit comments

Comments
 (0)