From 076290c2103eadc031f1a8069c5a296522eb9678 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Mon, 26 Apr 2021 13:31:06 +1000 Subject: [PATCH] fix!: simplify the codec interface, remove helper methods & classes (#75) BREAKING CHANGE: * Removes the codecs/codec export - there is no longer a helper function to building codecs, they should instead assert that they conform to the `BlockCodec` type, see json and raw examples. The `codec()` helper and `Encoder` and `Decoder` classes have been removed. Use type assertions to conform to simple signatures for now. In the future we may introduce more composable codec functionality which may require codecs to lean on additional helpers and classes. * Fix bases/base64 type export to be usable --- .github/workflows/mikeals-workflow.yml | 2 +- README.md | 44 +++++----------- package.json | 25 +++++---- src/codecs/codec.js | 71 -------------------------- src/codecs/interface.ts | 14 ++--- src/codecs/json.js | 14 +++-- src/codecs/raw.js | 27 +++++++--- src/index.js | 3 +- test/test-legacy.js | 5 +- test/test-multicodec.js | 32 +----------- test/ts-use/package.json | 2 +- tsconfig.json | 6 +-- 12 files changed, 69 insertions(+), 176 deletions(-) delete mode 100644 src/codecs/codec.js diff --git a/.github/workflows/mikeals-workflow.yml b/.github/workflows/mikeals-workflow.yml index 1b88e24a..d7787f0e 100644 --- a/.github/workflows/mikeals-workflow.yml +++ b/.github/workflows/mikeals-workflow.yml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [12.x, 14.x] + node-version: [12.x, 14.x, 16.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} diff --git a/README.md b/README.md index b48a5e68..0bce9b5e 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ # multiformats -This library defines common interfaces and low level building blocks for various interrelated multiformat technologies (multicodec, multihash, multibase, -and CID). They can be used to implement custom custom base -encoders / decoders / codecs, codec encoders /decoders and multihash hashers that comply to the interface that layers above assume. +This library defines common interfaces and low level building blocks for various interrelated multiformat technologies (multicodec, multihash, multibase, and CID). They can be used to implement custom custom base encoders / decoders / codecs, codec encoders /decoders and multihash hashers that comply to the interface that layers above assume. -Library provides implementations for most basics and many others can be found in linked repositories. +This library provides implementations for most basics and many others can be found in linked repositories. ## Interfaces @@ -45,9 +43,7 @@ block = await Block.create({ bytes: block.bytes, cid: block.cid, codec, hasher } ### Multibase Encoders / Decoders / Codecs -CIDs can be serialized to string representation using multibase encoders that -implement [`MultibaseEncoder`](https://github.com/multiformats/js-multiformats/blob/master/src/bases/interface.ts) interface. Library -provides quite a few implementations that can be imported: +CIDs can be serialized to string representation using multibase encoders that implement [`MultibaseEncoder`](https://github.com/multiformats/js-multiformats/blob/master/src/bases/interface.ts) interface. This library provides quite a few implementations that can be imported: ```js import { base64 } from "multiformats/bases/base64" @@ -55,9 +51,7 @@ cid.toString(base64.encoder) //> 'mAYAEEiCTojlxqRTl6svwqNJRVM2jCcPBxy+7mRTUfGDzy2gViA' ``` -Parsing CID string serialized CIDs requires multibase decoder that implements -[`MultibaseDecoder`](https://github.com/multiformats/js-multiformats/blob/master/src/bases/interface.ts) interface. Library provides a -decoder for every encoder it provides: +Parsing CID string serialized CIDs requires multibase decoder that implements [`MultibaseDecoder`](https://github.com/multiformats/js-multiformats/blob/master/src/bases/interface.ts) interface. This library provides a decoder for every encoder it provides: ```js CID.parse('mAYAEEiCTojlxqRTl6svwqNJRVM2jCcPBxy+7mRTUfGDzy2gViA', base64.decoder) @@ -93,35 +87,24 @@ v0.toV1().toString() ### Multicodec Encoders / Decoders / Codecs -Library defines [`BlockEncoder`, `BlockDecoder` and `BlockCodec` interfaces](https://github.com/multiformats/js-multiformats/blob/master/src/codecs/interface.ts) -and utility function to take care of the boilerplate when implementing them: +This library defines [`BlockEncoder`, `BlockDecoder` and `BlockCodec` interfaces](https://github.com/multiformats/js-multiformats/blob/master/src/codecs/interface.ts). Codec implementations should conform to the `BlockCodec` interface which implements both `BlockEncoder` and `BlockDecoder`. ```js -import { codec } from 'multiformats/codecs/codec' - -const json = codec({ +/** + * @template T + * @type {BlockCodec<0x0200, T>} + */ +export const { name, code, encode, decode } = { name: 'json', - // As per multiformats table - // https://github.com/multiformats/multicodec/blob/master/table.csv#L113 code: 0x0200, encode: json => new TextEncoder().encode(JSON.stringify(json)), decode: bytes => JSON.parse(new TextDecoder().decode(bytes)) -}) -``` - -Just like with multibase, here codecs are duals of `encoder` and `decoder` parts, -but they also implement both interfaces for convenience: - -```js -const hello = json.encoder.encode({ hello: 'world' }) -json.decode(b1) -//> { hello: 'world' } +} ``` ### Multihash Hashers -This library defines [`MultihashHasher` and `MultihashDigest` interfaces](https://github.com/multiformats/js-multiformats/blob/master/src/hashes/interface.ts) -and convinient function for implementing them: +This library defines [`MultihashHasher` and `MultihashDigest` interfaces](https://github.com/multiformats/js-multiformats/blob/master/src/hashes/interface.ts) and convinient function for implementing them: ```js import * as hasher from 'multiformats/hashes/hasher') @@ -141,8 +124,6 @@ CID.create(1, json.code, hash) //> CID(bagaaierasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea) ``` - - # Implementations By default, no base encodings (other than base32 & base58btc), hash functions, @@ -179,7 +160,6 @@ import the ones you need yourself. | `dag-pb` | `@ipld/dag-pb` | [ipld/js-dag-pb](https://github.com/ipld/js-dag-pb) | | `dag-jose` | `dag-jose`| [ceramicnetwork/js-dag-jose](https://github.com/ceramicnetwork/js-dag-jose) | - ## TypeScript support This project is distributed with type definitions for TypeScript. diff --git a/package.json b/package.json index a034f502..f9dc7523 100644 --- a/package.json +++ b/package.json @@ -7,15 +7,14 @@ "type": "module", "scripts": { "build": "npm run build:js && npm run build:types", - "build:js": "npm_config_yes=true ipjs build --tests --main && npm run build:copy", + "build:js": "ipjs build --tests --main && npm run build:copy", "build:copy": "cp -a tsconfig.json src vendor test dist/ && rm -rf dist/test/ts-use", "build:types": "npm run build:copy && cd dist && tsc --build", "build:vendor": "npm run build:vendor:varint && npm run build:vendor:base-x", - "build:vendor:varint": "npx brrp -x varint > vendor/varint.js", - "build:vendor:base-x": "npx brrp -x @multiformats/base-x > vendor/base-x.js", - "publish": "npm_config_yes=true ipjs publish", + "build:vendor:varint": "npm_config_yes=true npx brrp -x varint > vendor/varint.js", + "build:vendor:base-x": "npm_config_yes=true npx brrp -x @multiformats/base-x > vendor/base-x.js", + "publish": "ipjs publish", "lint": "standard", - "check": "tsc --build --noErrorTruncation", "test:cjs": "npm run build:js && mocha dist/cjs/node-test/test-*.js && npm run test:cjs:browser", "test:node": "hundreds mocha test/test-*.js", "test:cjs:browser": "polendina --page --worker --serviceworker --cleanup dist/cjs/browser-test/test-*.js", @@ -75,9 +74,6 @@ "./hashes/identity": { "import": "./src/hashes/identity.js" }, - "./codecs/codec": { - "import": "./src/codecs/codec.js" - }, "./codecs/json": { "import": "./src/codecs/json.js" }, @@ -86,16 +82,16 @@ } }, "devDependencies": { - "@types/node": "^14.14.37", - "@typescript-eslint/eslint-plugin": "^4.20.0", - "@typescript-eslint/parser": "^4.20.0", - "c8": "^7.6.0", + "@types/node": "^14.14.41", + "@typescript-eslint/eslint-plugin": "^4.22.0", + "@typescript-eslint/parser": "^4.22.0", + "c8": "^7.7.1", "hundreds": "0.0.9", "ipjs": "^5.0.0", "mocha": "^8.3.2", "polendina": "^1.1.0", "standard": "^16.0.3", - "typescript": "^4.2.3" + "typescript": "^4.2.4" }, "standard": { "ignore": [ @@ -120,6 +116,9 @@ "homepage": "https://github.com/multiformats/js-multiformats#readme", "typesVersions": { "*": { + "bases/base64": [ + "types/bases/base64-import.d.ts" + ], "*": [ "types/*" ], diff --git a/src/codecs/codec.js b/src/codecs/codec.js deleted file mode 100644 index c1bd94aa..00000000 --- a/src/codecs/codec.js +++ /dev/null @@ -1,71 +0,0 @@ -// @ts-check - -/** - * @template {string} Name - * @template {number} Code - * @template T - * - * @param {Object} options - * @param {Name} options.name - * @param {Code} options.code - * @param {(data:T) => Uint8Array} options.encode - * @param {(bytes:Uint8Array) => T} options.decode - * @returns {import('./interface').BlockCodec} - */ -export const codec = ({ name, code, decode, encode }) => { - const decoder = new Decoder(name, code, decode) - const encoder = new Encoder(name, code, encode) - - return { name, code, decode, encode, decoder, encoder } -} - -/** - * @template {number} Code - * @template T - * @typedef {import('./interface').BlockEncoder} BlockEncoder - */ - -/** - * @class - * @template T - * @template {string} Name - * @template {number} Code - * @implements {BlockEncoder} - */ -export class Encoder { - /** - * @param {Name} name - * @param {Code} code - * @param {(data:T) => Uint8Array} encode - */ - constructor (name, code, encode) { - this.name = name - this.code = code - this.encode = encode - } -} - -/** - * @template {number} Code - * @template T - * @typedef {import('./interface').BlockDecoder} BlockDecoder - */ - -/** - * @class - * @template {number} Code - * @template T - * @implements {BlockDecoder} - */ -export class Decoder { - /** - * @param {string} name - * @param {Code} code - * @param {(bytes:Uint8Array) => T} decode - */ - constructor (name, code, decode) { - this.name = name - this.code = code - this.decode = decode - } -} diff --git a/src/codecs/interface.ts b/src/codecs/interface.ts index e7a98b52..dc8527c1 100644 --- a/src/codecs/interface.ts +++ b/src/codecs/interface.ts @@ -16,18 +16,12 @@ export interface BlockDecoder { } /** - * IPLD codec that is just Encoder + Decoder however it is - * separate those capabilties as sender requires encoder and receiver - * requires decoder. + * An IPLD codec is a combination of both encoder and decoder. */ -export interface BlockCodec extends BlockEncoder, BlockDecoder { - encoder: BlockEncoder, - decoder: BlockDecoder -} - +export interface BlockCodec extends BlockEncoder, BlockDecoder {} -// This just a hack to retain type information abouth the data that -// is incoded `T` Because it's a union `data` field is never going +// 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 diff --git a/src/codecs/json.js b/src/codecs/json.js index 09ea2a09..fd8be455 100644 --- a/src/codecs/json.js +++ b/src/codecs/json.js @@ -1,10 +1,18 @@ // @ts-check -import { codec } from './codec.js' +/** + * @template {number} Code + * @template T + * @typedef {import('./interface').BlockCodec} BlockCodec + */ -export const { name, code, decode, encode, decoder, encoder } = codec({ +/** + * @template T + * @type {BlockCodec<0x0200, T>} + */ +export const { name, code, encode, decode } = { name: 'json', code: 0x0200, encode: json => new TextEncoder().encode(JSON.stringify(json)), decode: bytes => JSON.parse(new TextDecoder().decode(bytes)) -}) +} diff --git a/src/codecs/raw.js b/src/codecs/raw.js index f2ee233f..9f2639db 100644 --- a/src/codecs/raw.js +++ b/src/codecs/raw.js @@ -1,11 +1,26 @@ // @ts-check import { coerce } from '../bytes.js' -import { codec } from './codec.js' -export const { name, code, decode, encode, decoder, encoder } = codec({ +/** + * @template {number} Code + * @template T + * @typedef {import('./interface').BlockCodec} BlockCodec + */ + +/** + * @param {Uint8Array} bytes + * @returns {Uint8Array} + */ +const raw = (bytes) => coerce(bytes) + +/** + * @template T + * @type {BlockCodec<0x55, Uint8Array>} + */ +export const { name, code, encode, decode } = { name: 'raw', - code: 85, - decode: coerce, - encode: coerce -}) + code: 0x55, + decode: raw, + encode: raw +} diff --git a/src/index.js b/src/index.js index 15931971..aed6be18 100644 --- a/src/index.js +++ b/src/index.js @@ -3,6 +3,5 @@ import * as varint from './varint.js' import * as bytes from './bytes.js' import * as hasher from './hashes/hasher.js' import * as digest from './hashes/digest.js' -import * as codec from './codecs/codec.js' -export { CID, hasher, digest, varint, bytes, codec } +export { CID, hasher, digest, varint, bytes } diff --git a/test/test-legacy.js b/test/test-legacy.js index 4e0468d0..63e55014 100644 --- a/test/test-legacy.js +++ b/test/test-legacy.js @@ -5,7 +5,6 @@ import { legacy } from 'multiformats/legacy' import * as rawCodec from 'multiformats/codecs/raw' import * as jsonCodec from 'multiformats/codecs/json' import { sha256, sha512 } from 'multiformats/hashes/sha2' -import { codec } from 'multiformats/codecs/codec' import { CID } from 'multiformats/cid' const same = assert.deepStrictEqual @@ -36,7 +35,7 @@ describe('multicodec', () => { raw = legacy(rawCodec, { hashes }) json = legacy(jsonCodec, { hashes }) link = await raw.util.cid(Buffer.from('test')) - custom = legacy(codec({ + custom = legacy({ name: 'custom', code: 6787678, encode: o => { @@ -52,7 +51,7 @@ describe('multicodec', () => { if (obj.o.link) obj.link = CID.asCID(link) return obj } - }), { hashes }) + }, { hashes }) }) test('encode/decode raw', () => { const buff = raw.util.serialize(Buffer.from('test')) diff --git a/test/test-multicodec.js b/test/test-multicodec.js index 38668b69..9f1ab164 100644 --- a/test/test-multicodec.js +++ b/test/test-multicodec.js @@ -3,7 +3,7 @@ import * as bytes from '../src/bytes.js' import assert from 'assert' import * as raw from 'multiformats/codecs/raw' import * as json from 'multiformats/codecs/json' -import { codec } from 'multiformats/codecs/codec' + const same = assert.deepStrictEqual const test = it @@ -31,37 +31,7 @@ describe('multicodec', () => { same(json.decode(buff), { hello: 'world' }) }) - test('json.encoder', () => { - const { encoder } = json - same(encoder === json.encoder, true, 'getter cached decoder') - - const buff = encoder.encode({ hello: 'world' }) - same(buff, bytes.fromString(JSON.stringify({ hello: 'world' }))) - }) - - test('json.decoder', () => { - const { decoder } = json - same(decoder === json.decoder, true, 'getter cached encoder') - - const buff = json.encode({ hello: 'world' }) - same(decoder.decode(buff), { hello: 'world' }) - }) - test('raw cannot encode string', async () => { await testThrow(() => raw.encode('asdf'), 'Unknown type, must be binary type') }) - - test('add with function', () => { - const blip = codec({ - code: 200, - name: 'blip', - encode: (a) => a[1], - decode: (a) => a - }) - - const two = bytes.fromString('two') - const three = bytes.fromString('three') - same(blip.encode(['one', two, three]), two) - same(blip.decode(three, 200), three) - }) }) diff --git a/test/ts-use/package.json b/test/ts-use/package.json index 44f4d5e7..c4ef82b8 100644 --- a/test/ts-use/package.json +++ b/test/ts-use/package.json @@ -5,6 +5,6 @@ "multiformats": "file:../../dist/" }, "scripts": { - "test": "npm install && npx -p typescript tsc --noEmit" + "test": "npm install && npm_config_yes=true npx -p typescript tsc --noEmit" } } diff --git a/tsconfig.json b/tsconfig.json index 2634e3f1..082f7583 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,17 +20,17 @@ "moduleResolution": "node", "declaration": true, "declarationMap": true, - "emitDeclarationOnly": true, "outDir": "types", "skipLibCheck": true, "stripInternal": true, "resolveJsonModule": true, + "emitDeclarationOnly": true, + "baseUrl": ".", "paths": { "multiformats": [ "src" ] - }, - "baseUrl": "." + } }, "include": [ "src"