From 553bdee84467477086481f012842b6eabee3e2bf Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Wed, 19 Jan 2022 00:18:47 +0000 Subject: [PATCH 01/20] feat: define sync multihash hasher interface --- .gitignore | 1 + src/hashes/identity.js | 20 ++++++++++++-------- src/hashes/interface.ts | 32 +++++++++++++++++++++++++------- test/test-multihash.js | 13 ++++++++++++- test/tsconfig.json | 14 +++++++++++--- 5 files changed, 61 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index 52ee7db2..4f8f0207 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ node_modules yarn.lock types test/ts-use/tsconfig.tsbuildinfo +test/tsconfig.tsbuildinfo diff --git a/src/hashes/identity.js b/src/hashes/identity.js index 1a928578..e7c2ce5f 100644 --- a/src/hashes/identity.js +++ b/src/hashes/identity.js @@ -1,10 +1,14 @@ -// @ts-check - -import { from } from './hasher.js' import { coerce } from '../bytes.js' +import * as Digest from './digest.js' + +export const code = 0x0 +export const name = 'identity' + +/** + * @param {Uint8Array} input + * @returns {Digest.Digest} + */ +export const digest = (input) => Digest.create(code, coerce(input)) -export const identity = from({ - name: 'identity', - code: 0x0, - encode: (input) => coerce(input) -}) +/** @type {import('./interface').SyncMultihashHasher} */ +export const identity = { code, name, digest } diff --git a/src/hashes/interface.ts b/src/hashes/interface.ts index 20b7f139..721745e8 100644 --- a/src/hashes/interface.ts +++ b/src/hashes/interface.ts @@ -9,11 +9,11 @@ // 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 + code: Code /** * Raw digest (without a hashing algorithm info) @@ -35,20 +35,38 @@ export interface MultihashDigest { * Hasher represents a hashing algorithm implementation that produces as * `MultihashDigest`. */ -export interface MultihashHasher { +export interface MultihashHasher { /** - * Takes binary `input` and returns it (multi) hash digest. + * Takes binary `input` and returns it (multi) hash digest. Return value is + * either promise of a digest or a digest. This way general use can `await` + * while performance critical code may asses return value to decide whether + * await is needed. + * * @param {Uint8Array} input */ - digest(input: Uint8Array): Promise + digest(input: Uint8Array): Promise | MultihashDigest /** * Name of the multihash */ - name: string + name: string /** * Code of the multihash */ - code: number + code: Code +} + +/** + * Sync variant of `MultihashHasher` that refines return type of the `digest` + * to `MultihashDigest`. It is subtype of `MultihashHasher` so implementations + * of this interface can be passed anywhere `MultihashHasher` is expected, + * allowing consumer to either `await` or check the return type to decide + * whether to await or proceed with return value. + * + * `SyncMultihashHasher` is useful in certain APIs where async hashing would be + * impractical e.g. implementation of Hash Array Mapped Trie (HAMT). + */ +export interface SyncMultihashHasher extends MultihashHasher { + digest(input: Uint8Array): MultihashDigest } diff --git a/test/test-multihash.js b/test/test-multihash.js index 9bdeff80..14a2e206 100644 --- a/test/test-multihash.js +++ b/test/test-multihash.js @@ -63,7 +63,7 @@ describe('multihash', () => { assert.deepStrictEqual(hash2.bytes, hash.bytes) }) - it('hash identity', async () => { + it('hash identity async', async () => { const hash = await identity.digest(fromString('test')) assert.deepStrictEqual(hash.code, identity.code) assert.deepStrictEqual(identity.code, 0) @@ -73,6 +73,17 @@ describe('multihash', () => { assert.deepStrictEqual(hash2.code, identity.code) assert.deepStrictEqual(hash2.bytes, hash.bytes) }) + + it('hash identity sync', () => { + const hash = identity.digest(fromString('test')) + assert.deepStrictEqual(hash.code, identity.code) + assert.deepStrictEqual(identity.code, 0) + assert.deepStrictEqual(hash.digest, fromString('test')) + + const hash2 = decodeDigest(hash.bytes) + assert.deepStrictEqual(hash2.code, identity.code) + assert.deepStrictEqual(hash2.bytes, hash.bytes) + }) }) describe('decode', () => { for (const { encoding, hex, size } of valid) { diff --git a/test/tsconfig.json b/test/tsconfig.json index c625d1d4..4cb095d6 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -28,13 +28,21 @@ "skipLibCheck": true, "stripInternal": true, "resolveJsonModule": true, - "noEmit": true + "noEmit": true, + "paths": { + "multiformats": [ + "../types/src/index" + ], + "multiformats/*": [ + "../types/src/*" + ] + } }, "include": [ - "test/", "." ], "exclude": [ - "ts-use/" + "ts-use/", + "node_modules" ] } From b677a650620eaed000aa61e5bde4a84d74370fb6 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Wed, 19 Jan 2022 00:28:02 +0000 Subject: [PATCH 02/20] chore: enable composite code path --- test/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/test/tsconfig.json b/test/tsconfig.json index 4cb095d6..8068304b 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -5,6 +5,7 @@ } ], "compilerOptions": { + "composite": true, "allowJs": true, "checkJs": true, "forceConsistentCasingInFileNames": true, From 655028b4a2b469eb79fcf3f2a980be66e1667159 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Wed, 19 Jan 2022 08:43:46 +0000 Subject: [PATCH 03/20] feat: define CID as interface --- package.json | 3 + src/bases/base.js | 89 +++---- src/block.js | 59 ++--- src/cid.js | 573 ++++++++++++++++++++++++------------------- src/cid/interface.ts | 44 ++++ src/index.js | 3 +- src/interface.js | 1 + src/interface.ts | 4 + test/test-cid.js | 104 +++++--- 9 files changed, 502 insertions(+), 378 deletions(-) create mode 100644 src/cid/interface.ts create mode 100644 src/interface.js create mode 100644 src/interface.ts diff --git a/package.json b/package.json index 94a6283b..e2ba72ca 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,9 @@ }, "./codecs/raw": { "import": "./src/codecs/raw.js" + }, + "./interface": { + "import": "./src/interface.js" } }, "devDependencies": { diff --git a/src/bases/base.js b/src/bases/base.js index 680b0187..70b91aee 100644 --- a/src/bases/base.js +++ b/src/bases/base.js @@ -1,20 +1,8 @@ import basex from '../../vendor/base-x.js' import { coerce } from '../bytes.js' +import * as API from '../interface.js' -/** - * @typedef {import('./interface').BaseEncoder} BaseEncoder - * @typedef {import('./interface').BaseDecoder} BaseDecoder - * @typedef {import('./interface').BaseCodec} BaseCodec - */ - -/** - * @template {string} T - * @typedef {import('./interface').Multibase} Multibase - */ -/** - * @template {string} T - * @typedef {import('./interface').MultibaseEncoder} MultibaseEncoder - */ +export { API } /** * Class represents both BaseEncoder and MultibaseEncoder meaning it @@ -23,8 +11,8 @@ import { coerce } from '../bytes.js' * @class * @template {string} Base * @template {string} Prefix - * @implements {MultibaseEncoder} - * @implements {BaseEncoder} + * @implements {API.MultibaseEncoder} + * @implements {API.BaseEncoder} */ class Encoder { /** @@ -40,7 +28,7 @@ class Encoder { /** * @param {Uint8Array} bytes - * @returns {Multibase} + * @returns {API.Multibase} */ encode (bytes) { if (bytes instanceof Uint8Array) { @@ -51,16 +39,6 @@ class Encoder { } } -/** - * @template {string} Prefix - * @typedef {import('./interface').MultibaseDecoder} MultibaseDecoder - */ - -/** - * @template {string} Prefix - * @typedef {import('./interface').UnibaseDecoder} UnibaseDecoder - */ - /** * @template {string} Prefix */ @@ -68,12 +46,11 @@ class Encoder { * Class represents both BaseDecoder and MultibaseDecoder so it could be used * to decode multibases (with matching prefix) or just base decode strings * with corresponding base encoding. - * @class * @template {string} Base * @template {string} Prefix - * @implements {MultibaseDecoder} - * @implements {UnibaseDecoder} - * @implements {BaseDecoder} + * @implements {API.MultibaseDecoder} + * @implements {API.UnibaseDecoder} + * @implements {API.BaseDecoder} */ class Decoder { /** @@ -107,7 +84,7 @@ class Decoder { /** * @template {string} OtherPrefix - * @param {UnibaseDecoder|ComposedDecoder} decoder + * @param {API.UnibaseDecoder|ComposedDecoder} decoder * @returns {ComposedDecoder} */ or (decoder) { @@ -117,22 +94,17 @@ class Decoder { /** * @template {string} Prefix - * @typedef {import('./interface').CombobaseDecoder} CombobaseDecoder + * @typedef {Record>} Decoders */ /** * @template {string} Prefix - * @typedef {Record>} Decoders - */ - -/** - * @template {string} Prefix - * @implements {MultibaseDecoder} - * @implements {CombobaseDecoder} + * @implements {API.MultibaseDecoder} + * @implements {API.CombobaseDecoder} */ class ComposedDecoder { /** - * @param {Record>} decoders + * @param {Decoders} decoders */ constructor (decoders) { this.decoders = decoders @@ -140,7 +112,7 @@ class ComposedDecoder { /** * @template {string} OtherPrefix - * @param {UnibaseDecoder|ComposedDecoder} decoder + * @param {API.UnibaseDecoder|ComposedDecoder} decoder * @returns {ComposedDecoder} */ or (decoder) { @@ -157,7 +129,13 @@ class ComposedDecoder { if (decoder) { return decoder.decode(input) } else { - throw RangeError(`Unable to decode multibase string ${JSON.stringify(input)}, only inputs prefixed with ${Object.keys(this.decoders)} are supported`) + throw RangeError( + `Unable to decode multibase string ${JSON.stringify( + input + )}, only inputs prefixed with ${Object.keys( + this.decoders + )} are supported` + ) } } } @@ -165,30 +143,25 @@ class ComposedDecoder { /** * @template {string} L * @template {string} R - * @param {UnibaseDecoder|CombobaseDecoder} left - * @param {UnibaseDecoder|CombobaseDecoder} right + * @param {API.UnibaseDecoder|API.CombobaseDecoder} left + * @param {API.UnibaseDecoder|API.CombobaseDecoder} right * @returns {ComposedDecoder} */ export const or = (left, right) => new ComposedDecoder(/** @type {Decoders} */({ - ...(left.decoders || { [/** @type UnibaseDecoder */(left).prefix]: left }), - ...(right.decoders || { [/** @type UnibaseDecoder */(right).prefix]: right }) + ...(left.decoders || { [/** @type API.UnibaseDecoder */(left).prefix]: left }), + ...(right.decoders || { [/** @type API.UnibaseDecoder */(right).prefix]: right }) })) -/** - * @template T - * @typedef {import('./interface').MultibaseCodec} MultibaseCodec - */ - /** * @class * @template {string} Base * @template {string} Prefix - * @implements {MultibaseCodec} - * @implements {MultibaseEncoder} - * @implements {MultibaseDecoder} - * @implements {BaseCodec} - * @implements {BaseEncoder} - * @implements {BaseDecoder} + * @implements {API.MultibaseCodec} + * @implements {API.MultibaseEncoder} + * @implements {API.MultibaseDecoder} + * @implements {API.BaseCodec} + * @implements {API.BaseEncoder} + * @implements {API.BaseDecoder} */ export class Codec { /** diff --git a/src/block.js b/src/block.js index 75bed063..1df3a462 100644 --- a/src/block.js +++ b/src/block.js @@ -1,4 +1,7 @@ import { bytes as binary, CID } from './index.js' +import * as API from './interface.js' + +export { API } const readonly = ({ enumerable = true, configurable = false } = {}) => ({ enumerable, @@ -10,7 +13,7 @@ const readonly = ({ enumerable = true, configurable = false } = {}) => ({ * @template T * @param {T} source * @param {Array} base - * @returns {Iterable<[string, CID]>} + * @returns {Iterable<[string, CID.CID]>} */ const links = function * (source, base) { if (source == null) return @@ -94,8 +97,8 @@ const get = (source, path) => { class Block { /** * @param {Object} options - * @param {CID} options.cid - * @param {ByteView} options.bytes + * @param {CID.CID} options.cid + * @param {API.ByteView} options.bytes * @param {T} options.value */ constructor ({ cid, bytes, value }) { @@ -134,11 +137,11 @@ class Block { /** * @template T * @template {number} Code - * @template {number} Algorithm + * @template {number} Alg * @param {Object} options * @param {T} options.value - * @param {BlockEncoder} options.codec - * @param {Hasher} options.hasher + * @param {API.BlockEncoder} options.codec + * @param {API.MultihashHasher} options.hasher * @returns {Promise>} */ const encode = async ({ value, codec, hasher }) => { @@ -155,11 +158,11 @@ const encode = async ({ value, codec, hasher }) => { /** * @template T * @template {number} Code - * @template {number} Algorithm + * @template {number} Alg * @param {Object} options - * @param {ByteView} options.bytes - * @param {BlockDecoder} options.codec - * @param {Hasher} options.hasher + * @param {API.ByteView} options.bytes + * @param {API.BlockDecoder} options.codec + * @param {API.MultihashHasher} options.hasher * @returns {Promise>} */ const decode = async ({ bytes, codec, hasher }) => { @@ -175,13 +178,13 @@ const decode = async ({ bytes, codec, hasher }) => { /** * @typedef {Object} RequiredCreateOptions - * @property {CID} options.cid + * @property {CID.CID} options.cid */ /** * @template T * @template {number} Code - * @param {{ cid: CID, value:T, codec?: BlockDecoder, bytes: ByteView }|{cid:CID, bytes:ByteView, value?:void, codec:BlockDecoder}} options + * @param {{ cid: CID.CID, value:T, codec?: API.BlockDecoder, bytes: API.ByteView }|{cid:CID.CID, bytes:API.ByteView, value?:void, codec:API.BlockDecoder}} options * @returns {Block} */ const createUnsafe = ({ bytes, cid, value: maybeValue, codec }) => { @@ -197,12 +200,12 @@ const createUnsafe = ({ bytes, cid, value: maybeValue, codec }) => { /** * @template T * @template {number} Code - * @template {number} Algorithm + * @template {number} Alg * @param {Object} options - * @param {CID} options.cid - * @param {ByteView} options.bytes - * @param {BlockDecoder} options.codec - * @param {Hasher} options.hasher + * @param {CID.CID} options.cid + * @param {API.ByteView} options.bytes + * @param {API.BlockDecoder} options.codec + * @param {API.MultihashHasher} options.hasher * @returns {Promise>} */ const create = async ({ bytes, cid, hasher, codec }) => { @@ -218,25 +221,3 @@ const create = async ({ bytes, cid, hasher, codec }) => { } export { encode, decode, create, createUnsafe, Block } - -/** - * @template T - * @typedef {import('./codecs/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 Algorithm - * @typedef {import('./hashes/interface').MultihashHasher} Hasher - */ diff --git a/src/cid.js b/src/cid.js index cb63e1d4..1cacfe9a 100644 --- a/src/cid.js +++ b/src/cid.js @@ -3,48 +3,318 @@ import * as Digest from './hashes/digest.js' import { base58btc } from './bases/base58.js' import { base32 } from './bases/base32.js' import { coerce } from './bytes.js' +import * as API from './interface.js' + +export * from './interface.js' +export { API } + +/** + * @template {number} Format + * @template {number} Alg + * @template {API.CIDVersion} Version + * @template T + * @param {API.CID|T} input + * @returns {API.CID|null} + */ +export const asCID = (input) => { + const value = /** @type {any} */(input) + if (value instanceof CIDView) { + // If value is instance of CID then we're all set. + return value + } else if (value != null && value.asCID === value) { + // If value isn't instance of this CID class but `this.asCID === this` is + // true it is CID instance coming from a different implemnetation (diff + // version or duplicate). In that case we rebase it to this `CID` + // implemnetation so caller is guaranteed to get instance with expected + // API. + const { version, code, multihash, bytes } = value + return new CIDView( + version, + code, + /** @type {API.MultihashDigest} */ (multihash), + bytes || encodeCID(version, code, multihash.bytes) + ) + } else if (value != null && value[cidSymbol] === true) { + // If value is a CID from older implementation that used to be tagged via + // symbol we still rebase it to the this `CID` implementation by + // delegating that to a constructor. + const { version, multihash, code } = value + const digest = + /** @type {API.MultihashDigest} */ + (Digest.decode(multihash)) + return create(version, code, digest) + } else { + // Otherwise value is not a CID (or an incompatible version of it) in + // which case we return `null`. + return null + } +} + +/** + * @deprecated + * @param {any} value + * @returns {value is CID} + */ +export const isCID = (value) => { + deprecate(/^0\.0/, IS_CID_DEPRECATION) + return !!(value && (value[cidSymbol] || value.asCID === value)) +} + +/** + * @template {number} Format + * @template {number} Alg + * @template {API.CIDVersion} Version + * @param {Version} version - Version of the CID + * @param {Format} code - Code of the codec content is encoded in. + * @param {API.MultihashDigest} digest - (Multi)hash of the of the content. + * @returns {API.CID} + */ +export const create = (version, code, digest) => { + if (typeof code !== 'number') { + throw new Error('String codecs are no longer supported') + } + + switch (version) { + case 0: { + if (code !== DAG_PB_CODE) { + throw new Error(`Version 0 CID must use dag-pb (code: ${DAG_PB_CODE}) block encoding`) + } else { + return new CIDView(version, code, digest, digest.bytes) + } + } + case 1: { + const bytes = encodeCID(version, code, digest.bytes) + return new CIDView(version, code, digest, bytes) + } + default: { + throw new Error('Invalid version') + } + } +} /** - * @typedef {import('./hashes/interface').MultihashDigest} MultihashDigest - * @typedef {0 | 1} CIDVersion + * Simplified version of `create` for CIDv0. + * @param {API.MultihashDigest} digest - Multihash. + * @returns {API.CIDv0} */ +export const createV0 = (digest) => create(0, DAG_PB_CODE, digest) /** - * @template Prefix - * @typedef {import('./bases/interface').MultibaseEncoder} MultibaseEncoder + * Simplified version of `create` for CIDv1. + * @template {number} Code + * @template {number} Alg + * @param {Code} code - Content encoding format code. + * @param {API.MultihashDigest} digest - Miltihash of the content. + * @returns {API.CIDv1} */ +export const createV1 = (code, digest) => create(1, code, digest) /** - * @template Prefix - * @typedef {import('./bases/interface').MultibaseDecoder} MultibaseDecoder + * Decoded a CID from its binary representation. The byte array must contain + * only the CID with no additional bytes. + * + * An error will be thrown if the bytes provided do not contain a valid + * binary representation of a CID. + * + * @param {Uint8Array} bytes + * @returns {API.CID} + */ +export const decode = (bytes) => { + const [cid, remainder] = decodeFirst(bytes) + if (remainder.length) { + throw new Error('Incorrect length') + } + return cid +} + +/** + * Decoded a CID from its binary representation at the beginning of a byte + * array. + * + * Returns an array with the first element containing the CID and the second + * element containing the remainder of the original byte array. The remainder + * will be a zero-length byte array if the provided bytes only contained a + * binary CID representation. + * + * @param {Uint8Array} bytes + * @returns {[API.CID, Uint8Array]} */ +export const decodeFirst = (bytes) => { + const specs = inspectBytes(bytes) + const prefixSize = specs.size - specs.multihashSize + const multihashBytes = coerce( + bytes.subarray(prefixSize, prefixSize + specs.multihashSize) + ) + if (multihashBytes.byteLength !== specs.multihashSize) { + throw new Error('Incorrect length') + } + const digestBytes = multihashBytes.subarray( + specs.multihashSize - specs.digestSize + ) + const digest = new Digest.Digest( + specs.multihashCode, + specs.digestSize, + digestBytes, + multihashBytes + ) + const cid = specs.version === 0 + ? createV0(/** @type {API.MultihashDigest} */(digest)) + : createV1(specs.codec, digest) + return [cid, bytes.subarray(specs.size)] +} -export class CID { +/** + * Inspect the initial bytes of a CID to determine its properties. + * + * Involves decoding up to 4 varints. Typically this will require only 4 to 6 + * bytes but for larger multicodec code values and larger multihash digest + * lengths these varints can be quite large. It is recommended that at least + * 10 bytes be made available in the `initialBytes` argument for a complete + * inspection. + * + * @param {Uint8Array} initialBytes + * @returns {{ version:API.CIDVersion, codec:number, multihashCode:number, digestSize:number, multihashSize:number, size:number }} + */ +export const inspectBytes = (initialBytes) => { + let offset = 0 + const next = () => { + const [i, length] = varint.decode(initialBytes.subarray(offset)) + offset += length + return i + } + + let version = next() + let codec = DAG_PB_CODE + if (version === 18) { + // CIDv0 + version = 0 + offset = 0 + } else if (version === 1) { + codec = next() + } + + if (version !== 0 && version !== 1) { + throw new RangeError(`Invalid CID version ${version}`) + } + + const prefixSize = offset + const multihashCode = next() // multihash code + const digestSize = next() // multihash length + const size = offset + digestSize + const multihashSize = size - prefixSize + + return { version, codec, multihashCode, digestSize, multihashSize, size } +} + +/** + * Takes cid in a string representation and creates an instance. If `base` + * decoder is not provided will use a default from the configuration. It will + * throw an error if encoding of the CID is not compatible with supplied (or + * a default decoder). + * + * @template {string} Prefix + * @param {string} source + * @param {API.MultibaseDecoder} [base] + * @returns {API.CID} + */ +export const parse = (source, base) => { + const [prefix, bytes] = parseCIDtoBytes(source, base) + + const cid = decode(bytes) + + // Cache string representation to avoid computing it on `this.toString()` + baseCache(cid).set(prefix, source) + + return cid +} + +/** + * @template {number} Format + * @template {number} Alg + * @template {API.CIDVersion} Version + * @param {API.CID} cid + * @param {any} other + * @returns {other is cid} + */ +export const equals = (cid, other) => { + return ( + other && + cid.code === other.code && + cid.version === other.version && + Digest.equals(cid.multihash, other.multihash) + ) +} + +/** + * @param {API.CID} cid + * @param {API.MultibaseEncoder} [base] + * @returns {string} + */ +export const toString = (cid, base) => { + const { bytes, version } = cid + switch (version) { + case 0: + return toStringV0( + bytes, + baseCache(cid), + /** @type {API.MultibaseEncoder<"z">} */ (base) || base58btc.encoder + ) + default: + return toStringV1(bytes, baseCache(cid), base || base32.encoder) + } +} + +/** @type {WeakMap>} */ +const cache = new WeakMap() + +/** + * @param {API.CID} cid + * @returns {Map} + */ +const baseCache = (cid) => { + const baseCache = cache.get(cid) + if (baseCache == null) { + const baseCache = new Map() + cache.set(cid, baseCache) + return baseCache + } + return baseCache +} + +/** + * @template {number} Format + * @template {number} Algorithm + * @template {API.CIDVersion} Version + * @implements {API.CID} + */ + +class CIDView { /** - * @param {CIDVersion} version - * @param {number} code - * @param {MultihashDigest} multihash + * @param {Version} version + * @param {Format} code + * @param {API.MultihashDigest} multihash * @param {Uint8Array} bytes * */ constructor (version, code, multihash, bytes) { + /** @readonly */ this.code = code + /** @readonly */ this.version = version + /** @readonly */ this.multihash = multihash + /** @readonly */ this.bytes = bytes // ArrayBufferView + /** @readonly */ this.byteOffset = bytes.byteOffset + /** @readonly */ this.byteLength = bytes.byteLength // Circular reference - /** @private */ + /** @readonly */ this.asCID = this - /** - * @type {Map} - * @private - */ - this._baseCache = new Map() // Configure private properties Object.defineProperties(this, { @@ -56,20 +326,19 @@ export class CID { multihash: readonly, bytes: readonly, - _baseCache: hidden, asCID: hidden }) } /** - * @returns {CID} + * @returns {API.CIDv0} */ toV0 () { switch (this.version) { case 0: { - return this + return /** @type {API.CIDv0} */ (this) } - default: { + case 1: { const { code, multihash } = this if (code !== DAG_PB_CODE) { @@ -78,56 +347,58 @@ export class CID { // sha2-256 if (multihash.code !== SHA_256_CODE) { - throw new Error('Cannot convert non sha2-256 multihash CID to CIDv0') + throw new Error( + 'Cannot convert non sha2-256 multihash CID to CIDv0' + ) } - return CID.createV0(multihash) + return createV0( + /** @type {API.MultihashDigest} */ (multihash) + ) + } + default: { + throw Error( + `Can not convert CID version ${this.version} to version 0. This is a bug please report` + ) } } } /** - * @returns {CID} + * @returns {API.CIDv1} */ toV1 () { switch (this.version) { case 0: { const { code, digest } = this.multihash const multihash = Digest.create(code, digest) - return CID.createV1(this.code, multihash) + return createV1(this.code, multihash) } case 1: { - return this + return /** @type {API.CIDv1} */ (this) } - /* c8 ignore next 3 */ default: { - throw Error(`Can not convert CID version ${this.version} to version 0. This is a bug please report`) + throw Error( + `Can not convert CID version ${this.version} to version 1. This is a bug please report` + ) } } } /** - * @param {any} other + * @param {unknown} other + * @returns {other is API.CID} */ equals (other) { - return other && - this.code === other.code && - this.version === other.version && - Digest.equals(this.multihash, other.multihash) + return equals(this, other) } /** - * @param {MultibaseEncoder} [base] + * @param {API.MultibaseEncoder} [base] * @returns {string} */ toString (base) { - const { bytes, version, _baseCache } = this - switch (version) { - case 0: - return toStringV0(bytes, _baseCache, base || base58btc.encoder) - default: - return toStringV1(bytes, _baseCache, base || base32.encoder) - } + return toString(this, base) } toJSON () { @@ -145,18 +416,7 @@ export class CID { // Legacy [Symbol.for('nodejs.util.inspect.custom')] () { - return 'CID(' + this.toString() + ')' - } - - // Deprecated - - /** - * @param {any} value - * @returns {value is CID} - */ - static isCID (value) { - deprecate(/^0\.0/, IS_CID_DEPRECATION) - return !!(value && (value[cidSymbol] || value.asCID === value)) + return `CID(${this.toString()})` } get toBaseEncodedString () { @@ -164,11 +424,15 @@ export class CID { } get codec () { - throw new Error('"codec" property is deprecated, use integer "code" property instead') + throw new Error( + '"codec" property is deprecated, use integer "code" property instead' + ) } get buffer () { - throw new Error('Deprecated .buffer property, use .bytes to get Uint8Array instead') + throw new Error( + 'Deprecated .buffer property, use .bytes to get Uint8Array instead' + ) } get multibaseName () { @@ -178,205 +442,12 @@ export class CID { get prefix () { throw new Error('"prefix" property is deprecated') } - - /** - * Takes any input `value` and returns a `CID` instance if it was - * a `CID` otherwise returns `null`. If `value` is instanceof `CID` - * it will return value back. If `value` is not instance of this CID - * class, but is compatible CID it will return new instance of this - * `CID` class. Otherwise returs null. - * - * This allows two different incompatible versions of CID library to - * co-exist and interop as long as binary interface is compatible. - * @param {any} value - * @returns {CID|null} - */ - static asCID (value) { - if (value instanceof CID) { - // If value is instance of CID then we're all set. - return value - } else if (value != null && value.asCID === value) { - // If value isn't instance of this CID class but `this.asCID === this` is - // true it is CID instance coming from a different implemnetation (diff - // version or duplicate). In that case we rebase it to this `CID` - // implemnetation so caller is guaranteed to get instance with expected - // API. - const { version, code, multihash, bytes } = value - return new CID(version, code, multihash, bytes || encodeCID(version, code, multihash.bytes)) - } else if (value != null && value[cidSymbol] === true) { - // If value is a CID from older implementation that used to be tagged via - // symbol we still rebase it to the this `CID` implementation by - // delegating that to a constructor. - const { version, multihash, code } = value - const digest = Digest.decode(multihash) - return CID.create(version, code, digest) - } else { - // Otherwise value is not a CID (or an incompatible version of it) in - // which case we return `null`. - return null - } - } - - /** - * - * @param {CIDVersion} version - Version of the CID - * @param {number} code - Code of the codec content is encoded in. - * @param {MultihashDigest} digest - (Multi)hash of the of the content. - * @returns {CID} - */ - static create (version, code, digest) { - if (typeof code !== 'number') { - throw new Error('String codecs are no longer supported') - } - - switch (version) { - case 0: { - if (code !== DAG_PB_CODE) { - throw new Error(`Version 0 CID must use dag-pb (code: ${DAG_PB_CODE}) block encoding`) - } else { - return new CID(version, code, digest, digest.bytes) - } - } - case 1: { - const bytes = encodeCID(version, code, digest.bytes) - return new CID(version, code, digest, bytes) - } - default: { - throw new Error('Invalid version') - } - } - } - - /** - * Simplified version of `create` for CIDv0. - * @param {MultihashDigest} digest - Multihash. - */ - static createV0 (digest) { - return CID.create(0, DAG_PB_CODE, digest) - } - - /** - * Simplified version of `create` for CIDv1. - * @template {number} Code - * @param {Code} code - Content encoding format code. - * @param {MultihashDigest} digest - Miltihash of the content. - * @returns {CID} - */ - static createV1 (code, digest) { - return CID.create(1, code, digest) - } - - /** - * Decoded a CID from its binary representation. The byte array must contain - * only the CID with no additional bytes. - * - * An error will be thrown if the bytes provided do not contain a valid - * binary representation of a CID. - * - * @param {Uint8Array} bytes - * @returns {CID} - */ - static decode (bytes) { - const [cid, remainder] = CID.decodeFirst(bytes) - if (remainder.length) { - throw new Error('Incorrect length') - } - return cid - } - - /** - * Decoded a CID from its binary representation at the beginning of a byte - * array. - * - * Returns an array with the first element containing the CID and the second - * element containing the remainder of the original byte array. The remainder - * will be a zero-length byte array if the provided bytes only contained a - * binary CID representation. - * - * @param {Uint8Array} bytes - * @returns {[CID, Uint8Array]} - */ - static decodeFirst (bytes) { - const specs = CID.inspectBytes(bytes) - const prefixSize = specs.size - specs.multihashSize - const multihashBytes = coerce(bytes.subarray(prefixSize, prefixSize + specs.multihashSize)) - if (multihashBytes.byteLength !== specs.multihashSize) { - throw new Error('Incorrect length') - } - const digestBytes = multihashBytes.subarray(specs.multihashSize - specs.digestSize) - const digest = new Digest.Digest(specs.multihashCode, specs.digestSize, digestBytes, multihashBytes) - const cid = specs.version === 0 ? CID.createV0(digest) : CID.createV1(specs.codec, digest) - return [cid, bytes.subarray(specs.size)] - } - - /** - * Inspect the initial bytes of a CID to determine its properties. - * - * Involves decoding up to 4 varints. Typically this will require only 4 to 6 - * bytes but for larger multicodec code values and larger multihash digest - * lengths these varints can be quite large. It is recommended that at least - * 10 bytes be made available in the `initialBytes` argument for a complete - * inspection. - * - * @param {Uint8Array} initialBytes - * @returns {{ version:CIDVersion, codec:number, multihashCode:number, digestSize:number, multihashSize:number, size:number }} - */ - static inspectBytes (initialBytes) { - let offset = 0 - const next = () => { - const [i, length] = varint.decode(initialBytes.subarray(offset)) - offset += length - return i - } - - let version = next() - let codec = DAG_PB_CODE - if (version === 18) { // CIDv0 - version = 0 - offset = 0 - } else if (version === 1) { - codec = next() - } - - if (version !== 0 && version !== 1) { - throw new RangeError(`Invalid CID version ${version}`) - } - - const prefixSize = offset - const multihashCode = next() // multihash code - const digestSize = next() // multihash length - const size = offset + digestSize - const multihashSize = size - prefixSize - - return { version, codec, multihashCode, digestSize, multihashSize, size } - } - - /** - * Takes cid in a string representation and creates an instance. If `base` - * decoder is not provided will use a default from the configuration. It will - * throw an error if encoding of the CID is not compatible with supplied (or - * a default decoder). - * - * @template {string} Prefix - * @param {string} source - * @param {MultibaseDecoder} [base] - */ - static parse (source, base) { - const [prefix, bytes] = parseCIDtoBytes(source, base) - - const cid = CID.decode(bytes) - // Cache string representation to avoid computing it on `this.toString()` - // @ts-ignore - Can't access private - cid._baseCache.set(prefix, source) - - return cid - } } /** * @template {string} Prefix * @param {string} source - * @param {MultibaseDecoder} [base] + * @param {API.MultibaseDecoder} [base] * @returns {[string, Uint8Array]} */ const parseCIDtoBytes = (source, base) => { @@ -407,7 +478,7 @@ const parseCIDtoBytes = (source, base) => { * * @param {Uint8Array} bytes * @param {Map} cache - * @param {MultibaseEncoder<'z'>} base + * @param {API.MultibaseEncoder<'z'>} base */ const toStringV0 = (bytes, cache, base) => { const { prefix } = base @@ -429,7 +500,7 @@ const toStringV0 = (bytes, cache, base) => { * @template {string} Prefix * @param {Uint8Array} bytes * @param {Map} cache - * @param {MultibaseEncoder} base + * @param {API.MultibaseEncoder} base */ const toStringV1 = (bytes, cache, base) => { const { prefix } = base @@ -447,7 +518,7 @@ const DAG_PB_CODE = 0x70 const SHA_256_CODE = 0x12 /** - * @param {CIDVersion} version + * @param {API.CIDVersion} version * @param {number} code * @param {Uint8Array} multihash * @returns {Uint8Array} diff --git a/src/cid/interface.ts b/src/cid/interface.ts new file mode 100644 index 00000000..c2fc83fc --- /dev/null +++ b/src/cid/interface.ts @@ -0,0 +1,44 @@ +/* eslint-disable no-use-before-define */ +import type { MultihashDigest } from '../hashes/interface' +import type { MultibaseEncoder, MultibaseDecoder } from '../bases/interface' + +export type { MultihashDigest, MultibaseEncoder, MultibaseDecoder } +export type CIDVersion = 0 | 1 + +export type DAG_PB = 0x70 +export type SHA_256 = 0x12 + +export interface CID< + Format extends number = number, + Algorithm extends number = number, + Version extends CIDVersion = CIDVersion +> { + readonly version: Version + readonly code: Format + readonly multihash: MultihashDigest + + readonly byteOffset: number + readonly byteLength: number + readonly bytes: Uint8Array + + readonly asCID: this + + equals(other: unknown): other is CID + + toString(base?: MultibaseEncoder): string + toJSON(): {version: Version, code:Format, hash:Uint8Array} + + toV0(): CIDv0 + toV1(): CIDv1 +} + +export interface CIDv0 extends CID { + readonly version: 0 +} + +export interface CIDv1< + Format extends number = number, + Algorithm extends number = number +> extends CID { + readonly version: 1 +} diff --git a/src/index.js b/src/index.js index aed6be18..d30d9a7a 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,8 @@ -import { CID } from './cid.js' +import * as CID from './cid.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' +export * from './interface.js' export { CID, hasher, digest, varint, bytes } diff --git a/src/interface.js b/src/interface.js new file mode 100644 index 00000000..d9b3f4f7 --- /dev/null +++ b/src/interface.js @@ -0,0 +1 @@ +// this is dummy module overlayed by interface.ts diff --git a/src/interface.ts b/src/interface.ts new file mode 100644 index 00000000..0eb11629 --- /dev/null +++ b/src/interface.ts @@ -0,0 +1,4 @@ +export * from './bases/interface' +export * from './hashes/interface' +export * from './codecs/interface' +export * from './cid/interface' diff --git a/test/test-cid.js b/test/test-cid.js index 905d4644..51c88f6b 100644 --- a/test/test-cid.js +++ b/test/test-cid.js @@ -10,6 +10,9 @@ import { sha256, sha512 } from 'multiformats/hashes/sha2' import invalidMultihash from './fixtures/invalid-multihash.js' import chai from 'chai' import chaiAsPromised from 'chai-as-promised' +import * as API from 'multiformats/interface' + +export { API } chai.use(chaiAsPromised) const { assert } = chai @@ -92,7 +95,7 @@ describe('CID', () => { const cidStr = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n' const oldCid = CID.parse(cidStr) const newCid = CID.asCID(oldCid) - assert.deepStrictEqual(/** @type {CID} */(newCid).toString(), cidStr) + assert.deepStrictEqual(/** @type {API.CID} */(newCid).toString(), cidStr) }) it('inspect bytes', () => { @@ -196,7 +199,7 @@ describe('CID', () => { const cidStr = 'bafybeidskjjd4zmr7oh6ku6wp72vvbxyibcli2r6if3ocdcy7jjjusvl2u' const oldCid = CID.parse(cidStr) const newCid = CID.asCID(oldCid) - assert.deepStrictEqual(/** @type {CID} */(newCid).toString(), cidStr) + assert.deepStrictEqual(/** @type {API.CID} */(newCid).toString(), cidStr) }) }) @@ -247,8 +250,6 @@ describe('CID', () => { it('works with deepEquals', () => { const ch1 = CID.parse(h1) - // @ts-expect-error - '_baseCache' is private - ch1._baseCache.set('herp', 'derp') assert.deepStrictEqual(ch1, CID.parse(h1)) assert.notDeepEqual(ch1, CID.parse(h2)) }) @@ -344,6 +345,20 @@ describe('CID', () => { const cid = CID.create(0, 112, hash) assert.deepStrictEqual(cid.toV0() === cid, true) }) + + it('should fail to convert unknown version', async () => { + const hash = await sha256.digest(textEncoder.encode(`TEST${Date.now()}`)) + const cid = CID.create(0, 112, hash) + const cid1 = Object.assign(Object.create(Object.getPrototypeOf(cid)), { ...cid }) + const cid2 = Object.assign(Object.create(Object.getPrototypeOf(cid)), { ...cid, version: 3 }) + + assert.deepStrictEqual(cid1.toV0().version, 0) + assert.deepStrictEqual(cid1.toV1().version, 1) + assert.equal(cid2.version, 3) + + assert.throws(() => cid2.toV1(), /Can not convert CID version 3 to version 1/) + assert.throws(() => cid2.toV0(), /Can not convert CID version 3 to version 0/) + }) }) describe('caching', () => { @@ -358,30 +373,52 @@ describe('CID', () => { const hash = await sha256.digest(textEncoder.encode('abc')) const cid = CID.create(1, 112, hash) - // @ts-expect-error - _baseCache is private - assert.deepStrictEqual(cid._baseCache.size, 0) - - assert.deepStrictEqual(cid.toString(base64), 'mAXASILp4Fr+PAc/qQUFA3l2uIiOwA2Gjlhd6nLQQ/2HyABWt') - - // @ts-expect-error - _baseCache is private - assert.deepStrictEqual(cid._baseCache.get(base64.prefix), 'mAXASILp4Fr+PAc/qQUFA3l2uIiOwA2Gjlhd6nLQQ/2HyABWt') + const b32 = { + ...base32, + callCount: 0, + /** + * @param {Uint8Array} bytes + */ + encode (bytes) { + this.callCount += 1 + return base32.encode(bytes) + '!' + } + } - // @ts-expect-error - _baseCache is private - assert.deepStrictEqual(cid._baseCache.has(base32.prefix), false) + const b64 = { + ...base64, + callCount: 0, + /** + * @param {Uint8Array} bytes + */ + encode (bytes) { + this.callCount += 1 + return base64.encode(bytes) + } + } const base32String = 'bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu' - assert.deepStrictEqual(cid.toString(), base32String) + assert.deepEqual(cid.toString(b32), `${base32String}!`) + assert.deepEqual(b32.callCount, 1) + assert.deepEqual(cid.toString(), `${base32String}!`) + assert.deepEqual(b32.callCount, 1) - // @ts-expect-error - _baseCache is private - assert.deepStrictEqual(cid._baseCache.get(base32.prefix), base32String) - assert.deepStrictEqual(cid.toString(base64), 'mAXASILp4Fr+PAc/qQUFA3l2uIiOwA2Gjlhd6nLQQ/2HyABWt') + assert.deepStrictEqual(cid.toString(b64), 'mAXASILp4Fr+PAc/qQUFA3l2uIiOwA2Gjlhd6nLQQ/2HyABWt') + assert.equal(b64.callCount, 1) + assert.deepStrictEqual(cid.toString(b64), 'mAXASILp4Fr+PAc/qQUFA3l2uIiOwA2Gjlhd6nLQQ/2HyABWt') + assert.equal(b64.callCount, 1) }) it('should cache string representation when constructed with one', () => { const base32String = 'bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu' const cid = CID.parse(base32String) - // @ts-expect-error - _baseCache is private - assert.deepStrictEqual(cid._baseCache.get(base32.prefix), base32String) + + assert.deepStrictEqual(cid.toString({ + ...base32, + encode () { + throw Error('Should not call decode') + } + }), base32String) }) }) @@ -422,6 +459,7 @@ describe('CID', () => { const version = 1 const code = 112 + const cid = CID.create(version, code, hash) const incompatibleCID = new IncompatibleCID(version, code, hash) assert.ok(CID.isCID(incompatibleCID)) @@ -429,8 +467,8 @@ describe('CID', () => { // @ts-expect-error - no such method assert.strictEqual(typeof incompatibleCID.toV0, 'undefined') - const cid1 = /** @type {CID} */(CID.asCID(incompatibleCID)) - assert.ok(cid1 instanceof CID) + const cid1 = /** @type {API.CID} */(CID.asCID(incompatibleCID)) + assert.ok(cid1 instanceof cid.constructor) assert.strictEqual(cid1.code, code) assert.strictEqual(cid1.version, version) assert.ok(equals(cid1.multihash.bytes, hash.bytes)) @@ -441,8 +479,8 @@ describe('CID', () => { const duckCID = { version, code, multihash: hash } // @ts-expect-error - no such property duckCID.asCID = duckCID - const cid3 = /** @type {CID} */ (CID.asCID(duckCID)) - assert.ok(cid3 instanceof CID) + const cid3 = /** @type {API.CID} */ (CID.asCID(duckCID)) + assert.ok(cid3 instanceof cid.constructor) assert.strictEqual(cid3.code, code) assert.strictEqual(cid3.version, version) assert.ok(equals(cid3.multihash.bytes, hash.bytes)) @@ -450,16 +488,16 @@ describe('CID', () => { const cid4 = CID.asCID(cid3) assert.strictEqual(cid3, cid4) - const cid5 = /** @type {CID} */(CID.asCID(new OLDCID(1, 'raw', Uint8Array.from(hash.bytes)))) - assert.ok(cid5 instanceof CID) + const cid5 = /** @type {API.CID} */(CID.asCID(new OLDCID(1, 'raw', Uint8Array.from(hash.bytes)))) + assert.ok(cid5 instanceof cid.constructor) assert.strictEqual(cid5.version, 1) assert.ok(equals(cid5.multihash.bytes, hash.bytes)) assert.strictEqual(cid5.code, 85) }) /** - * @param {CID} x - * @param {CID} y + * @param {API.CID} x + * @param {API.CID} y */ const digestsame = (x, y) => { // @ts-ignore - not sure what this supposed to be @@ -496,7 +534,7 @@ describe('CID', () => { const hash = await sha256.digest(textEncoder.encode('abc')) const cid = CID.create(1, 112, hash) - const parsed = /** @type {CID} */(CID.parse(cid.toString(base58btc))) + const parsed = CID.parse(cid.toString(base58btc)) digestsame(cid, parsed) }) @@ -556,7 +594,7 @@ describe('CID', () => { it('new CID from old CID', async () => { const hash = await sha256.digest(textEncoder.encode('abc')) - const cid = /** @type {CID} */ (CID.asCID(new OLDCID(1, 'raw', Uint8Array.from(hash.bytes)))) + const cid = /** @type {API.CID} */ (CID.asCID(new OLDCID(1, 'raw', Uint8Array.from(hash.bytes)))) assert.deepStrictEqual(cid.version, 1) equalDigest(cid.multihash, hash) @@ -576,6 +614,8 @@ describe('CID', () => { it('codec', async () => { const hash = await sha256.digest(textEncoder.encode('abc')) const cid = CID.create(1, 112, hash) + + // @ts-expect-error - codec is not defined property assert.throws(() => cid.codec, '"codec" property is deprecated, use integer "code" property instead') // @ts-expect-error - 'string' is not assignable to parameter of type 'number' assert.throws(() => CID.create(1, 'dag-pb', hash), 'String codecs are no longer supported') @@ -584,12 +624,16 @@ describe('CID', () => { it('multibaseName', async () => { const hash = await sha256.digest(textEncoder.encode('abc')) const cid = CID.create(1, 112, hash) + + // @ts-ignore - multibaseName is not defined property assert.throws(() => cid.multibaseName, '"multibaseName" property is deprecated') }) it('prefix', async () => { const hash = await sha256.digest(textEncoder.encode('abc')) const cid = CID.create(1, 112, hash) + + // @ts-ignore - prefix is not defined property assert.throws(() => cid.prefix, '"prefix" property is deprecated') }) @@ -609,6 +653,8 @@ describe('CID', () => { it('buffer', async () => { const hash = await sha256.digest(textEncoder.encode('abc')) const cid = CID.create(1, 112, hash) + + // @ts-expect-error - cid.buffer is not defined property assert.throws(() => cid.buffer, 'Deprecated .buffer property, use .bytes to get Uint8Array instead') }) }) From 9947c49e85a1553ca9f9bc884b6b27ae6f02f38c Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Wed, 19 Jan 2022 18:24:14 +0000 Subject: [PATCH 04/20] chore: add missing type params --- src/block.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/block.js b/src/block.js index 1df3a462..e30f0844 100644 --- a/src/block.js +++ b/src/block.js @@ -202,7 +202,7 @@ const createUnsafe = ({ bytes, cid, value: maybeValue, codec }) => { * @template {number} Code * @template {number} Alg * @param {Object} options - * @param {CID.CID} options.cid + * @param {CID.CID} options.cid * @param {API.ByteView} options.bytes * @param {API.BlockDecoder} options.codec * @param {API.MultihashHasher} options.hasher From 3f2fafadfcd8d7251f7791c73a0beaba849381fd Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Wed, 19 Jan 2022 18:30:53 +0000 Subject: [PATCH 05/20] chore: remove unecessary exports --- src/bases/base.js | 6 +++--- src/cid.js | 2 ++ test/test-cid.js | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/bases/base.js b/src/bases/base.js index 70b91aee..7dba1bef 100644 --- a/src/bases/base.js +++ b/src/bases/base.js @@ -1,8 +1,8 @@ import basex from '../../vendor/base-x.js' import { coerce } from '../bytes.js' -import * as API from '../interface.js' - -export { API } +// Linter can see that API is used in types. +// eslint-disable-next-line +import * as API from './interface.js' /** * Class represents both BaseEncoder and MultibaseEncoder meaning it diff --git a/src/cid.js b/src/cid.js index 1cacfe9a..ac5eb1b1 100644 --- a/src/cid.js +++ b/src/cid.js @@ -3,6 +3,8 @@ import * as Digest from './hashes/digest.js' import { base58btc } from './bases/base58.js' import { base32 } from './bases/base32.js' import { coerce } from './bytes.js' +// Linter can see that API is used in types. +// eslint-disable-next-line import * as API from './interface.js' export * from './interface.js' diff --git a/test/test-cid.js b/test/test-cid.js index 51c88f6b..70bc88cf 100644 --- a/test/test-cid.js +++ b/test/test-cid.js @@ -10,10 +10,10 @@ import { sha256, sha512 } from 'multiformats/hashes/sha2' import invalidMultihash from './fixtures/invalid-multihash.js' import chai from 'chai' import chaiAsPromised from 'chai-as-promised' +// Linter can see that API is used in types. +// eslint-disable-next-line import * as API from 'multiformats/interface' -export { API } - chai.use(chaiAsPromised) const { assert } = chai From e841c28c6c46d73f8b1c291013f9e3c3f83cae42 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Wed, 19 Jan 2022 18:31:33 +0000 Subject: [PATCH 06/20] chore: add clarifying comments --- src/cid.js | 2 +- src/index.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cid.js b/src/cid.js index ac5eb1b1..10fdae8c 100644 --- a/src/cid.js +++ b/src/cid.js @@ -7,8 +7,8 @@ import { coerce } from './bytes.js' // eslint-disable-next-line import * as API from './interface.js' +// This way TS will also expose all the types from module export * from './interface.js' -export { API } /** * @template {number} Format diff --git a/src/index.js b/src/index.js index d30d9a7a..f3cc8e34 100644 --- a/src/index.js +++ b/src/index.js @@ -3,6 +3,7 @@ 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' +// This way TS will also expose all the types from module export * from './interface.js' export { CID, hasher, digest, varint, bytes } From a306d52924c2877b056d963a9b2607ca7b99c779 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Wed, 19 Jan 2022 18:31:50 +0000 Subject: [PATCH 07/20] fix: type errors --- src/cid.js | 2 +- test/test-traversal.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/cid.js b/src/cid.js index 10fdae8c..49372b05 100644 --- a/src/cid.js +++ b/src/cid.js @@ -55,7 +55,7 @@ export const asCID = (input) => { /** * @deprecated * @param {any} value - * @returns {value is CID} + * @returns {value is API.CID} */ export const isCID = (value) => { deprecate(/^0\.0/, IS_CID_DEPRECATION) diff --git a/test/test-traversal.js b/test/test-traversal.js index f8a42c72..e14b0d3b 100644 --- a/test/test-traversal.js +++ b/test/test-traversal.js @@ -4,6 +4,8 @@ import * as dagPB from '@ipld/dag-pb' import { sha256 as hasher } from 'multiformats/hashes/sha2' import * as main from 'multiformats/block' import { walk } from 'multiformats/traversal' +// eslint-disable-next-line +import * as API from 'multiformats/interface' import { assert } from 'chai' import { fromString } from '../src/bytes.js' @@ -43,7 +45,7 @@ describe('traversal', () => { const cidA = blockA.cid /** - * @param {import('multiformats').CID} cid + * @param {API.CID} cid */ const load = async (cid) => { if (cid.equals(cidE)) { @@ -70,7 +72,7 @@ describe('traversal', () => { */ const loadWrapper = (load, arr = []) => /** - * @param {import('multiformats').CID} cid + * @param {API.CID} cid */ (cid) => { arr.push(cid.toString()) From 6da6273eb63f87fc300984a7fa188c23019a8efc Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Wed, 19 Jan 2022 18:42:47 +0000 Subject: [PATCH 08/20] chore: cleanup block module --- src/block.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/block.js b/src/block.js index e30f0844..e52731e6 100644 --- a/src/block.js +++ b/src/block.js @@ -1,8 +1,8 @@ import { bytes as binary, CID } from './index.js' +// Linter can see that API is used in types. +// eslint-disable-next-line import * as API from './interface.js' -export { API } - const readonly = ({ enumerable = true, configurable = false } = {}) => ({ enumerable, configurable, @@ -13,7 +13,7 @@ const readonly = ({ enumerable = true, configurable = false } = {}) => ({ * @template T * @param {T} source * @param {Array} base - * @returns {Iterable<[string, CID.CID]>} + * @returns {Iterable<[string, API.CID]>} */ const links = function * (source, base) { if (source == null) return @@ -97,7 +97,7 @@ const get = (source, path) => { class Block { /** * @param {Object} options - * @param {CID.CID} options.cid + * @param {API.CID} options.cid * @param {API.ByteView} options.bytes * @param {T} options.value */ @@ -178,13 +178,13 @@ const decode = async ({ bytes, codec, hasher }) => { /** * @typedef {Object} RequiredCreateOptions - * @property {CID.CID} options.cid + * @property {API.CID} options.cid */ /** * @template T * @template {number} Code - * @param {{ cid: CID.CID, value:T, codec?: API.BlockDecoder, bytes: API.ByteView }|{cid:CID.CID, bytes:API.ByteView, value?:void, codec:API.BlockDecoder}} options + * @param {{ cid: API.CID, value:T, codec?: API.BlockDecoder, bytes: API.ByteView }|{cid:API.CID, bytes:API.ByteView, value?:void, codec:API.BlockDecoder}} options * @returns {Block} */ const createUnsafe = ({ bytes, cid, value: maybeValue, codec }) => { @@ -202,7 +202,7 @@ const createUnsafe = ({ bytes, cid, value: maybeValue, codec }) => { * @template {number} Code * @template {number} Alg * @param {Object} options - * @param {CID.CID} options.cid + * @param {API.CID} options.cid * @param {API.ByteView} options.bytes * @param {API.BlockDecoder} options.codec * @param {API.MultihashHasher} options.hasher From 88182b6f5f55015d35d6e13b226b5bf531b15f08 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Wed, 19 Jan 2022 18:43:06 +0000 Subject: [PATCH 09/20] chore: remove uintented changes --- src/bases/base.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/bases/base.js b/src/bases/base.js index 7dba1bef..d8ce1caa 100644 --- a/src/bases/base.js +++ b/src/bases/base.js @@ -129,13 +129,9 @@ class ComposedDecoder { if (decoder) { return decoder.decode(input) } else { - throw RangeError( - `Unable to decode multibase string ${JSON.stringify( - input - )}, only inputs prefixed with ${Object.keys( - this.decoders - )} are supported` - ) + throw RangeError(`Unable to decode multibase string + ${JSON.stringify(input)}, only inputs prefixed with + ${Object.keys(this.decoders)} are supported`) } } } From 99fcb43dc41b96a07ac3fe93547fa930f5bd79a3 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Wed, 19 Jan 2022 18:44:33 +0000 Subject: [PATCH 10/20] chore: rename Algorithm to Alg TS confuses it with Alogrithm from crypto.subtle stuff --- src/cid.js | 16 +++++++--------- src/cid/interface.ts | 10 +++++----- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/cid.js b/src/cid.js index 49372b05..a7020311 100644 --- a/src/cid.js +++ b/src/cid.js @@ -285,16 +285,16 @@ const baseCache = (cid) => { /** * @template {number} Format - * @template {number} Algorithm + * @template {number} Alg * @template {API.CIDVersion} Version - * @implements {API.CID} + * @implements {API.CID} */ class CIDView { /** * @param {Version} version * @param {Format} code - * @param {API.MultihashDigest} multihash + * @param {API.MultihashDigest} multihash * @param {Uint8Array} bytes * */ @@ -349,9 +349,7 @@ class CIDView { // sha2-256 if (multihash.code !== SHA_256_CODE) { - throw new Error( - 'Cannot convert non sha2-256 multihash CID to CIDv0' - ) + throw new Error('Cannot convert non sha2-256 multihash CID to CIDv0') } return createV0( @@ -367,7 +365,7 @@ class CIDView { } /** - * @returns {API.CIDv1} + * @returns {API.CIDv1} */ toV1 () { switch (this.version) { @@ -377,7 +375,7 @@ class CIDView { return createV1(this.code, multihash) } case 1: { - return /** @type {API.CIDv1} */ (this) + return /** @type {API.CIDv1} */ (this) } default: { throw Error( @@ -389,7 +387,7 @@ class CIDView { /** * @param {unknown} other - * @returns {other is API.CID} + * @returns {other is API.CID} */ equals (other) { return equals(this, other) diff --git a/src/cid/interface.ts b/src/cid/interface.ts index c2fc83fc..e4e95bf6 100644 --- a/src/cid/interface.ts +++ b/src/cid/interface.ts @@ -10,12 +10,12 @@ export type SHA_256 = 0x12 export interface CID< Format extends number = number, - Algorithm extends number = number, + Alg extends number = number, Version extends CIDVersion = CIDVersion > { readonly version: Version readonly code: Format - readonly multihash: MultihashDigest + readonly multihash: MultihashDigest readonly byteOffset: number readonly byteLength: number @@ -23,7 +23,7 @@ export interface CID< readonly asCID: this - equals(other: unknown): other is CID + equals(other: unknown): other is CID toString(base?: MultibaseEncoder): string toJSON(): {version: Version, code:Format, hash:Uint8Array} @@ -38,7 +38,7 @@ export interface CIDv0 extends CID { export interface CIDv1< Format extends number = number, - Algorithm extends number = number -> extends CID { + Alg extends number = number +> extends CID { readonly version: 1 } From f624c80093fb571a69e77183ede0dab8f6d20b4a Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Wed, 19 Jan 2022 18:46:45 +0000 Subject: [PATCH 11/20] chore: undo unintended change --- src/bases/base.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/bases/base.js b/src/bases/base.js index d8ce1caa..c83c92b8 100644 --- a/src/bases/base.js +++ b/src/bases/base.js @@ -129,9 +129,7 @@ class ComposedDecoder { if (decoder) { return decoder.decode(input) } else { - throw RangeError(`Unable to decode multibase string - ${JSON.stringify(input)}, only inputs prefixed with - ${Object.keys(this.decoders)} are supported`) + throw RangeError(`Unable to decode multibase string ${JSON.stringify(input)}, only inputs prefixed with ${Object.keys(this.decoders)} are supported`) } } } From aa6a478243f9fa2dfd839d013750f48df138301b Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Wed, 19 Jan 2022 19:08:45 +0000 Subject: [PATCH 12/20] chore: fix import --- src/bases/base.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bases/base.js b/src/bases/base.js index c83c92b8..5c14190a 100644 --- a/src/bases/base.js +++ b/src/bases/base.js @@ -2,7 +2,7 @@ import basex from '../../vendor/base-x.js' import { coerce } from '../bytes.js' // Linter can see that API is used in types. // eslint-disable-next-line -import * as API from './interface.js' +import * as API from '../interface.js' /** * Class represents both BaseEncoder and MultibaseEncoder meaning it From 3c835a0adc967602bb0290c0364a52cb1885c30e Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Wed, 30 Mar 2022 01:32:02 -0700 Subject: [PATCH 13/20] fix: revert CID class --- src/block.js | 4 +- src/cid.js | 122 +++++++++++++++++++++++++++++++++++++---------- src/index.js | 2 +- test/test-cid.js | 26 ++++------ 4 files changed, 108 insertions(+), 46 deletions(-) diff --git a/src/block.js b/src/block.js index e52731e6..4d71d2bf 100644 --- a/src/block.js +++ b/src/block.js @@ -13,7 +13,7 @@ const readonly = ({ enumerable = true, configurable = false } = {}) => ({ * @template T * @param {T} source * @param {Array} base - * @returns {Iterable<[string, API.CID]>} + * @returns {Iterable<[string, CID]>} */ const links = function * (source, base) { if (source == null) return @@ -104,7 +104,7 @@ class Block { constructor ({ cid, bytes, value }) { if (!cid || !bytes || typeof value === 'undefined') throw new Error('Missing required argument') - this.cid = cid + this.cid = /** @type {CID} */(cid) this.bytes = bytes this.value = value this.asBlock = this diff --git a/src/cid.js b/src/cid.js index a7020311..61778df9 100644 --- a/src/cid.js +++ b/src/cid.js @@ -20,7 +20,7 @@ export * from './interface.js' */ export const asCID = (input) => { const value = /** @type {any} */(input) - if (value instanceof CIDView) { + if (value instanceof CID) { // If value is instance of CID then we're all set. return value } else if (value != null && value.asCID === value) { @@ -30,7 +30,7 @@ export const asCID = (input) => { // implemnetation so caller is guaranteed to get instance with expected // API. const { version, code, multihash, bytes } = value - return new CIDView( + return new CID( version, code, /** @type {API.MultihashDigest} */ (multihash), @@ -52,16 +52,6 @@ export const asCID = (input) => { } } -/** - * @deprecated - * @param {any} value - * @returns {value is API.CID} - */ -export const isCID = (value) => { - deprecate(/^0\.0/, IS_CID_DEPRECATION) - return !!(value && (value[cidSymbol] || value.asCID === value)) -} - /** * @template {number} Format * @template {number} Alg @@ -81,12 +71,12 @@ export const create = (version, code, digest) => { if (code !== DAG_PB_CODE) { throw new Error(`Version 0 CID must use dag-pb (code: ${DAG_PB_CODE}) block encoding`) } else { - return new CIDView(version, code, digest, digest.bytes) + return new CID(version, code, digest, digest.bytes) } } case 1: { const bytes = encodeCID(version, code, digest.bytes) - return new CIDView(version, code, digest, bytes) + return new CID(version, code, digest, bytes) } default: { throw new Error('Invalid version') @@ -284,13 +274,13 @@ const baseCache = (cid) => { } /** - * @template {number} Format - * @template {number} Alg - * @template {API.CIDVersion} Version + * @template {number} [Format=number] + * @template {number} [Alg=number] + * @template {API.CIDVersion} [Version=API.CIDVersion] * @implements {API.CID} */ -class CIDView { +export class CID { /** * @param {Version} version * @param {Format} code @@ -333,12 +323,12 @@ class CIDView { } /** - * @returns {API.CIDv0} + * @returns {CID} */ toV0 () { switch (this.version) { case 0: { - return /** @type {API.CIDv0} */ (this) + return /** @type {CID} */ (this) } case 1: { const { code, multihash } = this @@ -352,9 +342,9 @@ class CIDView { throw new Error('Cannot convert non sha2-256 multihash CID to CIDv0') } - return createV0( + return /** @type {CID} */(createV0( /** @type {API.MultihashDigest} */ (multihash) - ) + )) } default: { throw Error( @@ -365,17 +355,17 @@ class CIDView { } /** - * @returns {API.CIDv1} + * @returns {CID} */ toV1 () { switch (this.version) { case 0: { const { code, digest } = this.multihash const multihash = Digest.create(code, digest) - return createV1(this.code, multihash) + return /** @type {CID} */(createV1(this.code, multihash)) } case 1: { - return /** @type {API.CIDv1} */ (this) + return /** @type {CID} */ (this) } default: { throw Error( @@ -387,7 +377,7 @@ class CIDView { /** * @param {unknown} other - * @returns {other is API.CID} + * @returns {other is CID} */ equals (other) { return equals(this, other) @@ -419,6 +409,17 @@ class CIDView { return `CID(${this.toString()})` } + // Deprecated + + /** + * @param {any} value + * @returns {value is CID} + */ + static isCID (value) { + deprecate(/^0\.0/, IS_CID_DEPRECATION) + return !!(value && (value[cidSymbol] || value.asCID === value)) + } + get toBaseEncodedString () { throw new Error('Deprecated, use .toString()') } @@ -442,6 +443,75 @@ class CIDView { get prefix () { throw new Error('"prefix" property is deprecated') } + + /** + * @param {any} value + */ + static asCID (value) { + return /** @type {CID|null} */(asCID(value)) + } + + /** + * @template {number} Format + * @template {number} Alg + * @template {API.CIDVersion} Version + * @param {Version} version - Version of the CID + * @param {Format} code - Code of the codec content is encoded in. + * @param {API.MultihashDigest} digest - (Multi)hash of the of the content. + */ + static create (version, code, digest) { + return /** @type {CID} */(create(version, code, digest)) + } + + /** + * Simplified version of `create` for CIDv0. + * @param {API.MultihashDigest} digest - Multihash. + */ + static createV0 (digest) { + return CID.create(0, DAG_PB_CODE, digest) + } + + /** + * Simplified version of `create` for CIDv1. + * @template {number} Code + * @template {number} Alg + * @param {Code} code - Content encoding format code. + * @param {API.MultihashDigest} digest - Miltihash of the content. + */ + static createV1 (code, digest) { + return CID.create(1, code, digest) + } + + /** + * @param {Uint8Array} bytes + */ + + static decode (bytes) { + return /** @type {CID} */(decode(bytes)) + } + + /** + * @param {Uint8Array} bytes + */ + static decodeFirst (bytes) { + return /** @type {[CID, Uint8Array]} */(decodeFirst(bytes)) + } + + /** + * @param {Uint8Array} initialBytes + */ + static inspectBytes (initialBytes) { + return inspectBytes(initialBytes) + } + + /** + * @template {string} Prefix + * @param {string} source + * @param {API.MultibaseDecoder} [base] + */ + static parse (source, base) { + return /** @type {CID} */(parse(source, base)) + } } /** diff --git a/src/index.js b/src/index.js index f3cc8e34..377a9771 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ -import * as CID from './cid.js' +import { CID } from './cid.js' import * as varint from './varint.js' import * as bytes from './bytes.js' import * as hasher from './hashes/hasher.js' diff --git a/test/test-cid.js b/test/test-cid.js index 70bc88cf..be0f30b7 100644 --- a/test/test-cid.js +++ b/test/test-cid.js @@ -95,7 +95,7 @@ describe('CID', () => { const cidStr = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n' const oldCid = CID.parse(cidStr) const newCid = CID.asCID(oldCid) - assert.deepStrictEqual(/** @type {API.CID} */(newCid).toString(), cidStr) + assert.deepStrictEqual(newCid.toString(), cidStr) }) it('inspect bytes', () => { @@ -199,7 +199,7 @@ describe('CID', () => { const cidStr = 'bafybeidskjjd4zmr7oh6ku6wp72vvbxyibcli2r6if3ocdcy7jjjusvl2u' const oldCid = CID.parse(cidStr) const newCid = CID.asCID(oldCid) - assert.deepStrictEqual(/** @type {API.CID} */(newCid).toString(), cidStr) + assert.deepStrictEqual(newCid.toString(), cidStr) }) }) @@ -459,7 +459,6 @@ describe('CID', () => { const version = 1 const code = 112 - const cid = CID.create(version, code, hash) const incompatibleCID = new IncompatibleCID(version, code, hash) assert.ok(CID.isCID(incompatibleCID)) @@ -467,8 +466,8 @@ describe('CID', () => { // @ts-expect-error - no such method assert.strictEqual(typeof incompatibleCID.toV0, 'undefined') - const cid1 = /** @type {API.CID} */(CID.asCID(incompatibleCID)) - assert.ok(cid1 instanceof cid.constructor) + const cid1 = /** @type {CID} */(CID.asCID(incompatibleCID)) + assert.ok(cid1 instanceof CID) assert.strictEqual(cid1.code, code) assert.strictEqual(cid1.version, version) assert.ok(equals(cid1.multihash.bytes, hash.bytes)) @@ -479,8 +478,8 @@ describe('CID', () => { const duckCID = { version, code, multihash: hash } // @ts-expect-error - no such property duckCID.asCID = duckCID - const cid3 = /** @type {API.CID} */ (CID.asCID(duckCID)) - assert.ok(cid3 instanceof cid.constructor) + const cid3 = /** @type {CID} */ (CID.asCID(duckCID)) + assert.ok(cid3 instanceof CID) assert.strictEqual(cid3.code, code) assert.strictEqual(cid3.version, version) assert.ok(equals(cid3.multihash.bytes, hash.bytes)) @@ -488,8 +487,8 @@ describe('CID', () => { const cid4 = CID.asCID(cid3) assert.strictEqual(cid3, cid4) - const cid5 = /** @type {API.CID} */(CID.asCID(new OLDCID(1, 'raw', Uint8Array.from(hash.bytes)))) - assert.ok(cid5 instanceof cid.constructor) + const cid5 = /** @type {CID} */(CID.asCID(new OLDCID(1, 'raw', Uint8Array.from(hash.bytes)))) + assert.ok(cid5 instanceof CID) assert.strictEqual(cid5.version, 1) assert.ok(equals(cid5.multihash.bytes, hash.bytes)) assert.strictEqual(cid5.code, 85) @@ -594,7 +593,7 @@ describe('CID', () => { it('new CID from old CID', async () => { const hash = await sha256.digest(textEncoder.encode('abc')) - const cid = /** @type {API.CID} */ (CID.asCID(new OLDCID(1, 'raw', Uint8Array.from(hash.bytes)))) + const cid = /** @type {CID} */ (CID.asCID(new OLDCID(1, 'raw', Uint8Array.from(hash.bytes)))) assert.deepStrictEqual(cid.version, 1) equalDigest(cid.multihash, hash) @@ -615,7 +614,6 @@ describe('CID', () => { const hash = await sha256.digest(textEncoder.encode('abc')) const cid = CID.create(1, 112, hash) - // @ts-expect-error - codec is not defined property assert.throws(() => cid.codec, '"codec" property is deprecated, use integer "code" property instead') // @ts-expect-error - 'string' is not assignable to parameter of type 'number' assert.throws(() => CID.create(1, 'dag-pb', hash), 'String codecs are no longer supported') @@ -624,16 +622,12 @@ describe('CID', () => { it('multibaseName', async () => { const hash = await sha256.digest(textEncoder.encode('abc')) const cid = CID.create(1, 112, hash) - - // @ts-ignore - multibaseName is not defined property assert.throws(() => cid.multibaseName, '"multibaseName" property is deprecated') }) it('prefix', async () => { const hash = await sha256.digest(textEncoder.encode('abc')) const cid = CID.create(1, 112, hash) - - // @ts-ignore - prefix is not defined property assert.throws(() => cid.prefix, '"prefix" property is deprecated') }) @@ -653,8 +647,6 @@ describe('CID', () => { it('buffer', async () => { const hash = await sha256.digest(textEncoder.encode('abc')) const cid = CID.create(1, 112, hash) - - // @ts-expect-error - cid.buffer is not defined property assert.throws(() => cid.buffer, 'Deprecated .buffer property, use .bytes to get Uint8Array instead') }) }) From 6c92773cf52e917ccb5a358845647c4dd1ebf92c Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Wed, 30 Mar 2022 09:51:28 -0700 Subject: [PATCH 14/20] fix: add test to improve coverage --- src/cid.js | 4 ++-- src/cid/interface.ts | 4 ++++ test/test-cid.js | 19 +++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/cid.js b/src/cid.js index 61778df9..5ac43008 100644 --- a/src/cid.js +++ b/src/cid.js @@ -5,10 +5,10 @@ import { base32 } from './bases/base32.js' import { coerce } from './bytes.js' // Linter can see that API is used in types. // eslint-disable-next-line -import * as API from './interface.js' +import * as API from './cid/interface.js' // This way TS will also expose all the types from module -export * from './interface.js' +export * from './cid/interface.js' /** * @template {number} Format diff --git a/src/cid/interface.ts b/src/cid/interface.ts index e4e95bf6..2c80b297 100644 --- a/src/cid/interface.ts +++ b/src/cid/interface.ts @@ -42,3 +42,7 @@ export interface CIDv1< > extends CID { readonly version: 1 } + +// Export interface with different name because +// cid.js will shadow `CID` interface with a class +export type { CID as CIDType } diff --git a/test/test-cid.js b/test/test-cid.js index be0f30b7..baa01bb1 100644 --- a/test/test-cid.js +++ b/test/test-cid.js @@ -3,6 +3,7 @@ import OLDCID from 'cids' import { fromHex, toHex, equals } from '../src/bytes.js' import { varint, CID } from 'multiformats' +import * as CIDLib from 'multiformats/cid' import { base58btc } from 'multiformats/bases/base58' import { base32 } from 'multiformats/bases/base32' import { base64 } from 'multiformats/bases/base64' @@ -42,6 +43,15 @@ describe('CID', () => { assert.deepStrictEqual(cid.toString(), base58btc.baseEncode(hash.bytes)) }) + it('createV0', async () => { + const hash = await sha256.digest(textEncoder.encode('abc')) + const cid = CIDLib.createV0(hash) + + assert.deepStrictEqual(cid.code, 112) + assert.deepStrictEqual(cid.version, 0) + assert.deepStrictEqual(cid.multihash, hash) + assert.deepStrictEqual(cid.toString(), base58btc.baseEncode(hash.bytes)) + }) it('create from multihash', async () => { const hash = await sha256.digest(textEncoder.encode('abc')) @@ -155,6 +165,15 @@ describe('CID', () => { equalDigest(cid.multihash, hash) }) + it('createV1', async () => { + const hash = await sha256.digest(textEncoder.encode('abc')) + const cid = CIDLib.createV1(0x71, hash) + + assert.deepStrictEqual(cid.code, 0x71) + assert.deepStrictEqual(cid.version, 1) + equalDigest(cid.multihash, hash) + }) + it('can roundtrip through cid.toString()', async () => { const hash = await sha256.digest(textEncoder.encode('abc')) const cid1 = CID.create(1, 0x71, hash) From 680bd77cb203cd4e006fa3def10ab9a292c271ee Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Wed, 30 Mar 2022 18:00:19 -0700 Subject: [PATCH 15/20] fix: add missing files --- src/bases/base.js | 2 +- src/bases/interface.js | 1 + src/cid/interface.js | 1 + src/codecs/interface.js | 1 + src/hashes/interface.js | 1 + test/test-cid.js | 22 ++++++++++++++++++++-- test/tsconfig.json | 4 ++-- 7 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 src/bases/interface.js create mode 100644 src/cid/interface.js create mode 100644 src/codecs/interface.js create mode 100644 src/hashes/interface.js diff --git a/src/bases/base.js b/src/bases/base.js index 5c14190a..c83c92b8 100644 --- a/src/bases/base.js +++ b/src/bases/base.js @@ -2,7 +2,7 @@ import basex from '../../vendor/base-x.js' import { coerce } from '../bytes.js' // Linter can see that API is used in types. // eslint-disable-next-line -import * as API from '../interface.js' +import * as API from './interface.js' /** * Class represents both BaseEncoder and MultibaseEncoder meaning it diff --git a/src/bases/interface.js b/src/bases/interface.js new file mode 100644 index 00000000..d9b3f4f7 --- /dev/null +++ b/src/bases/interface.js @@ -0,0 +1 @@ +// this is dummy module overlayed by interface.ts diff --git a/src/cid/interface.js b/src/cid/interface.js new file mode 100644 index 00000000..d9b3f4f7 --- /dev/null +++ b/src/cid/interface.js @@ -0,0 +1 @@ +// this is dummy module overlayed by interface.ts diff --git a/src/codecs/interface.js b/src/codecs/interface.js new file mode 100644 index 00000000..d9b3f4f7 --- /dev/null +++ b/src/codecs/interface.js @@ -0,0 +1 @@ +// this is dummy module overlayed by interface.ts diff --git a/src/hashes/interface.js b/src/hashes/interface.js new file mode 100644 index 00000000..d9b3f4f7 --- /dev/null +++ b/src/hashes/interface.js @@ -0,0 +1 @@ +// this is dummy module overlayed by interface.ts diff --git a/test/test-cid.js b/test/test-cid.js index baa01bb1..5cf462f1 100644 --- a/test/test-cid.js +++ b/test/test-cid.js @@ -52,6 +52,16 @@ describe('CID', () => { assert.deepStrictEqual(cid.multihash, hash) assert.deepStrictEqual(cid.toString(), base58btc.baseEncode(hash.bytes)) }) + it('CID.createV0', async () => { + const hash = await sha256.digest(textEncoder.encode('abc')) + const cid = CID.createV0(hash) + + assert.deepStrictEqual(cid.code, 112) + assert.deepStrictEqual(cid.version, 0) + assert.deepStrictEqual(cid.multihash, hash) + assert.deepStrictEqual(cid.toString(), base58btc.baseEncode(hash.bytes)) + }) + it('create from multihash', async () => { const hash = await sha256.digest(textEncoder.encode('abc')) @@ -104,7 +114,7 @@ describe('CID', () => { it('should construct from an old CID', () => { const cidStr = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n' const oldCid = CID.parse(cidStr) - const newCid = CID.asCID(oldCid) + const newCid = /** @type {CID} */ (CID.asCID(oldCid)) assert.deepStrictEqual(newCid.toString(), cidStr) }) @@ -173,6 +183,14 @@ describe('CID', () => { assert.deepStrictEqual(cid.version, 1) equalDigest(cid.multihash, hash) }) + it('CID.createV1', async () => { + const hash = await sha256.digest(textEncoder.encode('abc')) + const cid = CID.createV1(0x71, hash) + + assert.deepStrictEqual(cid.code, 0x71) + assert.deepStrictEqual(cid.version, 1) + equalDigest(cid.multihash, hash) + }) it('can roundtrip through cid.toString()', async () => { const hash = await sha256.digest(textEncoder.encode('abc')) @@ -217,7 +235,7 @@ describe('CID', () => { it('should construct from an old CID without a multibaseName', () => { const cidStr = 'bafybeidskjjd4zmr7oh6ku6wp72vvbxyibcli2r6if3ocdcy7jjjusvl2u' const oldCid = CID.parse(cidStr) - const newCid = CID.asCID(oldCid) + const newCid = /** @type {} */(CID.asCID(oldCid)) assert.deepStrictEqual(newCid.toString(), cidStr) }) }) diff --git a/test/tsconfig.json b/test/tsconfig.json index 8068304b..ca33c243 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -32,10 +32,10 @@ "noEmit": true, "paths": { "multiformats": [ - "../types/src/index" + "../dist/types/src/index" ], "multiformats/*": [ - "../types/src/*" + "../dist/types/src/*" ] } }, From 22d0fc03c79bb15693d9e47ecf7a60bb04d4a507 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Wed, 30 Mar 2022 20:04:28 -0700 Subject: [PATCH 16/20] fix: type errors --- test/test-cid.js | 4 ++-- test/tsconfig.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test-cid.js b/test/test-cid.js index 5cf462f1..634536b3 100644 --- a/test/test-cid.js +++ b/test/test-cid.js @@ -13,7 +13,7 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' // Linter can see that API is used in types. // eslint-disable-next-line -import * as API from 'multiformats/interface' +import * as API from 'multiformats' chai.use(chaiAsPromised) const { assert } = chai @@ -235,7 +235,7 @@ describe('CID', () => { it('should construct from an old CID without a multibaseName', () => { const cidStr = 'bafybeidskjjd4zmr7oh6ku6wp72vvbxyibcli2r6if3ocdcy7jjjusvl2u' const oldCid = CID.parse(cidStr) - const newCid = /** @type {} */(CID.asCID(oldCid)) + const newCid = /** @type {CID} */(CID.asCID(oldCid)) assert.deepStrictEqual(newCid.toString(), cidStr) }) }) diff --git a/test/tsconfig.json b/test/tsconfig.json index ca33c243..8068304b 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -32,10 +32,10 @@ "noEmit": true, "paths": { "multiformats": [ - "../dist/types/src/index" + "../types/src/index" ], "multiformats/*": [ - "../dist/types/src/*" + "../types/src/*" ] } }, From f21a1ba0c6e0f97ba52e00b622c88ea9fc0af135 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Wed, 30 Mar 2022 20:07:54 -0700 Subject: [PATCH 17/20] Apply suggestions from code review Co-authored-by: Rod Vagg --- src/bases/base.js | 2 +- test/test-cid.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bases/base.js b/src/bases/base.js index c83c92b8..6de7a645 100644 --- a/src/bases/base.js +++ b/src/bases/base.js @@ -1,6 +1,6 @@ import basex from '../../vendor/base-x.js' import { coerce } from '../bytes.js' -// Linter can see that API is used in types. +// Linter can't see that API is used in types. // eslint-disable-next-line import * as API from './interface.js' diff --git a/test/test-cid.js b/test/test-cid.js index 634536b3..ad146c2f 100644 --- a/test/test-cid.js +++ b/test/test-cid.js @@ -52,6 +52,7 @@ describe('CID', () => { assert.deepStrictEqual(cid.multihash, hash) assert.deepStrictEqual(cid.toString(), base58btc.baseEncode(hash.bytes)) }) + it('CID.createV0', async () => { const hash = await sha256.digest(textEncoder.encode('abc')) const cid = CID.createV0(hash) @@ -183,6 +184,7 @@ describe('CID', () => { assert.deepStrictEqual(cid.version, 1) equalDigest(cid.multihash, hash) }) + it('CID.createV1', async () => { const hash = await sha256.digest(textEncoder.encode('abc')) const cid = CID.createV1(0x71, hash) From ec5e3d96541bc50b3c447509bca234c098eae7ad Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Fri, 10 Jun 2022 10:47:47 -0700 Subject: [PATCH 18/20] retain CID type info in Block impl --- src/block.js | 75 ++++++++++++++++++++++++++--------------- src/block/interface.js | 1 + src/block/interface.ts | 70 ++++++++++++++++++++++++++++++++++++++ src/cid.js | 45 +++++++++++++++---------- src/cid/interface.ts | 21 +++++++++++- src/codecs/interface.ts | 9 ++--- src/hashes/interface.ts | 4 +-- src/interface.ts | 1 + src/traversal.js | 17 +++++++--- test/test-block.js | 3 +- 10 files changed, 185 insertions(+), 61 deletions(-) create mode 100644 src/block/interface.js create mode 100644 src/block/interface.ts diff --git a/src/block.js b/src/block.js index 4d71d2bf..d4f5c5ec 100644 --- a/src/block.js +++ b/src/block.js @@ -13,7 +13,7 @@ const readonly = ({ enumerable = true, configurable = false } = {}) => ({ * @template T * @param {T} source * @param {Array} base - * @returns {Iterable<[string, CID]>} + * @returns {Iterable<[string, API.CIDView]>} */ const links = function * (source, base) { if (source == null) return @@ -74,6 +74,7 @@ const tree = function * (source, base) { * @template T * @param {T} source * @param {string[]} path + * @return {API.BlockCursorView} */ const get = (source, path) => { /** @type {Record} */ @@ -92,19 +93,23 @@ const get = (source, path) => { } /** - * @template T + * @template {unknown} T - Logical type of the data encoded in the block + * @template {number} C - multicodec code corresponding to codec used to encode the block + * @template {number} A - multicodec code corresponding to the hashing algorithm used in CID creation. + * @template {API.CIDVersion} V - CID version + * @implements {API.BlockView} */ class Block { /** * @param {Object} options - * @param {API.CID} options.cid + * @param {API.CIDView} options.cid * @param {API.ByteView} options.bytes * @param {T} options.value */ constructor ({ cid, bytes, value }) { - if (!cid || !bytes || typeof value === 'undefined') throw new Error('Missing required argument') + if (!cid || !bytes || typeof value === 'undefined') { throw new Error('Missing required argument') } - this.cid = /** @type {CID} */(cid) + this.cid = cid this.bytes = bytes this.value = value this.asBlock = this @@ -127,22 +132,22 @@ class Block { } /** - * @param {string} [path] - */ + * @param {string} [path] + */ get (path = '/') { return get(this.value, path.split('/').filter(Boolean)) } } /** - * @template T - * @template {number} Code - * @template {number} Alg + * @template {unknown} T - Logical type of the data encoded in the block + * @template {number} Code - multicodec code corresponding to codec used to encode the block + * @template {number} Alg - multicodec code corresponding to the hashing algorithm used in CID creation. * @param {Object} options * @param {T} options.value * @param {API.BlockEncoder} options.codec * @param {API.MultihashHasher} options.hasher - * @returns {Promise>} + * @returns {Promise>} */ const encode = async ({ value, codec, hasher }) => { if (typeof value === 'undefined') throw new Error('Missing required argument "value"') @@ -150,20 +155,24 @@ const encode = async ({ value, codec, hasher }) => { const bytes = codec.encode(value) const hash = await hasher.digest(bytes) - const cid = CID.create(1, codec.code, hash) + const cid = CID.create( + 1, + codec.code, + hash + ) return new Block({ value, bytes, cid }) } /** - * @template T - * @template {number} Code - * @template {number} Alg + * @template {unknown} T - Logical type of the data encoded in the block + * @template {number} Code - multicodec code corresponding to codec used to encode the block + * @template {number} Alg - multicodec code corresponding to the hashing algorithm used in CID creation. * @param {Object} options * @param {API.ByteView} options.bytes * @param {API.BlockDecoder} options.codec * @param {API.MultihashHasher} options.hasher - * @returns {Promise>} + * @returns {Promise>} */ const decode = async ({ bytes, codec, hasher }) => { if (!bytes) throw new Error('Missing required argument "bytes"') @@ -182,10 +191,12 @@ const decode = async ({ bytes, codec, hasher }) => { */ /** - * @template T - * @template {number} Code - * @param {{ cid: API.CID, value:T, codec?: API.BlockDecoder, bytes: API.ByteView }|{cid:API.CID, bytes:API.ByteView, value?:void, codec:API.BlockDecoder}} options - * @returns {Block} + * @template {unknown} T - Logical type of the data encoded in the block + * @template {number} Code - multicodec code corresponding to codec used to encode the block + * @template {number} Alg - multicodec code corresponding to the hashing algorithm used in CID creation. + * @template {API.CIDVersion} V - CID version + * @param {{ cid: API.Link, value:T, codec?: API.BlockDecoder, bytes: API.ByteView }|{cid:API.Link, bytes:API.ByteView, value?:void, codec:API.BlockDecoder}} options + * @returns {API.BlockView} */ const createUnsafe = ({ bytes, cid, value: maybeValue, codec }) => { const value = maybeValue !== undefined @@ -194,19 +205,24 @@ const createUnsafe = ({ bytes, cid, value: maybeValue, codec }) => { if (value === undefined) throw new Error('Missing required argument, must either provide "value" or "codec"') - return new Block({ cid, bytes, value }) + return new Block({ + cid: /** @type {API.CIDView} */(cid), + bytes, + value + }) } /** - * @template T - * @template {number} Code - * @template {number} Alg + * @template {unknown} T - Logical type of the data encoded in the block + * @template {number} Code - multicodec code corresponding to codec used to encode the block + * @template {number} Alg - multicodec code corresponding to the hashing algorithm used in CID creation. + * @template {API.CIDVersion} V - CID version * @param {Object} options - * @param {API.CID} options.cid + * @param {API.CID} options.cid * @param {API.ByteView} options.bytes * @param {API.BlockDecoder} options.codec * @param {API.MultihashHasher} options.hasher - * @returns {Promise>} + * @returns {Promise>} */ const create = async ({ bytes, cid, hasher, codec }) => { if (!bytes) throw new Error('Missing required argument "bytes"') @@ -217,7 +233,12 @@ const create = async ({ bytes, cid, hasher, codec }) => { throw new Error('CID hash does not match bytes') } - return createUnsafe({ bytes, cid, value, codec }) + return createUnsafe({ + bytes, + cid, + value, + codec + }) } export { encode, decode, create, createUnsafe, Block } diff --git a/src/block/interface.js b/src/block/interface.js new file mode 100644 index 00000000..d9b3f4f7 --- /dev/null +++ b/src/block/interface.js @@ -0,0 +1 @@ +// this is dummy module overlayed by interface.ts diff --git a/src/block/interface.ts b/src/block/interface.ts new file mode 100644 index 00000000..7e73e681 --- /dev/null +++ b/src/block/interface.ts @@ -0,0 +1,70 @@ +/* eslint-disable no-use-before-define */ +import { Link, CIDView, CIDVersion } from '../cid/interface.js' + +/** + * A byte-encoded representation of some type of `Data`. + * + * A `ByteView` is essentially a `Uint8Array` that's been "tagged" with + * a `Data` type parameter indicating the type of encoded data. + * + * For example, a `ByteView<{ hello: "world" }>` is a `Uint8Array` containing a + * binary representation of a `{hello: "world"}. + */ +export interface ByteView extends Uint8Array, Phantom {} + +/** + * A utility type to retain an unused type parameter `T`. + * Similar to [phantom type parameters in Rust](https://doc.rust-lang.org/rust-by-example/generics/phantom.html). + * + * Capturing unused type parameters allows us to define "nominal types," which + * TypeScript does not natively support. Nominal types in turn allow us to capture + * semantics not represented in the actual type structure, without requring us to define + * new classes or pay additional runtime costs. + * + * For a concrete example, see {@link ByteView}, which extends the `Uint8Array` type to capture + * type information about the structure of the data encoded into the array. + */ +export interface Phantom { + // This field can not be represented because field name is non-existings + // unique symbol. But given that field is optional any object will valid + // type contstraint. + [Marker]?: T +} +declare const Marker: unique symbol + +/** + * Represents an IPLD block (including its CID) that can be decoded to data of + * type `T`. + * + * @template T - Logical type of the data encoded in the block + * @template C - multicodec code corresponding to codec used to encode the block + * @template A - multicodec code corresponding to the hashing algorithm used in CID creation. + * @template V - CID version + */ +export interface Block< + T = unknown, + C extends number = number, + A extends number = number, + V extends CIDVersion = 1 +> { + bytes: ByteView + cid: Link +} + +export type BlockCursorView = + | { value: unknown, remaining?: undefined } + | { value: CIDView, remaining: string } + +export interface BlockView< + T = unknown, + C extends number = number, + A extends number = number, + V extends CIDVersion = 1 +> extends Block { + cid: CIDView & Link + value: T + + links(): Iterable<[string, CIDView]> + tree(): Iterable + get(path:string): BlockCursorView +} diff --git a/src/cid.js b/src/cid.js index 1ba96783..02891f8a 100644 --- a/src/cid.js +++ b/src/cid.js @@ -14,8 +14,8 @@ export * from './cid/interface.js' * @template {number} Format * @template {number} Alg * @template {API.CIDVersion} Version - * @template T - * @param {API.CID|T} input + * @template {unknown} U + * @param {API.CID|U} input * @returns {API.CID|null} */ export const asCID = (input) => { @@ -342,9 +342,9 @@ export class CID { throw new Error('Cannot convert non sha2-256 multihash CID to CIDv0') } - return /** @type {CID} */(createV0( - /** @type {API.MultihashDigest} */ (multihash) - )) + return /** @type {CID} */ ( + createV0(/** @type {API.MultihashDigest} */ (multihash)) + ) } default: { throw Error( @@ -362,7 +362,9 @@ export class CID { case 0: { const { code, digest } = this.multihash const multihash = Digest.create(code, digest) - return /** @type {CID} */(createV1(this.code, multihash)) + return /** @type {CID} */ ( + createV1(this.code, multihash) + ) } case 1: { return /** @type {CID} */ (this) @@ -445,10 +447,15 @@ export class CID { } /** - * @param {any} value + * @template {number} C + * @template {number} A + * @template {API.CIDVersion} V + * @template {unknown} U + * @param {API.CID|U} value + * @returns {CID|null} */ static asCID (value) { - return /** @type {CID|null} */(asCID(value)) + return /** @type {CID|null} */ (asCID(value)) } /** @@ -460,7 +467,9 @@ export class CID { * @param {API.MultihashDigest} digest - (Multi)hash of the of the content. */ static create (version, code, digest) { - return /** @type {CID} */(create(version, code, digest)) + return /** @type {CID} */ ( + create(version, code, digest) + ) } /** @@ -472,12 +481,12 @@ export class CID { } /** - * Simplified version of `create` for CIDv1. - * @template {number} Code - * @template {number} Alg - * @param {Code} code - Content encoding format code. - * @param {API.MultihashDigest} digest - Miltihash of the content. - */ + * Simplified version of `create` for CIDv1. + * @template {number} Code + * @template {number} Alg + * @param {Code} code - Content encoding format code. + * @param {API.MultihashDigest} digest - Miltihash of the content. + */ static createV1 (code, digest) { return CID.create(1, code, digest) } @@ -487,14 +496,14 @@ export class CID { */ static decode (bytes) { - return /** @type {CID} */(decode(bytes)) + return /** @type {CID} */ (decode(bytes)) } /** * @param {Uint8Array} bytes */ static decodeFirst (bytes) { - return /** @type {[CID, Uint8Array]} */(decodeFirst(bytes)) + return /** @type {[CID, Uint8Array]} */ (decodeFirst(bytes)) } /** @@ -510,7 +519,7 @@ export class CID { * @param {API.MultibaseDecoder} [base] */ static parse (source, base) { - return /** @type {CID} */(parse(source, base)) + return /** @type {CID} */ (parse(source, base)) } } diff --git a/src/cid/interface.ts b/src/cid/interface.ts index 2c80b297..da71864e 100644 --- a/src/cid/interface.ts +++ b/src/cid/interface.ts @@ -1,6 +1,9 @@ +/* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */ /* eslint-disable no-use-before-define */ import type { MultihashDigest } from '../hashes/interface' import type { MultibaseEncoder, MultibaseDecoder } from '../bases/interface' +import type { Phantom } from '../block/interface' +export type { CID as CIDView } from '../cid' export type { MultihashDigest, MultibaseEncoder, MultibaseDecoder } export type CIDVersion = 0 | 1 @@ -26,12 +29,28 @@ export interface CID< equals(other: unknown): other is CID toString(base?: MultibaseEncoder): string - toJSON(): {version: Version, code:Format, hash:Uint8Array} + toJSON(): { version: Version, code:Format, hash:Uint8Array } toV0(): CIDv0 toV1(): CIDv1 } +/** + * Represents an IPLD link to a specific data of type `T`. + * + * @template T - Logical type of the data being linked to. + * @template C - multicodec code corresponding to a codec linked data is encoded with + * @template A - multicodec code corresponding to the hashing algorithm of the CID + * @template V - CID version + */ +export interface Link< + T extends unknown = unknown, + C extends number = number, + A extends number = number, + V extends CIDVersion = 1 +> extends CID, Phantom { +} + export interface CIDv0 extends CID { readonly version: 0 } diff --git a/src/codecs/interface.ts b/src/codecs/interface.ts index dc8527c1..af2c9762 100644 --- a/src/codecs/interface.ts +++ b/src/codecs/interface.ts @@ -1,3 +1,5 @@ +import type { ByteView } from '../block/interface' + /** * IPLD encoder part of the codec. */ @@ -20,9 +22,4 @@ export interface BlockDecoder { */ export interface BlockCodec extends BlockEncoder, BlockDecoder {} -// This just a hack to retain type information about the data that -// is encoded `T` Because it's a union `data` field is never going -// to be usable anyway. -export type ByteView = - | Uint8Array - | Uint8Array & { data: T } +export type { ByteView } diff --git a/src/hashes/interface.ts b/src/hashes/interface.ts index 09afdfe0..a8003aaa 100644 --- a/src/hashes/interface.ts +++ b/src/hashes/interface.ts @@ -44,7 +44,7 @@ export interface MultihashHasher { * * @param {Uint8Array} input */ - digest(input: Uint8Array): Promise | MultihashDigest + digest(input: Uint8Array): Promise> | MultihashDigest /** * Name of the multihash @@ -68,5 +68,5 @@ export interface MultihashHasher { * impractical e.g. implementation of Hash Array Mapped Trie (HAMT). */ export interface SyncMultihashHasher extends MultihashHasher { - digest(input: Uint8Array): MultihashDigest + digest(input: Uint8Array): MultihashDigest } diff --git a/src/interface.ts b/src/interface.ts index 0eb11629..ed29ee8e 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -2,3 +2,4 @@ export * from './bases/interface' export * from './hashes/interface' export * from './codecs/interface' export * from './cid/interface' +export * from './block/interface' diff --git a/src/traversal.js b/src/traversal.js index a13d1d5c..2f0cbbed 100644 --- a/src/traversal.js +++ b/src/traversal.js @@ -1,19 +1,26 @@ import { base58btc } from './bases/base58.js' /** - * @typedef {import('./cid.js').CID} CID + * @template [T=unknown] - Logical type of the data encoded in the block + * @template [C=number] - multicodec code corresponding to codec used to encode the block + * @template [A=number] - multicodec code corresponding to the hashing algorithm used in CID creation. + * @template [V=0|1] - CID version + * @typedef {import('./cid/interface').Link} Link */ /** - * @template T - * @typedef {import('./block.js').Block} Block + * @template [T=unknown] - Logical type of the data encoded in the block + * @template [C=number] - multicodec code corresponding to codec used to encode the block + * @template [A=number] - multicodec code corresponding to the hashing algorithm used in CID creation. + * @template [V=0|1] - CID version + * @typedef {import('./block/interface').BlockView} BlockView */ /** * @template T * @param {Object} options - * @param {CID} options.cid - * @param {(cid: CID) => Promise|null>} options.load + * @param {Link} options.cid + * @param {(cid: Link) => Promise|null>} options.load * @param {Set} [options.seen] */ const walk = async ({ cid, load, seen }) => { diff --git a/test/test-block.js b/test/test-block.js index c744e72f..8a65bec3 100644 --- a/test/test-block.js +++ b/test/test-block.js @@ -60,9 +60,8 @@ describe('block', () => { it('get', () => { let ret = block.get('link/test') assert.deepStrictEqual(ret.remaining, 'test') - assert.deepStrictEqual(ret.value.toString(), link.toString()) + assert.deepStrictEqual(String(ret.value), link.toString()) ret = block.get('nope') - // @ts-expect-error - 'string' is not expected assert.deepStrictEqual(ret, { value: 'skip' }) }) From 6023831dc4300311ff150e97408429a95e7f2c6e Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Fri, 10 Jun 2022 11:27:13 -0700 Subject: [PATCH 19/20] fix: type errors --- package.json | 3 +++ src/block.js | 6 ++++-- src/block/interface.ts | 7 ++++--- src/traversal.js | 8 +++----- test/test-block.js | 1 + test/test-bytes.js | 2 +- test/test-cid.js | 2 +- test/test-multibase-spec.js | 2 +- test/test-multibase.js | 2 +- test/test-multicodec.js | 2 +- test/test-multihash.js | 2 +- test/test-traversal.js | 4 ++-- test/tsconfig.json | 4 ++-- 13 files changed, 25 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index dc1ccab2..bc3bb40c 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,9 @@ }, "./interface": { "import": "./src/interface.js" + }, + "./bytes": { + "import": "./src/bytes.js" } }, "devDependencies": { diff --git a/src/block.js b/src/block.js index d4f5c5ec..3a53b766 100644 --- a/src/block.js +++ b/src/block.js @@ -74,7 +74,7 @@ const tree = function * (source, base) { * @template T * @param {T} source * @param {string[]} path - * @return {API.BlockCursorView} + * @return {API.BlockCursorView} */ const get = (source, path) => { /** @type {Record} */ @@ -133,6 +133,7 @@ class Block { /** * @param {string} [path] + * @return {API.BlockCursorView} */ get (path = '/') { return get(this.value, path.split('/').filter(Boolean)) @@ -206,7 +207,8 @@ const createUnsafe = ({ bytes, cid, value: maybeValue, codec }) => { if (value === undefined) throw new Error('Missing required argument, must either provide "value" or "codec"') return new Block({ - cid: /** @type {API.CIDView} */(cid), + // eslint-disable-next-line object-shorthand + cid: /** @type {API.CIDView} */ (cid), bytes, value }) diff --git a/src/block/interface.ts b/src/block/interface.ts index 7e73e681..a30b2352 100644 --- a/src/block/interface.ts +++ b/src/block/interface.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */ /* eslint-disable no-use-before-define */ import { Link, CIDView, CIDVersion } from '../cid/interface.js' @@ -51,8 +52,8 @@ export interface Block< cid: Link } -export type BlockCursorView = - | { value: unknown, remaining?: undefined } +export type BlockCursorView = + | { value: T, remaining?: undefined } | { value: CIDView, remaining: string } export interface BlockView< @@ -66,5 +67,5 @@ export interface BlockView< links(): Iterable<[string, CIDView]> tree(): Iterable - get(path:string): BlockCursorView + get(path:string): BlockCursorView } diff --git a/src/traversal.js b/src/traversal.js index 2f0cbbed..52f4eb52 100644 --- a/src/traversal.js +++ b/src/traversal.js @@ -1,11 +1,10 @@ import { base58btc } from './bases/base58.js' /** - * @template [T=unknown] - Logical type of the data encoded in the block * @template [C=number] - multicodec code corresponding to codec used to encode the block * @template [A=number] - multicodec code corresponding to the hashing algorithm used in CID creation. * @template [V=0|1] - CID version - * @typedef {import('./cid/interface').Link} Link + * @typedef {import('./cid/interface').CID} CID */ /** @@ -17,10 +16,9 @@ import { base58btc } from './bases/base58.js' */ /** - * @template T * @param {Object} options - * @param {Link} options.cid - * @param {(cid: Link) => Promise|null>} options.load + * @param {CID} options.cid + * @param {(cid: CID) => Promise} options.load * @param {Set} [options.seen] */ const walk = async ({ cid, load, seen }) => { diff --git a/test/test-block.js b/test/test-block.js index 8a65bec3..8f6d6fa9 100644 --- a/test/test-block.js +++ b/test/test-block.js @@ -62,6 +62,7 @@ describe('block', () => { assert.deepStrictEqual(ret.remaining, 'test') assert.deepStrictEqual(String(ret.value), link.toString()) ret = block.get('nope') + assert.deepStrictEqual(ret, { value: 'skip' }) }) diff --git a/test/test-bytes.js b/test/test-bytes.js index a764ded6..c420fa9b 100644 --- a/test/test-bytes.js +++ b/test/test-bytes.js @@ -1,5 +1,5 @@ /* globals describe, it */ -import * as bytes from '../src/bytes.js' +import * as bytes from 'multiformats/bytes' import { assert } from 'chai' describe('bytes', () => { diff --git a/test/test-cid.js b/test/test-cid.js index ad146c2f..b21cf181 100644 --- a/test/test-cid.js +++ b/test/test-cid.js @@ -1,7 +1,7 @@ /* globals describe, it */ import OLDCID from 'cids' -import { fromHex, toHex, equals } from '../src/bytes.js' +import { fromHex, toHex, equals } from 'multiformats/bytes' import { varint, CID } from 'multiformats' import * as CIDLib from 'multiformats/cid' import { base58btc } from 'multiformats/bases/base58' diff --git a/test/test-multibase-spec.js b/test/test-multibase-spec.js index 8d42e45c..3a4f69c5 100644 --- a/test/test-multibase-spec.js +++ b/test/test-multibase-spec.js @@ -2,7 +2,7 @@ 'use strict' import { bases } from 'multiformats/basics' -import { fromString } from '../src/bytes.js' +import { fromString } from 'multiformats/bytes' import chai from 'chai' import chaiAsPromised from 'chai-as-promised' diff --git a/test/test-multibase.js b/test/test-multibase.js index db4d37b0..1c63ce1c 100644 --- a/test/test-multibase.js +++ b/test/test-multibase.js @@ -1,5 +1,5 @@ /* globals describe, it */ -import * as bytes from '../src/bytes.js' +import * as bytes from 'multiformats/bytes' import * as b2 from 'multiformats/bases/base2' import * as b8 from 'multiformats/bases/base8' import * as b10 from 'multiformats/bases/base10' diff --git a/test/test-multicodec.js b/test/test-multicodec.js index b12d5d17..ee449de8 100644 --- a/test/test-multicodec.js +++ b/test/test-multicodec.js @@ -1,5 +1,5 @@ /* globals describe, it */ -import * as bytes from '../src/bytes.js' +import * as bytes from 'multiformats/bytes' import * as raw from 'multiformats/codecs/raw' import * as json from 'multiformats/codecs/json' import chai from 'chai' diff --git a/test/test-multihash.js b/test/test-multihash.js index d65f392d..85d48245 100644 --- a/test/test-multihash.js +++ b/test/test-multihash.js @@ -1,5 +1,5 @@ /* globals describe, it */ -import { fromHex, fromString } from '../src/bytes.js' +import { fromHex, fromString } from 'multiformats/bytes' import { hash as slSha256 } from '@stablelib/sha256' import { hash as slSha512 } from '@stablelib/sha512' import valid from './fixtures/valid-multihash.js' diff --git a/test/test-traversal.js b/test/test-traversal.js index e14b0d3b..d55a4bb2 100644 --- a/test/test-traversal.js +++ b/test/test-traversal.js @@ -7,7 +7,7 @@ import { walk } from 'multiformats/traversal' // eslint-disable-next-line import * as API from 'multiformats/interface' import { assert } from 'chai' -import { fromString } from '../src/bytes.js' +import { fromString } from 'multiformats/bytes' const { createLink, createNode } = dagPB @@ -165,7 +165,7 @@ describe('traversal', () => { /** @type {[]} */ const links = [] const value = createNode(fromString('test'), links) - const block = await main.encode({ value: value, codec, hasher }) + const block = await main.encode({ value, codec, hasher }) const cid = block.cid const expectedCallArray = [cid.toString()] /** @type {string[]} */ diff --git a/test/tsconfig.json b/test/tsconfig.json index 8068304b..ca33c243 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -32,10 +32,10 @@ "noEmit": true, "paths": { "multiformats": [ - "../types/src/index" + "../dist/types/src/index" ], "multiformats/*": [ - "../types/src/*" + "../dist/types/src/*" ] } }, From 7b36f00c1ecf0b62e2eb4e4ab9ea592ab89792e0 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Mon, 13 Jun 2022 18:32:04 -0700 Subject: [PATCH 20/20] try to fix typecheck --- .github/workflows/typecheck.yml | 2 +- test/tsconfig.json | 49 ------------------------ tsconfig.json | 21 ++++++++++- vendor/base-x.d.ts | 15 ++++---- vendor/varint.d.ts | 67 +++++++++++++++++---------------- 5 files changed, 62 insertions(+), 92 deletions(-) delete mode 100644 test/tsconfig.json diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index a18a1b6e..21dd9b5b 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -26,4 +26,4 @@ jobs: - name: Typecheck uses: gozala/typescript-error-reporter-action@v1.0.8 with: - project: test/tsconfig.json + project: tsconfig.json diff --git a/test/tsconfig.json b/test/tsconfig.json deleted file mode 100644 index ca33c243..00000000 --- a/test/tsconfig.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "references": [ - { - "path": "../" - } - ], - "compilerOptions": { - "composite": true, - "allowJs": true, - "checkJs": true, - "forceConsistentCasingInFileNames": true, - "noImplicitReturns": false, - "noImplicitAny": true, - "noImplicitThis": true, - "noFallthroughCasesInSwitch": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "strictFunctionTypes": false, - "strictNullChecks": true, - "strictPropertyInitialization": true, - "strictBindCallApply": true, - "strict": true, - "alwaysStrict": true, - "esModuleInterop": true, - "target": "ES2020", - "moduleResolution": "node", - "declaration": true, - "declarationMap": true, - "skipLibCheck": true, - "stripInternal": true, - "resolveJsonModule": true, - "noEmit": true, - "paths": { - "multiformats": [ - "../dist/types/src/index" - ], - "multiformats/*": [ - "../dist/types/src/*" - ] - } - }, - "include": [ - "." - ], - "exclude": [ - "ts-use/", - "node_modules" - ] -} diff --git a/tsconfig.json b/tsconfig.json index 43a6feb3..f85de4e4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,13 +26,30 @@ "resolveJsonModule": true, "emitDeclarationOnly": true, "baseUrl": ".", - "composite": true + "composite": true, + "paths": { + "multiformats": [ + "./src/index.js" + ], + "multiformats/interface": [ + "./src/interface" + ], + "multiformats/hashes/interface": [ + "./src/hashes/interface" + ], + "multiformats/*": [ + "./src/*.js" + ], + + } }, "include": [ - "src" + "src", + "test" ], "exclude": [ "vendor", + "test/ts-use", "node_modules" ], "compileOnSave": false diff --git a/vendor/base-x.d.ts b/vendor/base-x.d.ts index 789d1e8f..a8665dc9 100644 --- a/vendor/base-x.d.ts +++ b/vendor/base-x.d.ts @@ -1,9 +1,8 @@ -declare function base(ALPHABET: string, name: string): base.BaseConverter; -export = base; -declare namespace base { - interface BaseConverter { - encode(buffer: Uint8Array | number[]): string; - decodeUnsafe(string: string): Uint8Array | undefined; - decode(string: string): Uint8Array; - } + +export interface BaseConverter { + encode(buffer: Uint8Array | number[]): string; + decodeUnsafe(string: string): Uint8Array | undefined; + decode(string: string): Uint8Array; } + +export default function base(ALPHABET: string, name: string): BaseConverter diff --git a/vendor/varint.d.ts b/vendor/varint.d.ts index 11e2e2d7..f5a8a021 100644 --- a/vendor/varint.d.ts +++ b/vendor/varint.d.ts @@ -3,44 +3,47 @@ // Definitions by: David Brockman Smoliansky // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -/// +interface Varint { + encode: { + /** + * Encodes `num` into `buffer` starting at `offset`. returns `buffer`, with the encoded varint written into it. + * `varint.encode.bytes` will now be set to the number of bytes modified. + */ + (num: number, buffer: Uint8Array, offset?: number): Buffer; -export const encode: { - /** - * Encodes `num` into `buffer` starting at `offset`. returns `buffer`, with the encoded varint written into it. - * `varint.encode.bytes` will now be set to the number of bytes modified. - */ - (num: number, buffer: Uint8Array, offset?: number): Buffer; + /** + * Encodes `num` into `array` starting at `offset`. returns `array`, with the encoded varint written into it. + * If `array` is not provided, it will default to a new array. + * `varint.encode.bytes` will now be set to the number of bytes modified. + */ + (num: number, array?: number[], offset?: number): number[] - /** - * Encodes `num` into `array` starting at `offset`. returns `array`, with the encoded varint written into it. - * If `array` is not provided, it will default to a new array. - * `varint.encode.bytes` will now be set to the number of bytes modified. - */ - (num: number, array?: number[], offset?: number): number[] + /** + * Similar to `decode.bytes` when encoding a number it can be useful to know how many bytes where written (especially if you pass an output array). + * You can access this via `varint.encode.bytes` which holds the number of bytes written in the last encode. + */ + bytes: number + }, - /** - * Similar to `decode.bytes` when encoding a number it can be useful to know how many bytes where written (especially if you pass an output array). - * You can access this via `varint.encode.bytes` which holds the number of bytes written in the last encode. - */ - bytes: number -} + decode: { + /** + * Decodes `data`, which can be either a buffer or array of integers, from position `offset` or default 0 and returns the decoded original integer. + * Throws a `RangeError` when `data` does not represent a valid encoding. + */ + (buf: Uint8Array | number[], offset?: number): number -export const decode: { - /** - * Decodes `data`, which can be either a buffer or array of integers, from position `offset` or default 0 and returns the decoded original integer. - * Throws a `RangeError` when `data` does not represent a valid encoding. - */ - (buf: Uint8Array | number[], offset?: number): number + /** + * If you also require the length (number of bytes) that were required to decode the integer you can access it via `varint.decode.bytes`. + * This is an integer property that will tell you the number of bytes that the last .decode() call had to use to decode. + */ + bytes: number + }, /** - * If you also require the length (number of bytes) that were required to decode the integer you can access it via `varint.decode.bytes`. - * This is an integer property that will tell you the number of bytes that the last .decode() call had to use to decode. + * returns the number of bytes this number will be encoded as, up to a maximum of 8. */ - bytes: number + encodingLength(num: number): number } -/** - * returns the number of bytes this number will be encoded as, up to a maximum of 8. - */ -export function encodingLength(num: number): number +declare const varint:Varint +export default varint