Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Types & latest multiformats #6

Merged
merged 1 commit into from
Apr 26, 2021
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/mikeals-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x, 14.x]
node-version: [12.x, 14.x, 16.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
Expand Down
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
67 changes: 48 additions & 19 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": "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",
"publish": "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",
"coverage": "c8 --reporter=html mocha test/test-*.js && npx st -d coverage -p 8080"
"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 && npm_config_yes=true 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": "^7.0.0"
},
"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",
"hundreds": "^0.0.9",
"ipjs": "^5.0.0",
"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>"
}
71 changes: 61 additions & 10 deletions index.js → src/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
import CID from 'multiformats/cid'
import decodeNode from './pb-decode.js'
import encodeNode from './pb-encode.js'
import { CID } from 'multiformats/cid'
import { decodeNode } from './pb-decode.js'
import { encodeNode } from './pb-encode.js'

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

/**
* @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 +45,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 +109,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 +122,7 @@ function prepare (node) {
throw new TypeError('Invalid DAG-PB form')
}

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

if (node.Data) {
Expand All @@ -113,7 +143,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 +189,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 +208,11 @@ function validate (node) {
}
}

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

const pbn = {}
Expand All @@ -200,7 +238,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 +273,13 @@ function decode (bytes) {
return node
}

export { name, code, encode, decode, prepare, validate }
/**
* @template T
* @type {BlockCodec<0x70, PBNode>}
*/
export const { name, code, decode, encode } = {
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