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

Commit 04e3cf3

Browse files
authoredSep 29, 2021
fix: do not accept single items for ipfs.add (#3900)
The types allow passing single items to `ipfs.addAll` and multiple items to `ipfs.add`. Instead, only accept single items to `ipfs.add` and streams of item to `ipfs.addAll` and fail with a more helpful error message if you do not do this. BREAKING CHANGE: errors will now be thrown if multiple items are passed to `ipfs.add` or single items to `ipfs.addAll` (n.b. you can still pass a list of a single item to `ipfs.addAll`)
1 parent 5ddd0c5 commit 04e3cf3

36 files changed

+688
-146
lines changed
 

‎packages/interface-ipfs-core/src/add-all.js

+20
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,26 @@ export function testAddAll (factory, options) {
301301
await expect(all(ipfs.addAll(nonValid))).to.eventually.be.rejected()
302302
})
303303

304+
it('should fail when passed single file objects', async () => {
305+
const nonValid = { content: 'hello world' }
306+
307+
// @ts-expect-error nonValid is non valid
308+
await expect(all(ipfs.addAll(nonValid))).to.eventually.be.rejectedWith(/single item passed/)
309+
})
310+
311+
it('should fail when passed single strings', async () => {
312+
const nonValid = 'hello world'
313+
314+
await expect(all(ipfs.addAll(nonValid))).to.eventually.be.rejectedWith(/single item passed/)
315+
})
316+
317+
it('should fail when passed single buffers', async () => {
318+
const nonValid = uint8ArrayFromString('hello world')
319+
320+
// @ts-expect-error nonValid is non valid
321+
await expect(all(ipfs.addAll(nonValid))).to.eventually.be.rejectedWith(/single item passed/)
322+
})
323+
304324
it('should wrap content in a directory', async () => {
305325
const data = { path: 'testfile.txt', content: fixtures.smallFile.data }
306326

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

+7
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,13 @@ export function testAdd (factory, options) {
244244
await expect(ipfs.add(null)).to.eventually.be.rejected()
245245
})
246246

247+
it('should fail when passed multiple file objects', async () => {
248+
const nonValid = [{ content: 'hello' }, { content: 'world' }]
249+
250+
// @ts-expect-error nonValid is non valid
251+
await expect(ipfs.add(nonValid)).to.eventually.be.rejectedWith(/multiple items passed/)
252+
})
253+
247254
it('should wrap content in a directory', async () => {
248255
const data = { path: 'testfile.txt', content: fixtures.smallFile.data }
249256

‎packages/ipfs-cli/src/parser.js

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
import yargs from 'yargs'
32
import { ipfsPathHelp, disablePrinting } from './utils.js'
43
import { commandList } from './commands/index.js'

‎packages/ipfs-core-utils/package.json

+10-4
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,17 @@
3838
".": {
3939
"import": "./src/index.js"
4040
},
41-
"./files/normalise-input": {
42-
"import": "./src/files/normalise-input.js"
41+
"./files/normalise-input-single": {
42+
"import": "./src/files/normalise-input-single.js"
4343
},
44-
"./files/normalise-input.browser": {
45-
"import": "./src/files/normalise-input.browser.js"
44+
"./files/normalise-input-single.browser": {
45+
"import": "./src/files/normalise-input-single.browser.js"
46+
},
47+
"./files/normalise-input-multiple": {
48+
"import": "./src/files/normalise-input-multiple.js"
49+
},
50+
"./files/normalise-input-multiple.browser": {
51+
"import": "./src/files/normalise-input-multiple.browser.js"
4652
},
4753
"./files/normalise-content": {
4854
"import": "./src/files/normalise-content.js"

‎packages/ipfs-core-utils/src/files/normalise.js ‎packages/ipfs-core-utils/src/files/normalise-candidate-multiple.js

+16-29
Original file line numberDiff line numberDiff line change
@@ -14,33 +14,25 @@ import {
1414
} from 'ipfs-unixfs'
1515

1616
/**
17+
* @typedef {import('ipfs-core-types/src/utils').ImportCandidate} ImportCandidate
1718
* @typedef {import('ipfs-core-types/src/utils').ToContent} ToContent
1819
* @typedef {import('ipfs-unixfs-importer').ImportCandidate} ImporterImportCandidate
19-
* @typedef {import('ipfs-core-types/src/utils').ImportCandidate} ImportCandidate
2020
* @typedef {import('ipfs-core-types/src/utils').ImportCandidateStream} ImportCandidateStream
2121
*/
2222

2323
/**
24-
* @param {ImportCandidate | ImportCandidateStream} input
24+
* @param {ImportCandidateStream} input
2525
* @param {(content:ToContent) => Promise<AsyncIterable<Uint8Array>>} normaliseContent
2626
*/
2727
// eslint-disable-next-line complexity
28-
export async function * normalise (input, normaliseContent) {
29-
if (input === null || input === undefined) {
30-
throw errCode(new Error(`Unexpected input: ${input}`), 'ERR_UNEXPECTED_INPUT')
31-
}
32-
28+
export async function * normaliseCandidateMultiple (input, normaliseContent) {
3329
// String
34-
if (typeof input === 'string' || input instanceof String) {
35-
yield toFileObject(input.toString(), normaliseContent)
36-
return
37-
}
38-
3930
// Uint8Array|ArrayBuffer|TypedArray
4031
// Blob|File
41-
if (isBytes(input) || isBlob(input)) {
42-
yield toFileObject(input, normaliseContent)
43-
return
32+
// fs.ReadStream
33+
// @ts-expect-error _readableState is a property of a node fs.ReadStream
34+
if (typeof input === 'string' || input instanceof String || isBytes(input) || isBlob(input) || input._readableState) {
35+
throw errCode(new Error('Unexpected input: single item passed - if you are using ipfs.allAll, please use ipfs.add instead'), 'ERR_UNEXPECTED_INPUT')
4436
}
4537

4638
// Browser ReadableStream
@@ -67,42 +59,37 @@ export async function * normalise (input, normaliseContent) {
6759

6860
// (Async)Iterable<Number>
6961
// (Async)Iterable<Bytes>
70-
if (Number.isInteger(value) || isBytes(value)) {
71-
yield toFileObject(peekable, normaliseContent)
72-
return
62+
if (Number.isInteger(value)) {
63+
throw errCode(new Error('Unexpected input: single item passed - if you are using ipfs.allAll, please use ipfs.add instead'), 'ERR_UNEXPECTED_INPUT')
7364
}
7465

75-
// fs.ReadStream<Bytes>
66+
// (Async)Iterable<fs.ReadStream>
7667
if (value._readableState) {
77-
// @ts-ignore Node readable streams have a `.path` property so we need to pass it as the content
68+
// @ts-ignore Node fs.ReadStreams have a `.path` property so we need to pass it as the content
7869
yield * map(peekable, (/** @type {ImportCandidate} */ value) => toFileObject({ content: value }, normaliseContent))
7970
return
8071
}
8172

82-
// (Async)Iterable<Blob>
83-
// (Async)Iterable<String>
84-
// (Async)Iterable<{ path, content }>
85-
if (isFileObject(value) || isBlob(value) || typeof value === 'string' || value instanceof String) {
86-
yield * map(peekable, (/** @type {ImportCandidate} */ value) => toFileObject(value, normaliseContent))
73+
if (isBytes(value)) {
74+
yield toFileObject({ content: peekable }, normaliseContent)
8775
return
8876
}
8977

9078
// (Async)Iterable<(Async)Iterable<?>>
9179
// (Async)Iterable<ReadableStream<?>>
9280
// ReadableStream<(Async)Iterable<?>>
9381
// ReadableStream<ReadableStream<?>>
94-
if (value[Symbol.iterator] || value[Symbol.asyncIterator] || isReadableStream(value)) {
82+
if (isFileObject(value) || value[Symbol.iterator] || value[Symbol.asyncIterator] || isReadableStream(value) || isBlob(value)) {
9583
yield * map(peekable, (/** @type {ImportCandidate} */ value) => toFileObject(value, normaliseContent))
9684
return
9785
}
9886
}
9987

10088
// { path, content: ? }
101-
// Note: Detected _after_ (Async)Iterable<?> because Node.js streams have a
89+
// Note: Detected _after_ (Async)Iterable<?> because Node.js fs.ReadStreams have a
10290
// `path` property that passes this check.
10391
if (isFileObject(input)) {
104-
yield toFileObject(input, normaliseContent)
105-
return
92+
throw errCode(new Error('Unexpected input: single item passed - if you are using ipfs.allAll, please use ipfs.add instead'), 'ERR_UNEXPECTED_INPUT')
10693
}
10794

10895
throw errCode(new Error('Unexpected input: ' + typeof input), 'ERR_UNEXPECTED_INPUT')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import errCode from 'err-code'
2+
import browserStreamToIt from 'browser-readablestream-to-it'
3+
import itPeekable from 'it-peekable'
4+
import {
5+
isBytes,
6+
isBlob,
7+
isReadableStream,
8+
isFileObject
9+
} from './utils.js'
10+
import {
11+
parseMtime,
12+
parseMode
13+
} from 'ipfs-unixfs'
14+
15+
/**
16+
* @typedef {import('ipfs-core-types/src/utils').ToContent} ToContent
17+
* @typedef {import('ipfs-unixfs-importer').ImportCandidate} ImporterImportCandidate
18+
* @typedef {import('ipfs-core-types/src/utils').ImportCandidate} ImportCandidate
19+
* @typedef {import('ipfs-core-types/src/utils').ImportCandidateStream} ImportCandidateStream
20+
*/
21+
22+
/**
23+
* @param {ImportCandidate} input
24+
* @param {(content:ToContent) => Promise<AsyncIterable<Uint8Array>>} normaliseContent
25+
*/
26+
// eslint-disable-next-line complexity
27+
export async function * normaliseCandidateSingle (input, normaliseContent) {
28+
if (input === null || input === undefined) {
29+
throw errCode(new Error(`Unexpected input: ${input}`), 'ERR_UNEXPECTED_INPUT')
30+
}
31+
32+
// String
33+
if (typeof input === 'string' || input instanceof String) {
34+
yield toFileObject(input.toString(), normaliseContent)
35+
return
36+
}
37+
38+
// Uint8Array|ArrayBuffer|TypedArray
39+
// Blob|File
40+
if (isBytes(input) || isBlob(input)) {
41+
yield toFileObject(input, normaliseContent)
42+
return
43+
}
44+
45+
// Browser ReadableStream
46+
if (isReadableStream(input)) {
47+
input = browserStreamToIt(input)
48+
}
49+
50+
// Iterable<?>
51+
if (Symbol.iterator in input || Symbol.asyncIterator in input) {
52+
// @ts-ignore it's (async)iterable
53+
const peekable = itPeekable(input)
54+
55+
/** @type {any} value **/
56+
const { value, done } = await peekable.peek()
57+
58+
if (done) {
59+
// make sure empty iterators result in empty files
60+
yield { content: [] }
61+
return
62+
}
63+
64+
peekable.push(value)
65+
66+
// (Async)Iterable<Number>
67+
// (Async)Iterable<Bytes>
68+
// (Async)Iterable<String>
69+
if (Number.isInteger(value) || isBytes(value) || typeof value === 'string' || value instanceof String) {
70+
yield toFileObject(peekable, normaliseContent)
71+
return
72+
}
73+
74+
throw errCode(new Error('Unexpected input: multiple items passed - if you are using ipfs.add, please use ipfs.addAll instead'), 'ERR_UNEXPECTED_INPUT')
75+
}
76+
77+
// { path, content: ? }
78+
// Note: Detected _after_ (Async)Iterable<?> because Node.js fs.ReadStreams have a
79+
// `path` property that passes this check.
80+
if (isFileObject(input)) {
81+
yield toFileObject(input, normaliseContent)
82+
return
83+
}
84+
85+
throw errCode(new Error('Unexpected input: cannot convert "' + typeof input + '" into ImportCandidate'), 'ERR_UNEXPECTED_INPUT')
86+
}
87+
88+
/**
89+
* @param {ImportCandidate} input
90+
* @param {(content:ToContent) => Promise<AsyncIterable<Uint8Array>>} normaliseContent
91+
*/
92+
async function toFileObject (input, normaliseContent) {
93+
// @ts-ignore - Those properties don't exist on most input types
94+
const { path, mode, mtime, content } = input
95+
96+
/** @type {ImporterImportCandidate} */
97+
const file = {
98+
path: path || '',
99+
mode: parseMode(mode),
100+
mtime: parseMtime(mtime)
101+
}
102+
103+
if (content) {
104+
file.content = await normaliseContent(content)
105+
} else if (!path) { // Not already a file object with path or content prop
106+
// @ts-ignore - input still can be different ToContent
107+
file.content = await normaliseContent(input)
108+
}
109+
110+
return file
111+
}

‎packages/ipfs-core-utils/src/files/normalise-content.browser.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
} from './utils.js'
1010

1111
/**
12-
* @param {import('./normalise').ToContent} input
12+
* @param {import('ipfs-core-types/src/utils').ToContent} input
1313
*/
1414
export async function normaliseContent (input) {
1515
// Bytes

‎packages/ipfs-core-utils/src/files/normalise-content.js

+12-17
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,29 @@ import {
1212
} from './utils.js'
1313

1414
/**
15-
* @param {import('./normalise').ToContent} input
15+
* @template T
16+
* @param {T} thing
1617
*/
17-
export async function normaliseContent (input) {
18-
return toAsyncGenerator(input)
18+
async function * toAsyncIterable (thing) {
19+
yield thing
1920
}
2021

2122
/**
22-
* @param {import('./normalise').ToContent} input
23+
* @param {import('ipfs-core-types/src/utils').ToContent} input
2324
*/
24-
async function * toAsyncGenerator (input) {
25+
export async function normaliseContent (input) {
2526
// Bytes | String
2627
if (isBytes(input)) {
27-
yield toBytes(input)
28-
return
28+
return toAsyncIterable(toBytes(input))
2929
}
3030

3131
if (typeof input === 'string' || input instanceof String) {
32-
yield toBytes(input.toString())
33-
return
32+
return toAsyncIterable(toBytes(input.toString()))
3433
}
3534

3635
// Blob
3736
if (isBlob(input)) {
38-
yield * blobToIt(input)
39-
return
37+
return blobToIt(input)
4038
}
4139

4240
// Browser stream
@@ -54,22 +52,19 @@ async function * toAsyncGenerator (input) {
5452

5553
if (done) {
5654
// make sure empty iterators result in empty files
57-
yield * []
58-
return
55+
return toAsyncIterable(new Uint8Array(0))
5956
}
6057

6158
peekable.push(value)
6259

6360
// (Async)Iterable<Number>
6461
if (Number.isInteger(value)) {
65-
yield Uint8Array.from((await all(peekable)))
66-
return
62+
return toAsyncIterable(Uint8Array.from(await all(peekable)))
6763
}
6864

6965
// (Async)Iterable<Bytes|String>
7066
if (isBytes(value) || typeof value === 'string' || value instanceof String) {
71-
yield * map(peekable, toBytes)
72-
return
67+
return map(peekable, toBytes)
7368
}
7469
}
7570

Original file line numberDiff line numberDiff line change
@@ -1,25 +1,24 @@
11
import { normaliseContent } from './normalise-content.browser.js'
2-
import { normalise } from './normalise.js'
2+
import { normaliseCandidateMultiple } from './normalise-candidate-multiple.js'
33

44
/**
55
* @typedef {import('ipfs-core-types/src/utils').ImportCandidateStream} ImportCandidateStream
6-
* @typedef {import('ipfs-core-types/src/utils').ImportCandidate} ImportCandidate
76
* @typedef {import('ipfs-core-types/src/utils').BrowserImportCandidate} BrowserImportCandidate
87
*/
98

109
/**
11-
* Transforms any of the `ipfs.add` input types into
10+
* Transforms any of the `ipfs.addAll` input types into
1211
*
1312
* ```
1413
* AsyncIterable<{ path, mode, mtime, content: Blob }>
1514
* ```
1615
*
1716
* See https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/FILES.md#ipfsadddata-options
1817
*
19-
* @param {ImportCandidate | ImportCandidateStream} input
18+
* @param {ImportCandidateStream} input
2019
* @returns {AsyncGenerator<BrowserImportCandidate, void, undefined>}
2120
*/
2221
export function normaliseInput (input) {
23-
// @ts-ignore normaliseContent returns Blob and not AsyncIterator
24-
return normalise(input, normaliseContent)
22+
// @ts-expect-error browser normaliseContent returns a Blob not an AsyncIterable<Uint8Array>
23+
return normaliseCandidateMultiple(input, normaliseContent, true)
2524
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { normaliseContent } from './normalise-content.js'
2+
import { normaliseCandidateMultiple } from './normalise-candidate-multiple.js'
3+
4+
/**
5+
* @typedef {import('ipfs-core-types/src/utils').ImportCandidateStream} ImportCandidateStream
6+
*/
7+
8+
/**
9+
* Transforms any of the `ipfs.addAll` input types into
10+
*
11+
* ```
12+
* AsyncIterable<{ path, mode, mtime, content: AsyncIterable<Uint8Array> }>
13+
* ```
14+
*
15+
* See https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/FILES.md#ipfsadddata-options
16+
*
17+
* @param {ImportCandidateStream} input
18+
*/
19+
export function normaliseInput (input) {
20+
return normaliseCandidateMultiple(input, normaliseContent)
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { normaliseContent } from './normalise-content.browser.js'
2+
import { normaliseCandidateSingle } from './normalise-candidate-single.js'
3+
4+
/**
5+
* @typedef {import('ipfs-core-types/src/utils').ImportCandidate} ImportCandidate
6+
* @typedef {import('ipfs-core-types/src/utils').BrowserImportCandidate} BrowserImportCandidate
7+
*/
8+
9+
/**
10+
* Transforms any of the `ipfs.add` input types into
11+
*
12+
* ```
13+
* AsyncIterable<{ path, mode, mtime, content: Blob }>
14+
* ```
15+
*
16+
* See https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/FILES.md#ipfsadddata-options
17+
*
18+
* @param {ImportCandidate} input
19+
* @returns {BrowserImportCandidate}
20+
*/
21+
export function normaliseInput (input) {
22+
// @ts-expect-error browser normaliseContent returns a Blob not an AsyncIterable<Uint8Array>
23+
return normaliseCandidateSingle(input, normaliseContent)
24+
}

‎packages/ipfs-core-utils/src/files/normalise-input.js ‎packages/ipfs-core-utils/src/files/normalise-input-single.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { normaliseContent } from './normalise-content.js'
2-
import { normalise } from './normalise.js'
2+
import { normaliseCandidateSingle } from './normalise-candidate-single.js'
33

44
/**
5-
* @typedef {import('ipfs-core-types/src/utils').ImportCandidateStream} ImportCandidateStream
65
* @typedef {import('ipfs-core-types/src/utils').ImportCandidate} ImportCandidate
76
*/
87

@@ -15,8 +14,8 @@ import { normalise } from './normalise.js'
1514
*
1615
* See https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/FILES.md#ipfsadddata-options
1716
*
18-
* @param {ImportCandidate | ImportCandidateStream} input
17+
* @param {ImportCandidate} input
1918
*/
2019
export function normaliseInput (input) {
21-
return normalise(input, normaliseContent)
20+
return normaliseCandidateSingle(input, normaliseContent)
2221
}

‎packages/ipfs-core-utils/src/multipart-request.browser.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11

22
// Import browser version otherwise electron-renderer will end up with node
33
// version and fail.
4-
import { normaliseInput } from './files/normalise-input.browser.js'
4+
import { normaliseInput } from './files/normalise-input-multiple.browser.js'
55
import { modeToString } from './mode-to-string.js'
66

77
/**
88
* @typedef {import('ipfs-core-types/src/utils').ImportCandidateStream} ImportCandidateStream
9-
* @typedef {import('ipfs-core-types/src/utils').ImportCandidate} ImportCandidate
109
*/
1110

1211
/**
13-
* @param {ImportCandidateStream|ImportCandidate} source
12+
* @param {ImportCandidateStream} source
1413
* @param {AbortController} abortController
1514
* @param {Headers|Record<string, string>} [headers]
1615
*/

‎packages/ipfs-core-utils/src/multipart-request.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@ import { multipartRequest as multipartRequestBrowser } from './multipart-request
44
import { nanoid } from 'nanoid'
55

66
/**
7-
* @typedef {import('ipfs-core-types/src/utils').ImportCandidate} ImportCandidate
87
* @typedef {import('ipfs-core-types/src/utils').ImportCandidateStream} ImportCandidateStream
98
*/
109

1110
/**
12-
* @param {ImportCandidateStream|ImportCandidate} source
11+
* @param {ImportCandidateStream} source
1312
* @param {AbortController} abortController
1413
* @param {Headers|Record<string, string>} [headers]
1514
* @param {string} [boundary]

‎packages/ipfs-core-utils/src/multipart-request.node.js

+21-7
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,35 @@
1-
import { normaliseInput } from './files/normalise-input.js'
1+
import { normaliseInput } from './files/normalise-input-multiple.js'
22
import { nanoid } from 'nanoid'
33
import { modeToString } from './mode-to-string.js'
44
import mergeOpts from 'merge-options'
55
// @ts-expect-error no types
66
import toStream from 'it-to-stream'
77
import debug from 'debug'
8+
import itPeekable from 'it-peekable'
89

910
const merge = mergeOpts.bind({ ignoreUndefined: true })
1011
const log = debug('ipfs:core-utils:multipart-request')
1112

1213
/**
1314
* @typedef {import('ipfs-core-types/src/utils').ImportCandidateStream} ImportCandidateStream
14-
* @typedef {import('ipfs-core-types/src/utils').ImportCandidate} ImportCandidate
1515
*/
1616

1717
/**
18-
* @param {ImportCandidateStream|ImportCandidate} source
18+
* @param {ImportCandidateStream} source
1919
* @param {AbortController} abortController
2020
* @param {Headers|Record<string, string>} [headers]
2121
* @param {string} [boundary]
2222
*/
2323
export async function multipartRequest (source, abortController, headers = {}, boundary = `-----------------------------${nanoid()}`) {
2424
/**
25-
* @param {ImportCandidateStream|ImportCandidate} source
25+
* @param {ImportCandidateStream} source
2626
*/
2727
async function * streamFiles (source) {
2828
try {
2929
let index = 0
3030

31-
// @ts-ignore wrong input type for normaliseInput
32-
for await (const { content, path, mode, mtime } of normaliseInput(source)) {
31+
// @ts-ignore
32+
for await (const { content, path, mode, mtime } of source) {
3333
let fileSuffix = ''
3434
const type = content ? 'file' : 'dir'
3535

@@ -80,12 +80,26 @@ export async function multipartRequest (source, abortController, headers = {}, b
8080
}
8181
}
8282

83+
// peek at the first value in order to get the input stream moving
84+
// and to validate its contents.
85+
// We cannot do this in the `for await..of` in streamFiles due to
86+
// https://github.com/node-fetch/node-fetch/issues/753
87+
const peekable = itPeekable(normaliseInput(source))
88+
89+
/** @type {any} value **/
90+
const { value, done } = await peekable.peek()
91+
92+
if (!done) {
93+
peekable.push(value)
94+
}
95+
8396
return {
8497
parts: null,
8598
total: -1,
8699
headers: merge(headers, {
87100
'Content-Type': `multipart/form-data; boundary=${boundary}`
88101
}),
89-
body: await toStream(streamFiles(source))
102+
// @ts-expect-error normaliseInput returns unixfs importer import candidates
103+
body: toStream(streamFiles(peekable))
90104
}
91105
}

‎packages/ipfs-core-utils/test/files/normalise-input.spec.js ‎packages/ipfs-core-utils/test/files/normalise-input-multiple.spec.js

+87-46
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import blobToIt from 'blob-to-it'
55
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
66
import all from 'it-all'
77
import { File } from '@web-std/file'
8-
import { normaliseInput } from '../../src/files/normalise-input.js'
8+
import { normaliseInput } from '../../src/files/normalise-input-multiple.js'
99
import { isNode } from 'ipfs-utils/src/env.js'
1010
import resolve from 'aegir/utils/resolve.js'
1111

@@ -16,6 +16,7 @@ const NEWSTRING = () => new String('hello world') // eslint-disable-line no-new-
1616
const BUFFER = () => uint8ArrayFromString(STRING())
1717
const ARRAY = () => Array.from(BUFFER())
1818
const TYPEDARRAY = () => Uint8Array.from(ARRAY())
19+
const FILE = () => new File([BUFFER()], 'test-file.txt')
1920
/** @type {() => Blob} */
2021
let BLOB
2122

@@ -54,6 +55,14 @@ async function testContent (input) {
5455
await verifyNormalisation(result)
5556
}
5657

58+
/**
59+
* @param {*} input
60+
* @param {RegExp} message
61+
*/
62+
async function testFailure (input, message) {
63+
await expect(all(normaliseInput(input))).to.eventually.be.rejectedWith(message)
64+
}
65+
5766
/**
5867
* @template T
5968
* @param {T} thing
@@ -86,18 +95,18 @@ function browserReadableStreamOf (thing) {
8695
})
8796
}
8897

89-
describe('normalise-input', function () {
98+
describe('normalise-input-multiple', function () {
9099
/**
91100
* @param {() => any} content
92101
* @param {string} name
93-
* @param {boolean} isBytes
102+
* @param {{ acceptStream: boolean, acceptContentStream: boolean }} options
94103
*/
95-
function testInputType (content, name, isBytes) {
96-
it(name, async function () {
97-
await testContent(content())
104+
function testInputType (content, name, { acceptStream, acceptContentStream }) {
105+
it(`Failure ${name}`, async function () {
106+
await testFailure(content(), /single item passed/)
98107
})
99108

100-
if (isBytes) {
109+
if (acceptStream) {
101110
if (ReadableStream) {
102111
it(`ReadableStream<${name}>`, async function () {
103112
await testContent(browserReadableStreamOf(content()))
@@ -111,43 +120,35 @@ describe('normalise-input', function () {
111120
it(`AsyncIterable<${name}>`, async function () {
112121
await testContent(asyncIterableOf(content()))
113122
})
114-
}
115-
116-
it(`{ path: '', content: ${name} }`, async function () {
117-
await testContent({ path: '', content: content() })
118-
})
119-
120-
if (isBytes) {
123+
} else {
121124
if (ReadableStream) {
122-
it(`{ path: '', content: ReadableStream<${name}> }`, async function () {
123-
await testContent({ path: '', content: browserReadableStreamOf(content()) })
125+
it(`Failure ReadableStream<${name}>`, async function () {
126+
await testFailure(browserReadableStreamOf(content()), /single item passed/)
124127
})
125128
}
126129

127-
it(`{ path: '', content: Iterable<${name}> }`, async function () {
128-
await testContent({ path: '', content: iterableOf(content()) })
130+
it(`Failure Iterable<${name}>`, async function () {
131+
await testFailure(iterableOf(content()), /single item passed/)
129132
})
130133

131-
it(`{ path: '', content: AsyncIterable<${name}> }`, async function () {
132-
await testContent({ path: '', content: asyncIterableOf(content()) })
134+
it(`Failure AsyncIterable<${name}>`, async function () {
135+
await testFailure(asyncIterableOf(content()), /single item passed/)
133136
})
134137
}
135138

136-
if (ReadableStream) {
137-
it(`ReadableStream<${name}>`, async function () {
138-
await testContent(browserReadableStreamOf(content()))
139-
})
140-
}
139+
it(`Failure { path: '', content: ${name} }`, async function () {
140+
await testFailure({ path: '', content: content() }, /single item passed/)
141+
})
141142

142-
it(`Iterable<{ path: '', content: ${name} }`, async function () {
143+
it(`Iterable<{ path: '', content: ${name} }>`, async function () {
143144
await testContent(iterableOf({ path: '', content: content() }))
144145
})
145146

146-
it(`AsyncIterable<{ path: '', content: ${name} }`, async function () {
147+
it(`AsyncIterable<{ path: '', content: ${name} }>`, async function () {
147148
await testContent(asyncIterableOf({ path: '', content: content() }))
148149
})
149150

150-
if (isBytes) {
151+
if (acceptContentStream) {
151152
if (ReadableStream) {
152153
it(`Iterable<{ path: '', content: ReadableStream<${name}> }>`, async function () {
153154
await testContent(iterableOf({ path: '', content: browserReadableStreamOf(content()) }))
@@ -175,40 +176,85 @@ describe('normalise-input', function () {
175176
it(`AsyncIterable<{ path: '', content: AsyncIterable<${name}> }>`, async function () {
176177
await testContent(asyncIterableOf({ path: '', content: asyncIterableOf(content()) }))
177178
})
179+
} else {
180+
if (ReadableStream) {
181+
it(`Failure Iterable<{ path: '', content: ReadableStream<${name}> }>`, async function () {
182+
await testFailure(iterableOf({ path: '', content: browserReadableStreamOf(content()) }), /Unexpected input/)
183+
})
184+
}
185+
186+
it(`Failure Iterable<{ path: '', content: Iterable<${name}> }>`, async function () {
187+
await testFailure(iterableOf({ path: '', content: iterableOf(content()) }), /Unexpected input/)
188+
})
189+
190+
it(`Failure Iterable<{ path: '', content: AsyncIterable<${name}> }>`, async function () {
191+
await testFailure(iterableOf({ path: '', content: asyncIterableOf(content()) }), /Unexpected input/)
192+
})
193+
194+
if (ReadableStream) {
195+
it(`Failure AsyncIterable<{ path: '', content: ReadableStream<${name}> }>`, async function () {
196+
await testFailure(asyncIterableOf({ path: '', content: browserReadableStreamOf(content()) }), /Unexpected input/)
197+
})
198+
}
199+
200+
it(`Failure AsyncIterable<{ path: '', content: Iterable<${name}> }>`, async function () {
201+
await testFailure(asyncIterableOf({ path: '', content: iterableOf(content()) }), /Unexpected input/)
202+
})
203+
204+
it(`Failure AsyncIterable<{ path: '', content: AsyncIterable<${name}> }>`, async function () {
205+
await testFailure(asyncIterableOf({ path: '', content: asyncIterableOf(content()) }), /Unexpected input/)
206+
})
178207
}
179208
}
180209

181210
describe('String', () => {
182-
testInputType(STRING, 'String', true)
183-
testInputType(NEWSTRING, 'new String()', true)
211+
testInputType(STRING, 'String', {
212+
acceptStream: true,
213+
acceptContentStream: true
214+
})
215+
testInputType(NEWSTRING, 'new String()', {
216+
acceptStream: true,
217+
acceptContentStream: true
218+
})
184219
})
185220

186221
describe('Buffer', () => {
187-
testInputType(BUFFER, 'Buffer', true)
222+
testInputType(BUFFER, 'Buffer', {
223+
acceptStream: true,
224+
acceptContentStream: true
225+
})
188226
})
189227

190228
describe('Blob', () => {
191229
if (!Blob) {
192230
return
193231
}
194232

195-
testInputType(BLOB, 'Blob', false)
233+
testInputType(BLOB, 'Blob', {
234+
acceptStream: true,
235+
acceptContentStream: false
236+
})
196237
})
197238

198239
describe('@web-std/file', () => {
199-
it('normalizes File input', async () => {
200-
const FILE = new File([BUFFER()], 'test-file.txt')
201-
202-
await testContent(FILE)
240+
testInputType(FILE, 'File', {
241+
acceptStream: true,
242+
acceptContentStream: false
203243
})
204244
})
205245

206246
describe('Iterable<Number>', () => {
207-
testInputType(ARRAY, 'Iterable<Number>', false)
247+
testInputType(ARRAY, 'Iterable<Number>', {
248+
acceptStream: true,
249+
acceptContentStream: false
250+
})
208251
})
209252

210253
describe('TypedArray', () => {
211-
testInputType(TYPEDARRAY, 'TypedArray', true)
254+
testInputType(TYPEDARRAY, 'TypedArray', {
255+
acceptStream: true,
256+
acceptContentStream: true
257+
})
212258
})
213259

214260
if (isNode) {
@@ -226,14 +272,9 @@ describe('normalise-input', function () {
226272
return fs.createReadStream(path)
227273
}
228274

229-
testInputType(NODEFSREADSTREAM, 'Node fs.ReadStream', false)
230-
231-
it('Iterable<Node fs.ReadStream>', async function () {
232-
await testContent(iterableOf(NODEFSREADSTREAM()))
233-
})
234-
235-
it('AsyncIterable<Node fs.ReadStream>', async function () {
236-
await testContent(asyncIterableOf(NODEFSREADSTREAM()))
275+
testInputType(NODEFSREADSTREAM, 'Node fs.ReadStream', {
276+
acceptStream: true,
277+
acceptContentStream: false
237278
})
238279
})
239280
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
/* eslint-env mocha */
2+
3+
import { expect } from 'aegir/utils/chai.js'
4+
import blobToIt from 'blob-to-it'
5+
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
6+
import all from 'it-all'
7+
import { File } from '@web-std/file'
8+
import { normaliseInput } from '../../src/files/normalise-input-single.js'
9+
import { isNode } from 'ipfs-utils/src/env.js'
10+
import resolve from 'aegir/utils/resolve.js'
11+
12+
const { Blob, ReadableStream } = globalThis
13+
14+
const STRING = () => 'hello world'
15+
const NEWSTRING = () => new String('hello world') // eslint-disable-line no-new-wrappers
16+
const BUFFER = () => uint8ArrayFromString(STRING())
17+
const ARRAY = () => Array.from(BUFFER())
18+
const TYPEDARRAY = () => Uint8Array.from(ARRAY())
19+
const FILE = () => new File([BUFFER()], 'test-file.txt')
20+
/** @type {() => Blob} */
21+
let BLOB
22+
23+
if (Blob) {
24+
BLOB = () => new Blob([
25+
STRING()
26+
])
27+
}
28+
29+
/**
30+
* @param {import('ipfs-unixfs-importer').ImportCandidate[]} input
31+
*/
32+
async function verifyNormalisation (input) {
33+
expect(input.length).to.equal(1)
34+
expect(input[0].path).to.equal('')
35+
36+
let content = input[0].content
37+
38+
if (Blob && content instanceof Blob) {
39+
content = blobToIt(content)
40+
}
41+
42+
if (!content || content instanceof Uint8Array) {
43+
throw new Error('Content expected')
44+
}
45+
46+
await expect(all(content)).to.eventually.deep.equal([BUFFER()])
47+
}
48+
49+
/**
50+
* @param {*} input
51+
*/
52+
async function testContent (input) {
53+
const result = await all(normaliseInput(input))
54+
55+
await verifyNormalisation(result)
56+
}
57+
58+
/**
59+
* @param {*} input
60+
* @param {RegExp} message
61+
*/
62+
async function testFailure (input, message) {
63+
await expect(all(normaliseInput(input))).to.eventually.be.rejectedWith(message)
64+
}
65+
66+
/**
67+
* @template T
68+
* @param {T} thing
69+
* @returns {T[]}
70+
*/
71+
function iterableOf (thing) {
72+
return [thing]
73+
}
74+
75+
/**
76+
* @template T
77+
* @param {T} thing
78+
* @returns {AsyncIterable<T>}
79+
*/
80+
function asyncIterableOf (thing) {
81+
return (async function * () { // eslint-disable-line require-await
82+
yield thing
83+
}())
84+
}
85+
86+
/**
87+
* @param {*} thing
88+
*/
89+
function browserReadableStreamOf (thing) {
90+
return new ReadableStream({
91+
start (controller) {
92+
controller.enqueue(thing)
93+
controller.close()
94+
}
95+
})
96+
}
97+
98+
describe('normalise-input-single', function () {
99+
/**
100+
* @param {() => any} content
101+
* @param {string} name
102+
* @param {{ acceptStream: boolean }} options
103+
*/
104+
function testInputType (content, name, { acceptStream }) {
105+
it(name, async function () {
106+
await testContent(content())
107+
})
108+
109+
if (acceptStream) {
110+
if (ReadableStream) {
111+
it(`ReadableStream<${name}>`, async function () {
112+
await testContent(browserReadableStreamOf(content()))
113+
})
114+
}
115+
116+
it(`Iterable<${name}>`, async function () {
117+
await testContent(iterableOf(content()))
118+
})
119+
120+
it(`AsyncIterable<${name}>`, async function () {
121+
await testContent(asyncIterableOf(content()))
122+
})
123+
} else {
124+
if (ReadableStream) {
125+
it(`Failure ReadableStream<${name}>`, async function () {
126+
await testFailure(browserReadableStreamOf(content()), /Unexpected input/)
127+
})
128+
}
129+
130+
it(`Failure Iterable<${name}>`, async function () {
131+
await testFailure(iterableOf(content()), /Unexpected input/)
132+
})
133+
134+
it(`Failure AsyncIterable<${name}>`, async function () {
135+
await testFailure(asyncIterableOf(content()), /Unexpected input/)
136+
})
137+
}
138+
139+
it(`{ path: '', content: ${name} }`, async function () {
140+
await testContent({ path: '', content: content() })
141+
})
142+
143+
if (acceptStream) {
144+
if (ReadableStream) {
145+
it(`{ path: '', content: ReadableStream<${name}> }`, async function () {
146+
await testContent({ path: '', content: browserReadableStreamOf(content()) })
147+
})
148+
}
149+
150+
it(`{ path: '', content: Iterable<${name}> }`, async function () {
151+
await testContent({ path: '', content: iterableOf(content()) })
152+
})
153+
154+
it(`{ path: '', content: AsyncIterable<${name}> }`, async function () {
155+
await testContent({ path: '', content: asyncIterableOf(content()) })
156+
})
157+
}
158+
159+
if (ReadableStream) {
160+
if (acceptStream) {
161+
it(`ReadableStream<${name}>`, async function () {
162+
await testContent(browserReadableStreamOf(content()))
163+
})
164+
} else {
165+
it(`Failure ReadableStream<${name}>`, async function () {
166+
await testFailure(browserReadableStreamOf(content()), /multiple items passed/)
167+
})
168+
}
169+
}
170+
171+
it(`Failure Iterable<{ path: '', content: ${name} }>`, async function () {
172+
await testFailure(iterableOf({ path: '', content: content() }), /multiple items passed/)
173+
})
174+
175+
it(`Failure AsyncIterable<{ path: '', content: ${name} }>`, async function () {
176+
await testFailure(asyncIterableOf({ path: '', content: content() }), /multiple items passed/)
177+
})
178+
179+
if (acceptStream) {
180+
if (ReadableStream) {
181+
it(`Failure Iterable<{ path: '', content: ReadableStream<${name}> }>`, async function () {
182+
await testFailure(iterableOf({ path: '', content: browserReadableStreamOf(content()) }), /multiple items passed/)
183+
})
184+
}
185+
186+
it(`Failure Iterable<{ path: '', content: Iterable<${name}> }>`, async function () {
187+
await testFailure(iterableOf({ path: '', content: iterableOf(content()) }), /multiple items passed/)
188+
})
189+
190+
it(`Failure Iterable<{ path: '', content: AsyncIterable<${name}> }>`, async function () {
191+
await testFailure(iterableOf({ path: '', content: asyncIterableOf(content()) }), /multiple items passed/)
192+
})
193+
194+
if (ReadableStream) {
195+
it(`Failure AsyncIterable<{ path: '', content: ReadableStream<${name}> }>`, async function () {
196+
await testFailure(asyncIterableOf({ path: '', content: browserReadableStreamOf(content()) }), /multiple items passed/)
197+
})
198+
}
199+
200+
it(`Failure AsyncIterable<{ path: '', content: Iterable<${name}> }>`, async function () {
201+
await testFailure(asyncIterableOf({ path: '', content: iterableOf(content()) }), /multiple items passed/)
202+
})
203+
204+
it(`Failure AsyncIterable<{ path: '', content: AsyncIterable<${name}> }>`, async function () {
205+
await testFailure(asyncIterableOf({ path: '', content: asyncIterableOf(content()) }), /multiple items passed/)
206+
})
207+
}
208+
}
209+
210+
describe('String', () => {
211+
testInputType(STRING, 'String', {
212+
acceptStream: true
213+
})
214+
testInputType(NEWSTRING, 'new String()', {
215+
acceptStream: true
216+
})
217+
})
218+
219+
describe('Buffer', () => {
220+
testInputType(BUFFER, 'Buffer', {
221+
acceptStream: true
222+
})
223+
})
224+
225+
describe('Blob', () => {
226+
if (!Blob) {
227+
return
228+
}
229+
230+
testInputType(BLOB, 'Blob', {
231+
acceptStream: false
232+
})
233+
})
234+
235+
describe('@web-std/file', () => {
236+
testInputType(FILE, 'File', {
237+
acceptStream: false
238+
})
239+
})
240+
241+
describe('Iterable<Number>', () => {
242+
testInputType(ARRAY, 'Iterable<Number>', {
243+
acceptStream: false
244+
})
245+
})
246+
247+
describe('TypedArray', () => {
248+
testInputType(TYPEDARRAY, 'TypedArray', {
249+
acceptStream: true
250+
})
251+
})
252+
253+
if (isNode) {
254+
/** @type {import('fs')} */
255+
let fs
256+
257+
before(async () => {
258+
fs = await import('fs')
259+
})
260+
261+
describe('Node fs.ReadStream', () => {
262+
const NODEFSREADSTREAM = () => {
263+
const path = resolve('test/fixtures/file.txt', 'ipfs-core-utils')
264+
265+
return fs.createReadStream(path)
266+
}
267+
268+
testInputType(NODEFSREADSTREAM, 'Node fs.ReadStream', {
269+
acceptStream: false
270+
})
271+
})
272+
}
273+
})
+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
import './files/format-mode.spec.js'
33
import './files/format-mtime.spec.js'
4-
import './files/normalise-input.spec.js'
4+
import './files/normalise-input-multiple.spec.js'
5+
import './files/normalise-input-single.spec.js'
56
import './pins/normalise-input.spec.js'

‎packages/ipfs-core/src/components/add-all/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { importer } from 'ipfs-unixfs-importer'
2-
import { normaliseInput } from 'ipfs-core-utils/files/normalise-input'
2+
import { normaliseInput } from 'ipfs-core-utils/files/normalise-input-multiple'
33
import { parseChunkerString } from './utils.js'
44
import { pipe } from 'it-pipe'
55
import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'

‎packages/ipfs-core/src/components/add.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import last from 'it-last'
2+
import { normaliseInput } from 'ipfs-core-utils/files/normalise-input-single'
23

34
/**
45
* @param {Object} context
@@ -10,7 +11,7 @@ export function createAdd ({ addAll }) {
1011
*/
1112
async function add (entry, options = {}) {
1213
// @ts-ignore TODO: https://github.com/ipfs/js-ipfs/issues/3290
13-
const result = await last(addAll(entry, options))
14+
const result = await last(addAll(normaliseInput(entry), options))
1415
// Note this should never happen as `addAll` should yield at least one item
1516
// but to satisfy type checker we perfom this check and for good measure
1617
// throw an error in case it does happen.

‎packages/ipfs-core/src/version.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11

2-
export const ipfsCore = '0.11.0'
2+
export const ipfsCore = '0.11.1'
33
export const commit = ''
4-
export const interfaceIpfsCore = '^0.151.0'
4+
export const interfaceIpfsCore = '^0.151.1'

‎packages/ipfs-grpc-client/src/core-api/add-all.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { normaliseInput } from 'ipfs-core-utils/files/normalise-input'
1+
import { normaliseInput } from 'ipfs-core-utils/files/normalise-input-multiple'
22
import { CID } from 'multiformats/cid'
33
import { bidiToDuplex } from '../utils/bidi-to-duplex.js'
44
import { withTimeoutOption } from 'ipfs-core-utils/with-timeout-option'

‎packages/ipfs-http-client/src/add.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createAddAll } from './add-all.js'
22
import last from 'it-last'
33
import { configure } from './lib/configure.js'
4+
import { normaliseInput } from 'ipfs-core-utils/files/normalise-input-single'
45

56
/**
67
* @typedef {import('./types').HTTPClientExtraOptions} HTTPClientExtraOptions
@@ -18,7 +19,7 @@ export function createAdd (options) {
1819
*/
1920
async function add (input, options = {}) {
2021
// @ts-ignore - last may return undefined if source is empty
21-
return await last(all(input, options))
22+
return await last(all(normaliseInput(input), options))
2223
}
2324
return add
2425
})(options)

‎packages/ipfs-http-client/src/block/put.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const createPut = configure(api => {
2525
signal: signal,
2626
searchParams: toUrlSearchParams(options),
2727
...(
28-
await multipartRequest(data, controller, options.headers)
28+
await multipartRequest([data], controller, options.headers)
2929
)
3030
})
3131
res = await response.json()

‎packages/ipfs-http-client/src/config/replace.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const createReplace = configure(api => {
2323
signal,
2424
searchParams: toUrlSearchParams(options),
2525
...(
26-
await multipartRequest(uint8ArrayFromString(JSON.stringify(config)), controller, options.headers)
26+
await multipartRequest([uint8ArrayFromString(JSON.stringify(config))], controller, options.headers)
2727
)
2828
})
2929

‎packages/ipfs-http-client/src/dag/put.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export const createPut = (codecs, options) => {
3939
signal,
4040
searchParams: toUrlSearchParams(settings),
4141
...(
42-
await multipartRequest(serialized, controller, settings.headers)
42+
await multipartRequest([serialized], controller, settings.headers)
4343
)
4444
})
4545
const data = await res.json()

‎packages/ipfs-http-client/src/dht/put.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const createPut = configure(api => {
2828
...options
2929
}),
3030
...(
31-
await multipartRequest(value, controller, options.headers)
31+
await multipartRequest([value], controller, options.headers)
3232
)
3333
})
3434

‎packages/ipfs-http-client/src/files/write.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ export const createWrite = configure(api => {
2929
...options
3030
}),
3131
...(
32-
await multipartRequest({
32+
await multipartRequest([{
3333
content: input,
3434
path: 'arg',
3535
mode: modeToString(options.mode),
3636
mtime: parseMtime(options.mtime)
37-
}, controller, options.headers)
37+
}], controller, options.headers)
3838
)
3939
})
4040

‎packages/ipfs-http-client/src/object/patch/append-data.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const createAppendData = configure(api => {
2626
...options
2727
}),
2828
...(
29-
await multipartRequest(data, controller, options.headers)
29+
await multipartRequest([data], controller, options.headers)
3030
)
3131
})
3232

‎packages/ipfs-http-client/src/object/patch/set-data.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const createSetData = configure(api => {
2828
...options
2929
}),
3030
...(
31-
await multipartRequest(data, controller, options.headers)
31+
await multipartRequest([data], controller, options.headers)
3232
)
3333
})
3434

‎packages/ipfs-http-client/src/pubsub/publish.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const createPublish = configure(api => {
2727
signal,
2828
searchParams,
2929
...(
30-
await multipartRequest(data, controller, options.headers)
30+
await multipartRequest([data], controller, options.headers)
3131
)
3232
})
3333

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11

2-
export const ipfsHttpClient = '^53.0.0'
2+
export const ipfsHttpClient = '^53.0.1'

‎packages/ipfs-message-port-client/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,11 @@
4646
},
4747
"dependencies": {
4848
"browser-readablestream-to-it": "^1.0.1",
49+
"err-code": "^3.0.1",
4950
"ipfs-core-types": "^0.8.1",
5051
"ipfs-message-port-protocol": "^0.10.1",
5152
"ipfs-unixfs": "^6.0.3",
53+
"it-peekable": "^1.0.2",
5254
"multiformats": "^9.4.1"
5355
},
5456
"devDependencies": {

‎packages/ipfs-message-port-client/src/core.js

+50-7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
parseMode,
1717
parseMtime
1818
} from 'ipfs-unixfs'
19+
import itPeekable from 'it-peekable'
20+
import errCode from 'err-code'
1921

2022
/**
2123
* @template T
@@ -104,7 +106,7 @@ CoreClient.prototype.add = async function add (input, options = {}) {
104106

105107
const result = await this.remote.add({
106108
...options,
107-
input: encodeAddInput(input, transfer),
109+
input: await encodeAddInput(input, transfer),
108110
progress: undefined,
109111
progressCallback,
110112
transfer,
@@ -183,9 +185,9 @@ const identity = (v) => v
183185
*
184186
* @param {ImportCandidate} input
185187
* @param {Transferable[]} transfer
186-
* @returns {EncodedAddInput}
188+
* @returns {Promise<EncodedAddInput>}
187189
*/
188-
const encodeAddInput = (input, transfer) => {
190+
const encodeAddInput = async (input, transfer) => {
189191
// We want to get a Blob as input. If we got it we're set.
190192
if (input instanceof Blob) {
191193
return input
@@ -201,13 +203,17 @@ const encodeAddInput = (input, transfer) => {
201203
// be encoded via own specific encoder.
202204
const iterable = asIterable(input)
203205
if (iterable) {
204-
return encodeIterable(iterable, encodeIterableContent, transfer)
206+
return encodeIterable(
207+
await ensureIsByteStream(iterable),
208+
encodeIterableContent,
209+
transfer
210+
)
205211
}
206212

207213
const asyncIterable = asAsyncIterable(input)
208214
if (asyncIterable) {
209215
return encodeIterable(
210-
asyncIterable,
216+
await ensureIsByteStream(asyncIterable),
211217
encodeAsyncIterableContent,
212218
transfer
213219
)
@@ -216,7 +222,7 @@ const encodeAddInput = (input, transfer) => {
216222
const readableStream = asReadableStream(input)
217223
if (readableStream) {
218224
return encodeIterable(
219-
iterateReadableStream(readableStream),
225+
await ensureIsByteStream(iterateReadableStream(readableStream)),
220226
encodeAsyncIterableContent,
221227
transfer
222228
)
@@ -232,7 +238,7 @@ const encodeAddInput = (input, transfer) => {
232238
}
233239

234240
/**
235-
* Encodes input passed to the `ipfs.add` via the best possible strategy for the
241+
* Encodes input passed to the `ipfs.addAll` via the best possible strategy for the
236242
* given input.
237243
*
238244
* @param {ImportCandidateStream} input
@@ -448,3 +454,40 @@ const asFileObject = (input) => {
448454
return null
449455
}
450456
}
457+
458+
/**
459+
* @template T
460+
* @param {AsyncIterable<T> | Iterable<T>} input
461+
* @returns {Promise<AsyncIterable<T> | Iterable<T>>}
462+
*/
463+
const ensureIsByteStream = async (input) => {
464+
// @ts-ignore it's (async)iterable
465+
const peekable = itPeekable(input)
466+
467+
/** @type {any} value **/
468+
const { value, done } = await peekable.peek()
469+
470+
if (done) {
471+
// make sure empty iterators result in empty files
472+
return []
473+
}
474+
475+
peekable.push(value)
476+
477+
// (Async)Iterable<Number>
478+
// (Async)Iterable<Bytes>
479+
// (Async)Iterable<String>
480+
if (Number.isInteger(value) || isBytes(value) || typeof value === 'string' || value instanceof String) {
481+
return peekable
482+
}
483+
484+
throw errCode(new Error('Unexpected input: multiple items passed - if you are using ipfs.add, please use ipfs.addAll instead'), 'ERR_UNEXPECTED_INPUT')
485+
}
486+
487+
/**
488+
* @param {any} obj
489+
* @returns {obj is ArrayBufferView|ArrayBuffer}
490+
*/
491+
function isBytes (obj) {
492+
return ArrayBuffer.isView(obj) || obj instanceof ArrayBuffer
493+
}

‎packages/ipfs/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
"ipfs-client": "^0.7.1",
9090
"ipfs-core-types": "^0.8.1",
9191
"ipfs-http-client": "^53.0.1",
92-
"ipfs-interop": "^7.0.1",
92+
"ipfs-interop": "^7.0.2",
9393
"ipfs-utils": "^9.0.2",
9494
"ipfsd-ctl": "^10.0.4",
9595
"iso-url": "^1.0.0",

‎packages/ipfs/src/package.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11

22
export const name = 'ipfs'
3-
export const version = '0.59.0'
3+
export const version = '0.59.1'
44
export const node = '>=14.0.0'

0 commit comments

Comments
 (0)
This repository has been archived.