diff --git a/.travis.yml b/.travis.yml index 3df7565..dcbad7d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,9 +21,9 @@ jobs: include: - stage: check script: - - npx aegir commitlint --travis - - npx aegir dep-check - npm run lint + - npx aegir ts --preset check + - npx aegir dep-check - stage: test name: chrome diff --git a/README.md b/README.md index 781be99..3a27e0d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ js-multibase [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](https://protocol.ai) [![](https://img.shields.io/badge/project-multiformats-blue.svg?style=flat-square)](https://github.com/multiformats/multiformats) [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](https://webchat.freenode.net/?channels=%23ipfs) -[![Dependency Status](https://david-dm.org/multiformats/js-multibase.svg?style=flat-square)](https://david-dm.org/multiformats/js-multibase) [![codecov](https://img.shields.io/codecov/c/github/multiformats/js-multibase.svg?style=flat-square)](https://codecov.io/gh/multiformats/js-multibase) [![Travis CI](https://flat.badgen.net/travis/multiformats/js-multibase)](https://travis-ci.com/multiformats/js-multibase) diff --git a/package.json b/package.json index feabdc6..71f3120 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "formats" ], "devDependencies": { - "aegir": "^22.0.0", + "aegir": "ipfs/aegir#feat/typescript-support", "chai": "^4.1.2", "dirty-chai": "^2.0.1", "pre-commit": "^1.2.2" @@ -43,6 +43,9 @@ "base-x": "^3.0.8", "buffer": "^5.5.0" }, + "eslintConfig": { + "extends": "./node_modules/aegir/src/config/eslintrc.js" + }, "license": "MIT", "bugs": { "url": "https://github.com/multiformats/js-multibase/issues" diff --git a/src/base.js b/src/base.js index 1a66059..f915904 100644 --- a/src/base.js +++ b/src/base.js @@ -1,25 +1,51 @@ 'use strict' +const { Buffer } = require('buffer') +/** @typedef {import("./types").BaseConstructor} BaseConstructor */ +/** @typedef {import("./types").BaseNames} BaseNames */ +/** @typedef {import("./types").BaseCodes} BaseCodes */ + +/** + * Class to handle base encode/decode + */ class Base { + /** + * Base class + * + * @param {BaseCodes | BaseNames} name - base name + * @param {string} code - base code + * @param {BaseConstructor} implementation - base engine + * @param {string} alphabet + */ constructor (name, code, implementation, alphabet) { this.name = name this.code = code + /** @internal */ + this.codeBuffer = Buffer.from(code) this.alphabet = alphabet if (implementation && alphabet) { this.engine = implementation(alphabet) } } - encode (stringOrBuffer) { - return this.engine.encode(stringOrBuffer) - } - - decode (stringOrBuffer) { - return this.engine.decode(stringOrBuffer) + /** + * Encode value + * + * @param {Buffer | Uint8Array} value - Value to encode. + * @returns {string} Encoded value. + */ + encode (value) { + return this.engine.encode(value) } - isImplemented () { - return this.engine + /** + * Decode value + * + * @param {string} value - Value to decode. + * @returns {Buffer | Uint8Array} Value decoded. + */ + decode (value) { + return this.engine.decode(value) } } diff --git a/src/base16.js b/src/base16.js index f911f80..32a2b32 100644 --- a/src/base16.js +++ b/src/base16.js @@ -1,7 +1,16 @@ 'use strict' const { Buffer } = require('buffer') -module.exports = function base16 (alphabet) { +/** @typedef {import("./types").BaseInterface} BaseInterface */ + +/** + * Base 16 + * + * @internal + * @param {string} alphabet + * @returns {BaseInterface} + */ +const base16 = (alphabet) => { return { encode (input) { if (typeof input === 'string') { @@ -19,3 +28,5 @@ module.exports = function base16 (alphabet) { } } } + +module.exports = base16 diff --git a/src/base32.js b/src/base32.js index cc38046..14906d9 100644 --- a/src/base32.js +++ b/src/base32.js @@ -1,5 +1,14 @@ 'use strict' - +/** @typedef {import("./types").BaseInterface} BaseInterface */ +/** @typedef {import("./types").BaseConstructor} BaseConstructor */ + +/** + * + * @internal + * @param {string} input + * @param {string} alphabet + * @returns {Uint8Array} + */ function decode (input, alphabet) { input = input.replace(new RegExp('=', 'g'), '') const length = input.length @@ -20,9 +29,17 @@ function decode (input, alphabet) { } } - return output.buffer + return output } +/** + * Encode + * + * @internal + * @param {Buffer|Uint8Array} buffer + * @param {string} alphabet + * @returns {string} + */ function encode (buffer, alphabet) { const length = buffer.byteLength const view = new Uint8Array(buffer) @@ -59,12 +76,19 @@ function encode (buffer, alphabet) { return output } -module.exports = function base32 (alphabet) { +/** + * + * Base 32 + * + * @type {BaseConstructor} + * @internal + */ +const base32 = (alphabet) => { return { encode (input) { - if (typeof input === 'string') { - return encode(Uint8Array.from(input), alphabet) - } + // if (typeof input === 'string') { + // return encode(Uint8Array.from(input), alphabet) + // } return encode(input, alphabet) }, @@ -79,3 +103,5 @@ module.exports = function base32 (alphabet) { } } } + +module.exports = base32 diff --git a/src/base64.js b/src/base64.js index c4608bb..c2425c8 100644 --- a/src/base64.js +++ b/src/base64.js @@ -1,7 +1,7 @@ 'use strict' const { Buffer } = require('buffer') -module.exports = function base64 (alphabet) { +const base64 = (alphabet) => { // The alphabet is only used to know: // 1. If padding is enabled (must contain '=') // 2. If the output must be url-safe (must contain '-' and '_') @@ -42,3 +42,5 @@ module.exports = function base64 (alphabet) { } } } + +module.exports = base64 diff --git a/src/constants.js b/src/constants.js index 1b6bd37..20867eb 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,14 +1,21 @@ 'use strict' - const Base = require('./base.js') const baseX = require('base-x') const base16 = require('./base16') const base32 = require('./base32') const base64 = require('./base64') -// name, code, implementation, alphabet -const constants = [ - ['base1', '1', '', '1'], +/** @typedef {import("./types").BaseNames} BaseNames */ +/** @typedef {import("./types").BaseCodes} BaseCodes */ +/** @typedef {import("./types").BaseCodesMap} BaseCodesMap */ +/** @typedef {import("./types").BaseNamesMap} BaseNamesMap */ +/** @typedef {import("./types").BaseConstructor} BaseConstructor */ + +/** + * @internal + * @type {Array<[BaseNames, BaseCodes, BaseConstructor, string]>} - Name, Code, Engine, Alphabet + */ +const bases = [ ['base2', '0', baseX, '01'], ['base8', '7', baseX, '01234567'], ['base10', '9', baseX, '0123456789'], @@ -26,17 +33,19 @@ const constants = [ ['base64urlpad', 'U', base64, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_='] ] -const names = constants.reduce((prev, tupple) => { - prev[tupple[0]] = new Base(tupple[0], tupple[1], tupple[2], tupple[3]) +/** @type {BaseNamesMap} */ +const names = bases.reduce((prev, value) => { + prev[value[0]] = new Base(value[0], value[1], value[2], value[3]) return prev -}, {}) +}, /** @type {BaseNamesMap} */({})) -const codes = constants.reduce((prev, tupple) => { +/** @type {BaseCodesMap} */ +const codes = bases.reduce((prev, tupple) => { prev[tupple[1]] = names[tupple[0]] return prev -}, {}) +}, /** @type {BaseCodesMap} */({})) module.exports = { - names: names, - codes: codes + names, + codes } diff --git a/src/index.js b/src/index.js index 32760ea..a84af08 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,7 @@ /** * Implementation of the [multibase](https://github.com/multiformats/multibase) specification. + * + * @packageDocumentation * @module Multibase */ 'use strict' @@ -7,19 +9,15 @@ const { Buffer } = require('buffer') const constants = require('./constants') -exports = module.exports = multibase -exports.encode = encode -exports.decode = decode -exports.isEncoded = isEncoded -exports.names = Object.freeze(Object.keys(constants.names)) -exports.codes = Object.freeze(Object.keys(constants.codes)) +/** @typedef {import("./base")} Base */ +/** @typedef {import("./types").BaseCodes} BaseCodes */ +/** @typedef {import("./types").BaseNames} BaseNames */ /** * Create a new buffer with the multibase varint+code. * - * @param {string|number} nameOrCode - The multibase name or code number. + * @param {BaseCodes|BaseNames} nameOrCode - The multibase name or code number. * @param {Buffer} buf - The data to be prefixed with multibase. - * @memberof Multibase * @returns {Buffer} */ function multibase (nameOrCode, buf) { @@ -37,10 +35,9 @@ function multibase (nameOrCode, buf) { /** * Encode data with the specified base and add the multibase prefix. * - * @param {string|number} nameOrCode - The multibase name or code number. + * @param {BaseCodes|BaseNames} nameOrCode - The multibase name or code number. * @param {Buffer} buf - The data to be encoded. * @returns {Buffer} - * @memberof Multibase */ function encode (nameOrCode, buf) { const base = getBase(nameOrCode) @@ -55,15 +52,13 @@ function encode (nameOrCode, buf) { * * @param {Buffer|string} bufOrString * @returns {Buffer} - * @memberof Multibase - * */ function decode (bufOrString) { if (Buffer.isBuffer(bufOrString)) { bufOrString = bufOrString.toString() } - const code = bufOrString.substring(0, 1) + const code = /** @type {BaseCodes} */(bufOrString.substring(0, 1)) bufOrString = bufOrString.substring(1, bufOrString.length) if (typeof bufOrString === 'string') { @@ -78,8 +73,7 @@ function decode (bufOrString) { * Is the given data multibase encoded? * * @param {Buffer|string} bufOrString - * @returns {boolean} - * @memberof Multibase + * @returns {boolean|string} */ function isEncoded (bufOrString) { if (Buffer.isBuffer(bufOrString)) { @@ -91,7 +85,7 @@ function isEncoded (bufOrString) { return false } - const code = bufOrString.substring(0, 1) + const code = /** @type {BaseCodes} */(bufOrString.substring(0, 1)) try { const base = getBase(code) return base.name @@ -101,17 +95,26 @@ function isEncoded (bufOrString) { } /** - * @param {string} name + * Check if encoding is valid + * + * @param {BaseCodes|BaseNames} name * @param {Buffer} buf * @private - * @returns {undefined} + * @returns {void} */ function validEncode (name, buf) { const base = getBase(name) base.decode(buf.toString()) } +/** + * Get base to encode/decode without prefix + * + * @param {BaseCodes|BaseNames} nameOrCode + * @returns { Base } + */ function getBase (nameOrCode) { + /** @type {Base} */ let base if (constants.names[nameOrCode]) { @@ -119,12 +122,17 @@ function getBase (nameOrCode) { } else if (constants.codes[nameOrCode]) { base = constants.codes[nameOrCode] } else { - throw new Error('Unsupported encoding') - } - - if (!base.isImplemented()) { - throw new Error('Base ' + nameOrCode + ' is not implemented yet') + throw new Error(`Unsupported encoding: ${nameOrCode}`) } return base } + +module.exports = multibase + +multibase.encode = encode +multibase.decode = decode +multibase.isEncoded = isEncoded +multibase.names = Object.freeze(Object.keys(constants.names)) +multibase.codes = Object.freeze(Object.keys(constants.codes)) +multibase.getBase = getBase diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..dadff65 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,31 @@ +/** + * - Codes of the supported encodings + */ +export type BaseCodes = "0" | "7" | "9" | "f" | "b" | "c" | "v" | "t" | "h" | "Z" | "z" | "m" | "M" | "u" | "U"; + +/** + * - Names of the supported encodings + */ +export type BaseNames = "base64" | "base2" | "base8" | "base10" | "base16" | "base32" | "base32pad" | "base32hex" | "base32hexpad" | "base32z" | "base58flickr" | "base58btc" | "base64pad" | "base64url" | "base64urlpad"; + +/** + * Base + */ +export type BaseConstructor = (alphabet: string) => BaseInterface; + +/** + * BaseInterface + */ +export type BaseInterface = { + /** + * - Encode input + */ + encode: (input: Buffer | Uint8Array) => string; + /** + * - Decode input + */ + decode: (input: string) => Buffer | Uint8Array; +}; + +export type BaseNamesMap = { [K in BaseNames]: import('./base') } +export type BaseCodesMap = { [K in BaseNames]: import('./base') } \ No newline at end of file diff --git a/test/constants.spec.js b/test/constants.spec.js index 302e95c..4eb5cb9 100644 --- a/test/constants.spec.js +++ b/test/constants.spec.js @@ -10,11 +10,11 @@ const constants = require('../src/constants.js') describe('constants', () => { it('constants indexed by name', () => { const names = constants.names - expect(Object.keys(names).length).to.equal(16) + expect(Object.keys(names).length).to.equal(15) }) it('constants indexed by code', () => { const codes = constants.codes - expect(Object.keys(codes).length).to.equal(16) + expect(Object.keys(codes).length).to.equal(15) }) }) diff --git a/test/multibase.spec.js b/test/multibase.spec.js index bbd3ef0..5aac4b2 100644 --- a/test/multibase.spec.js +++ b/test/multibase.spec.js @@ -10,6 +10,11 @@ const constants = require('../src/constants.js') const unsupportedBases = [] +/** @typedef {import("./../src/types").BaseNames} BaseNames */ + +/** + * @type {Array<[BaseNames, string|Buffer, string]>} + */ const supportedBases = [ ['base2', 'yes mani !', '01111001011001010111001100100000011011010110000101101110011010010010000000100001'], ['base8', 'yes mani !', '7171312714403326055632220041'], @@ -97,25 +102,28 @@ describe('multibase', () => { it('fails on no buf', () => { expect(() => { + // @ts-ignore multibase('base16') }).to.throw(Error) }) it('fails on non supported name', () => { expect(() => { + // @ts-ignore multibase('base1001', Buffer.from('meh')) }).to.throw(Error) }) it('fails on non supported code', () => { expect(() => { + // @ts-ignore multibase('6', Buffer.from('meh')) }).to.throw(Error) }) }) for (const elements of supportedBases) { - const name = elements[0] + const name = /** @type {string} */(elements[0]) const input = elements[1] const output = elements[2] const base = constants.names[name] @@ -254,7 +262,7 @@ describe('multibase.isEncoded', () => { Symbol('test') ] - invalidInputs.forEach(input => { + invalidInputs.forEach((/** @type {string} */input) => { expect(() => multibase.isEncoded(input)).to.not.throw() expect(multibase.isEncoded(input)).to.be.false() }) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5cda99f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./node_modules/aegir/src/config/aegir-tsconfig.json" +} \ No newline at end of file