diff --git a/package.json b/package.json index 77f5f779..4697ebc1 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,9 @@ "./traversal": { "import": "./src/traversal.js" }, + "./bases/base": { + "import": "./src/bases/base.js" + }, "./bases/identity": { "import": "./src/bases/identity.js" }, @@ -103,6 +106,8 @@ "@ipld/dag-pb": "^2.1.14", "@stablelib/sha256": "^1.0.1", "@stablelib/sha512": "^1.0.1", + "@types/chai": "^4.3.0", + "@types/mocha": "^9.0.0", "@types/node": "^16.11.12", "@typescript-eslint/eslint-plugin": "^5.6.0", "@typescript-eslint/parser": "^5.6.0", @@ -114,7 +119,7 @@ "mocha": "^9.1.3", "polendina": "^2.0.0", "standard": "^16.0.4", - "typescript": "^4.5.2" + "typescript": "^4.5.4" }, "standard": { "ignore": [ diff --git a/src/bases/base.js b/src/bases/base.js index e2674974..d2e83a8b 100644 --- a/src/bases/base.js +++ b/src/bases/base.js @@ -56,11 +56,6 @@ class Encoder { * @typedef {import('./interface').MultibaseDecoder} MultibaseDecoder */ -/** - * @template {string} Prefix - * @typedef {import('./interface').UnibaseDecoder} UnibaseDecoder - */ - /** * @template {string} Prefix */ @@ -72,7 +67,7 @@ class Encoder { * @template {string} Base * @template {string} Prefix * @implements {MultibaseDecoder} - * @implements {UnibaseDecoder} + * @implements {CombobaseDecoder} * @implements {BaseDecoder} */ class Decoder { @@ -87,6 +82,22 @@ class Decoder { this.baseDecode = baseDecode } + /** + * @returns {CombobaseDecoder} + */ + get composed () { + return this + } + + /** + * @type {{[K in Prefix]: MultibaseDecoder}} + */ + get decoders () { + return /** @type {{[K in Prefix]: MultibaseDecoder}} */({ + [this.prefix]: /** @type {MultibaseDecoder} */(this) + }) + } + /** * @param {string} text */ @@ -107,7 +118,7 @@ class Decoder { /** * @template {string} OtherPrefix - * @param {UnibaseDecoder|ComposedDecoder} decoder + * @param {CombobaseDecoder} decoder * @returns {ComposedDecoder} */ or (decoder) { @@ -122,7 +133,7 @@ class Decoder { /** * @template {string} Prefix - * @typedef {Record>} Decoders + * @typedef {Record>} Decoders */ /** @@ -132,7 +143,7 @@ class Decoder { */ class ComposedDecoder { /** - * @param {Record>} decoders + * @param {{[K in Prefix]: MultibaseDecoder}} decoders */ constructor (decoders) { this.decoders = decoders @@ -140,7 +151,7 @@ class ComposedDecoder { /** * @template {string} OtherPrefix - * @param {UnibaseDecoder|ComposedDecoder} decoder + * @param {CombobaseDecoder} decoder * @returns {ComposedDecoder} */ or (decoder) { @@ -165,13 +176,13 @@ class ComposedDecoder { /** * @template {string} L * @template {string} R - * @param {UnibaseDecoder|CombobaseDecoder} left - * @param {UnibaseDecoder|CombobaseDecoder} right + * @param {CombobaseDecoder} left + * @param {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, + ...right.decoders })) /** @@ -206,6 +217,10 @@ export class Codec { this.decoder = new Decoder(name, prefix, baseDecode) } + get decoders () { + return this.decoder.decoders + } + /** * @param {Uint8Array} input */ @@ -353,9 +368,11 @@ const encode = (data, alphabet, bitsPerChar) => { /** * RFC4648 Factory * + * @template {string} Base + * @template {string} Prefix * @param {Object} options - * @param {string} options.name - * @param {string} options.prefix + * @param {Base} options.name + * @param {Prefix} options.prefix * @param {string} options.alphabet * @param {number} options.bitsPerChar */ diff --git a/src/bases/interface.ts b/src/bases/interface.ts index dd4374e2..664f7e4b 100644 --- a/src/bases/interface.ts +++ b/src/bases/interface.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-use-before-define */ // Base encoders / decoders just base encode / decode between binary and // textual representation. They are unaware of multibase. @@ -83,17 +84,9 @@ export interface MultibaseCodec { name: string prefix: Prefix encoder: MultibaseEncoder - decoder: MultibaseDecoder -} - - -export interface UnibaseDecoder extends MultibaseDecoder { - // Reserve this property so it can be used to derive type. - readonly decoders?: null - - readonly prefix: Prefix + decoder: CombobaseDecoder } export interface CombobaseDecoder extends MultibaseDecoder { - readonly decoders: Record> + readonly decoders: {[K in Prefix]?: MultibaseDecoder} } diff --git a/test/fixtures/test-throw.js b/test/fixtures/test-throw.js index 93f8ad7a..962e4638 100644 --- a/test/fixtures/test-throw.js +++ b/test/fixtures/test-throw.js @@ -1,9 +1,27 @@ - -export default function testThrow (fn, message) { +/** + * @param {Function} fn + * @param {string} message + */ +export const testThrowSync = (fn, message) => { try { fn() } catch (e) { - if (e.message !== message) throw e + if (/** @type {Error} */(e).message !== message) throw e + return + } + /* c8 ignore next */ + throw new Error('Test failed to throw') +} + +/** + * @param {Function} fn + * @param {string} message + */ +export const testThrowAsync = async (fn, message) => { + try { + await fn() + } catch (e) { + if (/** @type {Error} */(e).message !== message) throw e return } /* c8 ignore next */ diff --git a/test/test-block.js b/test/test-block.js index 96ef38de..cb928569 100644 --- a/test/test-block.js +++ b/test/test-block.js @@ -4,6 +4,7 @@ import { sha256 as hasher } from 'multiformats/hashes/sha2' import * as main from 'multiformats/block' import { CID, bytes } from 'multiformats' import { assert } from 'chai' +import { testThrowAsync, testThrowSync } from './fixtures/test-throw.js' const fixture = { hello: 'world' } const link = CID.parse('bafyreidykglsfhoixmivffc5uwhcgshx4j465xwqntbmu43nb2dzqwfvae') @@ -27,7 +28,15 @@ describe('block', () => { }) describe('reader', () => { - const value = { link, nope: 'skip', arr: [link], obj: { arr: [{ obj: {} }] }, bytes: Uint8Array.from('1234') } + const value = { + link, + nope: 'skip', + arr: [link], + obj: { arr: [{ obj: {} }] }, + // @ts-expect-error - 'string' is not assignable to parameter of type 'ArrayLike' + bytes: Uint8Array.from('1234') + } + // @ts-expect-error - 'boolean' is not assignable to type 'CID' const block = main.createUnsafe({ value, codec, hasher, cid: true, bytes: true }) it('links', () => { @@ -50,11 +59,20 @@ describe('block', () => { assert.deepStrictEqual(ret.remaining, 'test') assert.deepStrictEqual(ret.value.toString(), link.toString()) ret = block.get('nope') + // @ts-expect-error - 'string' is not expected assert.deepStrictEqual(ret, { value: 'skip' }) }) it('null links/tree', () => { - const block = main.createUnsafe({ value: null, codec, hasher, bytes: true, cid: true }) + const block = main.createUnsafe({ + value: null, + codec, + hasher, + // @ts-expect-error - 'boolean' is not assignable to type 'ByteView' + bytes: true, + // @ts-expect-error - 'boolean' is not assignable to type 'CID' + cid: true + }) // eslint-disable-next-line for (const x of block.tree()) { throw new Error(`tree should have nothing, got "${x}"`) @@ -68,58 +86,58 @@ describe('block', () => { it('kitchen sink', () => { const sink = { one: { two: { arr: [true, false, null], three: 3, buff, link } } } - const block = main.createUnsafe({ value: sink, codec, bytes: true, cid: true }) + const block = main.createUnsafe({ + value: sink, + codec, + // @ts-expect-error - 'boolean' is not assignable to type 'ByteView' + bytes: true, + // @ts-expect-error - 'boolean' is not assignable to type 'CID' + cid: true + }) assert.deepStrictEqual(sink, block.value) }) describe('errors', () => { it('constructor missing args', () => { - let threw = true - try { - threw = new main.Block({}) - threw = false - } catch (e) { - if (e.message !== 'Missing required argument') throw e - } - assert.deepStrictEqual(threw, true) + testThrowSync( + // @ts-expect-error - missing properties + () => new main.Block({}), + 'Missing required argument' + ) }) - const errTest = async (method, arg, message) => { - let threw = true - try { - await method(arg) - threw = false - } catch (e) { - if (e.message !== message) throw e - } - assert.deepStrictEqual(threw, true) - } - it('encode', async () => { - await errTest(main.encode, {}, 'Missing required argument "value"') - await errTest(main.encode, { value: true }, 'Missing required argument: codec or hasher') + // @ts-expect-error + await testThrowAsync(() => main.encode({}), 'Missing required argument "value"') + // @ts-expect-error + await testThrowAsync(() => main.encode({ value: true }), 'Missing required argument: codec or hasher') }) it('decode', async () => { - await errTest(main.decode, {}, 'Missing required argument "bytes"') - await errTest(main.decode, { bytes: true }, 'Missing required argument: codec or hasher') + // @ts-expect-error + await testThrowAsync(() => main.decode({}), 'Missing required argument "bytes"') + // @ts-expect-error + await testThrowAsync(() => main.decode({ bytes: true }), 'Missing required argument: codec or hasher') }) it('createUnsafe', async () => { - await errTest(main.createUnsafe, {}, 'Missing required argument, must either provide "value" or "codec"') + // @ts-expect-error + await testThrowAsync(() => main.createUnsafe({}), 'Missing required argument, must either provide "value" or "codec"') }) it('create', async () => { - await errTest(main.create, {}, 'Missing required argument "bytes"') - await errTest(main.create, { bytes: true }, 'Missing required argument "hasher"') + // @ts-expect-error + await testThrowAsync(() => main.create({}), 'Missing required argument "bytes"') + // @ts-expect-error + await testThrowAsync(() => main.create({ bytes: true }), 'Missing required argument "hasher"') const block = await main.encode({ value: fixture, codec, hasher }) const block2 = await main.encode({ value: { ...fixture, test: 'blah' }, codec, hasher }) - await errTest(main.create, { bytes: block.bytes, cid: block2.cid, codec, hasher }, 'CID hash does not match bytes') + await testThrowAsync(() => main.create({ bytes: block.bytes, cid: block2.cid, codec, hasher }), 'CID hash does not match bytes') }) it('get', async () => { const block = await main.encode({ value: fixture, codec, hasher }) - await errTest(path => block.get(path), '/asd/fs/dfasd/f', 'Object has no property at ["asd"]') + await testThrowAsync(() => block.get('/asd/fs/dfasd/f'), 'Object has no property at ["asd"]') }) }) }) diff --git a/test/test-bytes.js b/test/test-bytes.js index 93914b2e..a764ded6 100644 --- a/test/test-bytes.js +++ b/test/test-bytes.js @@ -4,8 +4,8 @@ import { assert } from 'chai' describe('bytes', () => { it('isBinary', () => { - assert.deepStrictEqual(bytes.isBinary(new ArrayBuffer()), true) - assert.deepStrictEqual(bytes.isBinary(new DataView(new ArrayBuffer())), true) + assert.deepStrictEqual(bytes.isBinary(new ArrayBuffer(0)), true) + assert.deepStrictEqual(bytes.isBinary(new DataView(new ArrayBuffer(0))), true) }) it('coerce', () => { diff --git a/test/test-cid.js b/test/test-cid.js index 2071a12e..0361d893 100644 --- a/test/test-cid.js +++ b/test/test-cid.js @@ -9,10 +9,13 @@ import { base32 } from 'multiformats/bases/base32' import { base64 } from 'multiformats/bases/base64' import { sha256, sha512 } from 'multiformats/hashes/sha2' import invalidMultihash from './fixtures/invalid-multihash.js' -import testThrow from './fixtures/test-throw.js' +import { testThrowSync as testThrow } from './fixtures/test-throw.js' const textEncoder = new TextEncoder() +/** + * @param {Function} fn + */ const testThrowAny = async fn => { try { await fn() @@ -99,7 +102,7 @@ describe('CID', () => { const cidStr = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n' const oldCid = CID.parse(cidStr) const newCid = CID.asCID(oldCid) - assert.deepStrictEqual(newCid.toString(), cidStr) + assert.deepStrictEqual(/** @type {CID} */(newCid).toString(), cidStr) }) it('inspect bytes', () => { @@ -156,7 +159,7 @@ describe('CID', () => { const cid = CID.create(1, 0x71, hash) assert.deepStrictEqual(cid.code, 0x71) assert.deepStrictEqual(cid.version, 1) - assert.ok(equals(cid.multihash, hash)) + equalDigest(cid.multihash, hash) }) it('can roundtrip through cid.toString()', async () => { @@ -203,7 +206,7 @@ describe('CID', () => { const cidStr = 'bafybeidskjjd4zmr7oh6ku6wp72vvbxyibcli2r6if3ocdcy7jjjusvl2u' const oldCid = CID.parse(cidStr) const newCid = CID.asCID(oldCid) - assert.deepStrictEqual(newCid.toString(), cidStr) + assert.deepStrictEqual(/** @type {CID} */(newCid).toString(), cidStr) }) }) @@ -254,6 +257,7 @@ 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)) @@ -291,6 +295,7 @@ describe('CID', () => { const form = JSON.stringify(hash.toString()) const mh = hash instanceof Uint8Array ? `textEncoder.encode(${form})` : form const name = `CID.create(${version}, ${code}, ${mh})` + // @ts-expect-error - version issn't always 0|1 it(name, async () => await testThrowAny(() => CID.create(version, code, hash))) } @@ -362,16 +367,22 @@ describe('CID', () => { it('should cache string representation when it matches the multibaseName it was constructed with', async () => { 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') + // @ts-expect-error - _baseCache is private assert.deepStrictEqual(cid._baseCache.has(base32.prefix), false) const base32String = 'bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu' assert.deepStrictEqual(cid.toString(), base32String) + // @ts-expect-error - _baseCache is private assert.deepStrictEqual(cid._baseCache.get(base32.prefix), base32String) assert.deepStrictEqual(cid.toString(base64), 'mAXASILp4Fr+PAc/qQUFA3l2uIiOwA2Gjlhd6nLQQ/2HyABWt') }) @@ -379,6 +390,7 @@ describe('CID', () => { 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) }) }) @@ -401,6 +413,11 @@ describe('CID', () => { it('asCID', async () => { const hash = await sha256.digest(textEncoder.encode('abc')) class IncompatibleCID { + /** + * @param {number} version + * @param {number} code + * @param {import('multiformats/hashes/interface').MultihashDigest} multihash + */ constructor (version, code, multihash) { this.version = version this.code = code @@ -419,46 +436,63 @@ describe('CID', () => { const incompatibleCID = new IncompatibleCID(version, code, hash) assert.ok(CID.isCID(incompatibleCID)) assert.strictEqual(incompatibleCID.toString(), '[object Object]') + // @ts-expect-error - no such method assert.strictEqual(typeof incompatibleCID.toV0, 'undefined') - const cid1 = CID.asCID(incompatibleCID) + 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, hash)) + assert.ok(equals(cid1.multihash.bytes, hash.bytes)) const cid2 = CID.asCID({ version, code, hash }) assert.strictEqual(cid2, null) const duckCID = { version, code, multihash: hash } + // @ts-expect-error - no such property duckCID.asCID = duckCID - const cid3 = CID.asCID(duckCID) + 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, hash)) + assert.ok(equals(cid3.multihash.bytes, hash.bytes)) const cid4 = CID.asCID(cid3) assert.strictEqual(cid3, cid4) - const cid5 = CID.asCID(new OLDCID(1, 'raw', Uint8Array.from(hash.bytes))) + 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, hash)) + assert.ok(equals(cid5.multihash.bytes, hash.bytes)) assert.strictEqual(cid5.code, 85) }) + /** + * @param {CID} x + * @param {CID} y + */ const digestsame = (x, y) => { - assert.deepStrictEqual(x.digest, y.digest) + // @ts-ignore - not sure what this supposed to be assert.deepStrictEqual(x.hash, y.hash) assert.deepStrictEqual(x.bytes, y.bytes) if (x.multihash) { - digestsame(x.multihash, y.multihash) + equalDigest(x.multihash, y.multihash) } const empty = { hash: null, bytes: null, digest: null, multihash: null } assert.deepStrictEqual({ ...x, ...empty }, { ...y, ...empty }) } + /** + * @typedef {import('multiformats/hashes/interface').MultihashDigest} MultihashDigest + * @param {MultihashDigest} x + * @param {MultihashDigest} y + */ + const equalDigest = (x, y) => { + assert.deepStrictEqual(x.digest, y.digest) + assert.deepStrictEqual(x.code, y.code) + assert.deepStrictEqual(x.digest, y.digest) + } + describe('CID.parse', async () => { it('parse 32 encoded CIDv1', async () => { const hash = await sha256.digest(textEncoder.encode('abc')) @@ -472,7 +506,7 @@ describe('CID', () => { const hash = await sha256.digest(textEncoder.encode('abc')) const cid = CID.create(1, 112, hash) - const parsed = CID.parse(cid.toString(base58btc)) + const parsed = /** @type {CID} */(CID.parse(cid.toString(base58btc))) digestsame(cid, parsed) }) @@ -532,17 +566,19 @@ describe('CID', () => { it('new CID from old CID', async () => { const hash = await sha256.digest(textEncoder.encode('abc')) - const 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) - assert.ok(equals(cid.multihash, hash)) + equalDigest(cid.multihash, hash) assert.deepStrictEqual(cid.code, 85) }) it('util.inspect', async () => { const hash = await sha256.digest(textEncoder.encode('abc')) const cid = CID.create(1, 112, hash) + // @ts-expect-error - no such method is known assert.deepStrictEqual(typeof cid[Symbol.for('nodejs.util.inspect.custom')], 'function') + // @ts-expect-error - no such method is known assert.deepStrictEqual(cid[Symbol.for('nodejs.util.inspect.custom')](), 'CID(bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu)') }) @@ -551,6 +587,7 @@ describe('CID', () => { const hash = await sha256.digest(textEncoder.encode('abc')) const cid = CID.create(1, 112, hash) await testThrow(() => cid.codec, '"codec" property is deprecated, use integer "code" property instead') + // @ts-expect-error - 'string' is not assignable to parameter of type 'number' await testThrow(() => CID.create(1, 'dag-pb', hash), 'String codecs are no longer supported') }) @@ -569,6 +606,7 @@ describe('CID', () => { it('toBaseEncodedString()', async () => { const hash = await sha256.digest(textEncoder.encode('abc')) const cid = CID.create(1, 112, hash) + // @ts-expect-error - deprecated await testThrow(() => cid.toBaseEncodedString(), 'Deprecated, use .toString()') }) }) diff --git a/test/test-multibase-spec.js b/test/test-multibase-spec.js index 13c8a1d7..736156ee 100644 --- a/test/test-multibase-spec.js +++ b/test/test-multibase-spec.js @@ -4,7 +4,7 @@ import { bases } from 'multiformats/basics' import { fromString } from '../src/bytes.js' import { assert } from 'chai' -import testThrow from './fixtures/test-throw.js' +import { testThrowSync as testThrow } from './fixtures/test-throw.js' const encoded = [ { @@ -171,7 +171,7 @@ describe('spec test', () => { for (const { input, tests } of encoded) { describe(`multibase spec ${index++}`, () => { for (const [name, output] of tests) { - const base = bases[name] + const base = bases[/** @type {keyof bases} */(name)] describe(name, () => { it('should encode buffer', () => { diff --git a/test/test-multibase.js b/test/test-multibase.js index ffeda305..3e602196 100644 --- a/test/test-multibase.js +++ b/test/test-multibase.js @@ -1,6 +1,7 @@ /* globals describe, it */ import * as bytes from '../src/bytes.js' import { assert } from 'chai' +import * as b from 'multiformats/bases/base' import * as b2 from 'multiformats/bases/base2' import * as b8 from 'multiformats/bases/base8' import * as b10 from 'multiformats/bases/base10' @@ -9,7 +10,7 @@ import * as b32 from 'multiformats/bases/base32' import * as b36 from 'multiformats/bases/base36' import * as b58 from 'multiformats/bases/base58' import * as b64 from 'multiformats/bases/base64' -import testThrow from './fixtures/test-throw.js' +import { testThrowAsync as testThrow } from './fixtures/test-throw.js' const { base16, base32, base58btc, base64 } = { ...b16, ...b32, ...b58, ...b64 } @@ -47,19 +48,26 @@ describe('multibase', () => { it('encode string failure', () => { const msg = 'Unknown type, must be binary type' + // @ts-expect-error - expects bytes testThrow(() => base32.encode('asdf'), msg) + // @ts-expect-error - expects bytes testThrow(() => base32.encoder.encode('asdf'), msg) }) it('decode int failure', () => { const msg = 'Can only multibase decode strings' + // @ts-expect-error - 'number' is not assignable to parameter of type 'string' testThrow(() => base32.decode(1), msg) + // @ts-expect-error - 'number' is not assignable to parameter of type 'string' testThrow(() => base32.decoder.decode(1), msg) }) const buff = bytes.fromString('test') const nonPrintableBuff = Uint8Array.from([239, 250, 254]) + /** + * @param {typeof b2|b8|b10|b16|b32|b36|b58|b64} bases + */ const baseTest = bases => { for (const base of Object.values(bases)) { if (base && base.name) { @@ -153,4 +161,36 @@ describe('multibase', () => { testThrow(() => base64.decode(b64.substring(0, b64.length - 1)), 'Unexpected end of data') }) + + it('infers prefix and name corretly', () => { + /** @type {'base32'} */ + const name = base32.name + + /** @type {'base16'} */ + // @ts-expect-error - TS catches mismatch + const name2 = base32.name + + /** @type {'b'} */ + const prefix = base32.prefix + assert.equal(prefix, 'b') + assert.equal(name, 'base32') + assert.equal(name2, name) + }) + + it('can utilize or combinator', () => { + const bases = { + ...b32, + ...b36, + ...b58, + ...b64 + } + + const composite = Object + .values(bases) + .map(codec => codec.decoder.composed) + .reduce(b.or) + + assert.equal(bytes.toString(composite.decode(base32.encode(bytes.fromString('test')))), 'test') + assert.equal(bytes.toString(composite.decode(base64.encode(bytes.fromString('test')))), 'test') + }) }) diff --git a/test/test-multicodec.js b/test/test-multicodec.js index 89e993bc..ee8c146a 100644 --- a/test/test-multicodec.js +++ b/test/test-multicodec.js @@ -3,13 +3,13 @@ import * as bytes from '../src/bytes.js' import { assert } from 'chai' import * as raw from 'multiformats/codecs/raw' import * as json from 'multiformats/codecs/json' -import testThrow from './fixtures/test-throw.js' +import { testThrowAsync } from './fixtures/test-throw.js' describe('multicodec', () => { it('encode/decode raw', () => { const buff = raw.encode(bytes.fromString('test')) assert.deepStrictEqual(buff, bytes.fromString('test')) - assert.deepStrictEqual(raw.decode(buff, 'raw'), bytes.fromString('test')) + assert.deepStrictEqual(raw.decode(buff), bytes.fromString('test')) }) it('encode/decode json', () => { @@ -19,6 +19,7 @@ describe('multicodec', () => { }) it('raw cannot encode string', async () => { - await testThrow(() => raw.encode('asdf'), 'Unknown type, must be binary type') + // @ts-expect-error - 'string' is not assignable to parameter of type 'Uint8Array' + await testThrowAsync(() => raw.encode('asdf'), 'Unknown type, must be binary type') }) }) diff --git a/test/test-multihash.js b/test/test-multihash.js index 6e5829d2..4d3d0941 100644 --- a/test/test-multihash.js +++ b/test/test-multihash.js @@ -8,8 +8,17 @@ import invalid from './fixtures/invalid-multihash.js' import { sha256, sha512 } from 'multiformats/hashes/sha2' import { identity } from 'multiformats/hashes/identity' import { decode as decodeDigest, create as createDigest } from 'multiformats/hashes/digest' +import { testThrowAsync } from './fixtures/test-throw.js' +/** + * @param {number|string} code + * @param {number} size + * @param {string} hex + */ const sample = (code, size, hex) => { + /** + * @param {number|string} i + */ const toHex = (i) => { if (typeof i === 'string') return i const h = i.toString(16) @@ -18,17 +27,6 @@ const sample = (code, size, hex) => { return fromHex(`${toHex(code)}${toHex(size)}${hex}`) } -const testThrowAsync = async (fn, message) => { - try { - await fn() - } catch (e) { - if (e.message !== message) throw e - return - } - /* c8 ignore next */ - throw new Error('Test failed to throw') -} - describe('multihash', () => { const empty = new Uint8Array(0) @@ -104,6 +102,7 @@ describe('multihash', () => { }) it('throw on hashing non-buffer', async () => { + // @ts-expect-error - string is incompatible arg await testThrowAsync(() => sha256.digest('asdf'), 'Unknown type, must be binary type') }) }) diff --git a/test/test-traversal.js b/test/test-traversal.js index 228b87ce..f8a42c72 100644 --- a/test/test-traversal.js +++ b/test/test-traversal.js @@ -5,6 +5,7 @@ import { sha256 as hasher } from 'multiformats/hashes/sha2' import * as main from 'multiformats/block' import { walk } from 'multiformats/traversal' import { assert } from 'chai' +import { fromString } from '../src/bytes.js' const { createLink, createNode } = dagPB @@ -16,31 +17,34 @@ describe('traversal', () => { // B C // / \ / \ // D D D E - const linksE = [] - const valueE = createNode(Uint8Array.from('string E qacdswa'), linksE) + const linksE = /** @type {[]} */([]) + const valueE = createNode(fromString('string E qacdswa'), linksE) const blockE = await main.encode({ value: valueE, codec, hasher }) const cidE = blockE.cid - const linksD = [] - const valueD = createNode(Uint8Array.from('string D zasa'), linksD) + const linksD = /** @type {[]} */([]) + const valueD = createNode(fromString('string D zasa'), linksD) const blockD = await main.encode({ value: valueD, codec, hasher }) const cidD = blockD.cid const linksC = [createLink('link1', 100, cidD), createLink('link2', 100, cidE)] - const valueC = createNode(Uint8Array.from('string C zxc'), linksC) + const valueC = createNode(fromString('string C zxc'), linksC) const blockC = await main.encode({ value: valueC, codec, hasher }) const cidC = blockC.cid const linksB = [createLink('link1', 100, cidD), createLink('link2', 100, cidD)] - const valueB = createNode(Uint8Array.from('string B lpokjiasd'), linksB) + const valueB = createNode(fromString('string B lpokjiasd'), linksB) const blockB = await main.encode({ value: valueB, codec, hasher }) const cidB = blockB.cid const linksA = [createLink('link1', 100, cidB), createLink('link2', 100, cidC)] - const valueA = createNode(Uint8Array.from('string A qwertcfdgshaa'), linksA) + const valueA = createNode(fromString('string A qwertcfdgshaa'), linksA) const blockA = await main.encode({ value: valueA, codec, hasher }) const cidA = blockA.cid + /** + * @param {import('multiformats').CID} cid + */ const load = async (cid) => { if (cid.equals(cidE)) { return blockE @@ -60,10 +64,18 @@ describe('traversal', () => { return null } - const loadWrapper = (load, arr = []) => (cid) => { - arr.push(cid.toString()) - return load(cid) - } + /** + * @param {typeof load} load + * @param {string[]} arr + */ + const loadWrapper = (load, arr = []) => + /** + * @param {import('multiformats').CID} cid + */ + (cid) => { + arr.push(cid.toString()) + return load(cid) + } it('block with no links', async () => { // Test Case 1 @@ -72,6 +84,7 @@ describe('traversal', () => { // // Expect load to be called with D const expectedCallArray = [cidD.toString()] + /** @type {string[]} */ const callArray = [] await walk({ cid: cidD, load: loadWrapper(load, callArray) }) @@ -90,6 +103,7 @@ describe('traversal', () => { // // Expect load to be called with C, then D, then E const expectedCallArray = [cidC.toString(), cidD.toString(), cidE.toString()] + /** @type {string[]} */ const callArray = [] await walk({ cid: cidC, load: loadWrapper(load, callArray) }) @@ -108,6 +122,7 @@ describe('traversal', () => { // // Expect load to be called with B, then D const expectedCallArray = [cidB.toString(), cidD.toString()] + /** @type {string[]} */ const callArray = [] await walk({ cid: cidB, load: loadWrapper(load, callArray) }) @@ -134,6 +149,7 @@ describe('traversal', () => { cidC.toString(), cidE.toString() ] + /** @type {string[]} */ const callArray = [] await walk({ cid: cidA, load: loadWrapper(load, callArray) }) @@ -144,11 +160,13 @@ describe('traversal', () => { }) it('null return', async () => { + /** @type {[]} */ const links = [] - const value = createNode(Uint8Array.from('test'), links) + const value = createNode(fromString('test'), links) const block = await main.encode({ value: value, codec, hasher }) const cid = block.cid const expectedCallArray = [cid.toString()] + /** @type {string[]} */ const callArray = [] await walk({ cid, load: loadWrapper(load, callArray) }) diff --git a/tsconfig.json b/tsconfig.json index 082f7583..d4471aa6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,7 @@ "strict": true, "alwaysStrict": true, "esModuleInterop": true, - "target": "ES2018", + "target": "ES2020", "moduleResolution": "node", "declaration": true, "declarationMap": true, @@ -26,18 +26,89 @@ "resolveJsonModule": true, "emitDeclarationOnly": true, "baseUrl": ".", + // unfortunately we have to manually path map each file so that + // ts can resolve imports correctly (which workarounds resolution + // occuring from node_modules/multiformats which is installed by dev + // dependency) + // Once https://www.typescriptlang.org/docs/handbook/esm-node.html + // is stabilized we will no longer need this hack. "paths": { "multiformats": [ - "src" + "src/index.js" + ], + "multiformats/cid": [ + "./src/cid.js" + ], + "multiformats/basics": [ + "./src/basics.js" + ], + "multiformats/block": [ + "./src/block.js" + ], + "multiformats/traversal": [ + "./src/traversal.js" + ], + "multiformats/bases/base": [ + "./src/bases/base.js" + ], + "multiformats/bases/identity": [ + "./src/bases/identity.js" + ], + "multiformats/bases/base2": [ + "./src/bases/base2.js" + ], + "multiformats/bases/base8": [ + "./src/bases/base8.js" + ], + "multiformats/bases/base10": [ + "./src/bases/base10.js" + ], + "multiformats/bases/base16": [ + "./src/bases/base16.js" + ], + "multiformats/bases/base32": [ + "./src/bases/base32.js" + ], + "multiformats/bases/base36": [ + "./src/bases/base36.js" + ], + "multiformats/bases/base58": [ + "./src/bases/base58.js" + ], + "multiformats/bases/base64": [ + "./src/bases/base64.js" + ], + "multiformats/hashes/hasher": [ + "./src/hashes/hasher.js" + ], + "multiformats/hashes/digest": [ + "./src/hashes/digest.js" + ], + "multiformats/hashes/sha2": [ + "./src/hashes/sha2-browser.js" + ], + "multiformats/hashes/identity": [ + "./src/hashes/identity.js" + ], + "multiformats/codecs/json": [ + "./src/codecs/json.js" + ], + "multiformats/codecs/raw": [ + "src/codecs/raw.js" + ], + "multiformats/*": [ + "src/*" ] } }, "include": [ - "src" + "src", + "test" ], "exclude": [ "vendor", - "node_modules" + "node_modules", + "node_modules/multiformats" ], "compileOnSave": false }