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

Commit 9801765

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 12b436b commit 9801765

File tree

4 files changed

+164
-195
lines changed

4 files changed

+164
-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')
@@ -283,122 +279,6 @@ class IPLDResolver {
283279
return fancyIterator(next)
284280
}
285281

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

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

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)