@nakanoaas/uuid58 1.2.2 → 2.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 +87 -17
- package/dist/index.cjs +233 -86
- package/dist/index.d.cts +144 -50
- package/dist/index.d.ts +144 -50
- package/dist/index.js +230 -87
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -124,7 +124,10 @@ function uuid58Encode(uuid: string): string; // always returns a 22-character st
|
|
|
124
124
|
```
|
|
125
125
|
|
|
126
126
|
- **Parameters:**
|
|
127
|
-
- `uuid`: The UUID string to encode
|
|
127
|
+
- `uuid`: The UUID string to encode: hyphenated
|
|
128
|
+
(`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`) or 32 hexadecimal digits, in any
|
|
129
|
+
letter case. Other forms (braces, `urn:uuid:`, whitespace, hyphens in other
|
|
130
|
+
positions) are rejected.
|
|
128
131
|
- **Returns:** A 22-character Base58-encoded string
|
|
129
132
|
- **Throws:** `Uuid58EncodeError` if the input is not a valid UUID
|
|
130
133
|
|
|
@@ -139,7 +142,7 @@ function uuid58EncodeSafe(uuid: string): string | Uuid58EncodeError; // string i
|
|
|
139
142
|
```
|
|
140
143
|
|
|
141
144
|
- **Parameters:**
|
|
142
|
-
- `uuid`: The UUID string to encode (
|
|
145
|
+
- `uuid`: The UUID string to encode (same forms as `uuid58Encode`)
|
|
143
146
|
- **Returns:** A 22-character Base58-encoded string, or an `Uuid58EncodeError`
|
|
144
147
|
if the input is not a valid UUID
|
|
145
148
|
- **Note:** This function does not throw; it returns the error object instead.
|
|
@@ -174,26 +177,70 @@ function uuid58DecodeSafe(uuid58: string): string | Uuid58DecodeError;
|
|
|
174
177
|
`Uuid58DecodeError` if the input is not a valid 22-character Base58 string
|
|
175
178
|
- **Note:** This function does not throw; it returns the error object instead.
|
|
176
179
|
|
|
180
|
+
### `uuid58EncodeBytes(bytes: Uint8Array)` / `uuid58EncodeBytesSafe`
|
|
181
|
+
|
|
182
|
+
Encodes 16 bytes, in big-endian (RFC 9562) order, as a 22-character string.
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
function uuid58EncodeBytes(bytes: Uint8Array): string;
|
|
186
|
+
function uuid58EncodeBytesSafe(bytes: Uint8Array): string | Uuid58EncodeError;
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
- **Throws / returns:** `Uuid58EncodeError` with kind `"invalid_length"` if
|
|
190
|
+
`bytes` is not exactly 16 bytes long
|
|
191
|
+
|
|
192
|
+
### `uuid58DecodeBytes(uuid58: string)` / `uuid58DecodeBytesSafe`
|
|
193
|
+
|
|
194
|
+
Decodes a 22-character string to a new 16-byte `Uint8Array` in big-endian
|
|
195
|
+
(RFC 9562) order.
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
function uuid58DecodeBytes(uuid58: string): Uint8Array;
|
|
199
|
+
function uuid58DecodeBytesSafe(uuid58: string): Uint8Array | Uuid58DecodeError;
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
- **Throws / returns:** `Uuid58DecodeError`, as for `uuid58Decode`
|
|
203
|
+
|
|
177
204
|
### Error Classes
|
|
178
205
|
|
|
206
|
+
Both error classes have a `kind` property with the error kind defined by the
|
|
207
|
+
[specification](https://github.com/nakanoasaservice/uuid58/blob/main/spec/SPEC.md)
|
|
208
|
+
(type `Uuid58ErrorKind`). Use it instead of parsing the message, which may
|
|
209
|
+
change.
|
|
210
|
+
|
|
211
|
+
| `kind` | Meaning |
|
|
212
|
+
| --------------------- | ------------------------------------------------------------------------------ |
|
|
213
|
+
| `"invalid_length"` | Wrong length: not 32 or 36 characters (UUID), not 22 (uuid58), or not 16 bytes |
|
|
214
|
+
| `"invalid_character"` | Right length, but a character not allowed in its position |
|
|
215
|
+
| `"overflow"` | (decoding only) Well formed, but the value is greater than 2^128 − 1 |
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
const result = uuid58DecodeSafe(input);
|
|
219
|
+
if (result instanceof Uuid58DecodeError && result.kind === "overflow") {
|
|
220
|
+
// ...
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
179
224
|
#### `Uuid58EncodeError`
|
|
180
225
|
|
|
181
|
-
Error thrown when an invalid UUID string is provided for
|
|
182
|
-
|
|
183
|
-
with optional hyphens).
|
|
226
|
+
Error thrown when an invalid UUID string or byte array is provided for
|
|
227
|
+
encoding. `kind` is `"invalid_length"` or `"invalid_character"`.
|
|
184
228
|
|
|
185
229
|
```typescript
|
|
186
|
-
class Uuid58EncodeError extends Error
|
|
230
|
+
class Uuid58EncodeError extends Error {
|
|
231
|
+
readonly kind: "invalid_length" | "invalid_character";
|
|
232
|
+
}
|
|
187
233
|
```
|
|
188
234
|
|
|
189
235
|
#### `Uuid58DecodeError`
|
|
190
236
|
|
|
191
|
-
Error thrown when an invalid
|
|
192
|
-
|
|
193
|
-
that are not exactly 22 characters long.
|
|
237
|
+
Error thrown when an invalid string is provided for decoding. `kind` is
|
|
238
|
+
`"invalid_length"`, `"invalid_character"`, or `"overflow"`.
|
|
194
239
|
|
|
195
240
|
```typescript
|
|
196
|
-
class Uuid58DecodeError extends Error
|
|
241
|
+
class Uuid58DecodeError extends Error {
|
|
242
|
+
readonly kind: Uuid58ErrorKind;
|
|
243
|
+
}
|
|
197
244
|
```
|
|
198
245
|
|
|
199
246
|
### `isUuid58(uuid58: string)`
|
|
@@ -272,15 +319,38 @@ characters. The Base58 alphabet used in this library is the same as Bitcoin's,
|
|
|
272
319
|
which excludes similar-looking characters (0, O, I, l) to prevent visual
|
|
273
320
|
ambiguity and confusion.
|
|
274
321
|
|
|
322
|
+
This package implements the
|
|
323
|
+
[uuid58 specification](https://github.com/nakanoasaservice/uuid58/blob/main/spec/SPEC.md).
|
|
324
|
+
Since 2.0 it follows the spec fully. 1.x accepted hyphens anywhere in the
|
|
325
|
+
input of `uuid58Encode` (see Section 9.1 of the spec).
|
|
326
|
+
|
|
327
|
+
## Migrating from 1.x
|
|
328
|
+
|
|
329
|
+
2.0 has two breaking changes:
|
|
330
|
+
|
|
331
|
+
- **Stricter UUID input.** `uuid58Encode` and `uuid58EncodeSafe` accept only
|
|
332
|
+
the hyphenated form (`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`) and 32
|
|
333
|
+
hexadecimal digits. 1.x removed every hyphen first, so inputs such as
|
|
334
|
+
`f4b247f-d1f87-45d4-aa06-1c6fc0a8dfaf` or `f4b247fd-1f8745d4aa061c6fc0a8dfaf`
|
|
335
|
+
were accepted; they are now rejected. Normalize such input yourself before
|
|
336
|
+
encoding if you need it, for example with `uuid.replace(/-/g, "")`.
|
|
337
|
+
- **Error constructors** take a second argument, the error kind
|
|
338
|
+
(`new Uuid58EncodeError(message, kind)`). This matters only if you
|
|
339
|
+
construct these errors yourself.
|
|
340
|
+
|
|
341
|
+
New in 2.0: the `kind` property on errors, and `uuid58EncodeBytes` /
|
|
342
|
+
`uuid58DecodeBytes` for `Uint8Array`.
|
|
343
|
+
|
|
275
344
|
## Development
|
|
276
345
|
|
|
277
|
-
This
|
|
346
|
+
This package lives in the `js/` directory of the repository and uses
|
|
347
|
+
[Vite+](https://viteplus.dev/) (`vp`) and pnpm. Run these commands in `js/`:
|
|
278
348
|
|
|
279
349
|
```bash
|
|
280
350
|
pnpm install
|
|
281
351
|
pnpm check # format, lint and type check
|
|
282
|
-
pnpm test # run tests
|
|
283
|
-
pnpm bench #
|
|
352
|
+
pnpm test # run tests, including the spec's conformance vectors
|
|
353
|
+
pnpm bench # build and benchmark (see bench/README.md)
|
|
284
354
|
pnpm build # build the npm package into dist/
|
|
285
355
|
```
|
|
286
356
|
|
|
@@ -289,14 +359,14 @@ by `vp pack` and is not committed.
|
|
|
289
359
|
|
|
290
360
|
### Releasing
|
|
291
361
|
|
|
292
|
-
1. Bump `version` in `package.json` and merge the change into `main`.
|
|
293
|
-
2. Tag that commit and push the tag:
|
|
362
|
+
1. Bump `version` in `js/package.json` and merge the change into `main`.
|
|
363
|
+
2. Tag that commit with a `js/` prefix and push the tag:
|
|
294
364
|
|
|
295
365
|
```bash
|
|
296
|
-
git tag v1.2.3 && git push origin v1.2.3
|
|
366
|
+
git tag js/v1.2.3 && git push origin js/v1.2.3
|
|
297
367
|
```
|
|
298
368
|
|
|
299
|
-
The `Publish` workflow then checks that the tag matches `package.json` and
|
|
369
|
+
The `Publish` workflow then checks that the tag matches `js/package.json` and
|
|
300
370
|
is on `main`, runs the tests, publishes to JSR and npm, and creates the
|
|
301
371
|
GitHub Release.
|
|
302
372
|
|
package/dist/index.cjs
CHANGED
|
@@ -17,57 +17,37 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
const UUID58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
20
|
+
// internal.ts
|
|
21
|
+
const MAX_PREVIEW_LENGTH = 40;
|
|
20
22
|
/**
|
|
21
23
|
* @internal
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
+
* 58^9, the largest power of 58 below 2^53. A 9-digit Base58 chunk therefore
|
|
25
|
+
* fits in a Number, so most digit arithmetic can avoid slow BigInt division.
|
|
24
26
|
*/
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
const BASE58_CHUNK = 7427658739644928n;
|
|
28
|
+
/** Appends `digits` Base58 digits of `value` (most significant first). */
|
|
29
|
+
function appendDigits(out, value, digits) {
|
|
30
|
+
let chunk = "";
|
|
31
|
+
for (let i = 0; i < digits; i++) {
|
|
32
|
+
const quotient = Math.floor(value / 58);
|
|
33
|
+
chunk = UUID58_ALPHABET[value - quotient * 58] + chunk;
|
|
34
|
+
value = quotient;
|
|
35
|
+
}
|
|
36
|
+
return out + chunk;
|
|
37
|
+
}
|
|
28
38
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* This regex ensures that a string:
|
|
32
|
-
* - Contains exactly 22 characters
|
|
33
|
-
* - Uses only characters from the Base58 alphabet
|
|
34
|
-
*
|
|
35
|
-
* **Note:** This regex does NOT validate that the decoded value fits within
|
|
36
|
-
* 128 bits (UUID size). For complete validation including 128-bit range
|
|
37
|
-
* checking, use {@link isUuid58} instead.
|
|
39
|
+
* @internal
|
|
40
|
+
* Encodes an unsigned 128-bit integer as a 22-character Base58 string.
|
|
38
41
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* const isValid = UUID58_REGEX.test("XDY9dmBbcMBXqcRvYw8xJ2"); // true
|
|
42
|
-
* const isValid = UUID58_REGEX.test("invalid"); // false
|
|
43
|
-
* const isValid = UUID58_REGEX.test("123"); // false (too short)
|
|
44
|
-
* ```
|
|
42
|
+
* The value is split into 4 + 9 + 9 digit chunks with two BigInt divisions,
|
|
43
|
+
* instead of 22 BigInt divisions (one per digit).
|
|
45
44
|
*/
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
* A valid UUID58 string must:
|
|
52
|
-
* - Be exactly 22 characters long
|
|
53
|
-
* - Contain only characters from the Base58 alphabet
|
|
54
|
-
* - Decode to a value that fits within 128 bits (UUID size)
|
|
55
|
-
*
|
|
56
|
-
* @param uuid58 - The string to check
|
|
57
|
-
* @returns `true` if the string is a valid UUID58 string, `false` otherwise
|
|
58
|
-
*
|
|
59
|
-
* @example
|
|
60
|
-
* ```typescript
|
|
61
|
-
* isUuid58("XDY9dmBbcMBXqcRvYw8xJ2"); // true
|
|
62
|
-
* isUuid58("invalid"); // false
|
|
63
|
-
* isUuid58("O0lI"); // false (contains invalid characters)
|
|
64
|
-
* ```
|
|
65
|
-
*/
|
|
66
|
-
function isUuid58(uuid58) {
|
|
67
|
-
return UUID58_REGEX.test(uuid58) && uuid58 <= MAX_UUID58;
|
|
45
|
+
function encodeUint128(num) {
|
|
46
|
+
const low = Number(num % BASE58_CHUNK);
|
|
47
|
+
num /= BASE58_CHUNK;
|
|
48
|
+
const mid = Number(num % BASE58_CHUNK);
|
|
49
|
+
return appendDigits(appendDigits(appendDigits("", Number(num / BASE58_CHUNK), 4), mid, 9), low, 9);
|
|
68
50
|
}
|
|
69
|
-
// internal.ts
|
|
70
|
-
const MAX_PREVIEW_LENGTH = 40;
|
|
71
51
|
/**
|
|
72
52
|
* @internal
|
|
73
53
|
* Formats untrusted input for inclusion in an error message.
|
|
@@ -81,16 +61,48 @@ function previewInput(input) {
|
|
|
81
61
|
}
|
|
82
62
|
// decode.ts
|
|
83
63
|
var _Uuid58DecodeError;
|
|
84
|
-
const
|
|
85
|
-
for (let i = 0; i < 58; i++)
|
|
64
|
+
const BASE58_INDEX = (/* @__PURE__ */ new Int8Array(128)).fill(-1);
|
|
65
|
+
for (let i = 0; i < 58; i++) BASE58_INDEX[UUID58_ALPHABET.charCodeAt(i)] = i;
|
|
66
|
+
const MAX_UINT128 = (1n << 128n) - 1n;
|
|
86
67
|
/**
|
|
87
|
-
* Error thrown when an invalid
|
|
88
|
-
*
|
|
68
|
+
* Error thrown when an invalid uuid58 string is provided for decoding.
|
|
69
|
+
* Its {@link Uuid58DecodeError.kind | kind} tells why.
|
|
89
70
|
*/
|
|
90
|
-
var Uuid58DecodeError = class extends Error {
|
|
71
|
+
var Uuid58DecodeError = class extends Error {
|
|
72
|
+
constructor(message, kind) {
|
|
73
|
+
super(message);
|
|
74
|
+
this.kind = kind;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
91
77
|
_Uuid58DecodeError = Uuid58DecodeError;
|
|
92
78
|
_Uuid58DecodeError.prototype.name = "Uuid58DecodeError";
|
|
93
79
|
/**
|
|
80
|
+
* @internal
|
|
81
|
+
* Decodes a uuid58 string to an unsigned 128-bit integer, checking length,
|
|
82
|
+
* then characters, then range (specification, Section 4).
|
|
83
|
+
*/
|
|
84
|
+
function decodeUint128(uuid58) {
|
|
85
|
+
if (uuid58.length !== 22) return new Uuid58DecodeError(`Expected Base58 string of length 22, but received string of length ${uuid58.length}: ${previewInput(uuid58)}`, "invalid_length");
|
|
86
|
+
let num = 0n;
|
|
87
|
+
for (let start = 0; start < 22;) {
|
|
88
|
+
const end = start === 0 ? 4 : start + 9;
|
|
89
|
+
let chunk = 0;
|
|
90
|
+
for (let i = start; i < end; i++) {
|
|
91
|
+
const code = uuid58.charCodeAt(i);
|
|
92
|
+
const index = code < 128 ? BASE58_INDEX[code] : -1;
|
|
93
|
+
if (index < 0) {
|
|
94
|
+
const char = String.fromCodePoint(uuid58.codePointAt(i));
|
|
95
|
+
return new Uuid58DecodeError(`Invalid Base58 character ${JSON.stringify(char)} found in input: ${previewInput(uuid58)}`, "invalid_character");
|
|
96
|
+
}
|
|
97
|
+
chunk = chunk * 58 + index;
|
|
98
|
+
}
|
|
99
|
+
num = num * BASE58_CHUNK + BigInt(chunk);
|
|
100
|
+
start = end;
|
|
101
|
+
}
|
|
102
|
+
if (num > MAX_UINT128) return new Uuid58DecodeError(`Decoded value exceeds 128 bits of UUID: ${previewInput(uuid58)}`, "overflow");
|
|
103
|
+
return num;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
94
106
|
* Converts a 22-character Base58-encoded string back to a standard UUID format, but instead of
|
|
95
107
|
* throwing an error for invalid input, it returns an `Uuid58DecodeError`
|
|
96
108
|
* instance.
|
|
@@ -103,19 +115,13 @@ _Uuid58DecodeError.prototype.name = "Uuid58DecodeError";
|
|
|
103
115
|
* @example
|
|
104
116
|
* ```typescript
|
|
105
117
|
* const uuid = uuid58DecodeSafe("XDY9dmBbcMBXqcRvYw8xJ2"); // returns "f4b247fd-1f87-45d4-aa06-1c6fc0a8dfaf"
|
|
106
|
-
* const error = uuid58DecodeSafe("invalid"); // returns Uuid58DecodeError
|
|
118
|
+
* const error = uuid58DecodeSafe("invalid"); // returns Uuid58DecodeError (kind: "invalid_length")
|
|
107
119
|
* ```
|
|
108
120
|
*/
|
|
109
121
|
function uuid58DecodeSafe(uuid58) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
for (const char of uuid58) {
|
|
113
|
-
const index = BASE58_MAP[char];
|
|
114
|
-
if (index === void 0) return new Uuid58DecodeError(`Invalid Base58 character ${JSON.stringify(char)} found in input: ${previewInput(uuid58)}`);
|
|
115
|
-
num = num * UUID58_LENGTH + index;
|
|
116
|
-
}
|
|
122
|
+
const num = decodeUint128(uuid58);
|
|
123
|
+
if (typeof num !== "bigint") return num;
|
|
117
124
|
const hex = num.toString(16).padStart(32, "0");
|
|
118
|
-
if (hex.length !== 32) return new Uuid58DecodeError(`Decoded value exceeds 128 bits of UUID: ${previewInput(uuid58)}`);
|
|
119
125
|
return hex.slice(0, 8) + "-" + hex.slice(8, 12) + "-" + hex.slice(12, 16) + "-" + hex.slice(16, 20) + "-" + hex.slice(20);
|
|
120
126
|
}
|
|
121
127
|
/**
|
|
@@ -138,18 +144,44 @@ function uuid58Decode(uuid58) {
|
|
|
138
144
|
// encode.ts
|
|
139
145
|
var _Uuid58EncodeError;
|
|
140
146
|
const HEX32_REGEX = /^[0-9a-f]{32}$/i;
|
|
147
|
+
const HYPHENATED_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
141
148
|
/**
|
|
142
|
-
* Error thrown when an invalid UUID string is provided for
|
|
143
|
-
*
|
|
149
|
+
* Error thrown when an invalid UUID string or byte array is provided for
|
|
150
|
+
* encoding. Its {@link Uuid58EncodeError.kind | kind} tells why.
|
|
144
151
|
*/
|
|
145
|
-
var Uuid58EncodeError = class extends Error {
|
|
152
|
+
var Uuid58EncodeError = class extends Error {
|
|
153
|
+
constructor(message, kind) {
|
|
154
|
+
super(message);
|
|
155
|
+
this.kind = kind;
|
|
156
|
+
}
|
|
157
|
+
};
|
|
146
158
|
_Uuid58EncodeError = Uuid58EncodeError;
|
|
147
159
|
_Uuid58EncodeError.prototype.name = "Uuid58EncodeError";
|
|
148
160
|
/**
|
|
161
|
+
* @internal
|
|
162
|
+
* Parses a UUID string in one of the two forms of the specification
|
|
163
|
+
* (Section 5.1) into an unsigned 128-bit integer.
|
|
164
|
+
*/
|
|
165
|
+
function parseUuidString(uuid) {
|
|
166
|
+
let hex;
|
|
167
|
+
if (uuid.length === 32) {
|
|
168
|
+
if (!HEX32_REGEX.test(uuid)) return new Uuid58EncodeError(`Invalid UUID: ${previewInput(uuid)} must be 32 hexadecimal digits`, "invalid_character");
|
|
169
|
+
hex = uuid;
|
|
170
|
+
} else if (uuid.length === 36) {
|
|
171
|
+
if (!HYPHENATED_REGEX.test(uuid)) return new Uuid58EncodeError(`Invalid UUID: ${previewInput(uuid)} must be in the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, "invalid_character");
|
|
172
|
+
hex = uuid.slice(0, 8) + uuid.slice(9, 13) + uuid.slice(14, 18) + uuid.slice(19, 23) + uuid.slice(24);
|
|
173
|
+
} else return new Uuid58EncodeError(`Invalid UUID length: expected 32 or 36 characters, got ${uuid.length} in ${previewInput(uuid)}`, "invalid_length");
|
|
174
|
+
return BigInt("0x" + hex);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
149
177
|
* Converts a standard UUID string to a 22-character Base58-encoded format, but instead of
|
|
150
178
|
* throwing an error for invalid input, it returns an `Uuid58EncodeError` instance.
|
|
151
179
|
*
|
|
152
|
-
*
|
|
180
|
+
* Only two forms are accepted, in any letter case: the hyphenated form
|
|
181
|
+
* (`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`) and 32 hexadecimal digits. Braces,
|
|
182
|
+
* `urn:uuid:`, whitespace, and hyphens in other positions are rejected.
|
|
183
|
+
*
|
|
184
|
+
* @param uuid - The UUID string to encode (hyphenated, or 32 hexadecimal digits)
|
|
153
185
|
* @returns A 22-character Base58-encoded string, or an `Uuid58EncodeError` if the input is
|
|
154
186
|
* not a valid UUID
|
|
155
187
|
* @note This function does not throw; it returns the error object instead.
|
|
@@ -163,27 +195,20 @@ _Uuid58EncodeError.prototype.name = "Uuid58EncodeError";
|
|
|
163
195
|
* // Uppercase UUID is also accepted
|
|
164
196
|
* const base58 = uuid58EncodeSafe("F4B247FD-1F87-45D4-AA06-1C6FC0A8DFAF"); // returns "XDY9dmBbcMBXqcRvYw8xJ2"
|
|
165
197
|
* // Invalid UUID format
|
|
166
|
-
* const error = uuid58EncodeSafe("invalid"); // returns Uuid58EncodeError
|
|
198
|
+
* const error = uuid58EncodeSafe("invalid"); // returns Uuid58EncodeError (kind: "invalid_length")
|
|
167
199
|
* ```
|
|
168
200
|
*/
|
|
169
201
|
function uuid58EncodeSafe(uuid) {
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
if (!HEX32_REGEX.test(hex)) return new Uuid58EncodeError(`Invalid UUID format: ${previewInput(uuid)} contains non-hexadecimal characters`);
|
|
173
|
-
let num = BigInt("0x" + hex);
|
|
174
|
-
let encoded = "";
|
|
175
|
-
do {
|
|
176
|
-
encoded = UUID58_ALPHABET[Number(num % UUID58_LENGTH)] + encoded;
|
|
177
|
-
num /= UUID58_LENGTH;
|
|
178
|
-
} while (num > 0n);
|
|
179
|
-
return encoded.padStart(22, UUID58_ALPHABET[0]);
|
|
202
|
+
const num = parseUuidString(uuid);
|
|
203
|
+
return typeof num === "bigint" ? encodeUint128(num) : num;
|
|
180
204
|
}
|
|
181
205
|
/**
|
|
182
206
|
* Converts a standard UUID string to a 22-character Base58-encoded format.
|
|
183
207
|
*
|
|
184
|
-
* @param uuid - The UUID string to encode
|
|
185
|
-
* (
|
|
186
|
-
*
|
|
208
|
+
* @param uuid - The UUID string to encode, either hyphenated
|
|
209
|
+
* ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") or as 32 hexadecimal digits
|
|
210
|
+
* ("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"). Hexadecimal digits may be in any case.
|
|
211
|
+
* Other forms, including hyphens in other positions, are rejected.
|
|
187
212
|
* @returns A 22-character Base58-encoded string representation of the UUID
|
|
188
213
|
* @throws {Uuid58EncodeError} If the input string is not a valid UUID format
|
|
189
214
|
*
|
|
@@ -202,7 +227,131 @@ function uuid58Encode(uuid) {
|
|
|
202
227
|
if (result instanceof Uuid58EncodeError) throw result;
|
|
203
228
|
return result;
|
|
204
229
|
}
|
|
230
|
+
// bytes.ts
|
|
231
|
+
/**
|
|
232
|
+
* Encodes 16 bytes, in big-endian (RFC 9562) order, as a 22-character uuid58
|
|
233
|
+
* string, but instead of throwing an error for invalid input, it returns an
|
|
234
|
+
* `Uuid58EncodeError` instance.
|
|
235
|
+
*
|
|
236
|
+
* @param bytes - The 16 bytes of the UUID
|
|
237
|
+
* @returns A 22-character uuid58 string, or an `Uuid58EncodeError` (kind
|
|
238
|
+
* `"invalid_length"`) if `bytes` is not exactly 16 bytes long
|
|
239
|
+
* @note This function does not throw; it returns the error object instead.
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```typescript
|
|
243
|
+
* const bytes = new Uint8Array(16); // nil UUID
|
|
244
|
+
* uuid58EncodeBytesSafe(bytes); // returns "1111111111111111111111"
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
247
|
+
function uuid58EncodeBytesSafe(bytes) {
|
|
248
|
+
if (bytes.length !== 16) return new Uuid58EncodeError(`Invalid byte length: expected 16 bytes, got ${bytes.length}`, "invalid_length");
|
|
249
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, 16);
|
|
250
|
+
return encodeUint128(view.getBigUint64(0) << 64n | view.getBigUint64(8));
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Encodes 16 bytes, in big-endian (RFC 9562) order, as a 22-character uuid58
|
|
254
|
+
* string.
|
|
255
|
+
*
|
|
256
|
+
* @param bytes - The 16 bytes of the UUID
|
|
257
|
+
* @returns A 22-character uuid58 string
|
|
258
|
+
* @throws {Uuid58EncodeError} If `bytes` is not exactly 16 bytes long
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```typescript
|
|
262
|
+
* uuid58EncodeBytes(new Uint8Array(16)); // returns "1111111111111111111111"
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
function uuid58EncodeBytes(bytes) {
|
|
266
|
+
const result = uuid58EncodeBytesSafe(bytes);
|
|
267
|
+
if (result instanceof Uuid58EncodeError) throw result;
|
|
268
|
+
return result;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Decodes a uuid58 string to 16 bytes in big-endian (RFC 9562) order, but
|
|
272
|
+
* instead of throwing an error for invalid input, it returns an
|
|
273
|
+
* `Uuid58DecodeError` instance.
|
|
274
|
+
*
|
|
275
|
+
* @param uuid58 - The 22-character uuid58 string to decode
|
|
276
|
+
* @returns A new 16-byte `Uint8Array`, or an `Uuid58DecodeError`
|
|
277
|
+
* @note This function does not throw; it returns the error object instead.
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* ```typescript
|
|
281
|
+
* uuid58DecodeBytesSafe("1111111111111111111111"); // returns Uint8Array(16) of zeros
|
|
282
|
+
* ```
|
|
283
|
+
*/
|
|
284
|
+
function uuid58DecodeBytesSafe(uuid58) {
|
|
285
|
+
const num = decodeUint128(uuid58);
|
|
286
|
+
if (typeof num !== "bigint") return num;
|
|
287
|
+
const bytes = /* @__PURE__ */ new Uint8Array(16);
|
|
288
|
+
const view = new DataView(bytes.buffer);
|
|
289
|
+
view.setBigUint64(0, num >> 64n);
|
|
290
|
+
view.setBigUint64(8, num & 18446744073709551615n);
|
|
291
|
+
return bytes;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Decodes a uuid58 string to 16 bytes in big-endian (RFC 9562) order.
|
|
295
|
+
*
|
|
296
|
+
* @param uuid58 - The 22-character uuid58 string to decode
|
|
297
|
+
* @returns A new 16-byte `Uint8Array`
|
|
298
|
+
* @throws {Uuid58DecodeError} If the input is not a valid uuid58 string
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
* ```typescript
|
|
302
|
+
* const bytes = uuid58DecodeBytes("XDY9dmBbcMBXqcRvYw8xJ2");
|
|
303
|
+
* ```
|
|
304
|
+
*/
|
|
305
|
+
function uuid58DecodeBytes(uuid58) {
|
|
306
|
+
const result = uuid58DecodeBytesSafe(uuid58);
|
|
307
|
+
if (result instanceof Uuid58DecodeError) throw result;
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
// check.ts
|
|
311
|
+
const MAX_UUID58 = "YcVfxkQb6JRzqk5kF2tNLv";
|
|
312
|
+
/**
|
|
313
|
+
* Regular expression pattern for validating UUID58 strings.
|
|
314
|
+
*
|
|
315
|
+
* This regex ensures that a string:
|
|
316
|
+
* - Contains exactly 22 characters
|
|
317
|
+
* - Uses only characters from the Base58 alphabet
|
|
318
|
+
*
|
|
319
|
+
* **Note:** This regex does NOT validate that the decoded value fits within
|
|
320
|
+
* 128 bits (UUID size). For complete validation including 128-bit range
|
|
321
|
+
* checking, use {@link isUuid58} instead.
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* ```typescript
|
|
325
|
+
* const isValid = UUID58_REGEX.test("XDY9dmBbcMBXqcRvYw8xJ2"); // true
|
|
326
|
+
* const isValid = UUID58_REGEX.test("invalid"); // false
|
|
327
|
+
* const isValid = UUID58_REGEX.test("123"); // false (too short)
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
const UUID58_REGEX = /^[1-9A-HJ-NP-Za-km-z]{22}$/u;
|
|
331
|
+
/**
|
|
332
|
+
* Checks if a given string is a valid UUID58-encodable string.
|
|
333
|
+
* This function efficiently validates without performing full decoding.
|
|
334
|
+
*
|
|
335
|
+
* A valid UUID58 string must:
|
|
336
|
+
* - Be exactly 22 characters long
|
|
337
|
+
* - Contain only characters from the Base58 alphabet
|
|
338
|
+
* - Decode to a value that fits within 128 bits (UUID size)
|
|
339
|
+
*
|
|
340
|
+
* @param uuid58 - The string to check
|
|
341
|
+
* @returns `true` if the string is a valid UUID58 string, `false` otherwise
|
|
342
|
+
*
|
|
343
|
+
* @example
|
|
344
|
+
* ```typescript
|
|
345
|
+
* isUuid58("XDY9dmBbcMBXqcRvYw8xJ2"); // true
|
|
346
|
+
* isUuid58("invalid"); // false
|
|
347
|
+
* isUuid58("O0lI"); // false (contains invalid characters)
|
|
348
|
+
* ```
|
|
349
|
+
*/
|
|
350
|
+
function isUuid58(uuid58) {
|
|
351
|
+
return UUID58_REGEX.test(uuid58) && uuid58 <= MAX_UUID58;
|
|
352
|
+
}
|
|
205
353
|
// generate.ts
|
|
354
|
+
const random = /* @__PURE__ */ new BigUint64Array(2);
|
|
206
355
|
/**
|
|
207
356
|
* Generates a new Base58-encoded UUID (always 22 characters).
|
|
208
357
|
*
|
|
@@ -217,16 +366,10 @@ function uuid58Encode(uuid) {
|
|
|
217
366
|
* ```
|
|
218
367
|
*/
|
|
219
368
|
function uuid58() {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
let encoded = "";
|
|
225
|
-
do {
|
|
226
|
-
encoded = UUID58_ALPHABET[Number(num % UUID58_LENGTH)] + encoded;
|
|
227
|
-
num /= UUID58_LENGTH;
|
|
228
|
-
} while (num > 0n);
|
|
229
|
-
return encoded.padStart(22, UUID58_ALPHABET[0]);
|
|
369
|
+
crypto.getRandomValues(random);
|
|
370
|
+
const high = random[0] & 18446744073709490175n | 16384n;
|
|
371
|
+
const low = random[1] & 4611686018427387903n | 9223372036854775808n;
|
|
372
|
+
return encodeUint128(high << 64n | low);
|
|
230
373
|
}
|
|
231
374
|
exports.UUID58_ALPHABET = UUID58_ALPHABET;
|
|
232
375
|
exports.UUID58_REGEX = UUID58_REGEX;
|
|
@@ -235,6 +378,10 @@ exports.Uuid58EncodeError = Uuid58EncodeError;
|
|
|
235
378
|
exports.isUuid58 = isUuid58;
|
|
236
379
|
exports.uuid58 = uuid58;
|
|
237
380
|
exports.uuid58Decode = uuid58Decode;
|
|
381
|
+
exports.uuid58DecodeBytes = uuid58DecodeBytes;
|
|
382
|
+
exports.uuid58DecodeBytesSafe = uuid58DecodeBytesSafe;
|
|
238
383
|
exports.uuid58DecodeSafe = uuid58DecodeSafe;
|
|
239
384
|
exports.uuid58Encode = uuid58Encode;
|
|
385
|
+
exports.uuid58EncodeBytes = uuid58EncodeBytes;
|
|
386
|
+
exports.uuid58EncodeBytesSafe = uuid58EncodeBytesSafe;
|
|
240
387
|
exports.uuid58EncodeSafe = uuid58EncodeSafe;
|