IPFSX

Designing a more ergonomic IPFS API

Lab Week 2018

How can IPFS API be more appealing to programmers?
Solving these can encourage developers to use IPFS
This is about improving developer experience
I created IPFSX as a playground for these experiments.
github.com/alanshaw/ipfsx
(It's currently just a mapping layer from the API I want to expose
to the API that IPFS or IPFS API exposes)
The biggest difference is making use of async iterators

What is async iterators❓

Firstly, Iterable is an interface for producing iterators
There are lots of things that are iterable in JS already:
arrays, objects, maps, sets.
You can use them in for (x of y) loops
An Iterable object exposes a function that creates an iterator
...which can be used to iterate over the items in an iterable.
An iterator implements the iterator protocol
i.e. it has a next method
which returns an object
{ done, value }
An async iterator instead returns a Promise
when you call next
You can use async iterables in
for await (x of y) loops
So we have a way to stream data using native language constructs!
🙌

No transpile!

We don't need to use Node.js streams or pull streams any more
...and it's easy to convert between pull streams and async iterables:
github.com/alanshaw/pull-stream-to-async-iterator github.com/alanshaw/async-iterator-to-pull-stream
...and Node.js streams are now async iterables:
github.com/nodejs/node/pull/17755
No more multi API add, addPullStream, addReadableStream
Smaller bundle, fewer dependencies!
Fewer tests to run (testing all combinations)
Easier to create an interface-ipfs-core compatible interface (fewer things to implement)
Less boilerplate converting between streams
Easier to error handle - only try catch
Better stack traces - not clipped at async boundaries

Enough!

What does it look like?

Create node


const ipfsx = require('ipfsx')
const IPFS = require('ipfs') // N.B. also works with ipfs-api!

const node = await ipfsx(new IPFS)
// node is READY to use
      

Add content


for await (const item of node.add('hello world!'))
  console.log(item.cid.toString())
      

"Shorthand"


const { cid } = await node.add('hello world!').first()
console.log(cid.toString())
      

Streaming add


const item = await node.add(fs.createReadStream(/*...*/)).first()
      

Add multiple


const adder = node.add([
  { path: 'root/file1', content: fs.createReadStream(/*...*/) },
  { path: 'root/file2', content: fs.createReadStream(/*...*/) }
])

for await (const res of adder)
  console.log(res.cid, res.path)
      

For fun, create your own iterator!


const generator = function * () {
  for (let i = 0; i < 10; i++)
    yield crypto.randomBytes()
}

const { cid } = await node.add(generator()).first()
console.log(cid)
      

For fun, create your own async iterator!


const generator = async function * () {
  for (let i = 0; i < 10; i++)
    yield new Promise(resolve => resolve(crypto.randomBytes()))
}

const { cid } = await node.add(generator()).first()
console.log(cid)
      

Cat it out!


let data = Buffer.alloc(0)
const cid = 'QmWGeRAEgtsHW3ec7U4qW2CyVy7eA2mFRVbk1nb24jFyks'

for await (const chunk of node.cat(cid, options))
  data = Buffer.concat([data, chunk])

console.log(data.toString()) // Hello, world!
      
Check out API docs and examples:
github.com/alanshaw/ipfsx/blob/master/API.md github.com/alanshaw/ipfsx/tree/master/examples
More background and rationale:
github.com/alanshaw/ipfsx#background github.com/alanshaw/ipfsx/blob/master/RATIONALE.md