Skip to content
This repository has been archived by the owner on Aug 11, 2021. It is now read-only.

feat: new IPLD Format API #124

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,20 @@
"npm": ">=3.0.0"
},
"dependencies": {
"async": "^2.6.1",
"cids": "~0.5.4",
"class-is": "^1.1.0",
"merge-options": "^1.0.1",
"multicodec": "~0.5.0",
"multihashing-async": "~0.5.1",
"protons": "^1.0.1",
"stable": "~0.1.8"
},
"devDependencies": {
"aegir": "^18.2.0",
"async": "^2.6.2",
"bs58": "^4.0.1",
"chai": "^4.1.2",
"chai-checkmark": "^1.0.1",
"chai-as-promised": "^7.1.1",
"detect-node": "^2.0.4",
"dirty-chai": "^2.0.1",
"ipfs-block": "~0.8.0",
Expand Down
25 changes: 15 additions & 10 deletions src/dag-link/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const CID = require('cids')
const assert = require('assert')
const withIs = require('class-is')
const visibility = require('../visibility')

// Link represents an IPFS Merkle DAG Link between Nodes.
class DAGLink {
Expand All @@ -16,25 +17,29 @@ class DAGLink {
this._nameBuf = null
this._size = size
this._cid = new CID(cid)

// Make sure we have a nice public API that can be used by an IPLD resolver
visibility.hidePrivateFields(this)
visibility.addEnumerableGetters(this, ['Hash', 'Name', 'Tsize'])
}

toString () {
return `DAGLink <${this._cid.toBaseEncodedString()} - name: "${this.name}", size: ${this.size}>`
return `DAGLink <${this._cid.toBaseEncodedString()} - name: "${this.Name}", size: ${this.Tsize}>`
}

toJSON () {
if (!this._json) {
this._json = Object.freeze({
name: this.name,
size: this.size,
cid: this._cid.toBaseEncodedString()
name: this.Name,
Copy link
Member

Choose a reason for hiding this comment

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

Should these keys get the go-name treatment too? E.g.

this._json = Object.freeze({
  Name: this.Name,
  Tsize: this.Tsize,
  // etc

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought about this, but I'm unsure. What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

I would, for consistency - we can remove some of the mapping steps from the HTTP API endpoints then too.

size: this.Tsize,
cid: this.Hash.toBaseEncodedString()
})
}

return Object.assign({}, this._json)
}

get name () {
get Name () {
return this._name
}

Expand All @@ -50,23 +55,23 @@ class DAGLink {
return this._nameBuf
}

set name (name) {
set Name (name) {
throw new Error("Can't set property: 'name' is immutable")
}

get size () {
get Tsize () {
return this._size
}

set size (size) {
set Tsize (size) {
throw new Error("Can't set property: 'size' is immutable")
}

get cid () {
get Hash () {
return this._cid
}

set cid (cid) {
set Hash (cid) {
throw new Error("Can't set property: 'cid' is immutable")
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/dag-link/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const DAGLink = require('./index')
function createDagLinkFromB58EncodedHash (link) {
return new DAGLink(
link.name ? link.name : link.Name,
link.size ? link.size : link.Size,
link.size ? link.size : link.Tsize,
link.hash || link.Hash || link.multihash || link.cid
)
}
Expand Down
26 changes: 8 additions & 18 deletions src/dag-node/addLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,30 @@ const DAGLink = require('../dag-link')
const DAGNode = require('./index')
const create = require('./create')

function asDAGLink (link, callback) {
const asDAGLink = async (link) => {
if (DAGLink.isDAGLink(link)) {
// It's a DAGLink instance
// no need to do anything

return callback(null, link)
return link
}

if (DAGNode.isDAGNode(link)) {
// It's a DAGNode instance
// convert to link
return toDAGLink(link, {}, callback)
return toDAGLink(link, {})
}

// It's a Object with name, multihash/hash/cid and size
try {
callback(null, new DAGLink(link.name, link.size, link.multihash || link.hash || link.cid))
} catch (err) {
return callback(err)
}
return new DAGLink(link.name, link.size, link.multihash || link.hash || link.cid)
}

function addLink (node, link, callback) {
const addLink = async (node, link) => {
const links = cloneLinks(node)
const data = cloneData(node)

asDAGLink(link, (error, link) => {
if (error) {
return callback(error)
}

links.push(link)
create(data, links, callback)
})
const dagLink = await asDAGLink(link)
links.push(dagLink)
return create(data, links)
}

module.exports = addLink
25 changes: 25 additions & 0 deletions src/dag-node/addNamedLink.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict'

/**
* Adds a link with its name as property to an object.
*
* The link won't be added if its name is empty or matches one of the existing
* properties.
*
* @param {Object} object - The object that contains an array of links
* @param {string} name - The name of the link to add
* @param {numner} position - The position within the array of links
*/
const addNamedLink = (object, name, position) => {
const skipNames = ['', ...Object.keys(this)]
if (skipNames.includes(name)) {
return
}
Object.defineProperty(object, name, {
enumerable: true,
configurable: true,
get: () => object._links[position]
})
}

module.exports = addNamedLink
27 changes: 8 additions & 19 deletions src/dag-node/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,25 @@ const linkSort = dagNodeUtil.linkSort
const DAGNode = require('./index.js')
const DAGLink = require('../dag-link')

function create (data, links, callback) {
if (typeof data === 'function') {
callback = data
data = undefined
} else if (typeof data === 'string') {
const create = async (data, links = []) => {
if (typeof data === 'string') {
data = Buffer.from(data)
}
if (typeof links === 'function') {
callback = links
links = []
}

if (!Buffer.isBuffer(data)) {
return callback(new Error('Passed \'data\' is not a buffer or a string!'))
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)

serialize({
data, links
}, (err, buffer) => {
if (err) {
return callback(err)
}

return callback(null, new DAGNode(data, links, buffer.length))
const serialized = await serialize({
Data: data,
Links: links
})

return new DAGNode(data, links, serialized.length)
}

module.exports = create
59 changes: 38 additions & 21 deletions src/dag-node/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

const assert = require('assert')
const withIs = require('class-is')
const addNamedLink = require('./addNamedLink')
const visibility = require('../visibility')

class DAGNode {
constructor (data, links, serializedSize) {
Expand All @@ -10,15 +12,26 @@ class DAGNode {
}

this._data = data || Buffer.alloc(0)
this._links = links || []
this._links = links
this._serializedSize = serializedSize

// Make sure we have a nice public API that can be used by an IPLD resolver
visibility.hidePrivateFields(this)
visibility.addEnumerableGetters(this, ['Data', 'Links'])

// Add getters for existing links by the name of the link
// This is how paths are traversed in IPFS. Links with names won't
// override existing fields like `data` or `links`.
links.forEach((link, position) => {
addNamedLink(this, link.Name, position)
})
}

toJSON () {
if (!this._json) {
this._json = Object.freeze({
data: this.data,
links: this.links.map((l) => l.toJSON()),
data: this.Data,
links: this._links.map((l) => l.toJSON()),
size: this.size
})
}
Expand All @@ -27,28 +40,12 @@ class DAGNode {
}

toString () {
return `DAGNode <data: "${this.data.toString('base64')}", links: ${this.links.length}, size: ${this.size}>`
}

get data () {
return this._data
}

set data (data) {
throw new Error("Can't set property: 'data' is immutable")
}

get links () {
return this._links
}

set links (links) {
throw new Error("Can't set property: 'links' is immutable")
return `DAGNode <data: "${this.Data.toString('base64')}", links: ${this.Links.length}, size: ${this.size}>`
}

get size () {
if (this._size === undefined) {
this._size = this.links.reduce((sum, l) => sum + l.size, this._serializedSize)
this._size = this._links.reduce((sum, l) => sum + l.Tsize, this._serializedSize)
}

return this._size
Expand All @@ -57,6 +54,26 @@ class DAGNode {
set size (size) {
throw new Error("Can't set property: 'size' is immutable")
}

// Getters for backwards compatible path resolving
get Data () {
return this._data
}
set Data (_) {
throw new Error("Can't set property: 'Data' is immutable")
}
get Links () {
return this._links.map((link) => {
return {
Name: link.Name,
Tsize: link.Tsize,
Hash: link.Hash
}
})
}
set Links (_) {
throw new Error("Can't set property: 'Links' is immutable")
}
}

exports = module.exports = withIs(DAGNode, { className: 'DAGNode', symbolName: '@ipld/js-ipld-dag-pb/dagnode' })
Expand Down
10 changes: 5 additions & 5 deletions src/dag-node/rmLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ const cloneData = dagNodeUtil.cloneData
const create = require('./create')
const CID = require('cids')

function rmLink (dagNode, nameOrCid, callback) {
const rmLink = async (dagNode, nameOrCid) => {
const data = cloneData(dagNode)
let links = cloneLinks(dagNode)

if (typeof nameOrCid === 'string') {
links = links.filter((link) => link.name !== nameOrCid)
links = links.filter((link) => link.Name !== nameOrCid)
} else if (Buffer.isBuffer(nameOrCid) || CID.isCID(nameOrCid)) {
links = links.filter((link) => !link.cid.equals(nameOrCid))
links = links.filter((link) => !link.Hash.equals(nameOrCid))
} else {
return callback(new Error('second arg needs to be a name or CID'), null)
throw new Error('second arg needs to be a name or CID')
}

create(data, links, callback)
return create(data, links)
}

module.exports = rmLink
Loading