@nakanoaas/uuid58 1.2.2 → 1.2.3

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
@@ -280,7 +280,7 @@ This project uses [Vite+](https://viteplus.dev/) (`vp`) and pnpm.
280
280
  pnpm install
281
281
  pnpm check # format, lint and type check
282
282
  pnpm test # run tests
283
- pnpm bench # run benchmarks
283
+ pnpm bench # build and benchmark (see bench/README.md)
284
284
  pnpm build # build the npm package into dist/
285
285
  ```
286
286
 
package/dist/index.cjs CHANGED
@@ -17,12 +17,6 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
17
17
  * ```
18
18
  */
19
19
  const UUID58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
20
- /**
21
- * @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
- */
25
- const UUID58_LENGTH = BigInt(58);
26
20
  // check.ts
27
21
  const MAX_UUID58 = "YcVfxkQb6JRzqk5kF2tNLv";
28
22
  /**
@@ -70,6 +64,35 @@ function isUuid58(uuid58) {
70
64
  const MAX_PREVIEW_LENGTH = 40;
71
65
  /**
72
66
  * @internal
67
+ * 58^9, the largest power of 58 below 2^53. A 9-digit Base58 chunk therefore
68
+ * fits in a Number, so most digit arithmetic can avoid slow BigInt division.
69
+ */
70
+ const BASE58_CHUNK = 7427658739644928n;
71
+ /** Appends `digits` Base58 digits of `value` (most significant first). */
72
+ function appendDigits(out, value, digits) {
73
+ let chunk = "";
74
+ for (let i = 0; i < digits; i++) {
75
+ const quotient = Math.floor(value / 58);
76
+ chunk = UUID58_ALPHABET[value - quotient * 58] + chunk;
77
+ value = quotient;
78
+ }
79
+ return out + chunk;
80
+ }
81
+ /**
82
+ * @internal
83
+ * Encodes an unsigned 128-bit integer as a 22-character Base58 string.
84
+ *
85
+ * The value is split into 4 + 9 + 9 digit chunks with two BigInt divisions,
86
+ * instead of 22 BigInt divisions (one per digit).
87
+ */
88
+ function encodeUint128(num) {
89
+ const low = Number(num % BASE58_CHUNK);
90
+ num /= BASE58_CHUNK;
91
+ const mid = Number(num % BASE58_CHUNK);
92
+ return appendDigits(appendDigits(appendDigits("", Number(num / BASE58_CHUNK), 4), mid, 9), low, 9);
93
+ }
94
+ /**
95
+ * @internal
73
96
  * Formats untrusted input for inclusion in an error message.
74
97
  *
75
98
  * The input is truncated so huge strings do not bloat error messages or logs,
@@ -81,8 +104,8 @@ function previewInput(input) {
81
104
  }
82
105
  // decode.ts
83
106
  var _Uuid58DecodeError;
84
- const BASE58_MAP = {};
85
- for (let i = 0; i < 58; i++) BASE58_MAP[UUID58_ALPHABET[i]] = BigInt(i);
107
+ const BASE58_INDEX = (/* @__PURE__ */ new Int8Array(128)).fill(-1);
108
+ for (let i = 0; i < 58; i++) BASE58_INDEX[UUID58_ALPHABET.charCodeAt(i)] = i;
86
109
  /**
87
110
  * Error thrown when an invalid Base58 string is provided for decoding.
88
111
  * This includes strings containing characters not in the Base58 alphabet.
@@ -109,10 +132,20 @@ _Uuid58DecodeError.prototype.name = "Uuid58DecodeError";
109
132
  function uuid58DecodeSafe(uuid58) {
110
133
  if (uuid58.length !== 22) return new Uuid58DecodeError(`Expected Base58 string of length 22, but received string of length ${uuid58.length}: ${previewInput(uuid58)}`);
111
134
  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;
135
+ for (let start = 0; start < 22;) {
136
+ const end = start === 0 ? 4 : start + 9;
137
+ let chunk = 0;
138
+ for (let i = start; i < end; i++) {
139
+ const code = uuid58.charCodeAt(i);
140
+ const index = code < 128 ? BASE58_INDEX[code] : -1;
141
+ if (index < 0) {
142
+ const char = String.fromCodePoint(uuid58.codePointAt(i));
143
+ return new Uuid58DecodeError(`Invalid Base58 character ${JSON.stringify(char)} found in input: ${previewInput(uuid58)}`);
144
+ }
145
+ chunk = chunk * 58 + index;
146
+ }
147
+ num = num * BASE58_CHUNK + BigInt(chunk);
148
+ start = end;
116
149
  }
117
150
  const hex = num.toString(16).padStart(32, "0");
118
151
  if (hex.length !== 32) return new Uuid58DecodeError(`Decoded value exceeds 128 bits of UUID: ${previewInput(uuid58)}`);
@@ -170,13 +203,7 @@ function uuid58EncodeSafe(uuid) {
170
203
  const hex = uuid.replace(/-/g, "");
171
204
  if (hex.length !== 32) return new Uuid58EncodeError(`Invalid UUID length: expected 32 characters (excluding hyphens), got ${hex.length} characters in ${previewInput(uuid)}`);
172
205
  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]);
206
+ return encodeUint128(BigInt("0x" + hex));
180
207
  }
181
208
  /**
182
209
  * Converts a standard UUID string to a 22-character Base58-encoded format.
@@ -203,6 +230,7 @@ function uuid58Encode(uuid) {
203
230
  return result;
204
231
  }
205
232
  // generate.ts
233
+ const random = /* @__PURE__ */ new BigUint64Array(2);
206
234
  /**
207
235
  * Generates a new Base58-encoded UUID (always 22 characters).
208
236
  *
@@ -217,16 +245,10 @@ function uuid58Encode(uuid) {
217
245
  * ```
218
246
  */
219
247
  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]);
248
+ crypto.getRandomValues(random);
249
+ const high = random[0] & 18446744073709490175n | 16384n;
250
+ const low = random[1] & 4611686018427387903n | 9223372036854775808n;
251
+ return encodeUint128(high << 64n | low);
230
252
  }
231
253
  exports.UUID58_ALPHABET = UUID58_ALPHABET;
232
254
  exports.UUID58_REGEX = UUID58_REGEX;
package/dist/index.js CHANGED
@@ -16,12 +16,6 @@
16
16
  * ```
17
17
  */
18
18
  const UUID58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
19
- /**
20
- * @internal
21
- * The length of the Base58 alphabet as BigInt for encoding/decoding operations.
22
- * This is exported for internal use but not part of the public API.
23
- */
24
- const UUID58_LENGTH = BigInt(58);
25
19
  // check.ts
26
20
  const MAX_UUID58 = "YcVfxkQb6JRzqk5kF2tNLv";
27
21
  /**
@@ -69,6 +63,35 @@ function isUuid58(uuid58) {
69
63
  const MAX_PREVIEW_LENGTH = 40;
70
64
  /**
71
65
  * @internal
66
+ * 58^9, the largest power of 58 below 2^53. A 9-digit Base58 chunk therefore
67
+ * fits in a Number, so most digit arithmetic can avoid slow BigInt division.
68
+ */
69
+ const BASE58_CHUNK = 7427658739644928n;
70
+ /** Appends `digits` Base58 digits of `value` (most significant first). */
71
+ function appendDigits(out, value, digits) {
72
+ let chunk = "";
73
+ for (let i = 0; i < digits; i++) {
74
+ const quotient = Math.floor(value / 58);
75
+ chunk = UUID58_ALPHABET[value - quotient * 58] + chunk;
76
+ value = quotient;
77
+ }
78
+ return out + chunk;
79
+ }
80
+ /**
81
+ * @internal
82
+ * Encodes an unsigned 128-bit integer as a 22-character Base58 string.
83
+ *
84
+ * The value is split into 4 + 9 + 9 digit chunks with two BigInt divisions,
85
+ * instead of 22 BigInt divisions (one per digit).
86
+ */
87
+ function encodeUint128(num) {
88
+ const low = Number(num % BASE58_CHUNK);
89
+ num /= BASE58_CHUNK;
90
+ const mid = Number(num % BASE58_CHUNK);
91
+ return appendDigits(appendDigits(appendDigits("", Number(num / BASE58_CHUNK), 4), mid, 9), low, 9);
92
+ }
93
+ /**
94
+ * @internal
72
95
  * Formats untrusted input for inclusion in an error message.
73
96
  *
74
97
  * The input is truncated so huge strings do not bloat error messages or logs,
@@ -80,8 +103,8 @@ function previewInput(input) {
80
103
  }
81
104
  // decode.ts
82
105
  var _Uuid58DecodeError;
83
- const BASE58_MAP = {};
84
- for (let i = 0; i < 58; i++) BASE58_MAP[UUID58_ALPHABET[i]] = BigInt(i);
106
+ const BASE58_INDEX = (/* @__PURE__ */ new Int8Array(128)).fill(-1);
107
+ for (let i = 0; i < 58; i++) BASE58_INDEX[UUID58_ALPHABET.charCodeAt(i)] = i;
85
108
  /**
86
109
  * Error thrown when an invalid Base58 string is provided for decoding.
87
110
  * This includes strings containing characters not in the Base58 alphabet.
@@ -108,10 +131,20 @@ _Uuid58DecodeError.prototype.name = "Uuid58DecodeError";
108
131
  function uuid58DecodeSafe(uuid58) {
109
132
  if (uuid58.length !== 22) return new Uuid58DecodeError(`Expected Base58 string of length 22, but received string of length ${uuid58.length}: ${previewInput(uuid58)}`);
110
133
  let num = 0n;
111
- for (const char of uuid58) {
112
- const index = BASE58_MAP[char];
113
- if (index === void 0) return new Uuid58DecodeError(`Invalid Base58 character ${JSON.stringify(char)} found in input: ${previewInput(uuid58)}`);
114
- num = num * UUID58_LENGTH + index;
134
+ for (let start = 0; start < 22;) {
135
+ const end = start === 0 ? 4 : start + 9;
136
+ let chunk = 0;
137
+ for (let i = start; i < end; i++) {
138
+ const code = uuid58.charCodeAt(i);
139
+ const index = code < 128 ? BASE58_INDEX[code] : -1;
140
+ if (index < 0) {
141
+ const char = String.fromCodePoint(uuid58.codePointAt(i));
142
+ return new Uuid58DecodeError(`Invalid Base58 character ${JSON.stringify(char)} found in input: ${previewInput(uuid58)}`);
143
+ }
144
+ chunk = chunk * 58 + index;
145
+ }
146
+ num = num * BASE58_CHUNK + BigInt(chunk);
147
+ start = end;
115
148
  }
116
149
  const hex = num.toString(16).padStart(32, "0");
117
150
  if (hex.length !== 32) return new Uuid58DecodeError(`Decoded value exceeds 128 bits of UUID: ${previewInput(uuid58)}`);
@@ -169,13 +202,7 @@ function uuid58EncodeSafe(uuid) {
169
202
  const hex = uuid.replace(/-/g, "");
170
203
  if (hex.length !== 32) return new Uuid58EncodeError(`Invalid UUID length: expected 32 characters (excluding hyphens), got ${hex.length} characters in ${previewInput(uuid)}`);
171
204
  if (!HEX32_REGEX.test(hex)) return new Uuid58EncodeError(`Invalid UUID format: ${previewInput(uuid)} contains non-hexadecimal characters`);
172
- let num = BigInt("0x" + hex);
173
- let encoded = "";
174
- do {
175
- encoded = UUID58_ALPHABET[Number(num % UUID58_LENGTH)] + encoded;
176
- num /= UUID58_LENGTH;
177
- } while (num > 0n);
178
- return encoded.padStart(22, UUID58_ALPHABET[0]);
205
+ return encodeUint128(BigInt("0x" + hex));
179
206
  }
180
207
  /**
181
208
  * Converts a standard UUID string to a 22-character Base58-encoded format.
@@ -202,6 +229,7 @@ function uuid58Encode(uuid) {
202
229
  return result;
203
230
  }
204
231
  // generate.ts
232
+ const random = /* @__PURE__ */ new BigUint64Array(2);
205
233
  /**
206
234
  * Generates a new Base58-encoded UUID (always 22 characters).
207
235
  *
@@ -216,15 +244,9 @@ function uuid58Encode(uuid) {
216
244
  * ```
217
245
  */
218
246
  function uuid58() {
219
- const bytes = crypto.getRandomValues(/* @__PURE__ */ new BigUint64Array(2));
220
- bytes[0] = bytes[0] & 18446744073709490175n | 16384n;
221
- bytes[1] = bytes[1] & 4611686018427387903n | 9223372036854775808n;
222
- let num = bytes[0] << 64n | bytes[1];
223
- let encoded = "";
224
- do {
225
- encoded = UUID58_ALPHABET[Number(num % UUID58_LENGTH)] + encoded;
226
- num /= UUID58_LENGTH;
227
- } while (num > 0n);
228
- return encoded.padStart(22, UUID58_ALPHABET[0]);
247
+ crypto.getRandomValues(random);
248
+ const high = random[0] & 18446744073709490175n | 16384n;
249
+ const low = random[1] & 4611686018427387903n | 9223372036854775808n;
250
+ return encodeUint128(high << 64n | low);
229
251
  }
230
252
  export { UUID58_ALPHABET, UUID58_REGEX, Uuid58DecodeError, Uuid58EncodeError, isUuid58, uuid58, uuid58Decode, uuid58DecodeSafe, uuid58Encode, uuid58EncodeSafe };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nakanoaas/uuid58",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "A tiny, zero-dependency library for generating and converting UUIDs to Base58-encoded strings.",
5
5
  "bugs": {
6
6
  "url": "https://github.com/nakanoasaservice/uuid58/issues"
@@ -35,7 +35,8 @@
35
35
  "build": "vp pack",
36
36
  "check": "vp check",
37
37
  "test": "vp test run",
38
- "bench": "vp test bench --run",
38
+ "bench": "vp pack && vp test bench --run --reporter=default",
39
+ "bench:baseline": "sh bench/save-baseline.sh",
39
40
  "jsr:dry-run": "vp pack && jsr publish --dry-run"
40
41
  },
41
42
  "devDependencies": {