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

refactor: make add only work on single items #3167

Merged
merged 10 commits into from
Jul 16, 2020

Conversation

achingbrain
Copy link
Member

@achingbrain achingbrain commented Jul 13, 2020

ipfs.add now returns single items only. Where a source would result in multiple items returned, only the last item is returned. As a first pass ipfs.add still takes multiple items but only accepting single items is documented and considered supported. In the future it may throw if multiple items are passed.

ipfs.addAll retains the previous behaviour of ipfs.add.

Examples:

const { cid, path, mode, mtime } = await ipfs.add('Hello world')
const { cid } = await ipfs.add(Uint8Array.from([0, 1, 2]))
const { cid } = await ipfs.add(fs.createReadStream('/path/to/file.txt'))
const { cid } = await ipfs.add({
  path: '/foo/bar/baz.txt',
  content: 'File content'
})

// Creates a DAG with multiple levels of directories.
// The returned cid is the CID for the root directory /foo

// You can retrieve the file content with an IPFS path
for await (const buf of ipfs.cat(`/ipfs/${cid}/bar/baz.txt`)) {
  ...
}

// Or get the CID of the nested file with ipfs.files.stat
const { cid: fileCid } = await ipfs.files.stat(`/ipfs/${cid}/bar/baz.txt`)

// or ipfs.dag.resolve
const { cid: fileCid } = await ipfs.dag.resolve(`/ipfs/${cid}/bar/baz.txt`)
// To have `/foo` included in the ipfs path, wrap it in a directory:
const { cid } = await ipfs.add({
  path: '/foo/bar/baz.txt',
  content: 'File content'
}, {
  wrapWithDirectory: true
})

for await (const buf of ipfs.cat(`/ipfs/${cid}/foo/bar/baz.txt`)) {
  ...
}

BREAKING CHANGES:

  • ipfs.add only works on single items - a Uint8Array, a String, an AsyncIterable etc
  • ipfs.addAll works on multiple items

…ultiple

`ipfs.add` returns single items only.  Where a source would result in multiple items returned, only the last item is returned.

`ipfs.addAll` retains the previous behaviour of `ipfs.add`.

Examples:

```javascript
const { cid, path, mode, mtime } = await ipfs.add('Hello world')
```

```javascript
const { cid } = await ipfs.add(Uint16Array.from([0, 1, 2]))
```

```javascript
const { cid } = await ipfs.add(fs.createReadStream('/path/to/file.txt'))
```

```javascript
const { cid } = await ipfs.add({
  path: '/foo/bar/baz.txt',
  content: 'File content'
})

// Creates a DAG with multiple levels of directories.
// The returned cid is the CID for the root directory /foo

// You can retrieve the file content with an IPFS path
for await (const buf of ipfs.cat(`/ipfs/${cid}/bar/baz.txt`)) {
  ...
}

// Or get the CID of the nested file with ipfs.files.stat
// (stat or something like it should really be a root level command)
const { cid: fileCid } = await ipfs.files.stat(`/ipfs/${cid}/bar/baz.txt`)
```

BREAKING CHANGES:

- `ipfs.add` only works on single items - a Uint8Array, a String, an AsyncIterable<Uint8Array> etc
- `ipfs.addAll` works on multiple items
@achingbrain achingbrain force-pushed the refactor/make-add-only-work-on-single-items branch from 1c8247c to 67f2b4e Compare July 13, 2020 21:51
@achingbrain achingbrain linked an issue Jul 15, 2020 that may be closed by this pull request
@achingbrain achingbrain requested a review from Gozala July 15, 2020 14:27
@achingbrain achingbrain marked this pull request as ready for review July 15, 2020 14:27
@achingbrain achingbrain changed the title Refactor: make add only work on single items refactor: make add only work on single items Jul 15, 2020
Copy link
Contributor

@Gozala Gozala left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have raised two things, but I'm leaving it up to you whether you think it's a right thing to do or not.

* `Uint8Array`
* `FileObject` (see below for definition)
* `Iterable<Uint8Array>`
* `AsyncIterable<Uint8Array>`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to go as far as drop Iterable<Uint8Array> and AsyncIterable<Uint8Array> from the list. I think convenience added here is no where near to the complexity it adds.

Iterables can trivially be turned into blobs:

ipfs.add(new Blob(chunks))

And both async and sync iterables could also trivially be turned into FileObjects

ipfs.add({ content: chunks })

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We support AsyncIterable<Uint8Array> in order to support adding node streams:

const fs = require('rs')

ipfs.add(fs.createReadStream('/path/to/file'))


module.exports = ({ addAll }) => {
return async function add (source, options) { // eslint-disable-line require-await
return last(addAll(source, options))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think just passing source through isn't a best option here. I would much rather see addAll([source]) all the single file inputs should work and passing non singular inputs should error anyway. Am I overlooking something here ?

Better yet would be to throw an error if multi-file input is passed as:

  1. It can explain clearly that addAll should be used in place of add.
  2. Error will occur before work is done as opposed to doing a work and then producing unclear error saying result isn't iterable.

If Iterable<Uint8Array> and AsyncIterable<Uint8Array> as suggested in other comment, it would be both trivial to implement and remove all the ambiguity.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Node streams throw a spanner in the works again. They are AsyncIterator<Uint8Array>, so addAll([source]) becomes addAll(Iterable<AsyncIterable<Uint8Array>>) which is not something we currently support. We could, I suppose, but so far no-one has asked for it.

I agree that add should reject multiple items, but this can be done in a follow up PR.

@@ -9,7 +9,7 @@ const { withTimeoutOption } = require('../../utils')
module.exports = ({ block, gcLock, preload, pin, options: constructorOptions }) => {
const isShardingEnabled = constructorOptions.EXPERIMENTAL && constructorOptions.EXPERIMENTAL.sharding

return withTimeoutOption(async function * add (source, options) {
return withTimeoutOption(async function * addAll (source, options) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to see a deprecation warning (via console.warn) for sources that represents a single file. So that we could remove support for that in future releases.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems weird to me that a new API would warn but the existing API would error (from the comment above) as you're going to break existing code and only inconvenience new code.

I think just make them both error, but this can happen in a follow up PR.

@achingbrain achingbrain merged commit 1760b89 into master Jul 16, 2020
@achingbrain achingbrain deleted the refactor/make-add-only-work-on-single-items branch July 16, 2020 10:55
SgtPooki referenced this pull request in ipfs/js-kubo-rpc-client Aug 18, 2022
`ipfs.add` now returns single items only.  Where a source would result in multiple items returned, only the last item is returned.  As a first pass `ipfs.add` still takes multiple items but only accepting single items is documented and considered supported.  In the future it may throw if multiple items are passed.

`ipfs.addAll` retains the previous behaviour of `ipfs.add`.

Examples:

```javascript
const { cid, path, mode, mtime } = await ipfs.add('Hello world')
```

```javascript
const { cid } = await ipfs.add(Uint8Array.from([0, 1, 2]))
```

```javascript
const { cid } = await ipfs.add(fs.createReadStream('/path/to/file.txt'))
```

```javascript
const { cid } = await ipfs.add({
  path: '/foo/bar/baz.txt',
  content: 'File content'
})

// Creates a DAG with multiple levels of directories.
// The returned cid is the CID for the root directory /foo

// You can retrieve the file content with an IPFS path
for await (const buf of ipfs.cat(`/ipfs/${cid}/bar/baz.txt`)) {
  ...
}

// Or get the CID of the nested file with ipfs.files.stat
const { cid: fileCid } = await ipfs.files.stat(`/ipfs/${cid}/bar/baz.txt`)

// or ipfs.dag.resolve
const { cid: fileCid } = await ipfs.dag.resolve(`/ipfs/${cid}/bar/baz.txt`)
```

```javascript
// To have `/foo` included in the ipfs path, wrap it in a directory:
const { cid } = await ipfs.add({
  path: '/foo/bar/baz.txt',
  content: 'File content'
}, {
  wrapWithDirectory: true
})

for await (const buf of ipfs.cat(`/ipfs/${cid}/foo/bar/baz.txt`)) {
  ...
}
```

BREAKING CHANGES:

- `ipfs.add` only works on single items - a Uint8Array, a String, an AsyncIterable<Uint8Array> etc
- `ipfs.addAll` works on multiple items
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Quirks of ipfs.add API
2 participants