@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 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 (with or without hyphens)
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 (with or without hyphens)
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 encoding. This includes
182
- strings that don't match the standard UUID format (32 hexadecimal characters
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 Base58 string is provided for decoding. This
192
- includes strings containing characters not in the Base58 alphabet or strings
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 project uses [Vite+](https://viteplus.dev/) (`vp`) and pnpm.
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 # run benchmarks
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
- * The length of the Base58 alphabet as BigInt for encoding/decoding operations.
23
- * This is exported for internal use but not part of the public API.
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 UUID58_LENGTH = BigInt(58);
26
- // check.ts
27
- const MAX_UUID58 = "YcVfxkQb6JRzqk5kF2tNLv";
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
- * Regular expression pattern for validating UUID58 strings.
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
- * @example
40
- * ```typescript
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
- const UUID58_REGEX = /^[1-9A-HJ-NP-Za-km-z]{22}$/u;
47
- /**
48
- * Checks if a given string is a valid UUID58-encodable string.
49
- * This function efficiently validates without performing full decoding.
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 BASE58_MAP = {};
85
- for (let i = 0; i < 58; i++) BASE58_MAP[UUID58_ALPHABET[i]] = BigInt(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 Base58 string is provided for decoding.
88
- * This includes strings containing characters not in the Base58 alphabet.
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
- if (uuid58.length !== 22) return new Uuid58DecodeError(`Expected Base58 string of length 22, but received string of length ${uuid58.length}: ${previewInput(uuid58)}`);
111
- let num = 0n;
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 encoding.
143
- * This includes strings that don't match the standard UUID format (32 hexadecimal characters with optional hyphens).
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
- * @param uuid - The UUID string to encode (with or without hyphens)
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 hex = uuid.replace(/-/g, "");
171
- if (hex.length !== 32) return new Uuid58EncodeError(`Invalid UUID length: expected 32 characters (excluding hyphens), got ${hex.length} characters in ${previewInput(uuid)}`);
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. Can be provided with or without hyphens
185
- * (format: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" or "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx").
186
- * The input is case-insensitive, so both uppercase and lowercase hexadecimal characters are accepted.
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
- const bytes = crypto.getRandomValues(/* @__PURE__ */ new BigUint64Array(2));
221
- bytes[0] = bytes[0] & 18446744073709490175n | 16384n;
222
- bytes[1] = bytes[1] & 4611686018427387903n | 9223372036854775808n;
223
- let num = bytes[0] << 64n | bytes[1];
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;