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

Commit 970df33

Browse files
committed
feat: new IPLD Format API
BREAKING CHANGE: The API is now async/await based There are numerous changes, the most significant one is that the API is no longer callback based, but it using async/await. For the full new API please see the [IPLD Formats spec]. [IPLD Formats spec]: https://github.com/ipld/interface-ipld-format
1 parent 784c464 commit 970df33

13 files changed

+305
-525
lines changed

README.md

+2-5
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,8 @@ const zlib = require('zlib')
6868

6969
// `gitObject` is a Buffer containing a git object
7070
inflatedObject = zlib.inflateSync(gitObject)
71-
IpldGit.util.deserialize(inflatedObject, (err, dagNode) => {
72-
if (err) throw err
73-
console.log(dagNode)
74-
})
75-
71+
const dagNode = IpldGit.util.deserialize(inflatedObject)
72+
console.log(dagNode)
7673
```
7774

7875
## Contribute

package.json

+3-6
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,18 @@
3636
},
3737
"homepage": "https://github.com/ipld/js-ipld-git",
3838
"dependencies": {
39-
"async": "^2.6.2",
4039
"cids": "~0.6.0",
4140
"multicodec": "~0.5.0",
4241
"multihashes": "~0.4.14",
43-
"multihashing-async": "~0.6.0",
42+
"multihashing-async": "~0.7.0",
4443
"smart-buffer": "^4.0.2",
45-
"traverse": "~0.6.6",
4644
"strftime": "~0.10.0"
4745
},
4846
"devDependencies": {
4947
"aegir": "^18.2.1",
5048
"chai": "^4.2.0",
51-
"deep-freeze": "0.0.1",
52-
"dirty-chai": "^2.0.1",
53-
"garbage": "0.0.0"
49+
"chai-as-promised": "^7.1.1",
50+
"dirty-chai": "^2.0.1"
5451
},
5552
"contributors": [
5653
"Alan Shaw <[email protected]>",

src/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22

33
exports.util = require('./util.js')
44
exports.resolver = require('./resolver.js')
5+
exports.codec = exports.util.codec
6+
exports.defaultHashAlg = exports.util.defaultHashAlg

src/resolver.js

+55-138
Original file line numberDiff line numberDiff line change
@@ -1,153 +1,70 @@
11
'use strict'
22

3-
const util = require('./util')
4-
const traverse = require('traverse')
5-
6-
exports = module.exports
7-
8-
exports.multicodec = 'git-raw'
9-
exports.defaultHashAlg = 'sha1'
3+
const CID = require('cids')
104

11-
const personInfoPaths = [
12-
'original',
13-
'name',
14-
'email',
15-
'date'
16-
]
17-
18-
exports.resolve = (binaryBlob, path, callback) => {
19-
if (typeof path === 'function') {
20-
callback = path
21-
path = undefined
22-
}
23-
24-
util.deserialize(binaryBlob, (err, node) => {
25-
if (err) {
26-
return callback(err)
27-
}
5+
const util = require('./util')
286

29-
if (!path || path === '/') {
30-
return callback(null, {
31-
value: node,
32-
remainderPath: ''
33-
})
7+
/**
8+
* Resolves a path within a Git block.
9+
*
10+
* Returns the value or a link and the partial mising path. This way the
11+
* IPLD Resolver can fetch the link and continue to resolve.
12+
*
13+
* @param {Buffer} binaryBlob - Binary representation of a Git block
14+
* @param {string} [path='/'] - Path that should be resolved
15+
* @returns {Object} result - Result of the path it it was resolved successfully
16+
* @returns {*} result.value - Value the path resolves to
17+
* @returns {string} result.remainderPath - If the path resolves half-way to a
18+
* link, then the `remainderPath` is the part after the link that can be used
19+
* for further resolving
20+
*/
21+
exports.resolve = (binaryBlob, path) => {
22+
let node = util.deserialize(binaryBlob)
23+
24+
const parts = path.split('/').filter(Boolean)
25+
while (parts.length) {
26+
const key = parts.shift()
27+
if (node[key] === undefined) {
28+
throw new Error(`Object has no property '${key}'`)
3429
}
3530

36-
if (Buffer.isBuffer(node)) { // git blob
37-
return callback(null, {
31+
node = node[key]
32+
if (CID.isCID(node)) {
33+
return {
3834
value: node,
39-
remainderPath: path
40-
})
41-
}
42-
43-
const parts = path.split('/')
44-
const val = traverse(node).get(parts)
45-
46-
if (val) {
47-
return callback(null, {
48-
value: val,
49-
remainderPath: ''
50-
})
51-
}
52-
53-
let value
54-
let len = parts.length
55-
56-
for (let i = 0; i < len; i++) {
57-
const partialPath = parts.shift()
58-
59-
if (Array.isArray(node)) {
60-
value = node[Number(partialPath)]
61-
} if (node[partialPath]) {
62-
value = node[partialPath]
63-
} else {
64-
// can't traverse more
65-
if (!value) {
66-
return callback(new Error('path not available at root'))
67-
} else {
68-
parts.unshift(partialPath)
69-
return callback(null, {
70-
value: value,
71-
remainderPath: parts.join('/')
72-
})
73-
}
35+
remainderPath: parts.join('/')
7436
}
75-
node = value
7637
}
77-
})
78-
}
79-
80-
exports.tree = (binaryBlob, options, callback) => {
81-
if (typeof options === 'function') {
82-
callback = options
83-
options = undefined
8438
}
8539

86-
options = options || {}
87-
88-
util.deserialize(binaryBlob, (err, node) => {
89-
if (err) {
90-
return callback(err)
91-
}
92-
93-
if (Buffer.isBuffer(node)) { // git blob
94-
return callback(null, [])
95-
}
96-
97-
let paths = []
98-
switch (node.gitType) {
99-
case 'commit':
100-
paths = [
101-
'message',
102-
'tree'
103-
]
104-
105-
paths = paths.concat(personInfoPaths.map((e) => 'author/' + e))
106-
paths = paths.concat(personInfoPaths.map((e) => 'committer/' + e))
107-
paths = paths.concat(node.parents.map((_, e) => 'parents/' + e))
108-
109-
if (node.encoding) {
110-
paths.push('encoding')
111-
}
112-
break
113-
case 'tag':
114-
paths = [
115-
'object',
116-
'type',
117-
'tag',
118-
'message'
119-
]
120-
121-
if (node.tagger) {
122-
paths = paths.concat(personInfoPaths.map((e) => 'tagger/' + e))
123-
}
124-
125-
break
126-
default: // tree
127-
Object.keys(node).forEach(dir => {
128-
paths.push(dir)
129-
paths.push(dir + '/hash')
130-
paths.push(dir + '/mode')
131-
})
132-
}
133-
callback(null, paths)
134-
})
40+
return {
41+
value: node,
42+
remainderPath: ''
43+
}
13544
}
13645

137-
exports.isLink = (binaryBlob, path, callback) => {
138-
exports.resolve(binaryBlob, path, (err, result) => {
139-
if (err) {
140-
return callback(err)
141-
}
142-
143-
if (result.remainderPath.length > 0) {
144-
return callback(new Error('path out of scope'))
145-
}
46+
const traverse = function * (node, path) {
47+
// Traverse only objects and arrays
48+
if (Buffer.isBuffer(node) || CID.isCID(node) || typeof node === 'string' ||
49+
node === null) {
50+
return
51+
}
52+
for (const item of Object.keys(node)) {
53+
const nextpath = path === undefined ? item : path + '/' + item
54+
yield nextpath
55+
yield * traverse(node[item], nextpath)
56+
}
57+
}
14658

147-
if (typeof result.value === 'object' && result.value['/']) {
148-
callback(null, result.value)
149-
} else {
150-
callback(null, false)
151-
}
152-
})
59+
/**
60+
* Return all available paths of a block.
61+
*
62+
* @generator
63+
* @param {Buffer} binaryBlob - Binary representation of a Bitcoin block
64+
* @yields {string} - A single path
65+
*/
66+
exports.tree = function * (binaryBlob) {
67+
const node = util.deserialize(binaryBlob)
68+
69+
yield * traverse(node)
15370
}

src/util.js

+45-51
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
'use strict'
22

3-
const setImmediate = require('async/setImmediate')
4-
const waterfall = require('async/waterfall')
53
const multihashing = require('multihashing-async')
64
const CID = require('cids')
5+
const multicodec = require('multicodec')
76

8-
const resolver = require('./resolver')
97
const gitUtil = require('./util/util')
108

119
const commit = require('./util/commit')
@@ -14,87 +12,83 @@ const tree = require('./util/tree')
1412

1513
exports = module.exports
1614

17-
exports.serialize = (dagNode, callback) => {
15+
exports.codec = multicodec.GIT_RAW
16+
exports.defaultHashAlg = multicodec.SHA1
17+
18+
/**
19+
* Serialize internal representation into a binary Git block.
20+
*
21+
* @param {GitBlock} dagNode - Internal representation of a Git block
22+
* @returns {Buffer}
23+
*/
24+
exports.serialize = (dagNode) => {
1825
if (dagNode === null) {
19-
setImmediate(() => callback(new Error('dagNode passed to serialize was null'), null))
20-
return
26+
throw new Error('dagNode passed to serialize was null')
2127
}
2228

2329
if (Buffer.isBuffer(dagNode)) {
2430
if (dagNode.slice(0, 4).toString() === 'blob') {
25-
setImmediate(() => callback(null, dagNode))
31+
return dagNode
2632
} else {
27-
setImmediate(() => callback(new Error('unexpected dagNode passed to serialize'), null))
33+
throw new Error('unexpected dagNode passed to serialize')
2834
}
29-
return
3035
}
3136

3237
switch (dagNode.gitType) {
3338
case 'commit':
34-
commit.serialize(dagNode, callback)
35-
break
39+
return commit.serialize(dagNode)
3640
case 'tag':
37-
tag.serialize(dagNode, callback)
38-
break
41+
return tag.serialize(dagNode)
3942
default:
4043
// assume tree as a file named 'type' is legal
41-
tree.serialize(dagNode, callback)
44+
return tree.serialize(dagNode)
4245
}
4346
}
4447

45-
exports.deserialize = (data, callback) => {
48+
/**
49+
* Deserialize Git block into the internal representation.
50+
*
51+
* @param {Buffer} data - Binary representation of a Git block.
52+
* @returns {BitcoinBlock}
53+
*/
54+
exports.deserialize = (data) => {
4655
let headLen = gitUtil.find(data, 0)
4756
let head = data.slice(0, headLen).toString()
4857
let typeLen = head.match(/([^ ]+) (\d+)/)
4958
if (!typeLen) {
50-
setImmediate(() => callback(new Error('invalid object header'), null))
51-
return
59+
throw new Error('invalid object header')
5260
}
5361

5462
switch (typeLen[1]) {
5563
case 'blob':
56-
callback(null, data)
57-
break
64+
return data
5865
case 'commit':
59-
commit.deserialize(data.slice(headLen + 1), callback)
60-
break
66+
return commit.deserialize(data.slice(headLen + 1))
6167
case 'tag':
62-
tag.deserialize(data.slice(headLen + 1), callback)
63-
break
68+
return tag.deserialize(data.slice(headLen + 1))
6469
case 'tree':
65-
tree.deserialize(data.slice(headLen + 1), callback)
66-
break
70+
return tree.deserialize(data.slice(headLen + 1))
6771
default:
68-
setImmediate(() => callback(new Error('unknown object type ' + typeLen[1]), null))
72+
throw new Error('unknown object type ' + typeLen[1])
6973
}
7074
}
7175

7276
/**
73-
* @callback CidCallback
74-
* @param {?Error} error - Error if getting the CID failed
75-
* @param {?CID} cid - CID if call was successful
76-
*/
77-
/**
78-
* Get the CID of the DAG-Node.
77+
* Calculate the CID of the binary blob.
7978
*
80-
* @param {Object} dagNode - Internal representation
81-
* @param {Object} [options] - Options to create the CID
82-
* @param {number} [options.version=1] - CID version number
83-
* @param {string} [options.hashAlg='sha1'] - Hashing algorithm
84-
* @param {CidCallback} callback - Callback that handles the return value
85-
* @returns {void}
79+
* @param {Object} binaryBlob - Encoded IPLD Node
80+
* @param {Object} [userOptions] - Options to create the CID
81+
* @param {number} [userOptions.cidVersion=1] - CID version number
82+
* @param {string} [UserOptions.hashAlg] - Defaults to the defaultHashAlg of the format
83+
* @returns {Promise.<CID>}
8684
*/
87-
exports.cid = (dagNode, options, callback) => {
88-
if (typeof options === 'function') {
89-
callback = options
90-
options = {}
91-
}
92-
options = options || {}
93-
const hashAlg = options.hashAlg || resolver.defaultHashAlg
94-
const version = typeof options.version === 'undefined' ? 1 : options.version
95-
waterfall([
96-
(cb) => exports.serialize(dagNode, cb),
97-
(serialized, cb) => multihashing(serialized, hashAlg, cb),
98-
(mh, cb) => cb(null, new CID(version, resolver.multicodec, mh))
99-
], callback)
85+
exports.cid = async (binaryBlob, userOptions) => {
86+
const defaultOptions = { cidVersion: 1, hashAlg: exports.defaultHashAlg }
87+
const options = Object.assign(defaultOptions, userOptions)
88+
89+
const multihash = await multihashing(binaryBlob, options.hashAlg)
90+
const codecName = multicodec.print[exports.codec]
91+
const cid = new CID(options.cidVersion, codecName, multihash)
92+
93+
return cid
10094
}

0 commit comments

Comments
 (0)