diff --git a/README.md b/README.md index cf083a8..85fb65e 100644 --- a/README.md +++ b/README.md @@ -1,100 +1,82 @@ -# IPFS IPLD +# IPLD Resolver [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) [![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/) [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) +[![Coverage Status](https://coveralls.io/repos/github/ipld/js-ipld-resolver/badge.svg?branch=master)](https://coveralls.io/github/ipld/js-ipld-resolver?branch=master) +[![Travis CI](https://travis-ci.org/ipld/js-ipld-resolver.svg?branch=master)](https://travis-ci.org/ipld/js-ipld-resolver) +[![Circle CI](https://circleci.com/gh/ipld/js-ipld-resolver.svg?style=svg)](https://circleci.com/gh/ipld/js-ipld-resolver) +[![Dependency Status](https://david-dm.org/ipld/js-ipld-resolver.svg?style=flat-square)](https://david-dm.org/ipld/js-ipld-resolver) +[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard) [![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) -[![Coverage Status](https://coveralls.io/repos/github/ipfs/js-ipfs-ipld/badge.svg?branch=master)](https://coveralls.io/github/ipfs/js-ipfs-ipld?branch=master) -[![Travis CI](https://travis-ci.org/ipfs/js-ipfs-ipld.svg?branch=master)](https://travis-ci.org/ipfs/js-ipfs-ipld) -[![Circle CI](https://circleci.com/gh/ipfs/js-ipfs-ipld.svg?style=svg)](https://circleci.com/gh/ipfs/js-ipfs-ipld) -[![Dependency Status](https://david-dm.org/ipfs/js-ipfs-ipld.svg?style=flat-square)](https://david-dm.org/ipfs/js-ipfs-ipld) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard) -> JavaScript implementation of the IPLDService +> JavaScript implementation of the IPLD Resolver ## Table of Contents -* [Install](#install) -* [Usage](#usage) -* [API](#api) - + [`resolve`](#resolve) - + [IPLDService](#ipldservice) +- [Install](#install) +- [Usage](#usage) +- [API](#api) + - [IPLD Resolver](#ipldresolver) - [`.put(node, cb)`](#putnode-cb) - [`.putStream([cb])`](#putstreamcb) - - [`.get(multihash, cb)`](#getmultihash-cb) - - [`.getStream(multihash)`](#getstreammultihash) - - [`.getRecursive(multihash, cb)`](#getrecursivemultihash-cb) - - [`.getRecursiveStream(multihash)`](#getrecursivestreammultihash) - - [`.remove(multihash, cb)`](#removemultihash-cb) -* [Contribute](#contribute) -* [License](#license) + - [`.get(cid, cb)`](#getcid-cb) + - [`.getStream(cid)`](#getstreamcid) + - [`.remove(cid, cb)`](#removecid-cb) +- [Contribute](#contribute) +- [License](#license) ## Install ```bash -npm install --save ipfs-ipld +> npm install --save ipfs-ipld ``` ## Usage ```js -const ipfsIPLD = require('ipfs-ipld') +const IPLDResolver = require('ipld-resolver') -// available components -ipfsIPLD.IPLDService -ipfsIPLD.resolve +// pass an optional blockService, if no blockService is passed, +// one is created in memory. +const ipldResolver = new IPLDResolver(blockService) ``` ## API -### `resolve` +### IPLD Resolver -> Resolve IPLD paths against a given IPLDService - -```js -const node = { - hello: { - world: 11, - some: 12 - } -} -const mh = ipld.multihash(ipld.marshal(node)) -ipldService.put(node, (err) => { - resolve(ipldService, `${mh}/hello/world`, (err, res) => { - console.log(res) - // => 11 -}) -``` - -### IPLDService - -#### `.put(node, cb)` +#### `.put(node, callback)` > Store the given node (any JavaScript object). -#### `.putStream([cb])` +#### `.putStream([callback])` Returns a sink pull-stream, to write IPLD objects to. -#### `.get(multihash, cb)` +#### `.get(cid, callback)` > Retrieve a node by the given `multihash`. -#### `.getStream(multihash)` +#### `.getStream(cid)` Returns a source pull-stream of the requested IPLD object. -#### `.getRecursive(multihash, cb)` +#### `.remove(cid, callback)` -> Retrieve a node by the given `multihash` and all linked nodes. +> Remove a node by the given `multihash` -#### `.getRecursiveStream(multihash)` +#### `.resolve(cid, path, callback)` -Returns a source pull-stream, which emits the requested node, and -all linked nodes. +> Resolves an IPLD path -#### `.remove(multihash, cb)` +#### `.support.add(multicodec, formatResolver, formatUtil)` -> Remove a node by the given `multihash` +> Add support to another IPLD Format + +#### `.support.rm(multicodec)` + +> Removes support of an IPLD Format ## Contribute diff --git a/package.json b/package.json index 59e0fb5..8366f5e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "ipfs-ipld", - "version": "3.0.0", - "description": "IPLD implementation in JavaScript", + "name": "ipld-resolver", + "version": "0.0.0", + "description": "IPLD Resolver Implementation in JavaScript", "main": "lib/index.js", "jsnext:main": "src/index.js", "pre-commit": [ @@ -34,23 +34,27 @@ "homepage": "https://github.com/ipfs/js-ipfs-ipld#readme", "license": "MIT", "devDependencies": { - "aegir": "^8.0.1", + "aegir": "^8.1.2", "async": "^2.0.1", "buffer-loader": "0.0.1", "chai": "^3.5.0", "fs-pull-blob-store": "^0.3.0", - "idb-pull-blob-store": "^0.4.0", - "ipfs-block-service": "^0.5.0", - "ipfs-repo": "^0.9.0", + "idb-pull-blob-store": "^0.5.1", + "ipfs-block-service": "^0.6.0", + "ipfs-repo": "^0.10.0", "lodash": "^4.15.0", "ncp": "^2.0.0", "pre-commit": "^1.1.3", "rimraf": "^2.5.4" }, "dependencies": { + "async": "^2.1.1", "babel-runtime": "^6.11.6", - "ipfs-block": "^0.3.0", - "ipld": "^0.6.0", + "cids": "^0.2.0", + "interface-pull-blob-store": "^0.5.0", + "ipfs-block": "^0.4.0", + "ipld-dag-cbor": "^0.7.0", + "ipld-dag-pb": "^0.1.0", "is-ipfs": "^0.2.0", "lodash.flatten": "^4.4.0", "lodash.includes": "^4.3.0", @@ -64,4 +68,4 @@ "greenkeeperio-bot ", "nicola " ] -} \ No newline at end of file +} diff --git a/src/index.js b/src/index.js index 38f7dc3..b8dad0f 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,168 @@ 'use strict' -exports.IPLDService = require('./ipld-service') -exports.resolve = require('./resolve') +const Block = require('ipfs-block') +const pull = require('pull-stream') +const CID = require('cids') +const until = require('async/until') +const IPFSRepo = require('ipfs-repo') +const MemoryStore = require('../node_modules/interface-pull-blob-store/lib/reference.js') +const BlockService = require('ipfs-block-service') + +const dagPB = require('ipld-dag-pb') +const dagCBOR = require('ipld-dag-cbor') + +class IPLDResolver { + constructor (blockService) { + // nicola will love this! + if (!blockService) { + const repo = new IPFSRepo('in-memory', { stores: MemoryStore }) + blockService = new BlockService(repo) + } + + this.bs = blockService + this.resolvers = {} + + this.support = {} + + // Adds support for an IPLD format + this.support.add = (multicodec, resolver, util) => { + if (this.resolvers[multicodec]) { + throw new Error(multicodec + 'already supported') + } + + this.resolvers[multicodec] = { + resolver: resolver, + util: util + } + } + + this.support.rm = (multicodec) => { + if (this.resolvers[multicodec]) { + delete this.resolvers[multicodec] + } + } + + // Support by default dag-pb and dag-cbor + this.support.add(dagPB.resolver.multicodec, dagPB.resolver, dagPB.util) + this.support.add(dagCBOR.resolver.multicodec, dagCBOR.resolver, dagCBOR.util) + } + + resolve (cid, path, callback) { + if (path === '/') { + return this.get(cid, callback) + } + + let value + + until( + () => { + if (!path || path === '' || path === '/') { + return true + } else { + // continue traversing + if (value) { + cid = new CID(value['/']) + } + return false + } + }, + (cb) => { + // get block + // use local resolver + // update path value + this.bs.get(cid, (err, block) => { + if (err) { + return cb(err) + } + const r = this.resolvers[cid.codec] + r.resolver.resolve(block, path, (err, result) => { + if (err) { + return cb(err) + } + value = result.value + path = result.remainderPath + cb() + }) + }) + }, + (err, results) => { + if (err) { + return callback(err) + } + return callback(null, value) + } + ) + } + + // Node operations (get and retrieve nodes, not values) + + put (nodeAndCID, callback) { + callback = callback || noop + pull( + pull.values([nodeAndCID]), + this.putStream(callback) + ) + } + + putStream (callback) { + callback = callback || noop + + return pull( + pull.asyncMap((nodeAndCID, cb) => { + const cid = nodeAndCID.cid + const r = this.resolvers[cid.codec] + + r.util.serialize(nodeAndCID.node, (err, serialized) => { + if (err) { + return cb(err) + } + cb(null, { + block: new Block(serialized), + cid: cid + }) + }) + }), + this.bs.putStream(), + pull.onEnd(callback) + ) + } + + get (cid, callback) { + pull( + this.getStream(cid), + pull.collect((err, res) => { + if (err) { + return callback(err) + } + callback(null, res[0]) + }) + ) + } + + getStream (cid) { + return pull( + this.bs.getStream(cid), + pull.asyncMap((block, cb) => { + const r = this.resolvers[cid.codec] + if (r) { + r.util.deserialize(block.data, (err, deserialized) => { + if (err) { + return cb(err) + } + cb(null, deserialized) + }) + } else { // multicodec unknown, send back raw data + cb(null, block.data) + } + }) + ) + } + + remove (cids, callback) { + this.bs.delete(cids, callback) + } +} + +function noop () {} + +module.exports = IPLDResolver diff --git a/src/ipld-service.js b/src/ipld-service.js deleted file mode 100644 index 75f3395..0000000 --- a/src/ipld-service.js +++ /dev/null @@ -1,115 +0,0 @@ -'use strict' - -const isIPFS = require('is-ipfs') -const Block = require('ipfs-block') -const ipld = require('ipld') -const pull = require('pull-stream') -const traverse = require('pull-traverse') -const mh = require('multihashes') - -const utils = require('./utils') - -module.exports = class IPLDService { - constructor (blockService) { - if (!blockService) { - throw new Error('IPLDService requires a BlockService instance') - } - - this.bs = blockService - } - - put (node, cb) { - cb = cb || noop - pull( - pull.values([node]), - this.putStream(cb) - ) - } - - putStream (cb) { - cb = cb || noop - return pull( - pull.map((node) => { - if (!(node instanceof Buffer)) { - node = ipld.marshal(node) - } - - return new Block(node, 'ipld') - }), - this.bs.putStream(), - pull.onEnd(cb) - ) - } - - get (key, cb) { - pull( - this.getStream(key), - pull.collect((err, res) => { - if (err) return cb(err) - cb(null, res[0]) - }) - ) - } - - getStream (key) { - const normalizedKey = normalizeKey(key) - - if (!normalizedKey) { - return pull.error(new Error('Invalid Key')) - } - - return pull( - this.bs.getStream(normalizedKey, 'ipld'), - pull.map((block) => ipld.unmarshal(block.data)) - ) - } - - getRecursive (key, cb) { - pull( - this.getRecursiveStream(key), - pull.collect(cb) - ) - } - - getRecursiveStream (key) { - return pull( - this.getStream(key), - pull.map((node) => traverse.widthFirst(node, (node) => { - return pull( - pull.values(utils.getKeys(node)), - pull.map((link) => this.getStream(link)), - pull.flatten() - ) - })), - pull.flatten() - ) - } - - remove (keys, cb) { - this.bs.delete(keys, 'ipld', cb) - } -} - -function noop () {} - -function normalizeKey (key) { - let res - const isMhash = isIPFS.multihash(key) - const isPath = isIPFS.path(key) - - if (!isMhash && !isPath) { - return null - } - - if (isMhash) { - res = key - } else if (isPath) { - res = key.replace('/ipfs/', '') - } - - if (typeof res === 'string') { - return mh.fromB58String(res) - } - - return res -} diff --git a/src/resolve.js b/src/resolve.js deleted file mode 100644 index 581c6dc..0000000 --- a/src/resolve.js +++ /dev/null @@ -1,81 +0,0 @@ -'use strict' - -const isIPFS = require('is-ipfs') -const includes = require('lodash.includes') -const ipld = require('ipld') - -const IPLDService = require('./ipld-service') - -const LINK_SYMBOL = ipld.LINK_SYMBOL - -module.exports = function resolve (service, path, cb) { - if (!(service instanceof IPLDService)) { - return cb(new Error('Missing IPLDService')) - } - - function access (parts, obj, cb) { - const isRoot = obj === null && (isIPFS.multihash(parts[0]) || isIPFS.ipfsPath('/' + parts.join('/'))) - const next = parts.shift() - const isLink = obj && Object.keys(obj).length === 1 && obj[LINK_SYMBOL] - const fetchLink = obj && (next ? !includes(Object.keys(obj), next) : true) - - if (!obj && !isRoot) { - cb(new Error('No root object provided')) - } else if (isLink && fetchLink) { - // resolve links in objects with an / property - const link = obj[LINK_SYMBOL] - const linkParts = splitLink(link) - let blockLink = '' - - if (linkParts[0] === 'ipfs') { - // /ipfs/ - blockLink = linkParts[1] - parts = linkParts.slice(2).concat(parts) - } else if (isIPFS.multihash(linkParts[0])) { - // / - blockLink = linkParts[0] - - parts = linkParts.slice(1).concat(parts) - } else { - return cb(new Error(`Invalid link: "${link}"`)) - } - - service.get(blockLink, (err, block) => { - if (err) { - return cb(err) - } - if (next) { - // Put back so it's resolved in the next node - parts.unshift(next) - } - access(parts, block, cb) - }) - } else if (isRoot) { - let blockLink = next - if (next === 'ipfs') { - blockLink = parts.shift() - } - service.get(blockLink, (err, block) => { - if (err) { - return cb(err) - } - - access(parts, block, cb) - }) - } else if (next) { - access(parts, obj[next], cb) - } else { - cb(null, obj) - } - } - - access(splitLink(path), null, cb) -} - -function splitLink (link) { - return link - // Remove prefix / - .replace(/^\//, '') - // Hack to ignore escaped slashes - .replace(/([^\\])\//g, '$1\u000B').split('\u000B') -} diff --git a/src/utils.js b/src/utils.js deleted file mode 100644 index f28266d..0000000 --- a/src/utils.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict' - -const flatten = require('lodash.flatten') -const ipld = require('ipld') - -const LINK_SYMBOL = ipld.LINK_SYMBOL - -exports = module.exports - -// Recursively find all LINK_SYMBOL values in a given node -exports.getKeys = (node) => { - return flatten(Object.keys(node).map((key) => { - if (key === LINK_SYMBOL) { - return node[key] - } - - if (node[key] && typeof node[key] === 'object') { - return exports.getKeys(node[key]) - } - - return [] - })) -} diff --git a/test/browser.js b/test/browser.js index 02576cf..6cdcf94 100644 --- a/test/browser.js +++ b/test/browser.js @@ -8,21 +8,19 @@ const IPFSRepo = require('ipfs-repo') const pull = require('pull-stream') const repoContext = require.context('buffer!./example-repo', true) -const tests = require('./ipld-tests') +const basePath = 'ipfs' + Math.random() const idb = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB -idb.deleteDatabase('ipfs') -idb.deleteDatabase('ipfs/blocks') +idb.deleteDatabase(basePath) +idb.deleteDatabase(basePath + '/blocks') -describe('ipfs merkle dag browser tests', function () { - before(function (done) { - this.timeout(10000) - - var repoData = [] +describe('Browser', () => { + before((done) => { + const repoData = [] repoContext.keys().forEach(function (key) { repoData.push({ key: key.replace('./', ''), @@ -30,8 +28,8 @@ describe('ipfs merkle dag browser tests', function () { }) }) - const mainBlob = new Store('ipfs') - const blocksBlob = new Store('ipfs/blocks') + const mainBlob = new Store(basePath) + const blocksBlob = new Store(basePath + '/blocks') eachSeries(repoData, (file, cb) => { if (_.startsWith(file.key, 'datastore/')) { @@ -49,6 +47,10 @@ describe('ipfs merkle dag browser tests', function () { }, done) }) - const repo = new IPFSRepo('ipfs', {stores: Store}) - tests(repo) + const repo = new IPFSRepo(basePath, { stores: Store }) + + require('./test-ipld-dag-pb')(repo) + require('./test-ipld-dag-cbor')(repo) + // require('./test-ipld-eth-block')(repo) + require('./test-ipld-all-together-now') }) diff --git a/test/ipld-tests.js b/test/ipld-tests.js deleted file mode 100644 index ed7fbac..0000000 --- a/test/ipld-tests.js +++ /dev/null @@ -1,332 +0,0 @@ -/* eslint-env mocha */ -'use strict' - -const expect = require('chai').expect -const BlockService = require('ipfs-block-service') -const ipld = require('ipld') -const multihash = require('multihashing') -const series = require('async/series') -const pull = require('pull-stream') - -const IPLDService = require('../src').IPLDService -const resolve = require('../src').resolve - -module.exports = (repo) => { - const bs = new BlockService(repo) - const ipldService = new IPLDService(bs) - - describe('IPLDService', () => { - it('throws when not passed a repo', () => { - expect(() => new IPLDService()).to.throw(/requires a BlockService/) - }) - - it('adds an ipld node', (done) => { - const node = { - name: 'hello.txt', - size: 11 - } - - ipldService.put(node, done) - }) - - it('putStream', (done) => { - pull( - pull.values([{name: 'pull.txt', size: 12}]), - ipldService.putStream(done) - ) - }) - - it('gets an ipld node', (done) => { - const node = { - name: 'world.txt', - size: 11 - } - - ipldService.put(node, (err) => { - expect(err).to.not.exist - - const mh = multihash(ipld.marshal(node), 'sha2-256') - - ipldService.get(mh, (err, fetchedNode) => { - expect(err).to.not.exist - expect(node).to.deep.equal(fetchedNode) - done() - }) - }) - }) - - it('getStream', (done) => { - const node = { - name: 'put.txt', - size: 15 - } - const mh = multihash(ipld.marshal(node), 'sha2-256') - pull( - pull.values([node]), - ipldService.putStream(read) - ) - - function read (err) { - expect(err).to.not.exist - pull( - ipldService.getStream(mh), - pull.collect((err, res) => { - expect(err).to.not.exist - expect(res[0]).to.be.eql(node) - done() - }) - ) - } - }) - - it('get ipld nodes recursively', (done) => { - // 1 -> 2 -> 3 - const node1 = {data: '1'} - const node2 = {data: '2'} - const node3 = {data: '3'} - - node2.ref = { - '/': ipld.multihash(ipld.marshal(node3)) - } - - node1.ref = { - '/': ipld.multihash(ipld.marshal(node2)) - } - - series([ - (cb) => ipldService.put(node1, cb), - (cb) => ipldService.put(node2, cb), - (cb) => ipldService.put(node3, cb), - (cb) => { - const mh = multihash(ipld.marshal(node1), 'sha2-256') - ipldService.getRecursive(mh, (err, nodes) => { - expect(err).to.not.exist - expect(nodes).to.have.length(3) - cb() - }) - } - ], (err) => { - expect(err).to.not.exist - done() - }) - }) - - it('removes and ipld node', (done) => { - const node = {data: 'short lived node'} - const mh = multihash(ipld.marshal(node), 'sha2-256') - - series([ - (cb) => ipldService.put(node, cb), - (cb) => ipldService.get(mh, cb), - (cb) => ipldService.remove(mh, cb), - (cb) => ipldService.get(mh, (err) => { - expect(err).to.exist - cb() - }) - ], done) - }) - }) - - describe('resolve', () => { - describe('inside a single object', (done) => { - const node = { - string: 'hello', - number: 42, - object: { - title: 'world' - }, - numbers: [0, 1, 2], - objects: [{ - title: 'test' - }], - multiple: { - levels: { - down: 'all good!' - } - } - } - const mh = ipld.multihash(ipld.marshal(node)) - - before((done) => { - ipldService.put(node, done) - }) - - it('resolves direct leaves of type string', (done) => { - resolve(ipldService, `${mh}/string`, (err, res) => { - expect(err).to.not.exist - expect(res).to.be.eql('hello') - done() - }) - }) - - it('resolves direct leaves of type number', (done) => { - resolve(ipldService, `${mh}/number`, (err, res) => { - expect(err).to.not.exist - expect(res).to.be.eql(42) - done() - }) - }) - - it('resolves direct leaves of type object', (done) => { - resolve(ipldService, `${mh}/object`, (err, res) => { - expect(err).to.not.exist - expect(res).to.be.eql({ - title: 'world' - }) - done() - }) - }) - - it('resolves subpaths', (done) => { - resolve(ipldService, `${mh}/multiple/levels/down`, (err, res) => { - expect(err).to.not.exist - expect(res).to.be.eql('all good!') - done() - }) - }) - - it('resolves arrays of primitives', (done) => { - resolve(ipldService, `${mh}/numbers/1`, (err, res) => { - expect(err).to.not.exist - expect(res).to.be.eql(1) - done() - }) - }) - - it('resolves arrays of objects', (done) => { - resolve(ipldService, `${mh}/objects/0/title`, (err, res) => { - expect(err).to.not.exist - expect(res).to.be.eql('test') - done() - }) - }) - }) - - describe('links are hashes', () => { - const aliceAbout = { - age: 22 - } - const bob = { - name: 'Bob' - } - const alice = { - name: 'Alice', - about: { - '/': ipld.multihash(ipld.marshal(aliceAbout)) - }, - friends: [{ - '/': ipld.multihash(ipld.marshal(bob)) - }] - } - const mh = ipld.multihash(ipld.marshal(alice)) - - before((done) => { - series([ - (cb) => ipldService.put(aliceAbout, cb), - (cb) => ipldService.put(alice, cb), - (cb) => ipldService.put(bob, cb) - ], done) - }) - - it('link to object', (done) => { - resolve(ipldService, `${mh}/about`, (err, res) => { - expect(err).to.not.exist - expect(res).to.be.eql(aliceAbout) - done() - }) - }) - - it('link to property in a different object', (done) => { - resolve(ipldService, `${mh}/about/age`, (err, res) => { - expect(err).to.not.exist - expect(res).to.be.eql(aliceAbout.age) - done() - }) - }) - - it('link to an element in array', (done) => { - resolve(ipldService, `${mh}/friends/0`, (err, res) => { - expect(err).to.not.exist - expect(res).to.be.eql(bob) - done() - }) - }) - - it('link to property in an element in array', (done) => { - resolve(ipldService, `${mh}/friends/0/name`, (err, res) => { - expect(err).to.not.exist - expect(res).to.be.eql(bob.name) - done() - }) - }) - }) - - describe('links are merkle paths', () => { - const draft = { - title: 'Title of the blogpost' - } - - const alice = { - name: 'Alice' - } - - const author = { - name: { - '/': `/${ipld.multihash(ipld.marshal(alice))}/name` - } - } - - const blogpost = { - title: { - '/': `/${ipld.multihash(ipld.marshal(draft))}/title` - }, - author: { - '/': `/ipfs/${ipld.multihash(ipld.marshal(author))}` - } - } - - const mh = ipld.multihash(ipld.marshal(blogpost)) - - before((done) => { - series([ - (cb) => ipldService.put(draft, cb), - (cb) => ipldService.put(alice, cb), - (cb) => ipldService.put(author, cb), - (cb) => ipldService.put(blogpost, cb) - ], done) - }) - - it('merkle-link pointing to a string', (done) => { - resolve(ipldService, `${mh}/title`, (err, res) => { - expect(err).to.not.exist - expect(res).to.be.eql(draft.title) - done() - }) - }) - - it('merkle-link pointing to an object', (done) => { - resolve(ipldService, `${mh}/author`, (err, res) => { - expect(err).to.not.exist - expect(res).to.be.eql(author) - done() - }) - }) - - it('merkle-link pointing to link to an object', (done) => { - resolve(ipldService, `${mh}/author/name`, (err, res) => { - expect(err).to.not.exist - expect(res).to.be.eql(alice.name) - done() - }) - }) - - it('ipfs merkle-link to an object', (done) => { - resolve(ipldService, `/ipfs/${mh}/author`, (err, res) => { - expect(err).to.not.exist - expect(res).to.be.eql(author) - done() - }) - }) - }) - }) -} diff --git a/test/node.js b/test/node.js index d316e9d..3bb567e 100644 --- a/test/node.js +++ b/test/node.js @@ -3,32 +3,26 @@ const ncp = require('ncp').ncp const rimraf = require('rimraf') -const expect = require('chai').expect const IPFSRepo = require('ipfs-repo') const Store = require('fs-pull-blob-store') -const tests = require('./ipld-tests') - -describe('node test blocks', () => { +describe('Node.js', () => { const repoExample = process.cwd() + '/test/example-repo' const repoTests = process.cwd() + '/test/repo-just-for-test' + Date.now() before((done) => { - ncp(repoExample, repoTests, (err) => { - process.env.IPFS_PATH = repoTests - expect(err).to.equal(null) - done() - }) + process.env.IPFS_PATH = repoTests + ncp(repoExample, repoTests, done) }) after((done) => { - rimraf(repoTests, (err) => { - expect(err).to.equal(null) - done() - }) + rimraf(repoTests, done) }) - const repo = new IPFSRepo(repoTests, {stores: Store}) + const repo = new IPFSRepo(repoTests, { stores: Store }) - tests(repo) + require('./test-ipld-dag-pb')(repo) + require('./test-ipld-dag-cbor')(repo) + // require('./test-ipld-eth-block')(repo) + require('./test-ipld-all-together-now') }) diff --git a/test/test-ipld-all-together-now.js b/test/test-ipld-all-together-now.js new file mode 100644 index 0000000..6575f17 --- /dev/null +++ b/test/test-ipld-all-together-now.js @@ -0,0 +1,72 @@ +/* eslint-env mocha */ +'use strict' + +/* + * Test different types of data structures living together + * & + * Test data made of mixed data structures! + */ + +const expect = require('chai').expect +const dagPB = require('ipld-dag-pb') +const dagCBOR = require('ipld-dag-cbor') +const series = require('async/series') +const pull = require('pull-stream') + +const IPLDResolver = require('../src') + +describe('IPLD Path Resolver for dag-cbor', () => { + let resolver + + let nodeCbor + let nodePb + let cidCbor + let cidPb + + before((done) => { + resolver = new IPLDResolver() + + series([ + (cb) => { + nodePb = new dagPB.DAGNode(new Buffer('I am inside a Protobuf')) + + dagPB.util.cid(nodePb, (err, cid) => { + expect(err).to.not.exist + cidPb = cid + cb() + }) + }, + (cb) => { + nodeCbor = { + someData: 'I am inside a Cbor object', + pb: { '/': cidPb.toBaseEncodedString() } + } + + dagCBOR.util.cid(nodeCbor, (err, cid) => { + expect(err).to.not.exist + cidCbor = cid + cb() + }) + } + ], store) + + function store () { + pull( + pull.values([ + { node: nodePb, cid: cidPb }, + { node: nodeCbor, cid: cidCbor } + ]), + resolver.putStream(done) + ) + } + }) + + it('resolve through different formats', (done) => { + resolver.resolve(cidCbor, 'pb/data', (err, result) => { + expect(err).to.not.exist + expect(result).to.eql(new Buffer('I am inside a Protobuf')) + done() + }) + }) +}) + diff --git a/test/test-ipld-dag-cbor.js b/test/test-ipld-dag-cbor.js new file mode 100644 index 0000000..8c813e3 --- /dev/null +++ b/test/test-ipld-dag-cbor.js @@ -0,0 +1,233 @@ +/* eslint-env mocha */ +'use strict' + +const expect = require('chai').expect +const BlockService = require('ipfs-block-service') +const dagCBOR = require('ipld-dag-cbor') +const series = require('async/series') +const pull = require('pull-stream') + +const IPLDResolver = require('../src') + +module.exports = (repo) => { + describe('IPLD Resolver with dag-cbor (MerkleDAG CBOR)', () => { + const bs = new BlockService(repo) + const resolver = new IPLDResolver(bs) + + let node1 + let node2 + let node3 + let cid1 + let cid2 + let cid3 + + before((done) => { + node1 = { someData: new Buffer('I am 1') } + node2 = { someData: new Buffer('I am 2') } + node3 = { someData: new Buffer('I am 3') } + + series([ + (cb) => { + dagCBOR.util.cid(node1, (err, cid) => { + expect(err).to.not.exist + cid1 = cid + cb() + }) + }, + (cb) => { + dagCBOR.util.cid(node2, (err, cid) => { + expect(err).to.not.exist + cid2 = cid + cb() + }) + }, + (cb) => { + dagCBOR.util.cid(node3, (err, cid) => { + expect(err).to.not.exist + cid3 = cid + cb() + }) + } + ], done) + }) + + it('creates an in memory repo if no blockService is passed', () => { + const r = new IPLDResolver() + expect(r.bs).to.exist + }) + + it('resolver.put', (done) => { + resolver.put({ + node: node1, + cid: cid1 + }, done) + }) + + it('resolver.putStream', (done) => { + pull( + pull.values([ + { node: node1, cid: cid1 }, + { node: node2, cid: cid2 }, + { node: node3, cid: cid3 } + ]), + resolver.putStream(done) + ) + }) + + it('resolver.get', (done) => { + resolver.put({ + node: node1, + cid: cid1 + }, (err) => { + expect(err).to.not.exist + resolver.get(cid1, (err, node) => { + expect(err).to.not.exist + expect(node1).to.eql(node) + done() + }) + }) + }) + + it('resolver.getStream', (done) => { + resolver.put({ + node: node1, + cid: cid1 + }, (err) => { + expect(err).to.not.exist + pull( + resolver.getStream(cid1), + pull.collect((err, nodes) => { + expect(err).to.not.exist + expect(node1).to.eql(nodes[0]) + done() + }) + ) + }) + }) + + it('resolver.remove', (done) => { + resolver.put({ + node: node1, + cid: cid1 + }, (err) => { + expect(err).to.not.exist + resolver.get(cid1, (err, node) => { + expect(err).to.not.exist + expect(node1).to.eql(node) + remove() + }) + }) + + function remove () { + resolver.remove(cid1, (err) => { + expect(err).to.not.exist + resolver.get(cid1, (err) => { + expect(err).to.exist + done() + }) + }) + } + }) + }) + + describe('IPLD Path Resolver for dag-cbor', () => { + let resolver + + let node1 + let node2 + let node3 + let cid1 + let cid2 + let cid3 + + before((done) => { + resolver = new IPLDResolver() + + series([ + (cb) => { + node1 = { + someData: 'I am 1' + } + + dagCBOR.util.cid(node1, (err, cid) => { + expect(err).to.not.exist + cid1 = cid + cb() + }) + }, + (cb) => { + node2 = { + someData: 'I am 2', + one: { '/': cid1.toBaseEncodedString() } + } + + dagCBOR.util.cid(node2, (err, cid) => { + expect(err).to.not.exist + cid2 = cid + cb() + }) + }, + (cb) => { + node3 = { + someData: 'I am 3', + one: { '/': cid1.toBaseEncodedString() }, + two: { '/': cid2.toBaseEncodedString() } + } + + dagCBOR.util.cid(node3, (err, cid) => { + expect(err).to.not.exist + cid3 = cid + cb() + }) + } + ], store) + + function store () { + pull( + pull.values([ + { node: node1, cid: cid1 }, + { node: node2, cid: cid2 }, + { node: node3, cid: cid3 } + ]), + resolver.putStream(done) + ) + } + }) + + it('root path (same as get)', (done) => { + resolver.resolve(cid1, '/', (err, result) => { + expect(err).to.not.exist + + dagCBOR.util.cid(result, (err, cid) => { + expect(err).to.not.exist + expect(cid).to.eql(cid1) + done() + }) + }) + }) + + it('value within 1st node scope', (done) => { + resolver.resolve(cid1, 'someData', (err, result) => { + expect(err).to.not.exist + expect(result).to.eql('I am 1') + done() + }) + }) + + it('value within nested scope (1 level)', (done) => { + resolver.resolve(cid2, 'one/someData', (err, result) => { + expect(err).to.not.exist + expect(result).to.eql('I am 1') + done() + }) + }) + + it('value within nested scope (2 levels)', (done) => { + resolver.resolve(cid3, 'two/one/someData', (err, result) => { + expect(err).to.not.exist + expect(result).to.eql('I am 1') + done() + }) + }) + }) +} diff --git a/test/test-ipld-dag-pb.js b/test/test-ipld-dag-pb.js new file mode 100644 index 0000000..cad147c --- /dev/null +++ b/test/test-ipld-dag-pb.js @@ -0,0 +1,233 @@ +/* eslint-env mocha */ +'use strict' + +const expect = require('chai').expect +const BlockService = require('ipfs-block-service') +const dagPB = require('ipld-dag-pb') +const series = require('async/series') +const pull = require('pull-stream') + +const IPLDResolver = require('../src') + +module.exports = (repo) => { + describe('IPLD Resolver with dag-pb (MerkleDAG Protobuf)', () => { + const bs = new BlockService(repo) + const resolver = new IPLDResolver(bs) + + let node1 + let node2 + let node3 + let cid1 + let cid2 + let cid3 + + before((done) => { + node1 = new dagPB.DAGNode(new Buffer('I am 1')) + node2 = new dagPB.DAGNode(new Buffer('I am 2')) + node3 = new dagPB.DAGNode(new Buffer('I am 3')) + + series([ + (cb) => { + dagPB.util.cid(node1, (err, cid) => { + expect(err).to.not.exist + cid1 = cid + cb() + }) + }, + (cb) => { + dagPB.util.cid(node2, (err, cid) => { + expect(err).to.not.exist + cid2 = cid + cb() + }) + }, + (cb) => { + dagPB.util.cid(node3, (err, cid) => { + expect(err).to.not.exist + cid3 = cid + cb() + }) + } + ], done) + }) + + it('creates an in memory repo if no blockService is passed', () => { + const r = new IPLDResolver() + expect(r.bs).to.exist + }) + + it('resolver.put', (done) => { + resolver.put({ + node: node1, + cid: cid1 + }, done) + }) + + it('resolver.putStream', (done) => { + pull( + pull.values([ + { node: node1, cid: cid1 }, + { node: node2, cid: cid2 }, + { node: node3, cid: cid3 } + ]), + resolver.putStream(done) + ) + }) + + it('resolver.get', (done) => { + resolver.put({ + node: node1, + cid: cid1 + }, (err) => { + expect(err).to.not.exist + resolver.get(cid1, (err, node) => { + expect(err).to.not.exist + done() + }) + }) + }) + + it('resolver.getStream', (done) => { + resolver.put({ + node: node1, + cid: cid1 + }, (err) => { + expect(err).to.not.exist + pull( + resolver.getStream(cid1), + pull.collect((err, nodes) => { + expect(err).to.not.exist + done() + }) + ) + }) + }) + + it('resolver.remove', (done) => { + resolver.put({ + node: node1, + cid: cid1 + }, (err) => { + expect(err).to.not.exist + resolver.get(cid1, (err, node) => { + expect(err).to.not.exist + remove() + }) + }) + + function remove () { + resolver.remove(cid1, (err) => { + expect(err).to.not.exist + resolver.get(cid1, (err) => { + expect(err).to.exist + done() + }) + }) + } + }) + }) + + describe('IPLD Path Resolver for dag-pb', () => { + let resolver + + let node1 + let node2 + let node3 + let cid1 + let cid2 + let cid3 + + before((done) => { + resolver = new IPLDResolver() + + node1 = new dagPB.DAGNode(new Buffer('I am 1')) + node2 = new dagPB.DAGNode(new Buffer('I am 2')) + node3 = new dagPB.DAGNode(new Buffer('I am 3')) + + series([ + (cb) => { + node2.addNodeLink('1', node1, cb) + }, + (cb) => { + node3.addNodeLink('1', node1, cb) + }, + (cb) => { + node3.addNodeLink('2', node2, cb) + } + ], cids) + + function cids () { + series([ + (cb) => { + dagPB.util.cid(node1, (err, cid) => { + expect(err).to.not.exist + cid1 = cid + cb() + }) + }, + (cb) => { + dagPB.util.cid(node2, (err, cid) => { + expect(err).to.not.exist + cid2 = cid + cb() + }) + }, + (cb) => { + dagPB.util.cid(node3, (err, cid) => { + expect(err).to.not.exist + cid3 = cid + cb() + }) + } + ], store) + } + + function store () { + pull( + pull.values([ + { node: node1, cid: cid1 }, + { node: node2, cid: cid2 }, + { node: node3, cid: cid3 } + ]), + resolver.putStream(done) + ) + } + }) + + it('root path (same as get)', (done) => { + resolver.resolve(cid1, '/', (err, result) => { + expect(err).to.not.exist + + dagPB.util.cid(result, (err, cid) => { + expect(err).to.not.exist + expect(cid).to.eql(cid1) + done() + }) + }) + }) + + it('value within 1st node scope', (done) => { + resolver.resolve(cid1, 'data', (err, result) => { + expect(err).to.not.exist + expect(result).to.eql(new Buffer('I am 1')) + done() + }) + }) + + it('value within nested scope (1 level)', (done) => { + resolver.resolve(cid2, 'links/0/data', (err, result) => { + expect(err).to.not.exist + expect(result).to.eql(new Buffer('I am 1')) + done() + }) + }) + + it('value within nested scope (2 levels)', (done) => { + resolver.resolve(cid3, 'links/1/links/0/data', (err, result) => { + expect(err).to.not.exist + expect(result).to.eql(new Buffer('I am 1')) + done() + }) + }) + }) +} diff --git a/test/test-ipld-eth-block.js b/test/test-ipld-eth-block.js new file mode 100644 index 0000000..98e1f9c --- /dev/null +++ b/test/test-ipld-eth-block.js @@ -0,0 +1,2 @@ +/* eslint-env mocha */ +'use strict' diff --git a/test/utils.spec.js b/test/utils.spec.js deleted file mode 100644 index 8886332..0000000 --- a/test/utils.spec.js +++ /dev/null @@ -1,28 +0,0 @@ -/* eslint-env mocha */ -'use strict' - -const expect = require('chai').expect - -const utils = require('../src/utils') - -describe('utils', () => { - describe('.getKeys', () => { - it('retrieves all nested keys', () => { - expect( - utils.getKeys({ - hello: 'world', - l1: {'/': 'link-1'}, - nested: { - l2: {'/': 'link-2'}, - even: { - deeper: true, - l3: {'/': 'link-3'} - } - } - }) - ).to.be.eql( - ['link-1', 'link-2', 'link-3'] - ) - }) - }) -})