@sdxc/uuid 2026.10.2 → 2026.10.11
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 +38 -21
- package/dist/index.d.ts +0 -12
- package/dist/index.js +0 -34
- package/dist/v4.d.ts +19 -0
- package/dist/v4.js +23 -0
- package/dist/v7.d.ts +21 -0
- package/dist/v7.js +83 -0
- package/package.json +15 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @sdxc/uuid
|
|
2
2
|
|
|
3
|
-
Validate
|
|
3
|
+
Validate and assert UUIDs behind a branded UUID type, and generate v4 or v7 values.
|
|
4
4
|
|
|
5
5
|
A UUID reaches your code as a `string`, and every layer below re-checks it because the type
|
|
6
6
|
says nothing. This package checks once and hands back a `UUID`: the same string, carrying
|
|
@@ -13,24 +13,38 @@ raw path segment it came from.
|
|
|
13
13
|
npm add @sdxc/uuid
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
[`
|
|
18
|
-
[`@sdxc/typeid`](https://www.npmjs.com/package/@sdxc/typeid) installs alongside it when you
|
|
16
|
+
The generators use the platform's Web Crypto API, available in browsers, Node, Bun, Deno and
|
|
17
|
+
Cloudflare Workers. [`@sdxc/typeid`](https://www.npmjs.com/package/@sdxc/typeid) installs alongside it when you
|
|
19
18
|
want prefixed identifiers built on these values.
|
|
20
19
|
|
|
21
20
|
## Usage
|
|
22
21
|
|
|
23
22
|
### Generate An Identifier
|
|
24
23
|
|
|
24
|
+
Each UUID version is its own entry point, and each exports the same `generateUUID()`. The
|
|
25
|
+
import path picks the version, so switching an identifier to the other one changes only that
|
|
26
|
+
line.
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { generateUUID } from "@sdxc/uuid/v7";
|
|
30
|
+
|
|
31
|
+
let id = generateUUID();
|
|
32
|
+
// "019a6f3e-5c21-7a4b-9d3e-8f1a2b3c4d5e" as UUID
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
A v7 value starts with the millisecond it was created, so identifiers sort in creation order
|
|
36
|
+
and new database rows append to the end of their primary-key index. Import from
|
|
37
|
+
`@sdxc/uuid/v4` instead when the identifier must not reveal when it was created:
|
|
38
|
+
|
|
25
39
|
```typescript
|
|
26
|
-
import { generateUUID } from "@sdxc/uuid";
|
|
40
|
+
import { generateUUID } from "@sdxc/uuid/v4";
|
|
27
41
|
|
|
28
42
|
let id = generateUUID();
|
|
29
43
|
// "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d" as UUID
|
|
30
44
|
```
|
|
31
45
|
|
|
32
|
-
|
|
33
|
-
|
|
46
|
+
Either way the result is already branded, so it flows into anything typed as `UUID` without a
|
|
47
|
+
cast.
|
|
34
48
|
|
|
35
49
|
### Narrow A String You Received
|
|
36
50
|
|
|
@@ -112,21 +126,23 @@ Narrows a string to `UUID` for the rest of the enclosing scope, throwing when it
|
|
|
112
126
|
`InvalidUUIDTypeError` for a non-string, `InvalidUUIDLengthError` for a string that is not 36
|
|
113
127
|
characters, and `InvalidUUIDFormatError` for 36 characters in the wrong shape.
|
|
114
128
|
|
|
115
|
-
### `generateUUID(): UUID`
|
|
129
|
+
### `generateUUID(): UUID` from `@sdxc/uuid/v7`
|
|
130
|
+
|
|
131
|
+
Returns a new version 7 UUID as defined by [RFC 9562](https://www.rfc-editor.org/rfc/rfc9562):
|
|
132
|
+
a 48-bit millisecond timestamp, a 12-bit counter and 62 random bits. Values sort in the order
|
|
133
|
+
they were generated, including several created in the same millisecond, which matters on
|
|
134
|
+
runtimes such as Cloudflare Workers where `Date.now()` holds still until the next I/O. The
|
|
135
|
+
guarantee holds within one process or isolate; values from two processes in the same
|
|
136
|
+
millisecond are unique and share no defined order.
|
|
116
137
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
no ordering.
|
|
138
|
+
The timestamp is readable by anyone holding the value, including through a
|
|
139
|
+
[`@sdxc/typeid`](https://www.npmjs.com/package/@sdxc/typeid) prefix. Use it for identifiers
|
|
140
|
+
whose creation time is fine to expose, and keep secrets on random values.
|
|
121
141
|
|
|
122
|
-
### `
|
|
142
|
+
### `generateUUID(): UUID` from `@sdxc/uuid/v4`
|
|
123
143
|
|
|
124
|
-
Returns a new version
|
|
125
|
-
|
|
126
|
-
[`@sdxc/typeid`](https://www.npmjs.com/package/@sdxc/typeid) is going to prefix the result
|
|
127
|
-
and something reads that prefixed id's order — a paginated listing, a table with no separate
|
|
128
|
-
`created_at` to sort by. Reach for `generateUUID` instead when the id must not reveal when it
|
|
129
|
-
was minted, such as a value handed to someone outside the system.
|
|
144
|
+
Returns a new version 4 UUID from `crypto.randomUUID()`: 122 random bits, so two values carry
|
|
145
|
+
no ordering and none reveals when it was created.
|
|
130
146
|
|
|
131
147
|
### Errors
|
|
132
148
|
|
|
@@ -198,7 +214,7 @@ takes a `UUID` and a prefix and encodes them as one sortable, self-describing st
|
|
|
198
214
|
|
|
199
215
|
```typescript
|
|
200
216
|
import { TypeID } from "@sdxc/typeid";
|
|
201
|
-
import { generateUUID } from "@sdxc/uuid";
|
|
217
|
+
import { generateUUID } from "@sdxc/uuid/v7";
|
|
202
218
|
|
|
203
219
|
let accountId = TypeID.fromUUID("account", generateUUID());
|
|
204
220
|
|
|
@@ -207,7 +223,8 @@ accountId.toString();
|
|
|
207
223
|
```
|
|
208
224
|
|
|
209
225
|
It asks for a `UUID` rather than a `string`, so the value you generate here is accepted and a
|
|
210
|
-
random 36 characters is not.
|
|
226
|
+
random 36 characters is not. TypeID keeps the UUID's sort order, so a v7 identifier stays
|
|
227
|
+
time-ordered once it is prefixed.
|
|
211
228
|
|
|
212
229
|
## Versioning
|
|
213
230
|
|
package/dist/index.d.ts
CHANGED
|
@@ -9,16 +9,4 @@ export type UUID = BaseUUID & {
|
|
|
9
9
|
};
|
|
10
10
|
export declare function isUUID(value: string): value is UUID;
|
|
11
11
|
export declare function assertUUID(value: string): asserts value is UUID;
|
|
12
|
-
export declare function generateUUID(): UUID;
|
|
13
|
-
/**
|
|
14
|
-
* Generates a UUID version 7: a 48-bit big-endian Unix millisecond timestamp
|
|
15
|
-
* followed by 74 random bits, so values sort the way they were created without
|
|
16
|
-
* a separate ordering column.
|
|
17
|
-
*
|
|
18
|
-
* @returns A UUIDv7, narrowed to {@link UUID}.
|
|
19
|
-
* @example
|
|
20
|
-
* let id = generateUUIDv7();
|
|
21
|
-
* // "018f4d2e-...": sorts after a UUIDv7 minted a millisecond earlier
|
|
22
|
-
*/
|
|
23
|
-
export declare function generateUUIDv7(): UUID;
|
|
24
12
|
export { InvalidUUIDFormatError, InvalidUUIDLengthError, InvalidUUIDTypeError };
|
package/dist/index.js
CHANGED
|
@@ -21,38 +21,4 @@ export function assertUUID(value) {
|
|
|
21
21
|
throw new InvalidUUIDFormatError(value);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
export function generateUUID() {
|
|
25
|
-
let id = crypto.randomUUID();
|
|
26
|
-
assertUUID(id);
|
|
27
|
-
return id;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Generates a UUID version 7: a 48-bit big-endian Unix millisecond timestamp
|
|
31
|
-
* followed by 74 random bits, so values sort the way they were created without
|
|
32
|
-
* a separate ordering column.
|
|
33
|
-
*
|
|
34
|
-
* @returns A UUIDv7, narrowed to {@link UUID}.
|
|
35
|
-
* @example
|
|
36
|
-
* let id = generateUUIDv7();
|
|
37
|
-
* // "018f4d2e-...": sorts after a UUIDv7 minted a millisecond earlier
|
|
38
|
-
*/
|
|
39
|
-
export function generateUUIDv7() {
|
|
40
|
-
let timestamp = BigInt(Date.now());
|
|
41
|
-
let random = crypto.getRandomValues(new Uint8Array(10));
|
|
42
|
-
let bytes = new Uint8Array(16);
|
|
43
|
-
bytes[0] = Number((timestamp >> 40n) & 0xffn);
|
|
44
|
-
bytes[1] = Number((timestamp >> 32n) & 0xffn);
|
|
45
|
-
bytes[2] = Number((timestamp >> 24n) & 0xffn);
|
|
46
|
-
bytes[3] = Number((timestamp >> 16n) & 0xffn);
|
|
47
|
-
bytes[4] = Number((timestamp >> 8n) & 0xffn);
|
|
48
|
-
bytes[5] = Number(timestamp & 0xffn);
|
|
49
|
-
bytes[6] = 0x70 | (random[0] & 0x0f);
|
|
50
|
-
bytes[7] = random[1];
|
|
51
|
-
bytes[8] = 0x80 | (random[2] & 0x3f);
|
|
52
|
-
bytes.set(random.subarray(3), 9);
|
|
53
|
-
let hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
54
|
-
let id = `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
55
|
-
assertUUID(id);
|
|
56
|
-
return id;
|
|
57
|
-
}
|
|
58
24
|
export { InvalidUUIDFormatError, InvalidUUIDLengthError, InvalidUUIDTypeError };
|
package/dist/v4.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Random UUID generation: version 4 values from the platform's `crypto.randomUUID()`,
|
|
3
|
+
* narrowed to the branded `UUID` type. Every free bit is random, so a value reveals nothing,
|
|
4
|
+
* including when it was created.
|
|
5
|
+
*
|
|
6
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
7
|
+
* @copyright Sergio Xalambrí 2026
|
|
8
|
+
*/
|
|
9
|
+
import type { UUID } from "./index.js";
|
|
10
|
+
/**
|
|
11
|
+
* Generates a version 4 UUID: 122 random bits, so values carry no ordering and expose no
|
|
12
|
+
* creation time.
|
|
13
|
+
*
|
|
14
|
+
* @returns A UUIDv4, narrowed to {@link UUID}.
|
|
15
|
+
* @example
|
|
16
|
+
* let id = generateUUID();
|
|
17
|
+
* // "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
|
|
18
|
+
*/
|
|
19
|
+
export declare function generateUUID(): UUID;
|
package/dist/v4.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Random UUID generation: version 4 values from the platform's `crypto.randomUUID()`,
|
|
3
|
+
* narrowed to the branded `UUID` type. Every free bit is random, so a value reveals nothing,
|
|
4
|
+
* including when it was created.
|
|
5
|
+
*
|
|
6
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
7
|
+
* @copyright Sergio Xalambrí 2026
|
|
8
|
+
*/
|
|
9
|
+
import { assertUUID } from "./index.js";
|
|
10
|
+
/**
|
|
11
|
+
* Generates a version 4 UUID: 122 random bits, so values carry no ordering and expose no
|
|
12
|
+
* creation time.
|
|
13
|
+
*
|
|
14
|
+
* @returns A UUIDv4, narrowed to {@link UUID}.
|
|
15
|
+
* @example
|
|
16
|
+
* let id = generateUUID();
|
|
17
|
+
* // "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
|
|
18
|
+
*/
|
|
19
|
+
export function generateUUID() {
|
|
20
|
+
let id = crypto.randomUUID();
|
|
21
|
+
assertUUID(id);
|
|
22
|
+
return id;
|
|
23
|
+
}
|
package/dist/v7.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time-ordered UUID generation: version 7 values whose string order follows the order they
|
|
3
|
+
* were generated in, including several in the same millisecond, so primary keys append to
|
|
4
|
+
* their index and an id alone can page a listing.
|
|
5
|
+
*
|
|
6
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
7
|
+
* @copyright Sergio Xalambrí 2026
|
|
8
|
+
*/
|
|
9
|
+
import type { UUID } from "./index.js";
|
|
10
|
+
/**
|
|
11
|
+
* Generates a version 7 UUID (RFC 9562): a 48-bit Unix millisecond timestamp, a 12-bit
|
|
12
|
+
* counter that increments within a millisecond, and 62 random bits. Each value sorts after
|
|
13
|
+
* the previous one from the same isolate, even while `Date.now()` stays put.
|
|
14
|
+
*
|
|
15
|
+
* @returns A UUIDv7, narrowed to {@link UUID}.
|
|
16
|
+
* @example
|
|
17
|
+
* let first = generateUUID();
|
|
18
|
+
* let second = generateUUID();
|
|
19
|
+
* first < second; // true
|
|
20
|
+
*/
|
|
21
|
+
export declare function generateUUID(): UUID;
|
package/dist/v7.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time-ordered UUID generation: version 7 values whose string order follows the order they
|
|
3
|
+
* were generated in, including several in the same millisecond, so primary keys append to
|
|
4
|
+
* their index and an id alone can page a listing.
|
|
5
|
+
*
|
|
6
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
7
|
+
* @copyright Sergio Xalambrí 2026
|
|
8
|
+
*/
|
|
9
|
+
import { assertUUID } from "./index.js";
|
|
10
|
+
/** Largest value the 12-bit `rand_a` counter holds before the timestamp has to advance. */
|
|
11
|
+
const COUNTER_MAX = 0xfff;
|
|
12
|
+
/**
|
|
13
|
+
* The last timestamp written and the counter beside it, shared by every call in the isolate.
|
|
14
|
+
* Keeping the timestamp from going backwards is what makes each value sort after the last.
|
|
15
|
+
*/
|
|
16
|
+
const CLOCK = { timestamp: -1, counter: 0 };
|
|
17
|
+
/**
|
|
18
|
+
* Generates a version 7 UUID (RFC 9562): a 48-bit Unix millisecond timestamp, a 12-bit
|
|
19
|
+
* counter that increments within a millisecond, and 62 random bits. Each value sorts after
|
|
20
|
+
* the previous one from the same isolate, even while `Date.now()` stays put.
|
|
21
|
+
*
|
|
22
|
+
* @returns A UUIDv7, narrowed to {@link UUID}.
|
|
23
|
+
* @example
|
|
24
|
+
* let first = generateUUID();
|
|
25
|
+
* let second = generateUUID();
|
|
26
|
+
* first < second; // true
|
|
27
|
+
*/
|
|
28
|
+
export function generateUUID() {
|
|
29
|
+
advance(Date.now());
|
|
30
|
+
let random = crypto.getRandomValues(new Uint8Array(8));
|
|
31
|
+
let bytes = new Uint8Array(16);
|
|
32
|
+
let timestamp = CLOCK.timestamp;
|
|
33
|
+
for (let index = 5; index >= 0; index--) {
|
|
34
|
+
bytes[index] = timestamp % 256;
|
|
35
|
+
timestamp = Math.floor(timestamp / 256);
|
|
36
|
+
}
|
|
37
|
+
bytes[6] = 0x70 | (CLOCK.counter >> 8);
|
|
38
|
+
bytes[7] = CLOCK.counter & 0xff;
|
|
39
|
+
bytes[8] = 0x80 | (random[0] & 0x3f);
|
|
40
|
+
bytes.set(random.subarray(1), 9);
|
|
41
|
+
let id = format(bytes);
|
|
42
|
+
assertUUID(id);
|
|
43
|
+
return id;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Moves the clock to `now`, or past the last timestamp when `now` is not later than it.
|
|
47
|
+
* A new millisecond seeds the counter randomly below its midpoint, leaving at least 2048
|
|
48
|
+
* increments; exhausting them moves the timestamp one millisecond ahead, as RFC 9562 allows.
|
|
49
|
+
*
|
|
50
|
+
* @param now Current Unix time in milliseconds.
|
|
51
|
+
*/
|
|
52
|
+
function advance(now) {
|
|
53
|
+
if (now > CLOCK.timestamp) {
|
|
54
|
+
CLOCK.timestamp = now;
|
|
55
|
+
CLOCK.counter = seed();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
CLOCK.counter++;
|
|
59
|
+
if (CLOCK.counter <= COUNTER_MAX)
|
|
60
|
+
return;
|
|
61
|
+
CLOCK.timestamp++;
|
|
62
|
+
CLOCK.counter = seed();
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Picks a random starting counter with its top bit clear, so the first value of a
|
|
66
|
+
* millisecond is unpredictable and still leaves half the counter's range to increment into.
|
|
67
|
+
*
|
|
68
|
+
* @returns An integer in `[0, 0x7ff]`.
|
|
69
|
+
*/
|
|
70
|
+
function seed() {
|
|
71
|
+
let [value] = crypto.getRandomValues(new Uint16Array(1));
|
|
72
|
+
return value & 0x7ff;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Writes 16 bytes as the canonical lowercase `8-4-4-4-12` UUID string.
|
|
76
|
+
*
|
|
77
|
+
* @param bytes The UUID's bytes in network order.
|
|
78
|
+
* @returns The hyphenated hex form.
|
|
79
|
+
*/
|
|
80
|
+
function format(bytes) {
|
|
81
|
+
let hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
82
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
83
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdxc/uuid",
|
|
3
|
-
"version": "2026.10.
|
|
4
|
-
"description": "Validate
|
|
3
|
+
"version": "2026.10.11",
|
|
4
|
+
"description": "Validate and assert UUIDs behind a branded UUID type, and generate v4 or v7 values",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
|
-
".": "./dist/index.js"
|
|
8
|
+
".": "./dist/index.js",
|
|
9
|
+
"./v4": "./dist/v4.js",
|
|
10
|
+
"./v7": "./dist/v7.js"
|
|
9
11
|
},
|
|
10
|
-
"gitHead": "
|
|
12
|
+
"gitHead": "6ac77f0839a66981ca666a16f5cf37a5473df2b1",
|
|
11
13
|
"publishConfig": {
|
|
12
14
|
"access": "public"
|
|
13
15
|
},
|
|
@@ -15,5 +17,14 @@
|
|
|
15
17
|
"type": "git",
|
|
16
18
|
"url": "git+https://github.com/sergiodxa/monorepo.git",
|
|
17
19
|
"directory": "packages/uuid"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://sdxc.sergiodxa.com/api/uuid",
|
|
22
|
+
"author": {
|
|
23
|
+
"name": "Sergio Xalambrí",
|
|
24
|
+
"url": "https://sergiodxa.com"
|
|
25
|
+
},
|
|
26
|
+
"funding": {
|
|
27
|
+
"type": "github",
|
|
28
|
+
"url": "https://github.com/sponsors/sergiodxa"
|
|
18
29
|
}
|
|
19
30
|
}
|