a collection of lightweight TypeScript packages for AT Protocol, the protocol powering Bluesky
atproto bluesky typescript npm

feat(multibase): initial commit

mary.my.id 37a48c7f 51b3bb8e

verified
+11
packages/utilities/multibase/README.md
··· 1 + # @atcute/multibase 2 + 3 + multibase utilities, only supports a limited set of codecs. 4 + 5 + ```ts 6 + import { toBase32 } from '@atcute/multibase'; 7 + 8 + const utf8 = new TextEncoder(); 9 + const base32 = toBase32(utf8.encode('lorem ipsum')); 10 + // ^? "nrxxezlnebuxa43vnu" 11 + ```
+22
packages/utilities/multibase/lib/bases/base16.ts
··· 1 + import { createRfc4648Decode, createRfc4648Encode } from '../utils.js'; 2 + 3 + const HAS_UINT8_BASE16_SUPPORT = 'fromHex' in Uint8Array; 4 + 5 + const BASE16_CHARSET = '0123456789abcdef'; 6 + const UPPER_RE = /[A-F]/; 7 + 8 + export const fromBase16 = !HAS_UINT8_BASE16_SUPPORT 9 + ? /*#__PURE__*/ createRfc4648Decode(BASE16_CHARSET, 4, false) 10 + : (str: string): Uint8Array => { 11 + if (UPPER_RE.test(str)) { 12 + throw new SyntaxError(`unexpected uppercase characters in base16 string`); 13 + } 14 + 15 + return Uint8Array.fromHex(str); 16 + }; 17 + 18 + export const toBase16 = !HAS_UINT8_BASE16_SUPPORT 19 + ? /*#__PURE__*/ createRfc4648Encode(BASE16_CHARSET, 4, false) 20 + : (bytes: Uint8Array): string => { 21 + return bytes.toHex(); 22 + };
+7
packages/utilities/multibase/lib/bases/base32.ts
··· 1 + import { createRfc4648Decode, createRfc4648Encode } from '../utils.js'; 2 + 3 + const BASE32_CHARSET = 'abcdefghijklmnopqrstuvwxyz234567'; 4 + 5 + export const fromBase32 = /*#__PURE__*/ createRfc4648Decode(BASE32_CHARSET, 5, false); 6 + 7 + export const toBase32 = /*#__PURE__*/ createRfc4648Encode(BASE32_CHARSET, 5, false);
+7
packages/utilities/multibase/lib/bases/base58.ts
··· 1 + import { createRfc4648Decode, createRfc4648Encode } from '../utils.js'; 2 + 3 + const BASE58BTC_CHARSET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; 4 + 5 + export const fromBase58Btc = /*#__PURE__*/ createRfc4648Decode(BASE58BTC_CHARSET, 5, false); 6 + 7 + export const toBase58Btc = /*#__PURE__*/ createRfc4648Encode(BASE58BTC_CHARSET, 5, false);
+62
packages/utilities/multibase/lib/bases/base64.ts
··· 1 + import { createRfc4648Decode, createRfc4648Encode } from '../utils.js'; 2 + 3 + const HAS_UINT8_BASE64_SUPPORT = 'fromBase64' in Uint8Array; 4 + 5 + const BASE64_CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; 6 + const BASE64URL_CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; 7 + 8 + export const fromBase64 = !HAS_UINT8_BASE64_SUPPORT 9 + ? /*#__PURE__*/ createRfc4648Decode(BASE64_CHARSET, 6, false) 10 + : (str: string): Uint8Array => { 11 + if (str[str.length - 1] === '=') { 12 + throw new SyntaxError(`unexpected padding in base64 string`); 13 + } 14 + 15 + return Uint8Array.fromBase64(str, { alphabet: 'base64', lastChunkHandling: 'loose' }); 16 + }; 17 + 18 + export const toBase64 = !HAS_UINT8_BASE64_SUPPORT 19 + ? /*#__PURE__*/ createRfc4648Encode(BASE64_CHARSET, 6, false) 20 + : (bytes: Uint8Array): string => { 21 + return bytes.toBase64({ alphabet: 'base64', omitPadding: true }); 22 + }; 23 + 24 + export const fromBase64Pad = !HAS_UINT8_BASE64_SUPPORT 25 + ? /*#__PURE__*/ createRfc4648Decode(BASE64_CHARSET, 6, true) 26 + : (str: string): Uint8Array => { 27 + return Uint8Array.fromBase64(str, { alphabet: 'base64', lastChunkHandling: 'strict' }); 28 + }; 29 + 30 + export const toBase64Pad = !HAS_UINT8_BASE64_SUPPORT 31 + ? /*#__PURE__*/ createRfc4648Encode(BASE64_CHARSET, 6, true) 32 + : (bytes: Uint8Array): string => { 33 + return bytes.toBase64({ alphabet: 'base64', omitPadding: false }); 34 + }; 35 + 36 + export const fromBase64Url = !HAS_UINT8_BASE64_SUPPORT 37 + ? /*#__PURE__*/ createRfc4648Decode(BASE64URL_CHARSET, 6, false) 38 + : (str: string): Uint8Array => { 39 + if (str[str.length - 1] === '=') { 40 + throw new SyntaxError(`unexpected padding in base64 string`); 41 + } 42 + 43 + return Uint8Array.fromBase64(str, { alphabet: 'base64url', lastChunkHandling: 'loose' }); 44 + }; 45 + 46 + export const toBase64Url = !HAS_UINT8_BASE64_SUPPORT 47 + ? /*#__PURE__*/ createRfc4648Encode(BASE64URL_CHARSET, 6, false) 48 + : (bytes: Uint8Array): string => { 49 + return bytes.toBase64({ alphabet: 'base64url', omitPadding: true }); 50 + }; 51 + 52 + export const fromBase64UrlPad = !HAS_UINT8_BASE64_SUPPORT 53 + ? /*#__PURE__*/ createRfc4648Decode(BASE64URL_CHARSET, 6, true) 54 + : (str: string): Uint8Array => { 55 + return Uint8Array.fromBase64(str, { alphabet: 'base64url', lastChunkHandling: 'strict' }); 56 + }; 57 + 58 + export const toBase64UrlPad = !HAS_UINT8_BASE64_SUPPORT 59 + ? /*#__PURE__*/ createRfc4648Encode(BASE64URL_CHARSET, 6, true) 60 + : (bytes: Uint8Array): string => { 61 + return bytes.toBase64({ alphabet: 'base64url', omitPadding: false }); 62 + };
+17
packages/utilities/multibase/lib/env.ts
··· 1 + declare global { 2 + interface Uint8Array { 3 + toHex(): string; 4 + toBase64(options?: { alphabet?: 'base64' | 'base64url'; omitPadding?: boolean }): string; 5 + } 6 + 7 + interface Uint8ArrayConstructor { 8 + fromHex(base16: string): Uint8Array; 9 + fromBase64( 10 + base64: string, 11 + options?: { 12 + alphabet?: 'base64' | 'base64url'; 13 + lastChunkHandling?: 'loose' | 'strict' | 'stop-before-partial'; 14 + }, 15 + ): Uint8Array; 16 + } 17 + }
+4
packages/utilities/multibase/lib/index.ts
··· 1 + export * from './bases/base16.js'; 2 + export * from './bases/base32.js'; 3 + export * from './bases/base58.js'; 4 + export * from './bases/base64.js';
+82
packages/utilities/multibase/lib/utils.ts
··· 1 + export const createRfc4648Encode = (alphabet: string, bitsPerChar: number, pad: boolean) => { 2 + return (bytes: Uint8Array): string => { 3 + const mask = (1 << bitsPerChar) - 1; 4 + let str = ''; 5 + 6 + let bits = 0; // Number of bits currently in the buffer 7 + let buffer = 0; // Bits waiting to be written out, MSB first 8 + for (let i = 0; i < bytes.length; ++i) { 9 + // Slurp data into the buffer: 10 + buffer = (buffer << 8) | bytes[i]; 11 + bits += 8; 12 + 13 + // Write out as much as we can: 14 + while (bits > bitsPerChar) { 15 + bits -= bitsPerChar; 16 + str += alphabet[mask & (buffer >> bits)]; 17 + } 18 + } 19 + 20 + // Partial character: 21 + if (bits !== 0) { 22 + str += alphabet[mask & (buffer << (bitsPerChar - bits))]; 23 + } 24 + 25 + // Add padding characters until we hit a byte boundary: 26 + if (pad) { 27 + while (((str.length * bitsPerChar) & 7) !== 0) { 28 + str += '='; 29 + } 30 + } 31 + 32 + return str; 33 + }; 34 + }; 35 + 36 + export const createRfc4648Decode = (alphabet: string, bitsPerChar: number, pad: boolean) => { 37 + // Build the character lookup table: 38 + const codes: Record<string, number> = {}; 39 + for (let i = 0; i < alphabet.length; ++i) { 40 + codes[alphabet[i]] = i; 41 + } 42 + 43 + return (str: string) => { 44 + // Count the padding bytes: 45 + let end = str.length; 46 + while (pad && str[end - 1] === '=') { 47 + --end; 48 + } 49 + 50 + // Allocate the output: 51 + const bytes = new Uint8Array(((end * bitsPerChar) / 8) | 0); 52 + 53 + // Parse the data: 54 + let bits = 0; // Number of bits currently in the buffer 55 + let buffer = 0; // Bits waiting to be written out, MSB first 56 + let written = 0; // Next byte to write 57 + for (let i = 0; i < end; ++i) { 58 + // Read one character from the string: 59 + const value = codes[str[i]]; 60 + if (value === undefined) { 61 + throw new SyntaxError(`Non-${name} character`); 62 + } 63 + 64 + // Append the bits to the buffer: 65 + buffer = (buffer << bitsPerChar) | value; 66 + bits += bitsPerChar; 67 + 68 + // Write out some bits if the buffer has a byte's worth: 69 + if (bits >= 8) { 70 + bits -= 8; 71 + bytes[written++] = 0xff & (buffer >> bits); 72 + } 73 + } 74 + 75 + // Verify that we have received just enough bits: 76 + if (bits >= bitsPerChar || (0xff & (buffer << (8 - bits))) !== 0) { 77 + throw new SyntaxError('Unexpected end of data'); 78 + } 79 + 80 + return bytes; 81 + }; 82 + };
+29
packages/utilities/multibase/package.json
··· 1 + { 2 + "type": "module", 3 + "name": "@atcute/multibase", 4 + "version": "1.0.0", 5 + "description": "multibase utilities", 6 + "license": "MIT", 7 + "repository": { 8 + "url": "https://github.com/mary-ext/atcute", 9 + "directory": "packages/utilities/multibase" 10 + }, 11 + "files": [ 12 + "dist/", 13 + "lib/", 14 + "!lib/**/*.bench.ts", 15 + "!lib/**/*.test.ts" 16 + ], 17 + "exports": { 18 + ".": "./dist/index.js" 19 + }, 20 + "sideEffects": false, 21 + "scripts": { 22 + "build": "tsc --project tsconfig.build.json", 23 + "test": "bun test --coverage", 24 + "prepublish": "rm -rf dist; pnpm run build" 25 + }, 26 + "devDependencies": { 27 + "@types/bun": "^1.1.12" 28 + } 29 + }
+4
packages/utilities/multibase/tsconfig.build.json
··· 1 + { 2 + "extends": "./tsconfig.json", 3 + "exclude": ["**/*.test.ts"] 4 + }
+23
packages/utilities/multibase/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "types": ["bun"], 4 + "outDir": "dist/", 5 + "esModuleInterop": true, 6 + "skipLibCheck": true, 7 + "target": "ESNext", 8 + "allowJs": true, 9 + "resolveJsonModule": true, 10 + "moduleDetection": "force", 11 + "isolatedModules": true, 12 + "verbatimModuleSyntax": true, 13 + "strict": true, 14 + "noImplicitOverride": true, 15 + "noUnusedLocals": true, 16 + "noUnusedParameters": true, 17 + "noFallthroughCasesInSwitch": true, 18 + "module": "NodeNext", 19 + "sourceMap": true, 20 + "declaration": true, 21 + }, 22 + "include": ["lib"], 23 + }
+6
pnpm-lock.yaml
··· 240 240 specifier: ^1.1.12 241 241 version: 1.1.12 242 242 243 + packages/utilities/multibase: 244 + devDependencies: 245 + '@types/bun': 246 + specifier: ^1.1.12 247 + version: 1.1.12 248 + 243 249 packages/utilities/tid: 244 250 devDependencies: 245 251 '@types/bun':