From 029174d2da621e8d60f8bcedfccdfed30510b3b4 Mon Sep 17 00:00:00 2001 From: Volker Mische Date: Wed, 17 Jul 2019 17:02:13 +0200 Subject: [PATCH] feat: remove DAGNode.create() BREAKING CHANGE: DAGNode.create() is removed Instead of `DAGNode.create()`, please use `new DAGNode()` instead. It takes the same parameters and is compatible to `create()`. Example: Prior to this change: const node = DAGNode.create('some data', links) Now: const node = new DAGNode('some data', links) Closes #132. --- README.md | 14 ++--- src/dag-node/addLink.js | 6 +-- src/dag-node/create.js | 33 ------------ src/dag-node/dagNode.js | 32 +++++++++-- src/dag-node/index.js | 1 - src/dag-node/sortLinks.js | 13 +++++ src/dag-node/{util.js => toDagLink.js} | 14 +---- src/serialize.js | 64 ++++++++++++++++++++++ src/util.js | 44 ++------------- test/dag-node-test.js | 74 +++++++++++++------------- test/resolver.spec.js | 2 +- 11 files changed, 159 insertions(+), 138 deletions(-) delete mode 100644 src/dag-node/create.js create mode 100644 src/dag-node/sortLinks.js rename src/dag-node/{util.js => toDagLink.js} (58%) create mode 100644 src/serialize.js diff --git a/README.md b/README.md index 024794a..03d881e 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ - [Add and remove a Link](#add-and-remove-a-link) - [API](#api) - [DAGNode functions](#dagnode-functions) - - [DAGNode.create(data, links)](#dagnodecreatedata-links) + - [DAGNode constructor](#dagnode-constructor) - [addLink(node, link)](#addlinknode-link) - [rmLink(node, nameOrCid)](#rmlinknode-nameorcid) - [DAGNode instance methods and properties](#dagnode-instance-methods-and-properties) @@ -65,7 +65,6 @@ ```JavaScript const dagPB = require('ipld-dag-pb') -dagPB.DAGNode.create // create a DAGNode dagPB.DAGNode.addLink // add a Link to a DAGNode, creating a new one dagPB.DAGNode.rmLink // remove a Link to a DAGNode, creating a new one @@ -79,10 +78,10 @@ dagPB.util #### Create a DAGNode ```JavaScript -const node1 = DAGNode.create(Buffer.from('some data')) +const node1 = new DAGNode(Buffer.from('some data')) // node2 will have the same data as node1 -const node2 = DAGNode.create('some data') +const node2 = new DAGNode('some data') ``` #### Add and remove a Link @@ -114,15 +113,16 @@ const dagPB = require('ipld-dag-pb') const DAGNode = dagPB.DAGNode ``` -#### DAGNode.create(data, links) +#### DAGNode constructor - `data` - type: Buffer -- `links`- type: Array of DAGLink instances or Array of DAGLink instances in its json format (link.toJSON) +- `links`- (optional) type: Array of DAGLink instances or Array of DAGLink instances in its json format (link.toJSON) +- `serializedSize`- (optional) type: Number of bytes the serialized node has. If none is given, it will automatically be calculated. Create a DAGNode. ```JavaScript -const dagNode = DAGNode.create('data', links) +const dagNode = new DAGNode('data', links) ``` links can be a single or an array of DAGLinks instances or objects with the following pattern diff --git a/src/dag-node/addLink.js b/src/dag-node/addLink.js index 6ef9ba6..a39235a 100644 --- a/src/dag-node/addLink.js +++ b/src/dag-node/addLink.js @@ -1,7 +1,7 @@ 'use strict' -const sort = require('stable') -const { linkSort, toDAGLink } = require('./util') +const sortLinks = require('./sortLinks') +const toDAGLink = require('./toDagLink') const DAGLink = require('../dag-link') const DAGNode = require('./index') @@ -27,7 +27,7 @@ const asDAGLink = async (link) => { const addLink = async (node, link) => { const dagLink = await asDAGLink(link) node._links.push(dagLink) - node._links = sort(node._links, linkSort) + node._links = sortLinks(node._links) } module.exports = addLink diff --git a/src/dag-node/create.js b/src/dag-node/create.js deleted file mode 100644 index e93d8f8..0000000 --- a/src/dag-node/create.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict' - -const sort = require('stable') -const { - serialize -} = require('../util') -const dagNodeUtil = require('./util') -const linkSort = dagNodeUtil.linkSort -const DAGNode = require('./dagNode') -const DAGLink = require('../dag-link/dagLink') - -const create = (data, links = []) => { - if (typeof data === 'string') { - data = Buffer.from(data) - } - - if (!Buffer.isBuffer(data)) { - throw new Error('Passed \'data\' is not a buffer or a string!') - } - links = links.map((link) => { - return DAGLink.isDAGLink(link) ? link : DAGLink.util.createDagLinkFromB58EncodedHash(link) - }) - links = sort(links, linkSort) - - const serialized = serialize({ - Data: data, - Links: links - }) - - return new DAGNode(data, links, serialized.length) -} - -module.exports = create diff --git a/src/dag-node/dagNode.js b/src/dag-node/dagNode.js index 7db05f0..168e044 100644 --- a/src/dag-node/dagNode.js +++ b/src/dag-node/dagNode.js @@ -1,16 +1,38 @@ 'use strict' -const assert = require('assert') const withIs = require('class-is') +const sortLinks = require('./sortLinks') const visibility = require('../visibility') +const DAGLink = require('../dag-link/dagLink') +const { serializeDAGNode } = require('../serialize.js') class DAGNode { - constructor (data, links, serializedSize) { - if (serializedSize !== 0) { - assert(serializedSize, 'A DAGNode requires it\'s serialized size') + constructor (data, links = [], serializedSize = 0) { + if (!data) { + data = Buffer.alloc(0) + } + if (typeof data === 'string') { + data = Buffer.from(data) + } + if (!Buffer.isBuffer(data)) { + throw new Error('Passed \'data\' is not a buffer or a string!') + } + + links = links.map((link) => { + return DAGLink.isDAGLink(link) + ? link + : DAGLink.util.createDagLinkFromB58EncodedHash(link) + }) + links = sortLinks(links) + + if (serializedSize === 0) { + serializedSize = serializeDAGNode({ + Data: data, + Links: links + }).length } - this._data = data || Buffer.alloc(0) + this._data = data this._links = links this._serializedSize = serializedSize diff --git a/src/dag-node/index.js b/src/dag-node/index.js index 37ab625..4e0ef58 100644 --- a/src/dag-node/index.js +++ b/src/dag-node/index.js @@ -1,6 +1,5 @@ 'use strict' exports = module.exports = require('./dagNode') -exports.create = require('./create') exports.addLink = require('./addLink') exports.rmLink = require('./rmLink') diff --git a/src/dag-node/sortLinks.js b/src/dag-node/sortLinks.js new file mode 100644 index 0000000..df4ba2c --- /dev/null +++ b/src/dag-node/sortLinks.js @@ -0,0 +1,13 @@ +'use strict' + +const sort = require('stable') + +const linkSort = (a, b) => { + return Buffer.compare(a.nameAsBuffer, b.nameAsBuffer) +} + +const sortLinks = (links) => { + return sort(links, linkSort) +} + +module.exports = sortLinks diff --git a/src/dag-node/util.js b/src/dag-node/toDagLink.js similarity index 58% rename from src/dag-node/util.js rename to src/dag-node/toDagLink.js index ab1d943..a9ef33d 100644 --- a/src/dag-node/util.js +++ b/src/dag-node/toDagLink.js @@ -1,16 +1,7 @@ 'use strict' const DAGLink = require('./../dag-link/dagLink') -const { - cid, - serialize -} = require('../util') - -exports = module.exports - -function linkSort (a, b) { - return Buffer.compare(a.nameAsBuffer, b.nameAsBuffer) -} +const { cid, serialize } = require('../util') /* * toDAGLink converts a DAGNode to a DAGLink @@ -21,5 +12,4 @@ const toDAGLink = async (node, options = {}) => { return new DAGLink(options.name || '', serialized.length, nodeCid) } -exports.linkSort = linkSort -exports.toDAGLink = toDAGLink +module.exports = toDAGLink diff --git a/src/serialize.js b/src/serialize.js new file mode 100644 index 0000000..690c3f1 --- /dev/null +++ b/src/serialize.js @@ -0,0 +1,64 @@ +'use strict' + +const protons = require('protons') +const proto = protons(require('./dag.proto.js')) +const DAGLink = require('./dag-link/dagLink') + +exports = module.exports + +const toProtoBuf = (node) => { + const pbn = {} + + if (node.Data && node.Data.length > 0) { + pbn.Data = node.Data + } else { + // NOTE: this has to be null in order to match go-ipfs serialization + // `null !== new Buffer(0)` + pbn.Data = null + } + + if (node.Links && node.Links.length > 0) { + pbn.Links = node.Links + .map((link) => ({ + Hash: link.Hash.buffer, + Name: link.Name, + Tsize: link.Tsize + })) + } else { + pbn.Links = null + } + + return pbn +} + +/** + * Serialize internal representation into a binary PB block. + * + * @param {Object} node - Internal representation of a CBOR block + * @returns {Buffer} - The encoded binary representation + */ +const serializeDAGNode = (node) => { + const data = node.Data + const links = node.Links || [] + + const serialized = proto.PBNode.encode(toProtoBuf({ + Data: data, + Links: links + })) + + return serialized +} + +// Serialize an object where the `Links` might not be a `DAGLink` instance yet +const serializeDAGNodeLike = (data, links = []) => { + const node = { Data: data } + node.Links = links.map((link) => { + return DAGLink.isDAGLink(link) + ? link + : DAGLink.util.createDagLinkFromB58EncodedHash(link) + }) + return serializeDAGNode(node) +} + +exports.serializeDAGNode = serializeDAGNode +exports.serializeDAGNodeLike = serializeDAGNodeLike diff --git a/src/util.js b/src/util.js index 0ba8544..9dfeec5 100644 --- a/src/util.js +++ b/src/util.js @@ -7,6 +7,7 @@ const DAGLink = require('./dag-link/dagLink') const DAGNode = require('./dag-node/dagNode') const multicodec = require('multicodec') const multihashing = require('multihashing-async') +const { serializeDAGNode, serializeDAGNodeLike } = require('./serialize') exports = module.exports @@ -40,22 +41,11 @@ const cid = async (binaryBlob, userOptions) => { * @returns {Buffer} - The encoded binary representation */ const serialize = (node) => { - const data = node.Data - let links = node.Links || [] - - // If the node is not an instance of a DAGNode, the link.hash might be a Base58 encoded string; decode it - if (!DAGNode.isDAGNode(node) && links) { - links = links.map((link) => { - return DAGLink.isDAGLink(link) ? link : DAGLink.util.createDagLinkFromB58EncodedHash(link) - }) + if (DAGNode.isDAGNode(node)) { + return serializeDAGNode(node) + } else { + return serializeDAGNodeLike(node.Data, node.Links) } - - const serialized = proto.PBNode.encode(toProtoBuf({ - Data: data, - Links: links - })) - - return serialized } /** @@ -76,30 +66,6 @@ const deserialize = (buffer) => { return new DAGNode(data, links, buffer.length) } -function toProtoBuf (node) { - const pbn = {} - - if (node.Data && node.Data.length > 0) { - pbn.Data = node.Data - } else { - // NOTE: this has to be null in order to match go-ipfs serialization `null !== new Buffer(0)` - pbn.Data = null - } - - if (node.Links && node.Links.length > 0) { - pbn.Links = node.Links - .map((link) => ({ - Hash: link.Hash.buffer, - Name: link.Name, - Tsize: link.Tsize - })) - } else { - pbn.Links = null - } - - return pbn -} - exports.serialize = serialize exports.deserialize = deserialize exports.cid = cid diff --git a/test/dag-node-test.js b/test/dag-node-test.js index 64a1278..5d4548a 100644 --- a/test/dag-node-test.js +++ b/test/dag-node-test.js @@ -9,7 +9,7 @@ chai.use(dirtyChai) const dagPB = require('../src') const DAGLink = dagPB.DAGLink const DAGNode = dagPB.DAGNode -const toDAGLink = require('../src/dag-node/util').toDAGLink +const toDAGLink = require('../src/dag-node/toDagLink') const isNode = require('detect-node') const multihash = require('multihashes') const multicodec = require('multicodec') @@ -30,7 +30,7 @@ module.exports = (repo) => { it('create a node', () => { const data = Buffer.from('some data') - const node = DAGNode.create(data) + const node = new DAGNode(data) expect(node.Data.length).to.be.above(0) expect(Buffer.isBuffer(node.Data)).to.be.true() expect(node.size).to.be.above(0) @@ -43,7 +43,7 @@ module.exports = (repo) => { it('create a node with string data', () => { const data = 'some data' - const node = DAGNode.create(data) + const node = new DAGNode(data) expect(node.Data.length).to.be.above(0) expect(Buffer.isBuffer(node.Data)).to.be.true() expect(node.size).to.be.above(0) @@ -67,12 +67,12 @@ module.exports = (repo) => { const someData = Buffer.from('some data') - const node1 = DAGNode.create(someData, l1) + const node1 = new DAGNode(someData, l1) const l2 = l1.map((l) => { return new DAGLink(l.Name, l.Tsize, l.Hash) }) - const node2 = DAGNode.create(someData, l2) + const node2 = new DAGNode(someData, l2) expect(node2.Links).to.eql([l1[1], l1[0]]) expect(node1.toJSON()).to.eql(node2.toJSON()) @@ -84,14 +84,14 @@ module.exports = (repo) => { }) it('create with empty link name', () => { - const node = DAGNode.create(Buffer.from('hello'), [ + const node = new DAGNode(Buffer.from('hello'), [ new DAGLink('', 10, 'QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39U') ]) expect(node.Links[0].Name).to.be.eql('') }) it('create with undefined link name', () => { - const node = DAGNode.create(Buffer.from('hello'), [ + const node = new DAGNode(Buffer.from('hello'), [ new DAGLink(undefined, 10, 'QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39U') ]) expect(node.Links[0].Name).to.be.eql('') @@ -104,7 +104,7 @@ module.exports = (repo) => { it('create an empty node', () => { // this node is not in the repo as we don't copy node data to the browser - const node = DAGNode.create(Buffer.alloc(0)) + const node = new DAGNode(Buffer.alloc(0)) expect(node.Data.length).to.be.equal(0) expect(Buffer.isBuffer(node.Data)).to.be.true() expect(node.size).to.be.equal(0) @@ -115,17 +115,17 @@ module.exports = (repo) => { }) it('fail to create a node with other data types', () => { - expect(() => DAGNode.create({})).to.throw( + expect(() => new DAGNode({})).to.throw( 'Passed \'data\' is not a buffer or a string!' ) - expect(() => DAGNode.create([])).to.throw( + expect(() => new DAGNode([])).to.throw( 'Passed \'data\' is not a buffer or a string!' ) }) it('addLink by DAGNode', async () => { - const node1 = DAGNode.create(Buffer.from('1')) - const node2 = DAGNode.create(Buffer.from('2')) + const node1 = new DAGNode(Buffer.from('1')) + const node2 = new DAGNode(Buffer.from('2')) await DAGNode.addLink(node1, node2) expect(node1.Links.length).to.equal(1) expect(node1.Links[0].Tsize).to.eql(node2.size) @@ -133,8 +133,8 @@ module.exports = (repo) => { }) it('addLink by DAGLink', async () => { - const node1 = DAGNode.create(Buffer.from('1')) - const node2 = DAGNode.create(Buffer.from('2')) + const node1 = new DAGNode(Buffer.from('1')) + const node2 = new DAGNode(Buffer.from('2')) const link = await toDAGLink(node2) await DAGNode.addLink(node1, link) expect(node1.Links.length).to.equal(1) @@ -143,8 +143,8 @@ module.exports = (repo) => { }) it('addLink by object', async () => { - const node1 = DAGNode.create(Buffer.from('1')) - const node2 = DAGNode.create(Buffer.from('2')) + const node1 = new DAGNode(Buffer.from('1')) + const node2 = new DAGNode(Buffer.from('2')) const link = await toDAGLink(node2) const linkObject = link.toJSON() await DAGNode.addLink(node1, linkObject) @@ -154,8 +154,8 @@ module.exports = (repo) => { }) it('addLink by name', async () => { - const node1 = DAGNode.create(Buffer.from('1')) - const node2 = DAGNode.create(Buffer.from('2')) + const node1 = new DAGNode(Buffer.from('1')) + const node2 = new DAGNode(Buffer.from('2')) const link = await toDAGLink(node2, { name: 'banana' }) expect(node1.Links.length).to.equal(0) await DAGNode.addLink(node1, link) @@ -165,22 +165,22 @@ module.exports = (repo) => { }) it('addLink - add several links', async () => { - const node1 = DAGNode.create(Buffer.from('1')) + const node1 = new DAGNode(Buffer.from('1')) expect(node1.Links.length).to.equal(0) - const node2 = DAGNode.create(Buffer.from('2')) + const node2 = new DAGNode(Buffer.from('2')) await DAGNode.addLink(node1, node2) expect(node1.Links.length).to.equal(1) - const node3 = DAGNode.create(Buffer.from('3')) + const node3 = new DAGNode(Buffer.from('3')) await DAGNode.addLink(node1, node3) expect(node1.Links.length).to.equal(2) }) it('addLink by DAGNode.Links', async () => { const linkName = 'link-name' - const remote = DAGNode.create(Buffer.from('2')) - const source = DAGNode.create(Buffer.from('1')) + const remote = new DAGNode(Buffer.from('2')) + const source = new DAGNode(Buffer.from('1')) await DAGNode.addLink( source, await toDAGLink(remote, { @@ -199,11 +199,11 @@ module.exports = (repo) => { }) it('rmLink by name', async () => { - const node1 = DAGNode.create(Buffer.from('1')) + const node1 = new DAGNode(Buffer.from('1')) expect(node1.Links.length).to.eql(0) const withoutLink = node1.toJSON() - const node2 = DAGNode.create(Buffer.from('2')) + const node2 = new DAGNode(Buffer.from('2')) const link = await toDAGLink(node2, { name: 'banana' }) await DAGNode.addLink(node1, link) @@ -214,11 +214,11 @@ module.exports = (repo) => { }) it('rmLink by hash', async () => { - const node1 = DAGNode.create(Buffer.from('1')) + const node1 = new DAGNode(Buffer.from('1')) expect(node1.Links.length).to.eql(0) const withoutLink = node1.toJSON() - const node2 = DAGNode.create(Buffer.from('2')) + const node2 = new DAGNode(Buffer.from('2')) const link = await toDAGLink(node2, { name: 'banana' }) await DAGNode.addLink(node1, link) @@ -229,7 +229,7 @@ module.exports = (repo) => { }) it('get node CID', async () => { - const node = DAGNode.create(Buffer.from('some data')) + const node = new DAGNode(Buffer.from('some data')) const serialized = dagPB.util.serialize(node) const cid = await dagPB.util.cid(serialized) expect(cid.multihash).to.exist() @@ -240,7 +240,7 @@ module.exports = (repo) => { }) it('get node CID with version', async () => { - const node = DAGNode.create(Buffer.from('some data')) + const node = new DAGNode(Buffer.from('some data')) const serialized = dagPB.util.serialize(node) const cid = await dagPB.util.cid(serialized, { cidVersion: 0 }) expect(cid.multihash).to.exist() @@ -251,7 +251,7 @@ module.exports = (repo) => { }) it('get node CID with hashAlg', async () => { - const node = DAGNode.create(Buffer.from('some data')) + const node = new DAGNode(Buffer.from('some data')) const serialized = dagPB.util.serialize(node) const cid = await dagPB.util.cid(serialized, { hashAlg: multicodec.SHA2_512 }) expect(cid.multihash).to.exist() @@ -262,7 +262,7 @@ module.exports = (repo) => { }) it('marshal a node and store it with block-service', async () => { - const node = DAGNode.create(Buffer.from('some data')) + const node = new DAGNode(Buffer.from('some data')) const serialized = dagPB.util.serialize(node) const cid = await dagPB.util.cid(serialized) const block = new Block(Buffer.from(serialized), cid) @@ -372,7 +372,7 @@ module.exports = (repo) => { }) it('dagNode.toJSON with empty Node', () => { - const node = DAGNode.create(Buffer.alloc(0)) + const node = new DAGNode(Buffer.alloc(0)) expect(node.toJSON().data).to.eql(Buffer.alloc(0)) expect(node.toJSON().links).to.eql([]) expect(node.toJSON().size).to.exist() @@ -380,7 +380,7 @@ module.exports = (repo) => { it('dagNode.toJSON with data no links', () => { const data = Buffer.from('La cucaracha') - const node = DAGNode.create(data) + const node = new DAGNode(data) expect(node.toJSON().data).to.eql(data) expect(node.toJSON().links).to.eql([]) expect(node.toJSON().size).to.exist() @@ -404,12 +404,12 @@ module.exports = (repo) => { const link2 = new DAGLink(l2.Name, l2.Tsize, Buffer.from(bs58.decode(l2.Hash))) - const node = DAGNode.create(Buffer.from('hiya'), [link1, link2]) + const node = new DAGNode(Buffer.from('hiya'), [link1, link2]) expect(node.Links).to.have.lengthOf(2) }) it('toString', () => { - const node = DAGNode.create(Buffer.from('hello world')) + const node = new DAGNode(Buffer.from('hello world')) const expected = 'DAGNode