UNPKG

@sindresorhus/base62

Version:

Encode & decode strings, bytes, and integers to Base62

142 lines (107 loc) 3.4 kB
/* eslint-disable no-bitwise */ const BASE = 62; const BASE_BIGINT = 62n; const ALPHABET = [...'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz']; const INDICES = new Map(ALPHABET.map((character, index) => [character, index])); const cachedEncoder = new globalThis.TextEncoder(); const cachedDecoder = new globalThis.TextDecoder(); function assertString(value, label) { if (typeof value !== 'string') { throw new TypeError(`The \`${label}\` parameter must be a string, got \`${value}\` (${typeof value}).`); } } function getIndex(character) { const index = INDICES.get(character); if (index === undefined) { throw new TypeError(`Unexpected character for Base62 encoding: \`${character}\`.`); } return index; } export function encodeString(string) { assertString(string, 'string'); return encodeBytes(cachedEncoder.encode(string)); } export function decodeString(encodedString) { assertString(encodedString, 'encodedString'); return cachedDecoder.decode(decodeBytes(encodedString)); } export function encodeBytes(bytes) { if (!(bytes instanceof Uint8Array)) { throw new TypeError('The `bytes` parameter must be an instance of Uint8Array.'); } if (bytes.length === 0) { return ''; } // Prepend 0x01 to the byte array before encoding to ensure the BigInt conversion // does not strip any leading zeros and to prevent any byte sequence from being // interpreted as a numerically zero value. let value = 1n; for (const byte of bytes) { value = (value << 8n) | BigInt(byte); } return encodeBigInt(value); } export function decodeBytes(encodedString) { assertString(encodedString, 'encodedString'); if (encodedString.length === 0) { return new Uint8Array(); } let value = decodeBigInt(encodedString); const byteArray = []; while (value > 0n) { byteArray.push(Number(value & 0xFFn)); value >>= 8n; } // Remove the 0x01 that was prepended during encoding. return Uint8Array.from(byteArray.reverse().slice(1)); } export function encodeInteger(integer) { if (!Number.isInteger(integer)) { throw new TypeError(`Expected an integer, got \`${integer}\` (${typeof integer}).`); } if (integer < 0) { throw new TypeError('The integer must be non-negative.'); } if (integer === 0) { return ALPHABET[0]; } let encodedString = ''; while (integer > 0) { encodedString = ALPHABET[integer % BASE] + encodedString; integer = Math.floor(integer / BASE); } return encodedString; } export function decodeInteger(encodedString) { assertString(encodedString, 'encodedString'); let integer = 0; for (const character of encodedString) { integer = (integer * BASE) + getIndex(character); } return integer; } export function encodeBigInt(bigint) { if (typeof bigint !== 'bigint') { throw new TypeError(`Expected a bigint, got \`${bigint}\` (${typeof bigint}).`); } if (bigint < 0) { throw new TypeError('The bigint must be non-negative.'); } if (bigint === 0n) { return ALPHABET[0]; } let encodedString = ''; while (bigint > 0n) { encodedString = ALPHABET[Number(bigint % BASE_BIGINT)] + encodedString; bigint /= BigInt(BASE); } return encodedString; } export function decodeBigInt(encodedString) { assertString(encodedString, 'encodedString'); let bigint = 0n; for (const character of encodedString) { bigint = (bigint * BASE_BIGINT) + BigInt(getIndex(character)); } return bigint; }