Skip to content

Commit d36ccf4

Browse files
committed
refactor(encoding): remove base-x, manage encoding internally
1 parent 7e5551a commit d36ccf4

File tree

4 files changed

+35
-34
lines changed

4 files changed

+35
-34
lines changed

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# [uuidv7-js](https://github.com/TheEdoRan/uuidv7-js)
22

3-
UUIDv7 generator library for JavaScript that implements the [RFC 9562](https://datatracker.ietf.org/doc/html/rfc9562) spec.
3+
UUIDv7 generator library for JavaScript, [RFC 9562](https://datatracker.ietf.org/doc/html/rfc9562) compliant. Supports encoding/decoding UUIDs to custom alphabets.
44

55
## Installation
66

@@ -33,7 +33,7 @@ const decoded = decodeUUIDv7(encoded); // // 018ef3e8-90e2-7be4-b4ea-4be3bf8803b
3333
## Create a new instance
3434

3535
```typescript
36-
const uuid = new UUIDv7(opts?: { encodeAlphabet: string })
36+
const uuid = new UUIDv7(opts?: { encodeAlphabet?: string })
3737
```
3838

3939
Creates a new `UUIDv7` instance. By default it uses the [Base58](https://www.cs.utexas.edu/users/moore/acl2/manuals/current/manual/index-seo.php/BITCOIN_____A2BASE58-CHARACTERS_A2) alphabet to `encode` and `decode` UUIDs, but you can pass a custom alphabet (16-64 characters).

package.json

+1-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "uuidv7-js",
33
"version": "0.0.0-development",
44
"author": "Edoardo Ranghieri",
5-
"description": "UUIDv7 generator library for JavaScript that implements the RFC 9562 spec",
5+
"description": "UUIDv7 generator library for JavaScript, RFC 9562 compliant. Supports encoding/decoding UUIDs to custom alphabets.",
66
"private": false,
77
"main": "dist/index.js",
88
"module": "dist/index.mjs",
@@ -54,8 +54,5 @@
5454
"tsx": "^4.7.2",
5555
"typescript": "^5.4.5",
5656
"typescript-eslint": "^7.7.1"
57-
},
58-
"dependencies": {
59-
"base-x": "^4.0.0"
6057
}
6158
}

pnpm-lock.yaml

-9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.ts

+32-19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import baseX from "base-x";
2-
31
function addHyphens(id: string) {
42
return (
53
id.substring(0, 8) +
@@ -30,13 +28,13 @@ export class UUIDv7 {
3028
#lastRandA: number;
3129
#lastRandB: bigint;
3230
#lastUUID: bigint = -1n;
33-
#encoder: baseX.BaseConverter;
31+
#encodeAlphabet: string;
3432

3533
/**
3634
* Generates a new `UUIDv7` instance.
3735
* @param encodeAlphabet Alphabet used for encoding. Defaults to [Base58](https://www.cs.utexas.edu/users/moore/acl2/manuals/current/manual/index-seo.php/BITCOIN_____A2BASE58-CHARACTERS_A2) alphabet. 16-64 characters.
3836
*/
39-
constructor(opts?: { encodeAlphabet: string }) {
37+
constructor(opts?: { encodeAlphabet?: string }) {
4038
if (opts?.encodeAlphabet) {
4139
if (opts.encodeAlphabet.length < 16 || opts.encodeAlphabet.length > 64) {
4240
throw new Error("uuidv7: encode alphabet must be between 16 and 64 characters long");
@@ -47,7 +45,7 @@ export class UUIDv7 {
4745
}
4846
}
4947

50-
this.#encoder = baseX(opts?.encodeAlphabet ?? "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz");
48+
this.#encodeAlphabet = opts?.encodeAlphabet ?? "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
5149
}
5250

5351
/**
@@ -59,7 +57,7 @@ export class UUIDv7 {
5957
const hasCustomTimestamp = typeof customTimestamp === "number";
6058

6159
if (hasCustomTimestamp && (customTimestamp < 0 || customTimestamp > 2 ** 48 - 1)) {
62-
throw new Error("uuidv7: custom timestamp must be between 0 and 2 ** 48 - 1");
60+
throw new Error("uuidv7 gen: custom timestamp must be between 0 and 2 ** 48 - 1");
6361
}
6462

6563
let uuid = this.#lastUUID;
@@ -104,7 +102,7 @@ export class UUIDv7 {
104102
// if custom timestamp is provided, throw an error, since the limit was reached for
105103
// both randomly seeded counters.
106104
if (hasCustomTimestamp) {
107-
throw new Error("uuidv7: custom timestamp is too old");
105+
throw new Error("uuidv7 gen: cannot generate a UUIDv7 with this timestamp, counters limit reached");
108106
}
109107

110108
// if custom timestamp is not provided, skip this loop iteration, since both
@@ -160,7 +158,7 @@ export class UUIDv7 {
160158
*/
161159
genMany(amount: number, customTimestamp?: number) {
162160
if (amount <= 0) {
163-
throw new Error("uuidv7: generation amount must be greater than 0");
161+
throw new Error("uuidv7 genMany: generation amount must be greater than 0");
164162
}
165163

166164
return Array.from({ length: amount }, () => this.gen(customTimestamp));
@@ -172,7 +170,16 @@ export class UUIDv7 {
172170
* @returns {string} Encoded UUIDv7
173171
*/
174172
encode(id: string) {
175-
return this.#encoder.encode(Buffer.from(id.replace(/-/g, ""), "hex"));
173+
let n = BigInt("0x" + id.replace(/-/g, ""));
174+
let encoded = "";
175+
176+
while (n > 0n) {
177+
const charIdx = this.#encodeAlphabet[Number(n % BigInt(this.#encodeAlphabet.length))];
178+
encoded = charIdx + encoded;
179+
n /= BigInt(this.#encodeAlphabet.length);
180+
}
181+
182+
return encoded;
176183
}
177184

178185
/**
@@ -182,13 +189,7 @@ export class UUIDv7 {
182189
*/
183190
decode(encodedId: string) {
184191
try {
185-
const decoded = addHyphens(Buffer.from(this.#encoder.decode(encodedId)).toString("hex"));
186-
187-
if (!UUIDv7.isValid(decoded)) {
188-
return null;
189-
}
190-
191-
return decoded;
192+
return this.decodeOrThrow(encodedId);
192193
} catch {
193194
return null;
194195
}
@@ -200,10 +201,22 @@ export class UUIDv7 {
200201
* @returns {string} Decoded UUIDv7
201202
*/
202203
decodeOrThrow(encodedId: string) {
203-
const decoded = this.decode(encodedId);
204+
let n = 0n;
205+
206+
for (let i = 0; i < encodedId.length; i++) {
207+
const charIdx = this.#encodeAlphabet.indexOf(encodedId[i]!);
208+
209+
if (charIdx < 0) {
210+
throw new Error(`uuidv7 decode error: invalid character in id [${encodedId}] at index ${i}: "${encodedId[i]}"`);
211+
}
212+
213+
n = n * BigInt(this.#encodeAlphabet.length) + BigInt(charIdx);
214+
}
215+
216+
const decoded = addHyphens(n.toString(16).padStart(32, "0"));
204217

205-
if (!decoded) {
206-
throw new Error(`uuidv7: encoded UUID is not valid: ${encodedId}`);
218+
if (!UUIDv7.isValid(decoded)) {
219+
throw new Error(`uuidv7 decode error: cannot decode [${encodedId}] into a valid UUIDv7`);
207220
}
208221

209222
return decoded;

0 commit comments

Comments
 (0)