diff --git a/README.md b/README.md index 3f2f16e8c..178cde462 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,311 @@ -# interface-ipfs-core -A test suite and interface you can use to implement a IPFS core interface. +interface-ipfs-core +=================== + +[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) +[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) + +> A test suite and interface you can use to implement the IPFS Core API. + +The primary goal of this module is to define and ensure that both IPFS core implementations and their respective HTTP client libraries offer the same interface, so that developers can quickly change between a local and a remote node without having to change their applications. In addition to the definition of the expected interface, this module offers a suite of tests that can be run in order to check if the interface is used as described. + +The API is presented with both Node.js and Go primitives, however, there is not actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks. + +# Modules that implement the interface + +- [JavaScript IPFS implementation](https://github.com/ipfs/js-ipfs) +- [JavaScript ipfs-api](https://github.com/ipfs/js-ipfs-api) +- Soon, go-ipfs, go-ipfs-api, java-ipfs-api, python-ipfs-api and others will implement it as well. + +Send a PR to add a new one if you happen to find or write one. + +# Badge + +Include this badge in your readme if you make a new module that uses interface-stream-muxer API. + +![](/img/badge.png) + +# How to use the battery tests + +## Node.js + +Install interface-ipfs-core as one of the dependencies of your project and as a test file, using `mocha` (for Node.js) or a test runner with compatible API, do: + +``` +var test = require('interface-ipfs-core') + +var common = { + setup: function (cb) { + cb(null, yourIPFSInstance) + }, + teardown: function (cb) { + cb() + } +} + +// use all of the test suits +test.all(common) +``` + +## Go + +> WIP + +# API + +A valid (read: that follows this interface) IPFS core implementation, must expose the following API. + +## Object + +### `object.new` + +> Create a new MerkleDAG node, using a specific layout. Caveat: So far, only UnixFS object layouts are supported. + +##### `Go` **WIP** + +##### `JavaScript` - ipfs.object.new(layout, [callback]) + +`layout` is the MerkleDAG node type, it can be: `null`, `'unixfs-dir'`, `'unixfs-raw'`, `'unixfs-file'`, `'unixfs-metadata'`, `'unixfs-symlink'`. + +`callback` must follow `function (err, node) {}` signature, where `err` is an error if the operation was not successful and `node` is a MerkleDAG node of the type [DAGNode](https://github.com/vijayee/js-ipfs-merkle-dag/blob/master/src/dag-node.js) + +If no `callback` is passed, a promise is returned. + + + + + +### `object.put` + +> Store an MerkleDAG node. + +##### `Go` **WIP** + +##### `JavaScript` - ipfs.object.put(obj, [options, callback]) + +`obj` is the MerkleDAG Node to be stored. Can of type: + +- Object, with format `{ Data: , Links: [] }` +- Buffer, requiring that the encoding is specified on the encoding +- [DAGNode](https://github.com/vijayee/js-ipfs-merkle-dag/blob/master/src/dag-node.js). If no encoding is specified, Buffer is treated as the Data field. + +`options` is a optional argument of type object, that can contain the following properties: + +- `enc`, the encoding of the Buffer (json, yml, etc), if passed a Buffer. + +`callback` must follow `function (err, node) {}` signature, where `err` is an error if the operation was not successful and `node` is a MerkleDAG node of the type [DAGNode](https://github.com/vijayee/js-ipfs-merkle-dag/blob/master/src/dag-node.js) + +If no `callback` is passed, a promise is returned. + + + + + +### `object.get` + +> Fetch a MerkleDAG node + +##### `Go` **WIP** + +##### `JavaScript` - ipfs.object.get(multihash, [options, callback]) + +`multihash` is a [multihash]() which can be passed as: + +- Buffer, the raw Buffer of the multihash (or of and encoded version) +- String, the toString version of the multihash (or of an encoded version) + +`options` is a optional argument of type object, that can contain the following properties: + +- `enc`, the encoding of multihash (base58, base64, etc), if any. + +`callback` must follow `function (err, node) {}` signature, where `err` is an error if the operation was not successful and `node` is a MerkleDAG node of the type [DAGNode](https://github.com/vijayee/js-ipfs-merkle-dag/blob/master/src/dag-node.js) + +If no `callback` is passed, a promise is returned. + +### `object.data` + +> Returns the Data field of an object + +##### `Go` **WIP** + +##### `JavaScript` - ipfs.object.data(multihash, [options, callback]) +`multihash` is a [multihash]() which can be passed as: + +- Buffer, the raw Buffer of the multihash (or of and encoded version) +- String, the toString version of the multihash (or of an encoded version) + +`options` is a optional argument of type object, that can contain the following properties: + +- `enc`, the encoding of multihash (base58, base64, etc), if any. + +`callback` must follow `function (err, data) {}` signature, where `err` is an error if the operation was not successful and `data` is a Buffer with the data that the MerkleDAG node contained. + +If no `callback` is passed, a promise is returned. + +### `object.links` + +> Returns the Links field of an object + +##### `Go` **WIP** + +##### `JavaScript` - ipfs.object.links(multihash, [options, callback]) + +`multihash` is a [multihash]() which can be passed as: + +- Buffer, the raw Buffer of the multihash (or of and encoded version) +- String, the toString version of the multihash (or of an encoded version) + +`options` is a optional argument of type object, that can contain the following properties: + +- `enc`, the encoding of multihash (base58, base64, etc), if any. + +`callback` must follow `function (err, links) {}` signature, where `err` is an error if the operation was not successful and `links` is an Array of [DAGLink](https://github.com/vijayee/js-ipfs-merkle-dag/blob/master/src/dag-node.js#L199-L203) objects. + +If no `callback` is passed, a promise is returned. + + + + + +### `object.stat` + +> Returns stats about an Object + +##### `Go` **WIP** + +##### `JavaScript` - ipfs.object.stat(multihash, [options, callback]) + +`multihash` is a [multihash]() which can be passed as: + +- Buffer, the raw Buffer of the multihash (or of and encoded version) +- String, the toString version of the multihash (or of an encoded version) + +`options` is a optional argument of type object, that can contain the following properties: + +- `enc`, the encoding of multihash (base58, base64, etc), if any. + +`callback` must follow `function (err, stats) {}` signature, where `err` is an error if the operation was not successful and `stats` is an Object with following format: + +```JavaScript +{ + Hash: 'QmPTkMuuL6PD8L2SwTwbcs1NPg14U8mRzerB1ZrrBrkSDD', + NumLinks: 0, + BlockSize: 10, + LinksSize: 2, + DataSize: 8, + CumulativeSize: 10 +} +``` + +If no `callback` is passed, a promise is returned. + + + + + +### `object.patch` + +> `object.patch` exposes the available patch calls. + +#### `object.patch.addLink` + +> Add a Link to an existing MerkleDAG Object + +##### `Go` **WIP** + +##### `JavaScript` - ipfs.object.patch.addLink(multihash, DAGLink, [options, callback]) + +`multihash` is a [multihash]() which can be passed as: + +- Buffer, the raw Buffer of the multihash (or of and encoded version) +- String, the toString version of the multihash (or of an encoded version) + +`DAGLink` is the new link to be added on the node that is identified by the `multihash` + +`options` is a optional argument of type object, that can contain the following properties: + +- `enc`, the encoding of multihash (base58, base64, etc), if any. + +`callback` must follow `function (err, node) {}` signature, where `err` is an error if the operation was not successful and `node` is a MerkleDAG node of the type [DAGNode](https://github.com/vijayee/js-ipfs-merkle-dag/blob/master/src/dag-node.js) that resulted by the operation of adding a Link. + +If no `callback` is passed, a promise is returned. + + + + + +#### `object.patch.rmLink` + +> Remove a Link from an existing MerkleDAG Object + +##### `Go` **WIP** + +##### `JavaScript` - ipfs.object.patch.rmLink(multihash, DAGLink, [options, callback]) + +`multihash` is a [multihash]() which can be passed as: + +- Buffer, the raw Buffer of the multihash (or of and encoded version) +- String, the toString version of the multihash (or of an encoded version) + +`DAGLink` is the link to be removed on the node that is identified by the `multihash` + +`options` is a optional argument of type object, that can contain the following properties: + +- `enc`, the encoding of multihash (base58, base64, etc), if any. + +`callback` must follow `function (err, node) {}` signature, where `err` is an error if the operation was not successful and `node` is a MerkleDAG node of the type [DAGNode](https://github.com/vijayee/js-ipfs-merkle-dag/blob/master/src/dag-node.js) that resulted by the operation of adding a Link. + +If no `callback` is passed, a promise is returned. + + + + + +#### `object.patch.appendData` + +> Append Data to the Data field of an existing node. + +##### `Go` **WIP** + +##### `JavaScript` - ipfs.object.patch.appendData(multihash, data, [options, callback]) + +`multihash` is a [multihash]() which can be passed as: + +- Buffer, the raw Buffer of the multihash (or of and encoded version) +- String, the toString version of the multihash (or of an encoded version) + +`data` is a Buffer containing Data to be appended to the existing node. + +`options` is a optional argument of type object, that can contain the following properties: + +- `enc`, the encoding of multihash (base58, base64, etc), if any. + +`callback` must follow `function (err, node) {}` signature, where `err` is an error if the operation was not successful and `node` is a MerkleDAG node of the type [DAGNode](https://github.com/vijayee/js-ipfs-merkle-dag/blob/master/src/dag-node.js) that resulted by the operation of adding a Link. + +If no `callback` is passed, a promise is returned. + + + + + +#### `object.patch.setData` + +> Reset the Data field of a MerkleDAG Node to new Data + +##### `Go` **WIP** + +##### `JavaScript` - ipfs.object.patch.setData(multihash, data, [options, callback]) + +`multihash` is a [multihash]() which can be passed as: + +- Buffer, the raw Buffer of the multihash (or of and encoded version) +- String, the toString version of the multihash (or of an encoded version) + +`data` is a Buffer containing Data to replace the existing Data on the node. + +`options` is a optional argument of type object, that can contain the following properties: + +- `enc`, the encoding of multihash (base58, base64, etc), if any. + +`callback` must follow `function (err, node) {}` signature, where `err` is an error if the operation was not successful and `node` is a MerkleDAG node of the type [DAGNode](https://github.com/vijayee/js-ipfs-merkle-dag/blob/master/src/dag-node.js) that resulted by the operation of adding a Link. + +If no `callback` is passed, a promise is returned. diff --git a/img/badge.sketch b/img/badge.sketch new file mode 100755 index 000000000..990ad1c5b Binary files /dev/null and b/img/badge.sketch differ diff --git a/package.json b/package.json new file mode 100644 index 000000000..bab4a2445 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "interface-ipfs-core", + "version": "0.0.0", + "description": "A test suite and interface you can use to implement a IPFS core interface.", + "main": "src/index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ipfs/interface-ipfs-core.git" + }, + "keywords": [ + "IPFS" + ], + "author": "David Dias ", + "license": "MIT", + "bugs": { + "url": "https://github.com/ipfs/interface-ipfs-core/issues" + }, + "homepage": "https://github.com/ipfs/interface-ipfs-core#readme", + "dependencies": { + "chai": "^3.5.0" + } +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 000000000..fc20a1303 --- /dev/null +++ b/src/index.js @@ -0,0 +1,2 @@ +exports.all = () => {} +exports.object = require('./object') diff --git a/src/object.js b/src/object.js new file mode 100644 index 000000000..9b9d7af7f --- /dev/null +++ b/src/object.js @@ -0,0 +1,253 @@ +/* eslint-env mocha */ + +'use strict' + +const expect = require('chai').expect + +module.exports = (common) => { + let ipfs + + before((done) => { + common.setup((err, _ipfs) => { + expect(err).to.not.exist + ipfs = _ipfs + done() + }) + }) + + after((done) => { + common.teardown(done) + }) + + describe('.object', () => { + const testObject = Buffer(JSON.stringify({Data: 'testdata', Links: []})) + const testObjectHash = 'QmPTkMuuL6PD8L2SwTwbcs1NPg14U8mRzerB1ZrrBrkSDD' + const testPatchObject = Buffer(JSON.stringify({Data: 'new test data'})) + const testPatchObjectHash = 'QmWJDtdQWQSajQPx1UVAGWKaSGrHVWdjnrNhbooHP7LuF2' + + it('object.put', (done) => { + ipfs.object.put(testObject, 'json', (err, res) => { + expect(err).to.not.exist + expect(res).to.have.a.property('Hash', testObjectHash) + expect(res.Links).to.be.empty + done() + }) + }) + + it('object.get', (done) => { + ipfs.object.get(testObjectHash, (err, res) => { + expect(err).to.not.exist + expect(res).to.have.a.property('Data', 'testdata') + expect(res.Links).to.be.empty + done() + }) + }) + + it('object.data', (done) => { + ipfs.object.data(testObjectHash, (err, res) => { + expect(err).to.not.exist + + let buf = '' + res + .on('error', (err) => { + expect(err).to.not.exist + }) + .on('data', (data) => { + buf += data + }) + .on('end', () => { + expect(buf).to.equal('testdata') + done() + }) + }) + }) + + it('object.stat', (done) => { + ipfs.object.stat(testObjectHash, (err, res) => { + expect(err).to.not.exist + expect(res).to.be.eql({ + Hash: 'QmPTkMuuL6PD8L2SwTwbcs1NPg14U8mRzerB1ZrrBrkSDD', + NumLinks: 0, + BlockSize: 10, + LinksSize: 2, + DataSize: 8, + CumulativeSize: 10 + }) + done() + }) + }) + + it('object.links', (done) => { + ipfs.object.links(testObjectHash, (err, res) => { + expect(err).to.not.exist + + expect(res).to.be.eql({ + Hash: 'QmPTkMuuL6PD8L2SwTwbcs1NPg14U8mRzerB1ZrrBrkSDD' + }) + done() + }) + }) + + describe('object.patch', () => { + before((done) => { + ipfs.object.put(testPatchObject, 'json', (err, res) => { + expect(err).to.not.exist + done() + }) + }) + + it('.addLink', (done) => { + ipfs.object.patch + .addLink(testObjectHash, 'next', testPatchObjectHash, (err, res) => { + expect(err).to.not.exist + expect(res).to.be.eql({ + Hash: 'QmZFdJ3CQsY4kkyQtjoUo8oAzsEs5BNguxBhp8sjQMpgkd' + }) + ipfs.object.get(res.Hash, (err, res) => { + expect(err).to.not.exist + expect(res).to.be.eql({ + Data: 'testdata', + Links: [{ + Name: 'next', + Hash: 'QmWJDtdQWQSajQPx1UVAGWKaSGrHVWdjnrNhbooHP7LuF2', + Size: 15 + }] + }) + done() + }) + }) + }) + + it('.rmLink', (done) => { + ipfs.object.patch + .rmLink('QmZFdJ3CQsY4kkyQtjoUo8oAzsEs5BNguxBhp8sjQMpgkd', 'next', (err, res) => { + expect(err).to.not.exist + expect(res).to.be.eql({ + Hash: testObjectHash + }) + ipfs.object.get(res.Hash, (err, res) => { + expect(err).to.not.exist + expect(res).to.be.eql({ + Data: 'testdata', + Links: [] + }) + done() + }) + }) + }) + + it('.appendData', (done) => { + ipfs.object.patch + .appendData(testObjectHash, new Buffer(' hello'), (err, res) => { + expect(err).to.not.exist + expect(res).to.be.eql({ + Hash: 'Qmcjhr2QztQxCAoEf8tJPTGTVkTsUrTQ36JurH14DNYNsc' + }) + ipfs.object.get(res.Hash, (err, res) => { + expect(err).to.not.exist + expect(res).to.be.eql({ + Data: 'testdata hello', + Links: [] + }) + done() + }) + }) + }) + it('.setData', (done) => { + ipfs.object.patch + .setData(testObjectHash, new Buffer('hello world'), (err, res) => { + expect(err).to.not.exist + expect(res).to.be.eql({ + Hash: 'QmU1Sq1B7RPQD2XcQNLB58qJUyJffVJqihcxmmN1STPMxf' + }) + ipfs.object.get(res.Hash, (err, res) => { + expect(err).to.not.exist + expect(res).to.be.eql({ + Data: 'hello world', + Links: [] + }) + done() + }) + }) + }) + }) + + it('object.new', (done) => { + ipfs.object.new('unixfs-dir', (err, res) => { + expect(err).to.not.exist + expect(res).to.deep.equal({ + Hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn' + }) + done() + }) + }) + + describe('promise', () => { + it('object.put', () => { + return ipfs.object.put(testObject, 'json') + .then((res) => { + expect(res).to.have.a.property('Hash', testObjectHash) + expect(res.Links).to.be.empty + }) + }) + + it('object.get', () => { + return ipfs.object.get(testObjectHash) + .then((res) => { + expect(res).to.have.a.property('Data', 'testdata') + expect(res.Links).to.be.empty + }) + }) + + it('object.data', (done) => { + return ipfs.object.data(testObjectHash) + .then((res) => { + let buf = '' + res + .on('error', (err) => { + throw err + }) + .on('data', (data) => { + buf += data + }) + .on('end', () => { + expect(buf).to.equal('testdata') + done() + }) + }) + }) + + it('object.stat', () => { + return ipfs.object.stat(testObjectHash) + .then((res) => { + expect(res).to.be.eql({ + Hash: 'QmPTkMuuL6PD8L2SwTwbcs1NPg14U8mRzerB1ZrrBrkSDD', + NumLinks: 0, + BlockSize: 10, + LinksSize: 2, + DataSize: 8, + CumulativeSize: 10 + }) + }) + }) + + it('object.links', () => { + return ipfs.object.links(testObjectHash) + .then((res) => { + expect(res).to.be.eql({ + Hash: 'QmPTkMuuL6PD8L2SwTwbcs1NPg14U8mRzerB1ZrrBrkSDD' + }) + }) + }) + + it('object.new', () => { + return ipfs.object.new('unixfs-dir') + .then((res) => { + expect(res).to.deep.equal({ + Hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn' + }) + }) + }) + }) + }) +}