Skip to content
This repository was archived by the owner on Mar 10, 2020. It is now read-only.

Commit 2aaf632

Browse files
author
Alan Shaw
committed
refactor: async iterables
1 parent 23421f3 commit 2aaf632

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+462
-865
lines changed

Diff for: README.md

+75-32
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
- [Additional Options](#additional-options)
5454
- [Instance Utils](#instance-utils)
5555
- [Static Types and Utils](#static-types-and-utils)
56+
- [Glob source](#glob-source)
57+
- [URL source](#url-source)
5658
- [Development](#development)
5759
- [Testing](#testing)
5860
- [Contribute](#contribute)
@@ -210,34 +212,20 @@ const ipfs = ipfsClient({ timeout: '2m' })
210212

211213
- [Regular Files API](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md)
212214
- [`ipfs.add(data, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#add)
213-
- [`ipfs.addPullStream([options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#addpullstream)
214-
- [`ipfs.addReadableStream([options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#addreadablestream)
215-
- [`ipfs.addFromStream(stream)`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#addfromstream)
216-
- [`ipfs.addFromFs(path, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#addfromfs)
217-
- [`ipfs.addFromURL(url, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#addfromurl)
218215
- [`ipfs.cat(ipfsPath, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#cat)
219-
- [`ipfs.catPullStream(ipfsPath, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#catpullstream)
220-
- [`ipfs.catReadableStream(ipfsPath, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#catreadablestream)
221216
- [`ipfs.get(ipfsPath, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#get)
222-
- [`ipfs.getPullStream(ipfsPath, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#getpullstream)
223-
- [`ipfs.getReadableStream(ipfsPath, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#getreadablestream)
224217
- [`ipfs.ls(ipfsPath)`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#ls)
225-
- [`ipfs.lsPullStream(ipfsPath)`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#lspullstream)
226-
- [`ipfs.lsReadableStream(ipfsPath)`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#lsreadablestream)
227218
- [MFS (mutable file system) specific](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#mutable-file-system)
228-
229-
_Explore the Mutable File System through interactive coding challenges in our [ProtoSchool tutorial](https://proto.school/#/mutable-file-system/)._
230219
- [`ipfs.files.cp([from, to])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filescp)
231220
- [`ipfs.files.flush([path])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filesflush)
232221
- [`ipfs.files.ls([path], [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filesls)
233222
- [`ipfs.files.mkdir(path, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filesmkdir)
234223
- [`ipfs.files.mv([from, to])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filesmv)
235224
- [`ipfs.files.read(path, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filesread)
236-
- [`ipfs.files.readPullStream(path, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filesreadpullstream)
237-
- [`ipfs.files.readReadableStream(path, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filesreadreadablestream)
238225
- [`ipfs.files.rm(path, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filesrm)
239226
- [`ipfs.files.stat(path, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#filesstat)
240227
- [`ipfs.files.write(path, content, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#fileswrite)
228+
_Explore the Mutable File System through interactive coding challenges in our [ProtoSchool tutorial](https://proto.school/#/mutable-file-system/)._
241229

242230
- [block](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/BLOCK.md)
243231
- [`ipfs.block.get(cid, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/BLOCK.md#blockget)
@@ -246,20 +234,15 @@ const ipfs = ipfsClient({ timeout: '2m' })
246234

247235
- [refs](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/REFS.md)
248236
- [`ipfs.refs(ipfsPath, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/REFS.md#refs)
249-
- [`ipfs.refsReadableStream(ipfsPath, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/REFS.md#refsreadablestream)
250-
- [`ipfs.refsPullStream(ipfsPath, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/REFS.md#refspullstream)
251237
- [`ipfs.refs.local()`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/REFS.md#refslocal)
252-
- [`ipfs.refs.localReadableStream()`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/REFS.md#refslocalreadablestream)
253-
- [`ipfs.refs.localPullStream()`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/REFS.md#refslocalpullstream)
254238

255239
#### Graph
256240

257241
- [dag](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/DAG.md)
258-
259-
_Explore the DAG API through interactive coding challenges in our [ProtoSchool tutorial](https://proto.school/#/basics)._
260242
- [`ipfs.dag.get(cid, [path], [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/DAG.md#dagget)
261243
- [`ipfs.dag.put(dagNode, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/DAG.md#dagput)
262244
- [`ipfs.dag.tree(cid, [path], [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/DAG.md#dagtree)
245+
_Explore the DAG API through interactive coding challenges in our [ProtoSchool tutorial](https://proto.school/#/basics)._
263246

264247
- [object](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/OBJECT.md)
265248
- [`ipfs.object.data(multihash, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/OBJECT.md#objectdata)
@@ -278,9 +261,6 @@ const ipfs = ipfsClient({ timeout: '2m' })
278261
- [`ipfs.pin.ls([hash], [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/PIN.md#pinls)
279262
- [`ipfs.pin.rm(hash, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/PIN.md#pinrm)
280263

281-
- refs
282-
- `ipfs.refs.local()`
283-
284264
#### Network
285265

286266
- [bootstrap](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/BOOTSTRAP.md)
@@ -326,8 +306,6 @@ const ipfs = ipfsClient({ timeout: '2m' })
326306
- [`ipfs.dns(domain)`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/MISCELLANEOUS.md#dns)
327307
- [`ipfs.id()`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/MISCELLANEOUS.md#id)
328308
- [`ipfs.ping(id, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/MISCELLANEOUS.md#ping)
329-
- [`ipfs.pingPullStream(id, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/MISCELLANEOUS.md#pingpullstream)
330-
- [`ipfs.pingReadableStream(id, [options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/MISCELLANEOUS.md#pingreadablestream)
331309
- [`ipfs.stop()`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/MISCELLANEOUS.md#stop). Alias to `ipfs.shutdown`.
332310
- [`ipfs.version()`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/MISCELLANEOUS.md#version)
333311

@@ -341,8 +319,6 @@ const ipfs = ipfsClient({ timeout: '2m' })
341319
- [stats](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/STATS.md)
342320
- [`ipfs.stats.bitswap()`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/STATS.md#statsbitswap)
343321
- [`ipfs.stats.bw([options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/STATS.md#statsbw)
344-
- [`ipfs.stats.bwPullStream([options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/STATS.md#statsbwpullstream)
345-
- [`ipfs.stats.bwReadableStream([options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/STATS.md#statsbwreadablestream)
346322
- [`ipfs.stats.repo([options])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/STATS.md#statsrepo)
347323

348324
- log
@@ -382,15 +358,14 @@ Call this on your client instance to return an object containing the `host`, `po
382358

383359
Aside from the default export, `ipfs-http-client` exports various types and utilities that are included in the bundle:
384360

385-
- [`isIPFS`](https://www.npmjs.com/package/is-ipfs)
386361
- [`Buffer`](https://www.npmjs.com/package/buffer)
387-
- [`PeerId`](https://www.npmjs.com/package/peer-id)
388-
- [`PeerInfo`](https://www.npmjs.com/package/peer-info)
389362
- [`multiaddr`](https://www.npmjs.com/package/multiaddr)
390363
- [`multibase`](https://www.npmjs.com/package/multibase)
391364
- [`multicodec`](https://www.npmjs.com/package/multicodec)
392-
- [`multihash`](https://www.npmjs.com/package/multihash)
365+
- [`multihash`](https://www.npmjs.com/package/multihashes)
393366
- [`CID`](https://www.npmjs.com/package/cids)
367+
- [`globSource`](https://github.com/ipfs/js-ipfs-utils/blob/master/src/files/glob-source.js) (not available in the browser)
368+
- [`urlSource`](https://github.com/ipfs/js-ipfs-utils/blob/master/src/files/url-source.js)
394369

395370
These can be accessed like this, for example:
396371

@@ -400,6 +375,74 @@ const { CID } = require('ipfs-http-client')
400375
import { CID } from 'ipfs-http-client'
401376
```
402377

378+
##### Glob source
379+
380+
A utility to allow files on the file system to be easily added to IPFS.
381+
382+
###### `globSource(path, [options])`
383+
384+
- `path`: A path to a single file or directory to glob from
385+
- `options`: Optional options
386+
- `options.recursive`: If `path` is a directory, use option `{ recursive: true }` to add the directory and all its sub-directories.
387+
- `options.ignore`: To exclude file globs from the directory, use option `{ ignore: ['ignore/this/folder/**', 'and/this/file'] }`.
388+
- `options.hidden`: Hidden/dot files (files or folders starting with a `.`, for example, `.git/`) are not included by default. To add them, use the option `{ hidden: true }`.
389+
390+
Returns an async iterable that yields `{ path, content }` objects suitable for passing to `ipfs.add`.
391+
392+
###### Example
393+
394+
```js
395+
const IpfsHttpClient = require('ipfs-http-client')
396+
const { globSource } = IpfsHttpClient
397+
const ipfs = IpfsHttpClient()
398+
399+
for await (const file of ipfs.add(globSource('./docs', { recursive: true }))) {
400+
console.log(file)
401+
}
402+
/*
403+
{
404+
path: 'docs/assets/anchor.js',
405+
hash: 'QmVHxRocoWgUChLEvfEyDuuD6qJ4PhdDL2dTLcpUy3dSC2',
406+
size: 15347
407+
}
408+
{
409+
path: 'docs/assets/bass-addons.css',
410+
hash: 'QmPiLWKd6yseMWDTgHegb8T7wVS7zWGYgyvfj7dGNt2viQ',
411+
size: 232
412+
}
413+
...
414+
*/
415+
```
416+
417+
##### URL source
418+
419+
A utility to allow content from the internet to be easily added to IPFS.
420+
421+
###### `urlSource(url)`
422+
423+
- `url`: A string URL or [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) instance to send HTTP GET request to
424+
425+
Returns an async iterable that yields `{ path, content }` objects suitable for passing to `ipfs.add`.
426+
427+
###### Example
428+
429+
```js
430+
const IpfsHttpClient = require('ipfs-http-client')
431+
const { urlSource } = IpfsHttpClient
432+
const ipfs = IpfsHttpClient()
433+
434+
for await (const file of ipfs.add(urlSource('https://ipfs.io/images/ipfs-logo.svg'))) {
435+
console.log(file)
436+
}
437+
/*
438+
{
439+
path: 'ipfs-logo.svg',
440+
hash: 'QmTqZhR6f7jzdhLgPArDPnsbZpvvgxzCZycXK7ywkLxSyU',
441+
size: 3243
442+
}
443+
*/
444+
```
445+
403446
## Development
404447

405448
### Testing

Diff for: package.json

+12-28
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,9 @@
1515
],
1616
"main": "src/index.js",
1717
"browser": {
18-
"glob": false,
19-
"fs": false,
20-
"stream": "readable-stream",
21-
"ky-universal": "ky/umd",
2218
"./src/add/form-data.js": "./src/add/form-data.browser.js",
23-
"./src/add-from-fs/index.js": "./src/add-from-fs/index.browser.js",
24-
"./src/lib/buffer-to-form-data.js": "./src/lib/buffer-to-form-data.browser.js"
19+
"./src/lib/buffer-to-form-data.js": "./src/lib/buffer-to-form-data.browser.js",
20+
"ipfs-utils/src/files/glob-source": false
2521
},
2622
"repository": "github:ipfs/js-ipfs-http-client",
2723
"scripts": {
@@ -42,55 +38,43 @@
4238
},
4339
"dependencies": {
4440
"abort-controller": "^3.0.0",
45-
"async-iterator-to-pull-stream": "^1.3.0",
4641
"bignumber.js": "^9.0.0",
47-
"bl": "^4.0.0",
4842
"bs58": "^4.0.1",
4943
"buffer": "^5.4.2",
50-
"callbackify": "^1.1.0",
5144
"cids": "~0.7.1",
5245
"debug": "^4.1.0",
53-
"err-code": "^2.0.0",
54-
"explain-error": "^1.0.4",
5546
"form-data": "^3.0.0",
5647
"ipfs-block": "~0.8.1",
57-
"ipfs-utils": "^0.4.0",
48+
"ipfs-utils": "^0.5.0",
5849
"ipld-dag-cbor": "~0.15.0",
5950
"ipld-dag-pb": "^0.18.1",
6051
"ipld-raw": "^4.0.0",
61-
"is-ipfs": "~0.6.1",
62-
"it-all": "^1.0.1",
6352
"it-glob": "0.0.7",
6453
"it-tar": "^1.1.1",
6554
"it-to-stream": "^0.1.1",
6655
"iterable-ndjson": "^1.1.0",
6756
"ky": "^0.15.0",
6857
"ky-universal": "^0.3.0",
6958
"merge-options": "^2.0.0",
70-
"multiaddr": "^6.0.6",
59+
"multiaddr": "^7.2.1",
7160
"multiaddr-to-uri": "^5.0.0",
7261
"multibase": "~0.6.0",
73-
"multicodec": "~0.5.1",
62+
"multicodec": "^0.5.6",
7463
"multihashes": "~0.4.14",
7564
"parse-duration": "^0.1.1",
76-
"peer-id": "~0.12.3",
77-
"peer-info": "~0.15.1",
78-
"promise-nodeify": "^3.0.1"
65+
"stream-to-it": "^0.2.0"
7966
},
8067
"devDependencies": {
8168
"aegir": "^20.4.1",
8269
"async": "^3.1.0",
8370
"browser-process-platform": "~0.1.1",
84-
"cross-env": "^6.0.0",
85-
"detect-node": "^2.0.4",
8671
"go-ipfs-dep": "^0.4.22",
87-
"interface-ipfs-core": "~0.125.0",
88-
"ipfsd-ctl": "^1.0.0",
89-
"ndjson": "^1.5.0",
90-
"nock": "^11.4.0",
91-
"pull-stream": "^3.6.14",
92-
"pump": "^3.0.0",
93-
"stream-equal": "^1.1.1"
72+
"interface-ipfs-core": "github:ipfs/interface-js-ipfs-core#refactor/async-iterables",
73+
"ipfsd-ctl": "^1.0.2",
74+
"it-all": "^1.0.1",
75+
"it-concat": "^1.0.0",
76+
"it-pipe": "^1.1.0",
77+
"nock": "^11.4.0"
9478
},
9579
"engines": {
9680
"node": ">=10.3.0",

Diff for: src/add-from-fs/index.browser.js

-3
This file was deleted.

Diff for: src/add-from-fs/index.js

-8
This file was deleted.

Diff for: src/add-from-url.js

-21
This file was deleted.

Diff for: src/add/index.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
'use strict'
22

33
const ndjson = require('iterable-ndjson')
4+
const CID = require('cids')
45
const configure = require('../lib/configure')
5-
const toIterable = require('../lib/stream-to-iterable')
6+
const toIterable = require('stream-to-it/source')
67
const { toFormData } = require('./form-data')
78
const toCamel = require('../lib/object-to-camel')
89

@@ -53,5 +54,5 @@ module.exports = configure(({ ky }) => {
5354
})
5455

5556
function toCoreInterface ({ name, hash, size }) {
56-
return { path: name, hash, size: parseInt(size) }
57+
return { path: name, cid: new CID(hash), size: parseInt(size) }
5758
}

Diff for: src/bitswap/index.js

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
'use strict'
22

3-
const callbackify = require('callbackify')
4-
5-
module.exports = (config) => ({
6-
wantlist: callbackify.variadic(require('./wantlist')(config)),
7-
stat: callbackify.variadic(require('./stat')(config)),
8-
unwant: callbackify.variadic(require('./unwant')(config))
3+
module.exports = config => ({
4+
wantlist: require('./wantlist')(config),
5+
stat: require('./stat')(config),
6+
unwant: require('./unwant')(config)
97
})

Diff for: src/block/index.js

+6-21
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,8 @@
11
'use strict'
22

3-
const nodeify = require('promise-nodeify')
4-
const callbackify = require('callbackify')
5-
const { collectify } = require('../lib/converters')
6-
7-
module.exports = config => {
8-
const rm = require('./rm-async-iterator')(config)
9-
10-
return {
11-
get: callbackify.variadic(require('./get')(config)),
12-
stat: callbackify.variadic(require('./stat')(config)),
13-
put: callbackify.variadic(require('./put')(config)),
14-
rm: (input, options, callback) => {
15-
if (typeof options === 'function') {
16-
callback = options
17-
options = {}
18-
}
19-
return nodeify(collectify(rm)(input, options), callback)
20-
},
21-
_rmAsyncIterator: rm
22-
}
23-
}
3+
module.exports = config => ({
4+
get: require('./get')(config),
5+
stat: require('./stat')(config),
6+
put: require('./put')(config),
7+
rm: require('./rm')(config)
8+
})

Diff for: src/block/rm-async-iterator.js renamed to src/block/rm.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
const CID = require('cids')
44
const ndjson = require('iterable-ndjson')
55
const configure = require('../lib/configure')
6-
const toIterable = require('../lib/stream-to-iterable')
6+
const toIterable = require('stream-to-it/source')
77
const toCamel = require('../lib/object-to-camel')
88

99
module.exports = configure(({ ky }) => {

Diff for: src/bootstrap/index.js

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
'use strict'
22

3-
const callbackify = require('callbackify')
4-
53
module.exports = config => ({
6-
add: callbackify.variadic(require('./add')(config)),
7-
rm: callbackify.variadic(require('./rm')(config)),
8-
list: callbackify.variadic(require('./list')(config))
4+
add: require('./add')(config),
5+
rm: require('./rm')(config),
6+
list: require('./list')(config)
97
})

Diff for: src/cat.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
const CID = require('cids')
44
const { Buffer } = require('buffer')
55
const configure = require('./lib/configure')
6-
const toIterable = require('./lib/stream-to-iterable')
6+
const toIterable = require('stream-to-it/source')
77

88
module.exports = configure(({ ky }) => {
99
return async function * cat (path, options) {

0 commit comments

Comments
 (0)