Skip to content

Commit

Permalink
feat!: latest multiformats, introduce full type check & generation
Browse files Browse the repository at this point in the history
  • Loading branch information
rvagg committed Apr 10, 2021
1 parent 63506ba commit a0662d6
Show file tree
Hide file tree
Showing 17 changed files with 411 additions and 60 deletions.
2 changes: 1 addition & 1 deletion example-prepare.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import CID from 'multiformats/cid'
import { CID } from 'multiformats/cid'
import { prepare } from '@ipld/dag-pb'

console.log(prepare({ Data: 'some data' }))
Expand Down
2 changes: 1 addition & 1 deletion example.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import CID from 'multiformats/cid'
import { CID } from 'multiformats/cid'
import { sha256 } from 'multiformats/hashes/sha2'
import * as dagPB from '@ipld/dag-pb'

Expand Down
63 changes: 46 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,41 +1,70 @@
{
"name": "@ipld/dag-pb",
"version": "0.0.0",
"version": "0.0.0-dev",
"description": "JS implementation of DAG-PB",
"main": "./src/index.js",
"types": "./types/src/index.d.ts",
"type": "module",
"exports": {
"import": "./index.js"
},
"scripts": {
"lint": "standard",
"build": "npm_config_yes=true npx ipjs@latest build --tests",
"publish": "npm_config_yes=true npx ipjs@latest publish",
"test:cjs": "npm run build && mocha dist/cjs/node-test/test-*.js",
"build": "npm run build:js && npm run build:types",
"build:js": "npm_config_yes=true ipjs build --tests --main && npm run build:copy",
"build:copy": "cp -a tsconfig.json src *.js dist/ && mkdir -p dist/test && cp test/*.js dist/test/",
"build:types": "npm run build:copy && cd dist && tsc --build",
"check": "tsc --noEmit",
"publish": "npm_config_yes=true ipjs publish",
"test:cjs": "npm run build:js && mocha dist/cjs/node-test/test-*.js && npm run test:cjs:browser",
"test:node": "hundreds mocha test/test-*.js",
"test:browser": "polendina --cleanup dist/cjs/node-test/test-*.js",
"test": "npm run lint && npm run test:node && npm run test:cjs && npm run test:browser",
"test:cjs:browser": "polendina --page --worker --serviceworker --cleanup dist/cjs/node-test/test-*.js",
"test:ts": "npm run build:types && npm run test --prefix test/ts-use",
"test": "npm run lint && npm run test:node && npm run test:cjs && npm run test:ts",
"coverage": "c8 --reporter=html mocha test/test-*.js && npx st -d coverage -p 8080"
},
"exports": {
"import": "./src/index.js"
},
"license": "(Apache-2.0 AND MIT)",
"repository": {
"type": "git",
"url": "https://github.com/ipld/js-dag-pb.git"
},
"keywords": [
"IPFS",
"IPLD"
],
"bugs": {
"url": "https://github.com/ipld/js-dag-pb/issues"
},
"homepage": "https://github.com/ipld/js-dag-pb",
"dependencies": {
"multiformats": "^4.0.0"
"multiformats": "https://gitpkg.now.sh/multiformats/js-multiformats/dist?rvagg/moar-generic-blockcodec"
},
"devDependencies": {
"c8": "^7.3.1",
"@types/chai": "^4.2.16",
"@types/mocha": "^8.2.2",
"chai": "^4.2.0",
"chai-subset": "^1.6.0",
"hundreds": "0.0.8",
"mocha": "^8.1.3",
"polendina": "^1.1.0",
"standard": "^14.3.4"
"standard": "^16.0.3",
"typescript": "^4.2.3"
},
"author": "Rod Vagg <r@va.gg>",
"keywords": [
"IPFS",
"IPLD"
]
"standard": {
"ignore": [
"dist",
"test/ts-use/src/main.js"
]
},
"typesVersions": {
"*": {
"*": [
"types/*"
],
"types/*": [
"types/*"
]
}
},
"author": "Rod Vagg <r@va.gg>"
}
75 changes: 65 additions & 10 deletions index.js → src/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
import CID from 'multiformats/cid'
import decodeNode from './pb-decode.js'
import encodeNode from './pb-encode.js'
import { CID } from 'multiformats/cid'
import { codec } from 'multiformats/codecs/codec'
import { decodeNode } from './pb-decode.js'
import { encodeNode } from './pb-encode.js'

/**
* @template {number} Code
* @template T
* @typedef {import('multiformats/codecs/codec').CodecFeature<Code, T>} CodecFeature
*/

/**
* @typedef {import('./interface').PBLink} PBLink
*/

/**
* @typedef {import('./interface').PBNode} PBNode
*/

const code = 0x70
const name = 'dag-pb'
const pbNodeProperties = ['Data', 'Links']
const pbLinkProperties = ['Hash', 'Name', 'Tsize']

const textEncoder = new TextEncoder()

/**
* @param {PBLink} a
* @param {PBLink} b
* @returns {number}
*/
function linkComparator (a, b) {
if (a === b) {
return 0
Expand All @@ -31,10 +49,21 @@ function linkComparator (a, b) {
return x < y ? -1 : y < x ? 1 : 0
}

/**
* @param {any} node
* @param {string[]} properties
* @returns {boolean}
*/
function hasOnlyProperties (node, properties) {
return !Object.keys(node).some((p) => !properties.includes(p))
}

/**
* Converts a CID, or a PBLink-like object to a PBLink
*
* @param {any} link
* @returns {PBLink}
*/
function asLink (link) {
if (typeof link.asCID === 'object') {
const Hash = CID.asCID(link)
Expand Down Expand Up @@ -84,7 +113,11 @@ function asLink (link) {
return pbl
}

function prepare (node) {
/**
* @param {any} node
* @returns {PBNode}
*/
export function prepare (node) {
if (node instanceof Uint8Array || typeof node === 'string') {
node = { Data: node }
}
Expand All @@ -93,6 +126,7 @@ function prepare (node) {
throw new TypeError('Invalid DAG-PB form')
}

/** @type {PBNode} */
const pbn = {}

if (node.Data) {
Expand All @@ -113,7 +147,10 @@ function prepare (node) {
return pbn
}

function validate (node) {
/**
* @param {PBNode} node
*/
export function validate (node) {
/*
type PBLink struct {
Hash optional Link
Expand Down Expand Up @@ -156,6 +193,7 @@ function validate (node) {
throw new TypeError('Invalid DAG-PB form (link must have a Hash)')
}

// @ts-ignore private property for TS
if (link.Hash.asCID !== link.Hash) {
throw new TypeError('Invalid DAG-PB form (link Hash must be a CID)')
}
Expand All @@ -174,7 +212,11 @@ function validate (node) {
}
}

function encode (node) {
/**
* @param {PBNode} node
* @returns {Uint8Array}
*/
function _encode (node) {
validate(node)

const pbn = {}
Expand All @@ -200,7 +242,11 @@ function encode (node) {
return encodeNode(pbn)
}

function decode (bytes) {
/**
* @param {Uint8Array} bytes
* @returns {PBNode}
*/
function _decode (bytes) {
const pbn = decodeNode(bytes)

const node = {}
Expand Down Expand Up @@ -231,4 +277,13 @@ function decode (bytes) {
return node
}

export { name, code, encode, decode, prepare, validate }
/**
* @template T
* @type {CodecFeature<0x70, PBNode>}
*/
export const { name, code, decode, encode, decoder, encoder } = codec({
name: 'dag-pb',
code: 0x70,
encode: _encode,
decode: _decode
})
34 changes: 34 additions & 0 deletions src/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { CID } from 'multiformats/cid'

/*
PBNode and PBLink match the DAG-PB logical format, as described at:
https://github.com/ipld/specs/blob/master/block-layer/codecs/dag-pb.md#logical-format
*/

export interface PBLink {
Name?: string,
Tsize?: number,
Hash: CID
}

export interface PBNode {
Data?: Uint8Array,
Links: PBLink[]
}

// Raw versions of PBNode and PBLink used internally to deal with the underlying
// encode/decode byte interface.
// A future iteration could make pb-encode.js and pb-decode.js aware of PBNode
// and PBLink specifics (including CID and optionals).

export interface RawPBLink {
Name: string,
Tsize: number,
Hash: Uint8Array
}

export interface RawPBNode {
Data: Uint8Array,
Links: RawPBLink[]
}

39 changes: 36 additions & 3 deletions pb-decode.js → src/pb-decode.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
const textDecoder = new TextDecoder()

/**
* @typedef {import('./interface').RawPBLink} RawPBLink
*/

/**
* @typedef {import('./interface').RawPBNode} RawPBNode
*/

/**
* @param {Uint8Array} bytes
* @param {number} offset
* @returns {[number, number]}
*/
function decodeVarint (bytes, offset) {
let v = 0

Expand All @@ -22,6 +35,11 @@ function decodeVarint (bytes, offset) {
return [v, offset]
}

/**
* @param {Uint8Array} bytes
* @param {number} offset
* @returns {[Uint8Array, number]}
*/
function decodeBytes (bytes, offset) {
let byteLen
;[byteLen, offset] = decodeVarint(bytes, offset)
Expand All @@ -39,14 +57,24 @@ function decodeBytes (bytes, offset) {
return [bytes.subarray(offset, postOffset), postOffset]
}

/**
* @param {Uint8Array} bytes
* @param {number} index
* @returns {[number, number, number]}
*/
function decodeKey (bytes, index) {
let wire
;[wire, index] = decodeVarint(bytes, index)
// [wireType, fieldNum, newIndex]
return [wire & 0x7, wire >> 3, index]
}

/**
* @param {Uint8Array} bytes
* @returns {RawPBLink}
*/
function decodeLink (bytes) {
/** @type {RawPBLink} */
const link = {}
const l = bytes.length
let index = 0
Expand Down Expand Up @@ -106,10 +134,16 @@ function decodeLink (bytes) {
return link
}

function decodeNode (bytes) {
/**
* @param {Uint8Array} bytes
* @returns {RawPBNode}
*/
export function decodeNode (bytes) {
const l = bytes.length
let index = 0
/** @type {RawPBLink[]} */
const links = []
/** @type {Uint8Array|void} */
let data

while (index < l) {
Expand Down Expand Up @@ -144,12 +178,11 @@ function decodeNode (bytes) {
throw new Error('protobuf: (PBNode) unexpected end of data')
}

/** @type {RawPBNode} */
const node = {}
if (data) {
node.Data = data
}
node.Links = links
return node
}

export default decodeNode
Loading

0 comments on commit a0662d6

Please sign in to comment.