diff --git a/README.md b/README.md index 3b97cff4..d0549ebf 100644 --- a/README.md +++ b/README.md @@ -27,17 +27,25 @@ const cid = CID.create(1, dagcbor.code, hash) ``` However, if you're doing this much you should probably use multiformats -with the `Block` API. +with the `dag` API. ```js // Import basics package with dep-free codecs, hashes, and base encodings -import { block } from 'multiformats/basics' +import { dag } from 'multiformats/basics' import { sha256 } from 'multiformats/hashes/sha2' -import dagcbor from '@ipld/dag-cbor' +import cbor from '@ipld/dag-cbor' +import json from '@ipld/dag-json' + +const encoder = dag.encoder({ multicodec: dag.or(json), hasher: sha256 }) -const encoder = block.encoder(dagcbor, { hasher: sha256 }) -const hello = encoder.encode({ hello: 'world' }) +const hello = encoder.encodeBlock({ hello: 'world' }) const cid = await hello.cid() + +const greeting = encoder.encode({ code: json.code, value: { greeting: hello } }) +await greeting.cid() + +const decoder = dag.decoder({ multicodec: dag.or(json), hasher: sha256 }) +decoder.decode({ code: json.code, bytes: greeting.bytes }) ``` # Plugins diff --git a/package.json b/package.json index ae8e6e29..7c5df317 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ } }, "devDependencies": { + "@types/node": "14.11.1", "c8": "^7.3.0", "hundreds": "0.0.7", "mocha": "^8.1.1", diff --git a/src/bases/base64-browser.js b/src/bases/base64-browser.js index 372d81cd..0f37fce1 100644 --- a/src/bases/base64-browser.js +++ b/src/bases/base64-browser.js @@ -4,7 +4,7 @@ import b64 from './base64.js' const { base64, base64pad, base64url, base64urlpad, __browser } = b64({ - encode: b => btoa([].reduce.call(b, (p, c) => p + String.fromCharCode(c), '')), + encode: b => btoa(/** @type {string} */([].reduce.call(b, (p, c) => p + String.fromCharCode(c), ''))), decode: str => Uint8Array.from(atob(str), c => c.charCodeAt(0)), __browser: true }) diff --git a/src/bases/base64-import.js b/src/bases/base64-import.js index 9ab5dd69..1b99bdbb 100644 --- a/src/bases/base64-import.js +++ b/src/bases/base64-import.js @@ -1,6 +1,7 @@ // @ts-check +import { Buffer } from 'buffer' import { coerce } from '../bytes.js' import b64 from './base64.js' diff --git a/src/basics-browser.js b/src/basics-browser.js index 20e9afec..ca7b9d01 100644 --- a/src/basics-browser.js +++ b/src/basics-browser.js @@ -1,8 +1,8 @@ // @ts-check import * as base64 from './bases/base64-browser.js' -import { CID, Block, hasher, digest, varint, bytes, hashes, codecs, bases as _bases } from './basics.js' +import { CID, dag, hasher, digest, varint, bytes, hashes, codecs, bases as _bases } from './basics.js' const bases = { ..._bases, ...base64 } -export { CID, Block, hasher, digest, varint, bytes, hashes, codecs, bases } +export { CID, dag, hasher, digest, varint, bytes, hashes, codecs, bases } diff --git a/src/basics-import.js b/src/basics-import.js index 2421269f..01634d75 100644 --- a/src/basics-import.js +++ b/src/basics-import.js @@ -1,6 +1,6 @@ -import { CID, Block, hasher, digest, varint, bytes, hashes, codecs, bases as _bases } from './basics.js' +import { CID, dag, hasher, digest, varint, bytes, hashes, codecs, bases as _bases } from './basics.js' import * as base64 from './bases/base64-import.js' const bases = { ..._bases, ...base64 } -export { CID, Block, hasher, digest, varint, bytes, hashes, codecs, bases } +export { CID, dag, hasher, digest, varint, bytes, hashes, codecs, bases } diff --git a/src/basics.js b/src/basics.js index 37de1fe8..456312d9 100644 --- a/src/basics.js +++ b/src/basics.js @@ -7,10 +7,10 @@ import * as sha2 from './hashes/sha2.js' import raw from './codecs/raw.js' import json from './codecs/json.js' -import { CID, Block, hasher, digest, varint, bytes } from './index.js' +import { CID, dag, hasher, digest, varint, bytes } from './index.js' const bases = { ...base32, ...base58 } const hashes = { ...sha2 } const codecs = { raw, json } -export { CID, Block, hasher, digest, varint, bytes, hashes, bases, codecs } +export { CID, dag, hasher, digest, varint, bytes, hashes, bases, codecs } diff --git a/src/block.js b/src/block.js deleted file mode 100644 index 3bed5724..00000000 --- a/src/block.js +++ /dev/null @@ -1,308 +0,0 @@ -// @ts-check - -import CID from './cid.js' - -/** - * @template {number} Code - * @template T - * @class - */ -export default class Block { - /** - * @param {CID|null} cid - * @param {Code} code - * @param {T} data - * @param {Uint8Array} bytes - * @param {BlockConfig} config - */ - constructor (cid, code, data, bytes, { hasher }) { - /** @type {CID|Promise|null} */ - this._cid = cid - this.code = code - this.data = data - this.bytes = bytes - this.hasher = hasher - } - - async cid () { - const { _cid: cid } = this - if (cid != null) { - return await cid - } else { - const { bytes, code, hasher } = this - // First we store promise to avoid a race condition if cid is called - // whlie promise is pending. - const promise = createCID(hasher, bytes, code) - this._cid = promise - const cid = await promise - // Once promise resolves we store an actual CID. - this._cid = cid - return cid - } - } - - links () { - return links(this.data, []) - } - - tree () { - return tree(this.data, []) - } - - /** - * @param {string} path - */ - get (path) { - return get(this.data, path.split('/').filter(Boolean)) - } - - /** - * @template {number} Code - * @template T - * @param {Encoder} codec - * @param {BlockConfig} options - */ - static encoder (codec, options) { - return new BlockEncoder(codec, options) - } - - /** - * @template {number} Code - * @template T - * @param {Decoder} codec - * @param {BlockConfig} options - */ - static decoder (codec, options) { - return new BlockDecoder(codec, options) - } - - /** - * @template {number} Code - * @template T - * @param {Object} codec - * @param {Encoder} codec.encoder - * @param {Decoder} codec.decoder - * @param {BlockConfig} options - * @returns {BlockCodec} - */ - - static codec ({ encoder, decoder }, options) { - return new BlockCodec(encoder, decoder, options) - } -} - -/** - * @template T - * @param {T} source - * @param {Array} base - * @returns {Iterable<[string, CID]>} - */ -const links = function * (source, base) { - for (const [key, value] of Object.entries(source)) { - const path = [...base, key] - if (value != null && typeof value === 'object') { - if (Array.isArray(value)) { - for (const [index, element] of value.entries()) { - const elementPath = [...path, index] - const cid = CID.asCID(element) - if (cid) { - yield [elementPath.join('/'), cid] - } else if (typeof element === 'object') { - yield * links(element, elementPath) - } - } - } else { - const cid = CID.asCID(value) - if (cid) { - yield [path.join('/'), cid] - } else { - yield * links(value, path) - } - } - } - } -} - -/** - * @template T - * @param {T} source - * @param {Array} base - * @returns {Iterable} - */ -const tree = function * (source, base) { - for (const [key, value] of Object.entries(source)) { - const path = [...base, key] - yield path.join('/') - if (value != null && typeof value === 'object' && !CID.asCID(value)) { - if (Array.isArray(value)) { - for (const [index, element] of value.entries()) { - const elementPath = [...path, index] - yield elementPath.join('/') - if (typeof element === 'object' && !CID.asCID(element)) { - yield * tree(element, elementPath) - } - } - } else { - yield * tree(value, path) - } - } - } -} - -/** - * @template T - * @param {T} source - * @param {string[]} path - */ -const get = (source, path) => { - let node = source - for (const [index, key] of path.entries()) { - node = node[key] - if (node == null) { - throw new Error(`Object has no property at ${path.slice(0, index - 1).map(part => `[${JSON.stringify(part)}]`).join('')}`) - } - const cid = CID.asCID(node) - if (cid) { - return { value: cid, remaining: path.slice(index).join('/') } - } - } - return { value: node } -} - -/** - * - * @param {Hasher} hasher - * @param {Uint8Array} bytes - * @param {number} code - */ - -const createCID = async (hasher, bytes, code) => { - const multihash = await hasher.digest(bytes) - return CID.createV1(code, multihash) -} - -/** - * @template {number} Code - * @template T - */ -class BlockCodec { - /** - * @param {Encoder} encoder - * @param {Decoder} decoder - * @param {BlockConfig} config - */ - - constructor (encoder, decoder, config) { - this.encoder = new BlockEncoder(encoder, config) - this.decoder = new BlockDecoder(decoder, config) - this.config = config - } - - /** - * @param {Uint8Array} bytes - * @param {Partial} [options] - * @returns {Block} - */ - decode (bytes, options) { - return this.decoder.decode(bytes, { ...this.config, ...options }) - } - - /** - * @param {T} data - * @param {Partial} [options] - * @returns {Block} - */ - encode (data, options) { - return this.encoder.encode(data, { ...this.config, ...options }) - } -} - -/** - * @class - * @template {number} Code - * @template T - */ -class BlockEncoder { - /** - * @param {Encoder} codec - * @param {BlockConfig} config - */ - constructor (codec, config) { - this.codec = codec - this.config = config - } - - /** - * @param {T} data - * @param {Partial} [options] - * @returns {Block} - */ - encode (data, options) { - const { codec } = this - const bytes = codec.encode(data) - return new Block(null, codec.code, data, bytes, { ...this.config, ...options }) - } -} - -/** - * @class - * @template {number} Code - * @template T - */ -class BlockDecoder { - /** - * @param {Decoder} codec - * @param {BlockConfig} config - */ - constructor (codec, config) { - this.codec = codec - this.config = config - } - - /** - * @param {Uint8Array} bytes - * @param {Partial} [options] - * @returns {Block} - */ - decode (bytes, options) { - const data = this.codec.decode(bytes) - return new Block(null, this.codec.code, data, bytes, { ...this.config, ...options }) - } -} -/** - * @typedef {import('./block/interface').Config} BlockConfig - * @typedef {import('./hashes/interface').MultihashHasher} Hasher - **/ - -/** - * @template T - * @typedef {import('./bases/interface').MultibaseEncoder} MultibaseEncoder - */ - -/** - * @template T - * @typedef {import('./bases/interface').MultibaseDecoder} MultibaseDecoder - */ - -/** - * @template T - * @typedef {import('./bases/interface').MultibaseCodec} MultibaseCodec - */ - -/** - * @template {number} Code - * @template T - * @typedef {import('./codecs/interface').BlockEncoder} Encoder - */ - -/** - * @template {number} Code - * @template T - * @typedef {import('./codecs/interface').BlockDecoder} Decoder - */ - -/** - * @template {number} Code - * @template T - * @typedef {import('./codecs/interface').BlockCodec} Codec - */ diff --git a/src/block/interface.ts b/src/block/interface.ts deleted file mode 100644 index 4d8af806..00000000 --- a/src/block/interface.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Block -import CID from "../cid" -import { MultihashHasher } from '../hashes/interface' - -// Just a representation for awaitable `T`. -export type Awaitable = - | T - | Promise - - -export interface Block { - cid(): Awaitable - encode(): Awaitable -} - -export interface Config { - hasher: MultihashHasher -} diff --git a/src/bytes.js b/src/bytes.js index 5ed30fe1..1e005a7d 100644 --- a/src/bytes.js +++ b/src/bytes.js @@ -12,7 +12,7 @@ const toHex = d => d.reduce((hex, byte) => hex + byte.toString(16).padStart(2, ' */ const fromHex = hex => { if (!hex.length) return empty - return new Uint8Array(hex.match(/../g).map(b => parseInt(b, 16))) + return new Uint8Array(/** @type {string[]} */(hex.match(/../g)).map(b => parseInt(b, 16))) } /** diff --git a/src/codecs/codec.js b/src/codecs/codec.js index 5dd08675..7829e672 100644 --- a/src/codecs/codec.js +++ b/src/codecs/codec.js @@ -14,62 +14,249 @@ export const codec = ({ name, code, decode, encode }) => new Codec(name, code, encode, decode) -/** - * @template {number} Code - * @template T - * @typedef {import('./interface').BlockEncoder} BlockEncoder - */ - /** * @class * @template T * @template {string} Name * @template {number} Code * @implements {BlockEncoder} + * @implements {MulticodecEncoder} */ -export class Encoder { +class Encoder { /** * @param {Name} name * @param {Code} code - * @param {(data:T) => Uint8Array} encode + * @param {(data:T) => Uint8Array} encodeBlock */ - constructor (name, code, encode) { + constructor (name, code, encodeBlock) { this.name = name this.code = code - this.encode = encode + this.encodeBlock = encodeBlock + } + + get codecs () { + return { [this.code]: this } + } + + /** + * Encodes given input `{ code, data }` with this encoder, if `code` does not + * match the code here it will throw an exception. + * @param {Object} input + * @param {Code} input.code + * @param {T} input.value + * @returns {BlockView} + */ + encode ({ code, value }) { + if (code === this.code) { + return { code, bytes: this.encodeBlock(value) } + } else { + throw Error(`Codec (code: ${code}) is not supported, only supports: ${this.code}`) + } + } + + /** + * @template {number} OtherCode + * @param {MulticodecEncoder} other + * @returns {MulticodecEncoder} + */ + or (other) { + // Note: Need type annotate so it isn't inferred as `BlockEncoder`. + /** @type {BlockEncoder} */ + const defaultEncoder = this + return new ComposedEncoder(defaultEncoder, { + ...other.codecs, + [this.code]: this + }) } } /** * @template {number} Code * @template T - * @typedef {import('./interface').BlockDecoder} BlockDecoder + * @implements {BlockEncoder} + * @implements {MulticodecEncoder} */ +class ComposedEncoder { + /** + * @param {BlockEncoder} defaultEncoder + * @param {Record>} codecs + */ + constructor (defaultEncoder, codecs) { + this.defaultEncoder = defaultEncoder + this.codecs = codecs + } + + // BlockEncoder + + // Implementing `BlockEncoder` interface seems awkward because this + // represents composition and therefor `.code` and `.encodeBlock` have little + // sense. On the other hand making composed codec e.g. `dagCBOR.or(dagPB)` + // be a drop-in replacment for `dagCBOR` is so convinient that we chose to + // go for it. + get code () { + return this.defaultEncoder.code + } + + get name () { + return Object.values(this.codecs).map(codec => codec.name).join('|') + } + + /** + * @param {T} value + * @returns {ByteView} + */ + encodeBlock (value) { + return this.defaultEncoder.encodeBlock(value) + } + + /** + * @param {BlockSource} block + * @returns {BlockView} + */ + encode ({ code, value }) { + const codec = this.codecs[code] + if (codec) { + const bytes = codec.encodeBlock(value) + return { code, bytes } + } else { + throw Error(`Codec (code: ${code}) is not supported, only supports: ${Object.keys(this.codecs)}`) + } + } + + /** + * @template {number} OtherCode + * @param {MulticodecEncoder} other + * @returns {MulticodecEncoder} + */ + or (other) { + // Note: Need type annotate so it isn't inferred as `BlockEncoder`. + /** @type {BlockEncoder} */ + const encoder = this.defaultEncoder + return new ComposedEncoder(encoder, { + ...other.codecs, + ...this.codecs + }) + } +} /** * @class * @template {number} Code * @template T * @implements {BlockDecoder} + * @implements {MulticodecDecoder} */ -export class Decoder { +class Decoder { /** * @param {string} name * @param {Code} code - * @param {(bytes:Uint8Array) => T} decode + * @param {(bytes:Uint8Array) => T} decodeBlock */ - constructor (name, code, decode) { + constructor (name, code, decodeBlock) { this.name = name this.code = code - this.decode = decode + this.decodeBlock = decodeBlock + } + + get codecs () { + return { [this.code]: this } + } + + /** + * @param {BlockView} input + * @returns {BlockSource} + */ + decode ({ code, bytes }) { + if (this.code === code) { + return { code, value: this.decodeBlock(bytes) } + } else { + throw Error(`Codec (code: ${code}) is not supported, only supports: ${this.code}`) + } + } + + /** + * @template {number} OtherCode + * @param {MulticodecDecoder} other + * @returns {MulticodecDecoder} + */ + or (other) { + // Note: Need type annotate so it isn't inferred as `BlockEncoder`. + /** @type {BlockDecoder} */ + const defaultDecoder = this + return new ComposedDecoder(defaultDecoder, { + ...other.codecs, + [this.code]: this + }) } } /** * @template {number} Code * @template T - * @typedef {import('./interface').BlockCodec} BlockCodec + * @implements {BlockDecoder} + * @implements {MulticodecDecoder} */ +class ComposedDecoder { + /** + * @param {BlockDecoder} defaultDecoder + * @param {Record>} codecs + */ + constructor (defaultDecoder, codecs) { + this.defaultDecoder = defaultDecoder + this.codecs = codecs + } + + // BlockDecoder + + // Implementing `BlockDecoder` interface seems awkward because this + // represents composition and therefor `.code` and `.decodeBlock` have little + // sense. On the other hand making composed codec e.g. `dagCBOR.or(dagPB)` + // be a drop-in replacment for `dagCBOR` is so convinient that we chose to + // go for it. + get code () { + return this.defaultDecoder.code + } + + /** + * Decodes given bytes with this decoder. If it was encoded with a different + * codec than the default it will throw an exception. + * @param {ByteView} bytes + * @returns {T} + */ + decodeBlock (bytes) { + return this.defaultDecoder.decodeBlock(bytes) + } + + // MulticodecDecoder + + /** + * @param {BlockView} block + * @returns {BlockSource} + */ + decode ({ code, bytes }) { + const codec = this.codecs[code] + if (codec) { + const value = codec.decodeBlock(bytes) + return { code, value } + } else { + throw Error(`Codec (code: ${code}) is not supported, only supports: ${Object.keys(this.codecs)}`) + } + } + + /** + * @template {number} OtherCode + * @param {MulticodecDecoder} other + * @returns {MulticodecDecoder} + */ + or (other) { + /** @type {BlockDecoder} */ + const defaultDecoder = this.defaultDecoder + return new ComposedDecoder(defaultDecoder, { + ...other.codecs, + ...this.codecs + }) + } +} /** * @class @@ -77,32 +264,212 @@ export class Decoder { * @template {number} Code * @template T * @implements {BlockCodec} + * @implements {MulticodecCodec} */ -export class Codec { +class Codec { /** * @param {Name} name * @param {Code} code - * @param {(data:T) => Uint8Array} encode - * @param {(bytes:Uint8Array) => T} decode + * @param {(data:T) => Uint8Array} encodeBlock + * @param {(bytes:Uint8Array) => T} decodeBlock */ - constructor (name, code, encode, decode) { + constructor (name, code, encodeBlock, decodeBlock) { this.name = name this.code = code - this.encode = encode - this.decode = decode + this.encodeBlock = encodeBlock + this.decodeBlock = decodeBlock + } + + get codecs () { + return { [this.code]: this } + } + + /** + * Encodes given input `{ code, data }` with this encoder, if `code` does not + * match the code here it will throw an exception. + * @param {Object} input + * @param {Code} input.code + * @param {T} input.value + * @returns {BlockView} + */ + encode ({ code, value }) { + if (code === this.code) { + return { code, bytes: this.encodeBlock(value) } + } else { + throw Error(`Codec (code: ${code}) is not supported, only supports: ${this.code}`) + } + } + + /** + * @param {BlockView} input + * @returns {BlockSource} + */ + decode ({ code, bytes }) { + if (this.code === code) { + return { code, value: this.decodeBlock(bytes) } + } else { + throw Error(`Codec (code: ${code}) is not supported, only supports: ${this.code}`) + } + } + + /** + * @template {number} OtherCode + * @param {MulticodecCodec} other + * @returns {MulticodecCodec} + */ + or (other) { + /** @type {BlockCodec} */ + const defaultCodec = this + return new ComposedCodec(defaultCodec, { + ...other.codecs, + [this.code]: this + }) } get decoder () { - const { name, code, decode } = this - const decoder = new Decoder(name, code, decode) + const { name, code, decodeBlock } = this + const decoder = new Decoder(name, code, decodeBlock) Object.defineProperty(this, 'decoder', { value: decoder }) return decoder } get encoder () { - const { name, code, encode } = this - const encoder = new Encoder(name, code, encode) + const { name, code, encodeBlock } = this + const encoder = new Encoder(name, code, encodeBlock) Object.defineProperty(this, 'encoder', { value: encoder }) return encoder } } + +/** + * @class + * @template {number} Code + * @template T + * @implements {BlockCodec} + * @implements {MulticodecCodec} + */ +class ComposedCodec { + /** + * @param {BlockCodec} defaultCodec + * @param {Record>} codecs + */ + constructor (defaultCodec, codecs) { + this.defaultCodec = defaultCodec + this.codecs = codecs + + this.encoder = new ComposedEncoder(defaultCodec, codecs) + this.decoder = new ComposedDecoder(defaultCodec, codecs) + } + + // BlockCodec + + get name () { + return Object.values(this.codecs).map(codec => codec.name).join('|') + } + + get code () { + return this.defaultCodec.code + } + + /** + * @param {T} value + */ + encodeBlock (value) { + return this.encoder.encodeBlock(value) + } + + /** + * @param {ByteView} bytes + */ + decodeBlock (bytes) { + return this.decoder.decodeBlock(bytes) + } + + // MulticodecCodec + + /** + * Encodes given input `{ code, data }` with this encoder, if `code` does not + * match the code here it will throw an exception. + * @param {BlockSource} input + * @returns {BlockView} + */ + encode (input) { + return this.encoder.encode(input) + } + + /** + * @param {BlockView} input + * @returns {BlockSource} + */ + decode (input) { + return this.decoder.decode(input) + } + + /** + * @template {number} OtherCode + * @param {MulticodecCodec} other + * @returns {MulticodecCodec} + */ + or (other) { + /** @type {BlockCodec} */ + const defaultCodec = this.defaultCodec + + return new ComposedCodec(defaultCodec, { + ...this.codecs, + ...other.codecs + }) + } +} + +/** + * @template {number} Code + * @template T + * @typedef {import('./interface').Codec} MulticodecCodec + */ + +/** + * @template {number} Code + * @template T + * @typedef {import('./interface').BlockCodec} BlockCodec + */ + +/** + * @template {number} Code + * @template T + * @typedef {import('./interface').BlockEncoder} BlockEncoder + */ + +/** + * @template {number} Code + * @template T + * @typedef {import('./interface').Encoder} MulticodecEncoder + */ + +/** + * @template {number} Code + * @template T + * @typedef {import('./interface').BlockDecoder} BlockDecoder + */ + +/** + * @template {number} Code + * @template T + * @typedef {import('./interface').Decoder} MulticodecDecoder + */ + +/** + * @template {number} Code + * @template T + * @typedef {import('./interface').BlockView} BlockView + */ + +/** + * @template {number} Code + * @template T + * @typedef {import('./interface').BlockSource} BlockSource + */ + +/** + * @template T + * @typedef {import('./interface').ByteView} ByteView + */ diff --git a/src/codecs/interface.ts b/src/codecs/interface.ts index 3474ff38..0370bf02 100644 --- a/src/codecs/interface.ts +++ b/src/codecs/interface.ts @@ -1,26 +1,135 @@ +// IPLD Codec + /** * IPLD encoder part of the codec. */ export interface BlockEncoder { - name: string - code: Code - encode(data: T): ByteView + /** + * Name of the multicodec. + * @see https://github.com/multiformats/multicodec/blob/master/table.csv + */ + readonly name: string + + /** + * Code of the multicodec + * @see https://github.com/multiformats/multicodec/blob/master/table.csv + */ + readonly code: Code + + /** + * Takes JS value and serializes it to bytes. + */ + encodeBlock(value: T): ByteView } /** - * IPLD decoder part of the codec. + * IPLD encoder part of the codec. */ export interface BlockDecoder { - code: Code - decode(bytes: ByteView): T + /** + * Code of the multicodec + * @see https://github.com/multiformats/multicodec/blob/master/table.csv + */ + readonly code: Code + + /** + * Takes binary encoded JS value and turns it into an actual value. + */ + decodeBlock(bytes: ByteView): T +} + + +/** + * IPLD codec that is just Encoder + Decoder. To separate those capabilities + * (since sender requires encoder and receiver requires decoder). + */ +export interface BlockCodec extends BlockEncoder, BlockDecoder { + /** + * Encoder part of the codec. + */ + readonly encoder: BlockEncoder + /** + * Decoder part of the codec. + */ + readonly decoder: BlockDecoder } +// Multicodecs + + /** - * IPLD codec that is just Encoder + Decoder however it is - * separate those capabilties as sender requires encoder and receiver - * requires decoder. + * Composition of one or more `BlockEncoder`s so it can be used to encode JS + * values into multiple formats by disptaching to a corresponding `BlockEncoder`. */ -export interface BlockCodec extends BlockEncoder, BlockDecoder { } +export interface Encoder extends BlockEncoder { + /** + * Table of block encoders keyed by code. + */ + readonly codecs: Record> + + /** + * Encodes `{code, value}` input into `{code, bytes}`. If input `code` is not + * supported throws an exception. + */ + encode(input: BlockSource): BlockView + + or(other: Encoder): Encoder +} + +/** + * Composition of one or more `BlockDecoder`s so it can be used to decode JS + * values from multiple formats by disptaching to a corresponding `BlockDecoder`. + */ +export interface Decoder extends BlockDecoder { + readonly codecs: Record> + + /** + * Decodes `{code, bytes}` to `{code, value}`. If input `code` is not + * supported throws an exception. + */ + decode(view: BlockView): BlockSource + + or(other: Decoder): Decoder +} + +export interface Codec extends Encoder, Decoder, BlockCodec { + readonly codecs: Record> + + /** + * Encoder part of the codec. + */ + readonly encoder: Encoder + /** + * Decoder part of the codec. + */ + readonly decoder: Decoder + + or(other: Codec): Codec +} + + +export interface BlockSource { + /** + * Multiformat code of the encoding. + */ + code: Code + /** + * JS value. + */ + value: T +} + +export interface BlockView { + /** + * Multiformat code of the encoding. + */ + code: Code + + /** + * Binary encoded JS value. + */ + bytes: ByteView +} // This just a hack to retain type information abouth the data that diff --git a/src/dag.js b/src/dag.js new file mode 100644 index 00000000..bc557576 --- /dev/null +++ b/src/dag.js @@ -0,0 +1,288 @@ +// @ts-check + +import Block from './dag/block.js' + +/** + * @template {number} Code + * @template {number} HashAlgorithm + * @template T + * @param {Object} options + * @param {MulticodecCodec} options.multicodec + * @param {MultihashHasher} options.hasher + * @returns {Codec} + */ +export const codec = ({ multicodec, hasher }) => + new Codec(multicodec, hasher) + +/** + * @template {number} Code + * @template {number} HashAlgorithm + * @template T + * @param {Object} options + * @param {MulticodecEncoder} options.multicodec + * @param {MultihashHasher} options.hasher + * @returns {Encoder} + */ +export const encoder = ({ multicodec, hasher }) => + new Encoder(multicodec, hasher) + +/** + * @template {number} Code + * @template {number} HashAlgorithm + * @template T + * @param {Object} options + * @param {MulticodecDecoder} options.multicodec + * @param {MultihashHasher} options.hasher + * @returns {Decoder} + */ +export const decoder = ({ multicodec, hasher }) => + new Decoder(multicodec, hasher) + +/** + * @class + * @template {number} Code + * @template {number} Algorithm + * @template T + * @implements {DagEncoder} + */ +class Encoder { + /** + * @param {MulticodecEncoder} encoder + * @param {MultihashHasher} hasher + */ + constructor (encoder, hasher) { + this.codec = encoder + this.hasher = hasher + } + + /** + * @param {T} value + * @returns {BlockObject} + */ + encodeBlock (value) { + const bytes = this.codec.encodeBlock(value) + return Block.createWithHasher(value, bytes, this.codec.code, this.hasher) + } + + /** + * @param {BlockDraft} input + * @returns {BlockObject} + */ + encode (input) { + const { bytes, code } = this.codec.encode(input) + return Block.createWithHasher(input.value, bytes, code, this.hasher) + } + + /** + * @template {number} OtherCode + * @template {number} OtherAlgorithm + * @param {DagEncoder} other + * @returns {DagEncoder} + */ + or (other) { + return new Encoder(this.codec.or(other.codec), this.hasher.or(other.hasher)) + } +} + +/** + * @class + * @template {number} Code + * @template {number} Algorithm + * @template T + * @implements {DagDecoder} + */ +class Decoder { + /** + * @param {MulticodecDecoder & BlockDecoder} decoder + * @param {MultihashHasher} hasher + */ + constructor (decoder, hasher) { + this.codec = decoder + this.hasher = hasher + } + + /** + * @param {ByteView} bytes + * @returns {BlockObject} + */ + decodecBlock (bytes) { + const value = this.codec.decodeBlock(bytes) + return Block.createWithHasher(value, bytes, this.codec.code, this.hasher) + } + + /** + * @param {BlockView} input + * @returns {BlockObject} + */ + decode (input) { + if (input.cid) { + const { cid, bytes } = input + /** @type {Code} */ + const code = (cid.code) + const { value } = this.codec.decode({ code, bytes }) + + return Block.createWithCID(cid, value, bytes) + } else { + const { code, value } = this.codec.decode(input) + return Block.createWithHasher(value, input.bytes, code, this.hasher) + } + } + + /** + * @template {number} OtherCode + * @template {number} OtherAlgorithm + * @param {DagDecoder} other + * @returns {DagDecoder} + */ + or (other) { + return new Decoder(this.codec.or(other.codec), this.hasher.or(other.hasher)) + } +} + +/** + * @template {number} Code + * @template {number} Algorithm + * @template T + * @class + * @implements {DagCodec} + */ +class Codec { + /** + * @param {MulticodecCodec & BlockCodec} codec + * @param {MultihashHasher} hasher + */ + constructor (codec, hasher) { + this.codec = codec + this.hasher = hasher + + this.encoder = new Encoder(codec, hasher) + this.decoder = new Decoder(codec, hasher) + } + + encode (input) { + return this.encoder.encode(input) + } + + encodeBlock (value) { + return this.encoder.encodeBlock(value) + } + + decode (input) { + return this.decoder.decode(input) + } + + decodeBlock (bytes) { + return this.decoder.decodecBlock(bytes) + } + + /** + * @template {number} OtherCode + * @template {number} OtherAlgorithm + * @param {DagCodec} other + * @returns {DagCodec} + */ + or (other) { + return new Codec(this.codec.or(other.codec), this.hasher.or(other.hasher)) + } +} + +/** + * @template T + * @typedef {import('./dag/interface').ByteView} ByteView + */ + +/** + * @template {number} Code + * @template {number} Algorithm + * @template T + * @typedef {import('./dag/interface').BlockView} BlockView + */ + +/** + * @template {number} Code + * @template {number} Algorithm + * @template T + * @typedef {import('./dag/interface').BlockDraft} BlockDraft + */ + +/** + * @template T + * @typedef {import('./dag/interface').Block} BlockObject + */ + +/** + * @template {number} Code + * @typedef {import('./hashes/interface').MultihashHasher} MultihashHasher + **/ + +/** + * @template {number} Code + * @template {number} Algorithm + * @template T + * @typedef {import('./dag/interface').DagCodec} DagCodec + */ + +/** + * @template {number} Code + * @template {number} Algorithm + * @template T + * @typedef {import('./dag/interface').DagEncoder} DagEncoder + */ + +/** + * @template {number} Code + * @template {number} Algorithm + * @template T + * @typedef {import('./dag/interface').DagDecoder} DagDecoder + */ + +/** + * @template T + * @typedef {import('./bases/interface').MultibaseEncoder} MultibaseEncoder + */ + +/** + * @template T + * @typedef {import('./bases/interface').MultibaseDecoder} MultibaseDecoder + */ + +/** + * @template T + * @typedef {import('./bases/interface').MultibaseCodec} MultibaseCodec + */ + +/** + * @template {number} Code + * @template T + * @typedef {import('./codecs/interface').Encoder} MulticodecEncoder + */ + +/** + * @template {number} Code + * @template T + * @typedef {import('./codecs/interface').BlockEncoder} BlockEncoder + */ + +/** + * @template {number} Code + * @template T + * @typedef {import('./codecs/interface').BlockDecoder} BlockDecoder + */ + +/** + * @template {number} Code + * @template T + * @typedef {import('./codecs/interface').BlockCodec} BlockCodec + */ + +/** + * @template {number} Code + * @template T + * @typedef {import('./codecs/interface').Decoder} MulticodecDecoder + */ + +/** + * @template {number} Code + * @template T + * @typedef {import('./codecs/interface').Codec} MulticodecCodec + */ diff --git a/src/dag/block.js b/src/dag/block.js new file mode 100644 index 00000000..7065b6cb --- /dev/null +++ b/src/dag/block.js @@ -0,0 +1,236 @@ +// @ts-check + +import CID from '../cid.js' + +/** + * @template T + */ +export default class Block { + /** + * @param {T} value + * @param {ByteView} bytes + */ + constructor (value, bytes) { + this.value = value + this.bytes = bytes + this.value = value + } + + toData () { + return this.value + } + + toBytes () { + return this.bytes + } + + links () { + return Block.links(this.value, []) + } + + tree () { + return Block.tree(this.value, []) + } + + /** + * @param {string} path + */ + get (path = '/') { + return Block.get(this.value, path.split('/').filter(Boolean)) + } + + /** + * @template T + * @param {CID} cid + * @param {T} value + * @param {ByteView} bytes + */ + static createWithCID (cid, value, bytes) { + return new BlockWithCID(cid, value, bytes) + } + + /** + * @template {number} Code + * @template {number} Algorithm + * @template T + * @param {T} value + * @param {ByteView} bytes + * @param {Code} code + * @param {MultihashHasher} hasher + */ + static createWithHasher (value, bytes, code, hasher) { + return new BlockWithHasher(value, bytes, code, hasher) + } + + /** + * @template T + * @param {T} source + * @param {Array} base + * @returns {Iterable<[string, CID]>} + */ + static * links (source, base) { + for (const [key, value] of Object.entries(source)) { + const path = [...base, key] + if (value != null && typeof value === 'object') { + if (Array.isArray(value)) { + for (const [index, element] of value.entries()) { + const elementPath = [...path, index] + const cid = CID.asCID(element) + if (cid) { + yield [elementPath.join('/'), cid] + } else if (typeof element === 'object') { + yield * Block.links(element, elementPath) + } + } + } else { + const cid = CID.asCID(value) + if (cid) { + yield [path.join('/'), cid] + } else { + yield * Block.links(value, path) + } + } + } + } + } + + /** + * @template T + * @param {T} source + * @param {Array} base + * @returns {Iterable} + */ + static * tree (source, base) { + for (const [key, value] of Object.entries(source)) { + const path = [...base, key] + yield path.join('/') + if (value != null && typeof value === 'object' && !CID.asCID(value)) { + if (Array.isArray(value)) { + for (const [index, element] of value.entries()) { + const elementPath = [...path, index] + yield elementPath.join('/') + if (typeof element === 'object' && !CID.asCID(element)) { + yield * Block.tree(element, elementPath) + } + } + } else { + yield * Block.tree(value, path) + } + } + } + } + + /** + * @template T + * @param {T} source + * @param {string[]} path + */ + static get (source, path) { + let node = source + for (const [index, key] of path.entries()) { + node = node[key] + if (node == null) { + throw new Error(`Object has no property at ${path.slice(0, index + 1).map(part => `[${JSON.stringify(part)}]`).join('')}`) + } + const cid = CID.asCID(node) + if (cid) { + return { value: cid, remaining: path.slice(index + 1).join('/') } + } + } + return { value: node } + } +} + +/** + * @template {number} Code + * @template {number} Algorithm + * @template T + * @extends {Block} + * @implements {BlockInterface} + */ +class BlockWithHasher extends Block { + /** + * @param {T} value + * @param {ByteView} bytes + * @param {Code} code + * @param {MultihashHasher} hasher + */ + constructor (value, bytes, code, hasher) { + super(value, bytes) + this.code = code + this.hasher = hasher + /** @type {Promise|CID|null} */ + this._cid = null + } + + async cid () { + if (this._cid) { + return await this._cid + } else { + this._cid = BlockWithHasher.cid(this) + const cid = await this._cid + this._cid = cid + return cid + } + } + + /** + * @template {number} Code + * @template {number} Algorithm + * @template T + * @param {BlockWithHasher} self + */ + static async cid (self) { + const digest = await self.hasher.digestBytes(self.bytes) + return CID.createV1(self.code, digest) + } +} + +/** + * @template T + * @class + * @extends {Block} + * @implements {BlockInterface} + */ +class BlockWithCID extends Block { + /** + * @param {CID} cid + * @param {T} value + * @param {ByteView} bytes + */ + constructor (cid, value, bytes) { + super(value, bytes) + this._cid = cid + } + + async cid () { + return this._cid + } +} + +/** + * @template T + * @typedef {import('./interface').Block} BlockInterface + */ + +/** + * @template T + * @typedef {import('./interface').ByteView} ByteView + */ + +/** + * @template {number} Code + * @template T + * @typedef {import('../codecs/interface').BlockEncoder} BlockEncoder + */ + +/** + * @template {number} Code + * @template T + * @typedef {import('../codecs/interface').BlockDecoder} BlockDecoder + */ + +/** + * @template {number} Code + * @typedef {import('../hashes/interface').MultihashHasher} MultihashHasher + **/ diff --git a/src/dag/interface.ts b/src/dag/interface.ts new file mode 100644 index 00000000..a799af80 --- /dev/null +++ b/src/dag/interface.ts @@ -0,0 +1,92 @@ +// Block +import CID from '../cid' +import { MultihashHasher } from '../hashes/interface' +import { ByteView, Encoder, Decoder, Codec } from '../codecs/interface' + +// Just a representation for awaitable `T`. +export type Awaitable = + | T + | Promise + +export interface Config { + hasher: MultihashHasher +} + +export interface Block { + cid(): Awaitable + + /** + * Returns data corresponding to this block. + */ + toData(): T + + /** + * Returns binary representation of this block. + */ + toBytes(): ByteView +} + + +export type BlockView = + // In most cases CID for the encoded blocks is known, Which is why it + // is represented as pair of cid and bytes. + | { cid: CID, bytes: ByteView } + // In case where CID is unknown it still can be represented using `code` + // and `bytes`. + | { cid?: undefined, code: Codec, alogrithm?: Algorithm, bytes: ByteView } + +/** + * Represents a block draft to to be endoded. It has `code` + */ +export type BlockDraft = { + /** + * Block data in raw form. + */ + value: T + /** + * IPLD codec code to be used for encoding this block. + */ + code: Codec, + /** + * Optional hasher to be used for the CID creation for this block. If not + * provided dag encoder will use the default one. + */ + alogrithm: Algorithm +} + + +/** + * Simple DAG encoder allows encoding blocks into various encodings (all the + * encodings that it's codec supports) and hashes their content with a same + * hashing algorithm (it's bound to), unless `BlockDraft` also includes a + * hasher + */ +export interface DagEncoder { + codec: Encoder + hasher: MultihashHasher + + encode(block: BlockDraft): Block + + or(other: DagEncoder): DagEncoder +} + + +export interface DagDecoder { + codec: Decoder + hasher: MultihashHasher + + decode(block: BlockView): Block + + or(other: DagDecoder): DagDecoder +} + +export interface DagCodec extends DagEncoder, DagDecoder { + codec: Codec + + decoder: DagDecoder + encoder: DagEncoder + + or(other: DagCodec): DagCodec +} + +export { ByteView } diff --git a/src/hashes/hasher.js b/src/hashes/hasher.js index 0ce42066..53ade146 100644 --- a/src/hashes/hasher.js +++ b/src/hashes/hasher.js @@ -18,12 +18,11 @@ export const from = ({ name, code, encode }) => new Hasher(name, code, encode) * * @template {string} Name * @template {number} Code - * @class - * @implements {MultihashHasher} + * @implements {UnihashHasher} + * @implements {MultihashHasher} */ export class Hasher { /** - * * @param {Name} name * @param {Code} code * @param {(input: Uint8Array) => Await} encode @@ -34,25 +33,130 @@ export class Hasher { this.encode = encode } + // UnihashHasher interface /** - * @param {Uint8Array} input - * @returns {Promise} + * @param {Uint8Array} bytes + * @returns {Promise>} */ - async digest (input) { - if (input instanceof Uint8Array) { - const digest = await this.encode(input) + async digestBytes (bytes) { + if (bytes instanceof Uint8Array) { + const digest = await this.encode(bytes) return Digest.create(this.code, digest) } else { throw Error('Unknown type, must be binary type') } } + + // MultihashHasher interface + get hashers () { + return { [this.code]: this } + } + + /** + * @param {HashInput} input + * @returns {Promise>} + */ + async digest ({ code, bytes }) { + if (code === this.code) { + return await this.digestBytes(bytes) + } else { + throw Error(`Unsupported hashing algorithm (code: ${code}), this hasher only supports: ${this.code}`) + } + } + + /** + * @template {number} OtherCode + * @param {MultihashHasher} other + * @returns {MultihashHasher} + */ + or (other) { + /** @type {UnihashHasher} */ + const base = this + /** @type {MultihashHasher} */ + const extension = (other) + return new ComposedHasher(base, { + ...extension.hashers, + [base.code]: base + }) + } +} + +/** + * @template {number} Code + * @implements {UnihashHasher} + * @implements {MultihashHasher} + */ +class ComposedHasher { + /** + * @param {UnihashHasher} defaultHasher + * @param {Record>} hashers + */ + constructor (defaultHasher, hashers) { + this.defaultHasher = defaultHasher + this.hashers = hashers + } + + // UnihashHasher interface + get code () { + return this.defaultHasher.code + } + + /** + * @param {Uint8Array} bytes + * @returns {Promise>} + */ + async digestBytes (bytes) { + return await this.defaultHasher.digestBytes(bytes) + } + + // Multihasher interface + + /** + * @param {HashInput} input + * @returns {Promise>} + */ + async digest ({ code, bytes }) { + const hasher = this.hashers[code] + if (hasher) { + return await hasher.digestBytes(bytes) + } else { + throw Error(`Unsupported hashing algorithm (code: ${code}), this hasher only supports: ${Object.keys(this.hashers)} `) + } + } + + /** + * @template {number} OtherCode + * @param {MultihashHasher} other + * @returns {MultihashHasher} + */ + or (other) { + /** @type {MultihashHasher} */ + const base = (this) + /** @type {MultihashHasher} */ + const extension = (other) + return new ComposedHasher(this.defaultHasher, { + ...extension.hashers, + ...base.hashers + }) + } } /** - * @typedef {import('./interface').MultihashHasher} MultihashHasher + * @template {number} Code + * @typedef {import('./interface').Hasher} UnihashHasher + */ + +/** + * @template {number} Code + * @typedef {import('./interface').MultihashHasher} MultihashHasher + */ + +/** + * @template {number} Code + * @typedef {import('./interface').HashInput} HashInput */ /** * @template T - * @typedef {Promise|T} Await + * @typedef {import('./interface').Await} Await */ diff --git a/src/hashes/interface.ts b/src/hashes/interface.ts index 31e23ecd..91b48bc4 100644 --- a/src/hashes/interface.ts +++ b/src/hashes/interface.ts @@ -9,38 +9,52 @@ // a bunch of places that parse it to extract (code, digest, size). By creating // this first class representation we avoid reparsing and things generally fit // really nicely. -export interface MultihashDigest { +export interface MultihashDigest { /** * Code of the multihash */ - code: number + readonly code: Code /** * Raw digest (without a hashing algorithm info) */ - digest: Uint8Array + readonly digest: Uint8Array /** * byte length of the `this.digest` */ - size: number + readonly size: number /** * Binary representation of the this multihash digest. */ - bytes: Uint8Array + readonly bytes: Uint8Array } - /** * Hasher represents a hashing algorithm implementation that produces as * `MultihashDigest`. */ -export interface MultihashHasher { - /** - * Takes binary `input` and returns it (multi) hash digest. - * @param {Uint8Array} input - */ - digest(input: Uint8Array): Promise +export interface Hasher { + readonly code: Code + + + digestBytes(bytes: Uint8Array): Promise> +} + +export interface MultihashHasher extends Hasher { + readonly hashers: Record> + + digest(input: HashInput): Promise> + + or(other: MultihashHasher): MultihashHasher +} + +export interface HashInput { + readonly code: Code + readonly bytes: Uint8Array } +export type Await = + | Promise + | T diff --git a/src/index.js b/src/index.js index 3ce64497..d7dd502c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,11 +1,11 @@ // @ts-check import CID from './cid.js' -import Block from './block.js' +import * as dag from './dag.js' import * as varint from './varint.js' import * as bytes from './bytes.js' import * as hasher from './hashes/hasher.js' import * as digest from './hashes/digest.js' import * as codec from './codecs/codec.js' -export { CID, Block, hasher, digest, varint, bytes, codec } +export { CID, dag, hasher, digest, varint, bytes, codec } diff --git a/src/legacy.js b/src/legacy.js index d189526d..bfe2c55d 100644 --- a/src/legacy.js +++ b/src/legacy.js @@ -56,13 +56,13 @@ const legacy = (codec, { hashes }) => { * @param {T} o * @returns {Buffer} */ - const serialize = o => Buffer.from(codec.encode(fromLegacy(o))) + const serialize = o => Buffer.from(codec.encodeBlock(fromLegacy(o))) /** * @param {Uint8Array} b * @returns {T} */ - const deserialize = b => toLegacy(codec.decode(bytes.coerce(b))) + const deserialize = b => toLegacy(codec.decodeBlock(bytes.coerce(b))) /** * @@ -80,7 +80,7 @@ const legacy = (codec, { hashes }) => { throw new Error(`Hasher for ${hashAlg} was not provided in the configuration`) } - const hash = await hasher.digest(buff) + const hash = await hasher.digestBytes(buff) // https://github.com/bcoe/c8/issues/135 /* c8 ignore next */ return new OldCID(cidVersion, codec.name, Buffer.from(hash.bytes)) @@ -91,7 +91,7 @@ const legacy = (codec, { hashes }) => { * @param {string} path */ const resolve = (buff, path) => { - let value = codec.decode(buff) + let value = codec.decodeBlock(buff) const entries = path.split('/').filter(x => x) while (entries.length) { value = value[/** @type {string} */(entries.shift())] @@ -124,7 +124,7 @@ const legacy = (codec, { hashes }) => { * @param {Uint8Array} buff */ const tree = (buff) => { - return _tree(codec.decode(buff)) + return _tree(codec.decodeBlock(buff)) } const defaultHashAlg = 'sha2-256' diff --git a/src/varint.js b/src/varint.js index 15340f9a..503ce3a4 100644 --- a/src/varint.js +++ b/src/varint.js @@ -6,6 +6,7 @@ import varint from '../vendor/varint.js' */ export const decode = (data) => { const code = varint.decode(data) + // @ts-ignore return [code, varint.decode.bytes] } diff --git a/test/test-cid.js b/test/test-cid.js index be53b658..a95f6499 100644 --- a/test/test-cid.js +++ b/test/test-cid.js @@ -47,7 +47,7 @@ describe('CID', () => { }) test('create by parts', async () => { - const hash = await sha256.digest(Buffer.from('abc')) + const hash = await sha256.digestBytes(Buffer.from('abc')) const cid = CID.create(0, 112, hash) same(cid.code, 112) @@ -57,7 +57,7 @@ describe('CID', () => { }) test('create from multihash', async () => { - const hash = await sha256.digest(Buffer.from('abc')) + const hash = await sha256.digestBytes(Buffer.from('abc')) const cid = CID.decode(hash.bytes) @@ -74,7 +74,7 @@ describe('CID', () => { }) test('throws on trying to create a CIDv0 with a codec other than dag-pb', async () => { - const hash = await sha256.digest(Buffer.from('abc')) + const hash = await sha256.digestBytes(Buffer.from('abc')) const msg = 'Version 0 CID must use dag-pb (code: 112) block encoding' await testThrow(() => CID.create(0, 113, hash), msg) }) @@ -95,7 +95,7 @@ describe('CID', () => { }) test('.bytes', async () => { - const hash = await sha256.digest(Buffer.from('abc')) + const hash = await sha256.digestBytes(Buffer.from('abc')) const codec = 112 const cid = CID.create(0, codec, hash) const bytes = cid.bytes @@ -132,7 +132,7 @@ describe('CID', () => { }) test('create by parts', async () => { - const hash = await sha256.digest(Buffer.from('abc')) + const hash = await sha256.digestBytes(Buffer.from('abc')) const cid = CID.create(1, 0x71, hash) same(cid.code, 0x71) same(cid.version, 1) @@ -140,7 +140,7 @@ describe('CID', () => { }) test('can roundtrip through cid.toString()', async () => { - const hash = await sha256.digest(Buffer.from('abc')) + const hash = await sha256.digestBytes(Buffer.from('abc')) const cid1 = CID.create(1, 0x71, hash) const cid2 = CID.parse(cid1.toString()) @@ -167,7 +167,7 @@ describe('CID', () => { */ test('.bytes', async () => { - const hash = await sha256.digest(Buffer.from('abc')) + const hash = await sha256.digestBytes(Buffer.from('abc')) const code = 0x71 const cid = CID.create(1, code, hash) const bytes = cid.bytes @@ -272,25 +272,25 @@ describe('CID', () => { describe('conversion v0 <-> v1', () => { test('should convert v0 to v1', async () => { - const hash = await sha256.digest(Buffer.from(`TEST${Date.now()}`)) + const hash = await sha256.digestBytes(Buffer.from(`TEST${Date.now()}`)) const cid = (CID.create(0, 112, hash)).toV1() same(cid.version, 1) }) test('should convert v1 to v0', async () => { - const hash = await sha256.digest(Buffer.from(`TEST${Date.now()}`)) + const hash = await sha256.digestBytes(Buffer.from(`TEST${Date.now()}`)) const cid = (CID.create(1, 112, hash)).toV0() same(cid.version, 0) }) test('should not convert v1 to v0 if not dag-pb codec', async () => { - const hash = await sha256.digest(Buffer.from(`TEST${Date.now()}`)) + const hash = await sha256.digestBytes(Buffer.from(`TEST${Date.now()}`)) const cid = CID.create(1, 0x71, hash) await testThrow(() => cid.toV0(), 'Cannot convert a non dag-pb CID to CIDv0') }) test('should not convert v1 to v0 if not sha2-256 multihash', async () => { - const hash = await sha512.digest(Buffer.from(`TEST${Date.now()}`)) + const hash = await sha512.digestBytes(Buffer.from(`TEST${Date.now()}`)) const cid = CID.create(1, 112, hash) await testThrow(() => cid.toV0(), 'Cannot convert non sha2-256 multihash CID to CIDv0') }) @@ -298,14 +298,14 @@ describe('CID', () => { describe('caching', () => { test('should cache CID as buffer', async () => { - const hash = await sha256.digest(Buffer.from(`TEST${Date.now()}`)) + const hash = await sha256.digestBytes(Buffer.from(`TEST${Date.now()}`)) const cid = CID.create(1, 112, hash) assert.ok(cid.bytes) same(cid.bytes, cid.bytes) }) test('should cache string representation when it matches the multibaseName it was constructed with', async () => { - const hash = await sha256.digest(Buffer.from('abc')) + const hash = await sha256.digestBytes(Buffer.from('abc')) const cid = CID.create(1, 112, hash) same(cid._baseCache.size, 0) @@ -328,7 +328,7 @@ describe('CID', () => { }) test('toJSON()', async () => { - const hash = await sha256.digest(Buffer.from('abc')) + const hash = await sha256.digestBytes(Buffer.from('abc')) const cid = CID.create(1, 112, hash) const json = cid.toJSON() @@ -337,13 +337,13 @@ describe('CID', () => { }) test('isCID', async () => { - const hash = await sha256.digest(Buffer.from('abc')) + const hash = await sha256.digestBytes(Buffer.from('abc')) const cid = CID.create(1, 112, hash) assert.strictEqual(OLDCID.isCID(cid), false) }) test('asCID', async () => { - const hash = await sha256.digest(Buffer.from('abc')) + const hash = await sha256.digestBytes(Buffer.from('abc')) class IncompatibleCID { constructor (version, code, multihash) { this.version = version @@ -393,7 +393,7 @@ describe('CID', () => { }) test('new CID from old CID', async () => { - const hash = await sha256.digest(Buffer.from('abc')) + const hash = await sha256.digestBytes(Buffer.from('abc')) const cid = CID.asCID(new OLDCID(1, 'raw', Buffer.from(hash.bytes))) same(cid.version, 1) @@ -403,7 +403,7 @@ describe('CID', () => { if (!process.browser) { test('util.inspect', async () => { - const hash = await sha256.digest(Buffer.from('abc')) + const hash = await sha256.digestBytes(Buffer.from('abc')) const cid = CID.create(1, 112, hash) same(util.inspect(cid), 'CID(bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu)') }) @@ -411,23 +411,23 @@ describe('CID', () => { describe('deprecations', async () => { test('codec', async () => { - const hash = await sha256.digest(Buffer.from('abc')) + const hash = await sha256.digestBytes(Buffer.from('abc')) const cid = CID.create(1, 112, hash) await testThrow(() => cid.codec, '"codec" property is deprecated, use integer "code" property instead') await testThrow(() => CID.create(1, 'dag-pb', hash), 'String codecs are no longer supported') }) test('multibaseName', async () => { - const hash = await sha256.digest(Buffer.from('abc')) + const hash = await sha256.digestBytes(Buffer.from('abc')) const cid = CID.create(1, 112, hash) await testThrow(() => cid.multibaseName, '"multibaseName" property is deprecated') }) test('prefix', async () => { - const hash = await sha256.digest(Buffer.from('abc')) + const hash = await sha256.digestBytes(Buffer.from('abc')) const cid = CID.create(1, 112, hash) await testThrow(() => cid.prefix, '"prefix" property is deprecated') }) test('toBaseEncodedString()', async () => { - const hash = await sha256.digest(Buffer.from('abc')) + const hash = await sha256.digestBytes(Buffer.from('abc')) const cid = CID.create(1, 112, hash) await testThrow(() => cid.toBaseEncodedString(), 'Deprecated, use .toString()') }) @@ -438,7 +438,7 @@ describe('CID', () => { await testThrow(() => CID.decode(encoded), 'Invalid CID version 2') }) test('buffer', async () => { - const hash = await sha256.digest(Buffer.from('abc')) + const hash = await sha256.digestBytes(Buffer.from('abc')) const cid = CID.create(1, 112, hash) await testThrow(() => cid.buffer, 'Deprecated .buffer property, use .bytes to get Uint8Array instead') }) diff --git a/test/test-legacy.js b/test/test-legacy.js index 3ec2db72..c852a3b7 100644 --- a/test/test-legacy.js +++ b/test/test-legacy.js @@ -67,7 +67,7 @@ describe('multicodec', () => { const cid = await raw.util.cid(Buffer.from('test')) same(cid.version, 1) same(cid.codec, 'raw') - const { bytes } = await sha256.digest(Buffer.from('test')) + const { bytes } = await sha256.digestBytes(Buffer.from('test')) same(cid.multihash, Buffer.from(bytes)) }) test('resolve', () => { diff --git a/test/test-multicodec.js b/test/test-multicodec.js index b42eeffc..e76208f9 100644 --- a/test/test-multicodec.js +++ b/test/test-multicodec.js @@ -20,19 +20,19 @@ describe('multicodec', () => { const { codecs: { raw, json } } = multiformats test('encode/decode raw', () => { - const buff = raw.encode(bytes.fromString('test')) + const buff = raw.encodeBlock(bytes.fromString('test')) same(buff, bytes.fromString('test')) - same(raw.decode(buff, 'raw'), bytes.fromString('test')) + same(raw.decodeBlock(buff, 'raw'), bytes.fromString('test')) }) test('encode/decode json', () => { - const buff = json.encode({ hello: 'world' }) + const buff = json.encodeBlock({ hello: 'world' }) same(buff, bytes.fromString(JSON.stringify({ hello: 'world' }))) - same(json.decode(buff), { hello: 'world' }) + same(json.decodeBlock(buff), { hello: 'world' }) }) test('raw cannot encode string', async () => { - await testThrow(() => raw.encode('asdf'), 'Unknown type, must be binary type') + await testThrow(() => raw.encodeBlock('asdf'), 'Unknown type, must be binary type') }) test('add with function', () => { @@ -45,7 +45,7 @@ describe('multicodec', () => { const two = bytes.fromString('two') const three = bytes.fromString('three') - same(blip.encode(['one', two, three]), two) - same(blip.decode(three, 200), three) + same(blip.encodeBlock(['one', two, three]), two) + same(blip.decodeBlock(three, 200), three) }) }) diff --git a/test/test-multihash.js b/test/test-multihash.js index 118132b1..ed043ffe 100644 --- a/test/test-multihash.js +++ b/test/test-multihash.js @@ -42,7 +42,7 @@ describe('multihash', () => { } }) test('hash sha2-256', async () => { - const hash = await sha256.digest(fromString('test')) + const hash = await sha256.digestBytes(fromString('test')) same(hash.code, sha256.code) same(hash.digest, encode('sha256')(fromString('test'))) @@ -51,7 +51,7 @@ describe('multihash', () => { same(hash2.bytes, hash.bytes) }) test('hash sha2-512', async () => { - const hash = await sha512.digest(fromString('test')) + const hash = await sha512.digestBytes(fromString('test')) same(hash.code, sha512.code) same(hash.digest, encode('sha512')(fromString('test'))) @@ -76,7 +76,7 @@ describe('multihash', () => { } test('get from buffer', async () => { - const hash = await sha256.digest(fromString('test')) + const hash = await sha256.digestBytes(fromString('test')) same(hash.code, 18) }) @@ -90,7 +90,7 @@ describe('multihash', () => { }) }) test('throw on hashing non-buffer', async () => { - await testThrowAsync(() => sha256.digest('asdf'), 'Unknown type, must be binary type') + await testThrowAsync(() => sha256.digestBytes('asdf'), 'Unknown type, must be binary type') }) test('browser', () => { same(__browser, !!process.browser)