bufferbase 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +151 -63
- package/dist/cjs/bases.d.ts +152 -45
- package/dist/cjs/bases.d.ts.map +1 -1
- package/dist/cjs/bases.js +90 -77
- package/dist/cjs/bases.js.map +1 -1
- package/dist/cjs/bufferbase.d.ts +21 -27
- package/dist/cjs/bufferbase.d.ts.map +1 -1
- package/dist/cjs/bufferbase.js +21 -31
- package/dist/cjs/bufferbase.js.map +1 -1
- package/dist/cjs/codec-base.d.ts +56 -0
- package/dist/cjs/codec-base.d.ts.map +1 -0
- package/dist/cjs/codec-base.js +132 -0
- package/dist/cjs/codec-base.js.map +1 -0
- package/dist/cjs/codec-block.d.ts +47 -0
- package/dist/cjs/codec-block.d.ts.map +1 -0
- package/dist/cjs/codec-block.js +213 -0
- package/dist/cjs/codec-block.js.map +1 -0
- package/dist/cjs/codec-block85.d.ts +56 -0
- package/dist/cjs/codec-block85.d.ts.map +1 -0
- package/dist/cjs/codec-block85.js +271 -0
- package/dist/cjs/codec-block85.js.map +1 -0
- package/dist/cjs/codec-radix.d.ts +46 -0
- package/dist/cjs/codec-radix.d.ts.map +1 -0
- package/dist/cjs/codec-radix.js +181 -0
- package/dist/cjs/codec-radix.js.map +1 -0
- package/dist/cjs/codec.d.ts +25 -111
- package/dist/cjs/codec.d.ts.map +1 -1
- package/dist/cjs/codec.js +50 -228
- package/dist/cjs/codec.js.map +1 -1
- package/dist/cjs/codecs.d.ts +8 -59
- package/dist/cjs/codecs.d.ts.map +1 -1
- package/dist/cjs/codecs.js +6 -79
- package/dist/cjs/codecs.js.map +1 -1
- package/dist/cjs/errors.d.ts +65 -48
- package/dist/cjs/errors.d.ts.map +1 -1
- package/dist/cjs/errors.js +89 -51
- package/dist/cjs/errors.js.map +1 -1
- package/dist/cjs/example.js +44 -21
- package/dist/cjs/example.js.map +1 -1
- package/dist/cjs/functions.d.ts +27 -31
- package/dist/cjs/functions.d.ts.map +1 -1
- package/dist/cjs/functions.js +25 -47
- package/dist/cjs/functions.js.map +1 -1
- package/dist/cjs/index.d.ts +7 -5
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +17 -7
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/package.json +13 -7
- package/dist/cjs/types.d.ts +108 -71
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/esm/bases.d.ts +152 -45
- package/dist/esm/bases.d.ts.map +1 -1
- package/dist/esm/bases.js +88 -75
- package/dist/esm/bases.js.map +1 -1
- package/dist/esm/bufferbase.d.ts +21 -27
- package/dist/esm/bufferbase.d.ts.map +1 -1
- package/dist/esm/bufferbase.js +18 -30
- package/dist/esm/bufferbase.js.map +1 -1
- package/dist/esm/codec-base.d.ts +56 -0
- package/dist/esm/codec-base.d.ts.map +1 -0
- package/dist/esm/codec-base.js +127 -0
- package/dist/esm/codec-base.js.map +1 -0
- package/dist/esm/codec-block.d.ts +47 -0
- package/dist/esm/codec-block.d.ts.map +1 -0
- package/dist/esm/codec-block.js +209 -0
- package/dist/esm/codec-block.js.map +1 -0
- package/dist/esm/codec-block85.d.ts +56 -0
- package/dist/esm/codec-block85.d.ts.map +1 -0
- package/dist/esm/codec-block85.js +267 -0
- package/dist/esm/codec-block85.js.map +1 -0
- package/dist/esm/codec-radix.d.ts +46 -0
- package/dist/esm/codec-radix.d.ts.map +1 -0
- package/dist/esm/codec-radix.js +177 -0
- package/dist/esm/codec-radix.js.map +1 -0
- package/dist/esm/codec.d.ts +25 -111
- package/dist/esm/codec.d.ts.map +1 -1
- package/dist/esm/codec.js +44 -226
- package/dist/esm/codec.js.map +1 -1
- package/dist/esm/codecs.d.ts +8 -59
- package/dist/esm/codecs.d.ts.map +1 -1
- package/dist/esm/codecs.js +7 -80
- package/dist/esm/codecs.js.map +1 -1
- package/dist/esm/errors.d.ts +65 -48
- package/dist/esm/errors.d.ts.map +1 -1
- package/dist/esm/errors.js +83 -50
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/example.js +45 -22
- package/dist/esm/example.js.map +1 -1
- package/dist/esm/functions.d.ts +27 -31
- package/dist/esm/functions.d.ts.map +1 -1
- package/dist/esm/functions.js +26 -48
- package/dist/esm/functions.js.map +1 -1
- package/dist/esm/index.d.ts +7 -5
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +6 -6
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/package.json +13 -7
- package/dist/esm/types.d.ts +108 -71
- package/dist/esm/types.d.ts.map +1 -1
- package/package.json +13 -7
- package/src/algorithms.test.ts +442 -0
- package/src/bases.ts +98 -76
- package/src/bufferbase.ts +26 -36
- package/src/codec-base.ts +181 -0
- package/src/codec-block.ts +258 -0
- package/src/codec-block85.ts +315 -0
- package/src/codec-radix.ts +206 -0
- package/src/codec.test.ts +17 -9
- package/src/codec.ts +48 -247
- package/src/codecs.ts +11 -105
- package/src/errors.ts +90 -50
- package/src/example.ts +47 -22
- package/src/functions.ts +34 -54
- package/src/index.ts +27 -7
- package/src/types.ts +113 -86
package/src/codec.ts
CHANGED
|
@@ -1,266 +1,67 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
1
|
+
import type { CodecSpec, ICodec } from './types.js';
|
|
2
|
+
import { RadixCodec } from './codec-radix.js';
|
|
3
|
+
import { BlockCodec } from './codec-block.js';
|
|
4
|
+
import { Block85Codec } from './codec-block85.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
-
* @internal
|
|
8
|
-
*/
|
|
9
|
-
function toBuffer(input: BufferLike): Buffer {
|
|
10
|
-
if (Buffer.isBuffer(input)) {
|
|
11
|
-
return input;
|
|
12
|
-
}
|
|
13
|
-
return Buffer.from(input);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Codec for encoding and decoding buffers to/from a specific base.
|
|
7
|
+
* Builds the codec described by a spec.
|
|
18
8
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
9
|
+
* A bare string is read as a custom alphabet for a radix conversion, which is
|
|
10
|
+
* the only algorithm that works with an alphabet of any length.
|
|
21
11
|
*
|
|
22
12
|
* @example
|
|
23
13
|
* ```typescript
|
|
24
|
-
* import {
|
|
25
|
-
*
|
|
26
|
-
* // Use pre-defined codec
|
|
27
|
-
* const encoded = Codecs.base58.encode(Buffer.from('Hello'));
|
|
14
|
+
* import { createCodec } from 'bufferbase';
|
|
28
15
|
*
|
|
29
|
-
* // Create custom codec
|
|
30
16
|
* const binary = createCodec('01');
|
|
31
|
-
*
|
|
17
|
+
* binary.encode(Uint8Array.of(5)); // '101'
|
|
18
|
+
*
|
|
19
|
+
* const paddedHex = createCodec({ alphabet: '0123456789abcdef', algorithm: 'block' });
|
|
20
|
+
* paddedHex.encode(Uint8Array.of(0, 1)); // '0001'
|
|
32
21
|
* ```
|
|
33
22
|
*/
|
|
34
|
-
export
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
constructor(chars: string) {
|
|
46
|
-
this.chars = chars;
|
|
47
|
-
this.charToIndex = new Map();
|
|
48
|
-
for (let i = 0; i < chars.length; i++) {
|
|
49
|
-
this.charToIndex.set(chars[i], i);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Encodes a buffer to a base-encoded string.
|
|
55
|
-
*
|
|
56
|
-
* Leading zero bytes in the buffer are preserved as the first character
|
|
57
|
-
* of the encoding alphabet (e.g., '1' for Base58).
|
|
58
|
-
*
|
|
59
|
-
* @param input - The buffer or Uint8Array to encode
|
|
60
|
-
* @returns Base-encoded string
|
|
61
|
-
*
|
|
62
|
-
* @example
|
|
63
|
-
* ```typescript
|
|
64
|
-
* const codec = Codecs.base58;
|
|
65
|
-
* codec.encode(Buffer.from('Hello')); // '9Ajdvzr'
|
|
66
|
-
* codec.encode(new Uint8Array([0, 0, 1])); // '112' (preserves leading zeros)
|
|
67
|
-
* ```
|
|
68
|
-
*/
|
|
69
|
-
encode(input: BufferLike): string {
|
|
70
|
-
const buffer = toBuffer(input);
|
|
71
|
-
if (buffer.length === 0) {
|
|
72
|
-
return '';
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const base = this.chars.length;
|
|
76
|
-
const result: number[] = [];
|
|
77
|
-
|
|
78
|
-
// Count leading zeros
|
|
79
|
-
let leadingZeros = 0;
|
|
80
|
-
for (const byte of buffer) {
|
|
81
|
-
if (byte === 0) {
|
|
82
|
-
leadingZeros++;
|
|
83
|
-
} else {
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Convert to base
|
|
89
|
-
for (const byte of buffer) {
|
|
90
|
-
let carry = byte;
|
|
91
|
-
for (let j = 0; j < result.length; j++) {
|
|
92
|
-
carry += result[j] * 256;
|
|
93
|
-
result[j] = carry % base;
|
|
94
|
-
carry = Math.floor(carry / base);
|
|
95
|
-
}
|
|
96
|
-
while (carry > 0) {
|
|
97
|
-
result.push(carry % base);
|
|
98
|
-
carry = Math.floor(carry / base);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Add leading zeros
|
|
103
|
-
for (let i = 0; i < leadingZeros; i++) {
|
|
104
|
-
result.push(0);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Reverse and convert to string
|
|
108
|
-
return result
|
|
109
|
-
.reverse()
|
|
110
|
-
.map((index) => this.chars[index])
|
|
111
|
-
.join('');
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Decodes a base-encoded string to a buffer.
|
|
116
|
-
*
|
|
117
|
-
* Leading characters that represent zero (first char of alphabet)
|
|
118
|
-
* are converted to leading zero bytes in the result.
|
|
119
|
-
*
|
|
120
|
-
* @param encoded - The string to decode
|
|
121
|
-
* @param options - Decode options
|
|
122
|
-
* @returns Decoded buffer
|
|
123
|
-
* @throws {InvalidCharacterError} If the string contains invalid characters
|
|
124
|
-
* @throws {BufferSizeError} If the result exceeds the specified size
|
|
125
|
-
*
|
|
126
|
-
* @example
|
|
127
|
-
* ```typescript
|
|
128
|
-
* const codec = Codecs.base58;
|
|
129
|
-
* codec.decode('9Ajdvzr'); // Buffer('Hello')
|
|
130
|
-
* codec.decode('9Ajdvzr', { size: 32 }); // Pads to 32 bytes
|
|
131
|
-
* ```
|
|
132
|
-
*/
|
|
133
|
-
decode(encoded: string, options?: DecodeOptions): Buffer {
|
|
134
|
-
if (encoded.length === 0) {
|
|
135
|
-
if (options?.size !== undefined && options.size > 0) {
|
|
136
|
-
return Buffer.alloc(options.size);
|
|
137
|
-
}
|
|
138
|
-
return Buffer.alloc(0);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const base = this.chars.length;
|
|
142
|
-
const zeroChar = this.chars[0];
|
|
143
|
-
|
|
144
|
-
// Count leading zeros in encoded string
|
|
145
|
-
let leadingZeros = 0;
|
|
146
|
-
for (const char of encoded) {
|
|
147
|
-
if (char === zeroChar) {
|
|
148
|
-
leadingZeros++;
|
|
149
|
-
} else {
|
|
150
|
-
break;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Convert from base
|
|
155
|
-
const result: number[] = [];
|
|
156
|
-
for (const char of encoded) {
|
|
157
|
-
const value = this.charToIndex.get(char);
|
|
158
|
-
if (value === undefined) {
|
|
159
|
-
throw new InvalidCharacterError(char);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
let carry = value;
|
|
163
|
-
for (let i = 0; i < result.length; i++) {
|
|
164
|
-
carry += result[i] * base;
|
|
165
|
-
result[i] = carry % 256;
|
|
166
|
-
carry = Math.floor(carry / 256);
|
|
167
|
-
}
|
|
168
|
-
while (carry > 0) {
|
|
169
|
-
result.push(carry % 256);
|
|
170
|
-
carry = Math.floor(carry / 256);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Add leading zeros
|
|
175
|
-
for (let i = 0; i < leadingZeros; i++) {
|
|
176
|
-
result.push(0);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
result.reverse();
|
|
180
|
-
|
|
181
|
-
// Handle size option
|
|
182
|
-
const size = options?.size;
|
|
183
|
-
if (size !== undefined) {
|
|
184
|
-
if (result.length > size) {
|
|
185
|
-
throw new BufferSizeError(result.length, size);
|
|
186
|
-
}
|
|
187
|
-
if (result.length < size) {
|
|
188
|
-
// Pad with leading zeros
|
|
189
|
-
const padded = new Array(size - result.length).fill(0).concat(result);
|
|
190
|
-
return Buffer.from(padded);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return Buffer.from(result);
|
|
23
|
+
export function createCodec(spec: CodecSpec | string): ICodec {
|
|
24
|
+
const resolved: CodecSpec =
|
|
25
|
+
typeof spec === 'string' ? { alphabet: spec, algorithm: 'radix' } : spec;
|
|
26
|
+
|
|
27
|
+
switch (resolved.algorithm) {
|
|
28
|
+
case 'radix':
|
|
29
|
+
return new RadixCodec(resolved);
|
|
30
|
+
case 'block':
|
|
31
|
+
return new BlockCodec(resolved);
|
|
32
|
+
case 'block85':
|
|
33
|
+
return new Block85Codec(resolved);
|
|
195
34
|
}
|
|
35
|
+
}
|
|
196
36
|
|
|
197
|
-
|
|
198
|
-
* Validates if a string contains only valid characters for this base.
|
|
199
|
-
*
|
|
200
|
-
* This is a fast check that doesn't perform full decoding.
|
|
201
|
-
*
|
|
202
|
-
* @param input - The string to validate
|
|
203
|
-
* @returns `true` if all characters are valid, `false` otherwise
|
|
204
|
-
*
|
|
205
|
-
* @example
|
|
206
|
-
* ```typescript
|
|
207
|
-
* Codecs.base58.validate('9Ajdvzr'); // true
|
|
208
|
-
* Codecs.base58.validate('0invalid'); // false ('0' not in Base58)
|
|
209
|
-
* ```
|
|
210
|
-
*/
|
|
211
|
-
validate(input: string): boolean {
|
|
212
|
-
for (const char of input) {
|
|
213
|
-
if (!this.charToIndex.has(char)) {
|
|
214
|
-
return false;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return true;
|
|
218
|
-
}
|
|
37
|
+
const cache = new WeakMap<CodecSpec, ICodec>();
|
|
219
38
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
* @example
|
|
231
|
-
* ```typescript
|
|
232
|
-
* const base58str = '9Ajdvzr';
|
|
233
|
-
* const base64str = Codecs.base58.convertTo(Codecs.base64url, base58str);
|
|
234
|
-
* ```
|
|
235
|
-
*/
|
|
236
|
-
convertTo(target: ICodec, input: string): string {
|
|
237
|
-
const buffer = this.decode(input);
|
|
238
|
-
return target.encode(buffer);
|
|
39
|
+
/**
|
|
40
|
+
* Returns the codec for a spec, building it once per spec object.
|
|
41
|
+
*
|
|
42
|
+
* Named bases always resolve to the same spec object, so they are built once
|
|
43
|
+
* for the lifetime of the process.
|
|
44
|
+
*/
|
|
45
|
+
export function getCodec(spec: CodecSpec): ICodec {
|
|
46
|
+
const cached = cache.get(spec);
|
|
47
|
+
if (cached !== undefined) {
|
|
48
|
+
return cached;
|
|
239
49
|
}
|
|
50
|
+
const codec = createCodec(spec);
|
|
51
|
+
cache.set(spec, codec);
|
|
52
|
+
return codec;
|
|
240
53
|
}
|
|
241
54
|
|
|
55
|
+
export { RadixCodec } from './codec-radix.js';
|
|
56
|
+
export { BlockCodec } from './codec-block.js';
|
|
57
|
+
export { Block85Codec } from './codec-block85.js';
|
|
58
|
+
|
|
242
59
|
/**
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
* Use this to create codecs for custom base encodings.
|
|
246
|
-
* The base is determined by the length of the character set.
|
|
247
|
-
*
|
|
248
|
-
* @param chars - The character set to use for encoding/decoding
|
|
249
|
-
* @returns A new Codec instance
|
|
250
|
-
*
|
|
251
|
-
* @example
|
|
252
|
-
* ```typescript
|
|
253
|
-
* import { createCodec } from 'bufferbase';
|
|
254
|
-
*
|
|
255
|
-
* // Binary codec (base 2)
|
|
256
|
-
* const binary = createCodec('01');
|
|
257
|
-
* binary.encode(Buffer.from([5])); // '101'
|
|
258
|
-
*
|
|
259
|
-
* // Octal codec (base 8)
|
|
260
|
-
* const octal = createCodec('01234567');
|
|
261
|
-
* octal.encode(Buffer.from([255])); // '377'
|
|
262
|
-
* ```
|
|
60
|
+
* @deprecated Use `createCodec({ alphabet, algorithm: 'radix' })`, or
|
|
61
|
+
* `createCodec(alphabet)` for the same thing.
|
|
263
62
|
*/
|
|
264
|
-
export
|
|
265
|
-
|
|
63
|
+
export class Codec extends RadixCodec {
|
|
64
|
+
constructor(chars: string) {
|
|
65
|
+
super({ alphabet: chars, algorithm: 'radix' });
|
|
66
|
+
}
|
|
266
67
|
}
|
package/src/codecs.ts
CHANGED
|
@@ -1,118 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
/** Decimal (base 10) codec: 0-9 */
|
|
5
|
-
const decimal = new Codec(Chars.Decimal);
|
|
6
|
-
|
|
7
|
-
/** Hexadecimal (base 16) codec: 0-9A-F */
|
|
8
|
-
const base16 = new Codec(Chars.Base16);
|
|
9
|
-
|
|
10
|
-
/** Alias for base16 */
|
|
11
|
-
const hex = base16;
|
|
12
|
-
|
|
13
|
-
/** RFC 4648 Base32 codec: A-Z2-7 */
|
|
14
|
-
const base32 = new Codec(Chars.Base32);
|
|
15
|
-
|
|
16
|
-
/** Crockford's Base32 codec: 0-9A-HJKMNP-TV-Z (no I, L, O, U) */
|
|
17
|
-
const base32crockford = new Codec(Chars.Base32Crockford);
|
|
18
|
-
|
|
19
|
-
/** Base36 codec: 0-9A-Z */
|
|
20
|
-
const base36 = new Codec(Chars.Base36);
|
|
21
|
-
|
|
22
|
-
/** Base52 codec: A-Za-z (letters only) */
|
|
23
|
-
const base52 = new Codec(Chars.Base52);
|
|
24
|
-
|
|
25
|
-
/** Bitcoin Base58 codec: 1-9A-HJ-NP-Za-km-z (no 0, O, I, l) */
|
|
26
|
-
const base58 = new Codec(Chars.Base58);
|
|
27
|
-
|
|
28
|
-
/** Standard Base64 codec: A-Za-z0-9+/ */
|
|
29
|
-
const base64 = new Codec(Chars.Base64);
|
|
30
|
-
|
|
31
|
-
/** URL-safe Base64 codec: A-Za-z0-9-_ */
|
|
32
|
-
const base64url = new Codec(Chars.Base64Url);
|
|
33
|
-
|
|
34
|
-
/** XML token Base64 codec: A-Za-z0-9._ */
|
|
35
|
-
const base64xml = new Codec(Chars.Base64Xml);
|
|
36
|
-
|
|
37
|
-
/** XML name Base64 codec: A-Za-z0-9_: */
|
|
38
|
-
const base64xmlname = new Codec(Chars.Base64XmlName);
|
|
39
|
-
|
|
40
|
-
/** ASCII85 codec */
|
|
41
|
-
const ascii85 = new Codec(Chars.Ascii85);
|
|
42
|
-
|
|
43
|
-
/** Base85 codec */
|
|
44
|
-
const base85 = new Codec(Chars.Base85);
|
|
45
|
-
|
|
46
|
-
/** ZeroMQ Z85 codec */
|
|
47
|
-
const z85 = new Codec(Chars.Z85);
|
|
1
|
+
import type { ICodec } from './types.js';
|
|
2
|
+
import { Bases, type BaseName } from './bases.js';
|
|
3
|
+
import { getCodec } from './codec.js';
|
|
48
4
|
|
|
49
5
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* Use these for encoding/decoding without creating codec instances manually.
|
|
53
|
-
* Each codec provides `encode()`, `decode()`, `validate()`, and `convertTo()` methods.
|
|
6
|
+
* A ready-made codec for every name in `Bases`.
|
|
54
7
|
*
|
|
55
8
|
* @example
|
|
56
9
|
* ```typescript
|
|
57
10
|
* import { Codecs } from 'bufferbase';
|
|
58
11
|
*
|
|
59
|
-
* //
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* // Decode from Base58
|
|
63
|
-
* const decoded = Codecs.base58.decode(encoded);
|
|
64
|
-
*
|
|
65
|
-
* // Convert between bases
|
|
66
|
-
* const base64 = Codecs.base58.convertTo(Codecs.base64url, encoded);
|
|
67
|
-
*
|
|
68
|
-
* // Validate
|
|
69
|
-
* Codecs.base58.validate(encoded); // true
|
|
12
|
+
* Codecs.base58.encode(Buffer.from('Hello')); // '9Ajdvzr'
|
|
13
|
+
* Codecs.base64.encode(Buffer.from('fo')); // 'Zm8='
|
|
14
|
+
* Codecs.base58.convertTo(Codecs.base64url, '9Ajdvzr');
|
|
70
15
|
* ```
|
|
71
|
-
*
|
|
72
|
-
* Available codecs:
|
|
73
|
-
* - `decimal` - Base 10 (0-9)
|
|
74
|
-
* - `base16` / `hex` - Hexadecimal (0-9A-F)
|
|
75
|
-
* - `base32` - RFC 4648 Base32 (A-Z2-7)
|
|
76
|
-
* - `base32crockford` - Crockford's Base32
|
|
77
|
-
* - `base36` - Alphanumeric (0-9A-Z)
|
|
78
|
-
* - `base52` - Letters only (A-Za-z)
|
|
79
|
-
* - `base58` - Bitcoin alphabet
|
|
80
|
-
* - `base64` - Standard Base64
|
|
81
|
-
* - `base64url` - URL-safe Base64
|
|
82
|
-
* - `base64xml` - XML token Base64
|
|
83
|
-
* - `base64xmlname` - XML name Base64
|
|
84
|
-
* - `ascii85` - ASCII85 encoding
|
|
85
|
-
* - `base85` - Base85 encoding
|
|
86
|
-
* - `z85` - ZeroMQ Z85 encoding
|
|
87
16
|
*/
|
|
88
|
-
export const Codecs =
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
hex,
|
|
92
|
-
base32,
|
|
93
|
-
base32crockford,
|
|
94
|
-
base36,
|
|
95
|
-
base52,
|
|
96
|
-
base58,
|
|
97
|
-
base64,
|
|
98
|
-
base64url,
|
|
99
|
-
base64xml,
|
|
100
|
-
base64xmlname,
|
|
101
|
-
ascii85,
|
|
102
|
-
base85,
|
|
103
|
-
z85,
|
|
104
|
-
} as const;
|
|
17
|
+
export const Codecs = Object.fromEntries(
|
|
18
|
+
Object.entries(Bases).map(([name, spec]) => [name, getCodec(spec)]),
|
|
19
|
+
) as Record<BaseName, ICodec>;
|
|
105
20
|
|
|
106
21
|
/**
|
|
107
22
|
* Type representing valid codec names in the `Codecs` object.
|
|
108
|
-
*
|
|
109
|
-
* @example
|
|
110
|
-
* ```typescript
|
|
111
|
-
* import { Codecs, CodecName } from 'bufferbase';
|
|
112
|
-
*
|
|
113
|
-
* function getCodec(name: CodecName) {
|
|
114
|
-
* return Codecs[name];
|
|
115
|
-
* }
|
|
116
|
-
* ```
|
|
117
23
|
*/
|
|
118
|
-
export type CodecName =
|
|
24
|
+
export type CodecName = BaseName;
|
package/src/errors.ts
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Error thrown when an invalid character is encountered during decoding.
|
|
3
|
-
*
|
|
4
|
-
* @example
|
|
5
|
-
* ```typescript
|
|
6
|
-
* import { decode, InvalidCharacterError } from 'bufferbase';
|
|
7
|
-
*
|
|
8
|
-
* try {
|
|
9
|
-
* decode('invalid!', 'base58');
|
|
10
|
-
* } catch (e) {
|
|
11
|
-
* if (e instanceof InvalidCharacterError) {
|
|
12
|
-
* console.log(e.message); // "Invalid character found: '!'"
|
|
13
|
-
* }
|
|
14
|
-
* }
|
|
15
|
-
* ```
|
|
16
3
|
*/
|
|
17
4
|
export class InvalidCharacterError extends Error {
|
|
18
5
|
/**
|
|
@@ -26,24 +13,8 @@ export class InvalidCharacterError extends Error {
|
|
|
26
13
|
}
|
|
27
14
|
|
|
28
15
|
/**
|
|
29
|
-
* Error thrown when decoded buffer
|
|
30
|
-
*
|
|
31
|
-
* This error is thrown when using the `size` option in decode operations
|
|
32
|
-
* and the decoded data is larger than the specified size.
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* ```typescript
|
|
36
|
-
* import { decode, BufferSizeError } from 'bufferbase';
|
|
37
|
-
*
|
|
38
|
-
* try {
|
|
39
|
-
* // Trying to decode into a buffer too small
|
|
40
|
-
* decode('72k1xXWG59fYdzSNoA', 'base58', { size: 2 });
|
|
41
|
-
* } catch (e) {
|
|
42
|
-
* if (e instanceof BufferSizeError) {
|
|
43
|
-
* console.log(e.message); // "Buffer size 13 exceeds specified size 2"
|
|
44
|
-
* }
|
|
45
|
-
* }
|
|
46
|
-
* ```
|
|
16
|
+
* Error thrown when the decoded buffer does not have the size requested
|
|
17
|
+
* through `DecodeOptions.size`.
|
|
47
18
|
*/
|
|
48
19
|
export class BufferSizeError extends Error {
|
|
49
20
|
/**
|
|
@@ -51,37 +22,106 @@ export class BufferSizeError extends Error {
|
|
|
51
22
|
* @param expected - The expected (specified) buffer size
|
|
52
23
|
*/
|
|
53
24
|
constructor(actual: number, expected: number) {
|
|
54
|
-
super(`Buffer size ${actual}
|
|
25
|
+
super(`Buffer size ${actual} does not match specified size ${expected}`);
|
|
55
26
|
this.name = 'BufferSizeError';
|
|
56
27
|
}
|
|
57
28
|
}
|
|
58
29
|
|
|
59
30
|
/**
|
|
60
31
|
* Error thrown when an unknown base name is provided.
|
|
61
|
-
*
|
|
62
|
-
* This error is thrown when using the function API with an invalid base name
|
|
63
|
-
* that is neither a known encoding name nor a valid custom character set
|
|
64
|
-
* (character sets must be longer than 15 characters).
|
|
65
|
-
*
|
|
66
|
-
* @example
|
|
67
|
-
* ```typescript
|
|
68
|
-
* import { encode, UnknownBaseError } from 'bufferbase';
|
|
69
|
-
*
|
|
70
|
-
* try {
|
|
71
|
-
* encode(Buffer.from('test'), 'invalidbase');
|
|
72
|
-
* } catch (e) {
|
|
73
|
-
* if (e instanceof UnknownBaseError) {
|
|
74
|
-
* console.log(e.message); // "Unknown base encoding: 'invalidbase'"
|
|
75
|
-
* }
|
|
76
|
-
* }
|
|
77
|
-
* ```
|
|
78
32
|
*/
|
|
79
33
|
export class UnknownBaseError extends Error {
|
|
80
34
|
/**
|
|
81
35
|
* @param name - The unknown base name that was provided
|
|
82
36
|
*/
|
|
83
37
|
constructor(name: string) {
|
|
84
|
-
super(
|
|
38
|
+
super(
|
|
39
|
+
`Unknown base encoding: '${name}'. ` +
|
|
40
|
+
'Pass a CodecSpec such as { alphabet, algorithm: \'radix\' } to use a custom alphabet.',
|
|
41
|
+
);
|
|
85
42
|
this.name = 'UnknownBaseError';
|
|
86
43
|
}
|
|
87
44
|
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Error thrown when a string cannot be the encoding of any byte sequence
|
|
48
|
+
* because of its length.
|
|
49
|
+
*
|
|
50
|
+
* A Base64 string of 5 characters, for example, describes 30 bits: too many
|
|
51
|
+
* for 3 bytes and too few for 4.
|
|
52
|
+
*/
|
|
53
|
+
export class InvalidLengthError extends Error {
|
|
54
|
+
/**
|
|
55
|
+
* @param length - The length that cannot occur
|
|
56
|
+
* @param detail - What the length should have been
|
|
57
|
+
*/
|
|
58
|
+
constructor(length: number, detail: string) {
|
|
59
|
+
super(`Invalid encoded length ${length}: ${detail}`);
|
|
60
|
+
this.name = 'InvalidLengthError';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Error thrown when input is well-formed but is not the canonical encoding of
|
|
66
|
+
* the bytes it decodes to (RFC 4648 §3.5).
|
|
67
|
+
*
|
|
68
|
+
* Accepting such input makes decoding non-injective: `'Zm8='` and `'Zm9='`
|
|
69
|
+
* would both decode to `'fo'`. Pass `strict: false` in the codec spec to allow
|
|
70
|
+
* it.
|
|
71
|
+
*/
|
|
72
|
+
export class NonCanonicalError extends Error {
|
|
73
|
+
/**
|
|
74
|
+
* @param detail - What made the input non-canonical
|
|
75
|
+
*/
|
|
76
|
+
constructor(detail: string) {
|
|
77
|
+
super(`Non-canonical encoding: ${detail}`);
|
|
78
|
+
this.name = 'NonCanonicalError';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Error thrown when a codec spec cannot be realised, such as a `'block'`
|
|
84
|
+
* algorithm with an alphabet whose length is not a power of two.
|
|
85
|
+
*/
|
|
86
|
+
export class InvalidCodecError extends Error {
|
|
87
|
+
/**
|
|
88
|
+
* @param detail - Why the spec is unusable
|
|
89
|
+
*/
|
|
90
|
+
constructor(detail: string) {
|
|
91
|
+
super(`Invalid codec: ${detail}`);
|
|
92
|
+
this.name = 'InvalidCodecError';
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Error thrown when a group of characters denotes a number too large for the
|
|
98
|
+
* bytes it is supposed to encode.
|
|
99
|
+
*
|
|
100
|
+
* In an Ascii85 group, five characters must denote a value below 2^32; `'s8W-!'`
|
|
101
|
+
* does not, so it encodes nothing. Unlike `NonCanonicalError` this is never
|
|
102
|
+
* allowed, because there is no byte sequence to decode to.
|
|
103
|
+
*/
|
|
104
|
+
export class ValueRangeError extends Error {
|
|
105
|
+
/**
|
|
106
|
+
* @param group - The group of characters that is out of range
|
|
107
|
+
*/
|
|
108
|
+
constructor(group: string) {
|
|
109
|
+
super(`Encoded group '${group}' exceeds the maximum value for its block`);
|
|
110
|
+
this.name = 'ValueRangeError';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Error thrown when a trailing check symbol does not match the value it
|
|
116
|
+
* follows, which means the string was mistyped or corrupted.
|
|
117
|
+
*/
|
|
118
|
+
export class CheckSymbolError extends Error {
|
|
119
|
+
/**
|
|
120
|
+
* @param found - The check symbol present in the input
|
|
121
|
+
* @param expected - The check symbol the value calls for
|
|
122
|
+
*/
|
|
123
|
+
constructor(found: string, expected: string) {
|
|
124
|
+
super(`Check symbol '${found}' does not match the expected '${expected}'`);
|
|
125
|
+
this.name = 'CheckSymbolError';
|
|
126
|
+
}
|
|
127
|
+
}
|
package/src/example.ts
CHANGED
|
@@ -1,33 +1,58 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
|
-
import { encode, decode, convert, validate, Codecs } from './index.js';
|
|
2
|
+
import { encode, decode, convert, validate, createCodec, Chars, Codecs } from './index.js';
|
|
3
3
|
|
|
4
|
-
// Example buffer
|
|
5
4
|
const bytes = Buffer.from('Hello, World!', 'utf8');
|
|
6
5
|
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
// The same bytes under each algorithm.
|
|
7
|
+
console.log('=== Algorithms ===');
|
|
8
|
+
console.table({
|
|
9
|
+
'block base64': encode(bytes, 'base64'),
|
|
10
|
+
'block base32': encode(bytes, 'base32'),
|
|
11
|
+
'block85 ascii85': encode(bytes, 'ascii85'),
|
|
12
|
+
'radix base58': encode(bytes, 'base58'),
|
|
13
|
+
'radix base32crockford': encode(bytes, 'base32crockford'),
|
|
14
|
+
});
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
// Converting goes through the bytes, so any pair works.
|
|
17
|
+
console.log('\n=== Function API ===');
|
|
18
|
+
const base58 = encode(bytes, 'base58');
|
|
19
|
+
const base64url = convert(base58, 'base58', 'base64url');
|
|
15
20
|
console.table({
|
|
16
21
|
bytes: bytes.toString('utf8'),
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
base58,
|
|
23
|
+
base64url,
|
|
24
|
+
valid: validate(base64url, 'base64url'),
|
|
25
|
+
decoded: decode(base64url, 'base64url').toString('utf8'),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// validate() agrees with decode(): it checks length, padding and canonicality.
|
|
29
|
+
console.log('\n=== Rejected input ===');
|
|
30
|
+
console.table({
|
|
31
|
+
"'Zm8=' (canonical)": validate('Zm8=', 'base64'),
|
|
32
|
+
"'Zm9=' (spare bits set)": validate('Zm9=', 'base64'),
|
|
33
|
+
"'Zm9vd' (5 characters)": validate('Zm9vd', 'base64'),
|
|
22
34
|
});
|
|
23
35
|
|
|
24
|
-
//
|
|
25
|
-
console.log('\n===
|
|
26
|
-
const
|
|
27
|
-
|
|
36
|
+
// Crockford's Base32 reads what a person is likely to have written down.
|
|
37
|
+
console.log('\n=== Crockford ===');
|
|
38
|
+
const crockford = encode(bytes, 'base32crockfordcheck');
|
|
39
|
+
console.table({
|
|
40
|
+
encoded: crockford,
|
|
41
|
+
'as typed': decode(`${crockford.slice(0, 4)}-${crockford.slice(4)}`.toLowerCase(), 'base32crockfordcheck').toString('utf8'),
|
|
42
|
+
'one character changed': validate(`${crockford.slice(0, -2)}Z${crockford.slice(-1)}`, 'base32crockfordcheck'),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Codecs, and a custom alphabet.
|
|
46
|
+
console.log('\n=== Codecs and custom alphabets ===');
|
|
47
|
+
const binary = createCodec('01');
|
|
48
|
+
const adobe = createCodec({
|
|
49
|
+
alphabet: Chars.Ascii85,
|
|
50
|
+
algorithm: 'block85',
|
|
51
|
+
zeroShortcut: true,
|
|
52
|
+
delimiters: true,
|
|
53
|
+
});
|
|
28
54
|
console.table({
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
valid: Codecs.base64url.validate(converted),
|
|
55
|
+
'Codecs.base58': Codecs.base58.encode(bytes),
|
|
56
|
+
'binary of [5]': binary.encode(Uint8Array.of(5)),
|
|
57
|
+
'adobe ascii85': adobe.encode(bytes),
|
|
33
58
|
});
|