Skip to content

Commit 185595a

Browse files
committed
feat(uuid): implement support for UUID V6 - refs #6414
1 parent b6e6bf0 commit 185595a

File tree

7 files changed

+258
-2
lines changed

7 files changed

+258
-2
lines changed

_tools/check_mod_exports.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,11 @@ for await (
5050
/uuid(\/|\\)v3\.ts$/,
5151
/uuid(\/|\\)v4\.ts$/,
5252
/uuid(\/|\\)v5\.ts$/,
53+
/uuid(\/|\\)v6\.ts$/,
5354
/uuid(\/|\\)v7\.ts$/,
5455
/yaml(\/|\\)schema\.ts$/,
5556
/test\.ts$/,
57+
/_bench\.ts$/,
5658
/\.d\.ts$/,
5759
/(\/|\\)_/,
5860
/mod\.ts$/,

uuid/common.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export function isNil(id: string): boolean {
4141
* ```
4242
*/
4343
export function validate(uuid: string): boolean {
44-
return /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i
44+
return /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-6][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i
4545
.test(
4646
uuid,
4747
);

uuid/common_test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Deno.test("version() detects the RFC version of a UUID", () => {
2323
assertEquals(version("109156be-c4fb-41ea-b1b4-efe1671c5836"), 4);
2424
assertEquals(version("a981a0c2-68b1-35dc-bcfc-296e52ab01ec"), 3);
2525
assertEquals(version("90123e1c-7512-523e-bb28-76fab9f2f73d"), 5);
26+
assertEquals(version("1efed817-4119-6c50-974a-e638d7b30e9e"), 6);
2627
assertThrows(() => version(""));
2728
assertThrows(() => version("not a UUID"));
2829
assertThrows(() => version("00000000000000000000000000000000"));

uuid/deno.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"./v3": "./v3.ts",
1010
"./v4": "./v4.ts",
1111
"./v5": "./v5.ts",
12+
"./unstable-v6": "./unstable_v6.ts",
1213
"./unstable-v7": "./unstable_v7.ts"
1314
}
1415
}

uuid/mod.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
/**
55
* Generators and validators for
66
* {@link https://www.rfc-editor.org/rfc/rfc9562.html | RFC 9562} UUIDs for
7-
* versions v1, v3, v4 and v5.
7+
* versions v1, v3, v4, v5 and v6.
88
*
99
* Use the built-in
1010
* {@linkcode https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID | crypto.randomUUID()}

uuid/unstable_v6.ts

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// Copyright 2018-2025 the Deno authors. MIT license.
2+
// This module is browser compatible.
3+
4+
import { bytesToUuid } from "./_common.ts";
5+
6+
const UUID_RE =
7+
/^[0-9a-f]{8}-[0-9a-f]{4}-6[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
8+
9+
/**
10+
* Determines whether a string is a valid
11+
* {@link https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-6 | UUIDv6}.
12+
*
13+
* @param id UUID value.
14+
*
15+
* @returns `true` if the string is a valid UUIDv6, otherwise `false`.
16+
*
17+
* @example Usage
18+
* ```ts
19+
* import { validate } from "@std/uuid/unstable-v6";
20+
* import { assert, assertFalse } from "@std/assert";
21+
*
22+
* assert(validate("1efed67d-d966-6490-8b9a-755015853480"));
23+
* assertFalse(validate("1efed67d-d966-1490-8b9a-755015853480"));
24+
* ```
25+
*/
26+
export function validate(id: string): boolean {
27+
return UUID_RE.test(id);
28+
}
29+
30+
let _nodeId: number[];
31+
let _clockseq: number;
32+
33+
let _lastMSecs = 0;
34+
let _lastNSecs = 0;
35+
36+
/** Options for {@linkcode generate}. */
37+
export interface GenerateOptions {
38+
/**
39+
* An array of 6 bytes that represents a 48-bit IEEE 802 MAC address.
40+
*
41+
* @see {@link https://www.rfc-editor.org/rfc/rfc4122#section-4.1.6}
42+
*/
43+
node?: number[];
44+
/**
45+
* A 14-bit value used to avoid duplicates that could arise when the clock is
46+
* set backwards in time or if the node ID changes (0 - 16383).
47+
*
48+
* @see {@link https://www.rfc-editor.org/rfc/rfc4122#section-4.1.5}
49+
*/
50+
clockseq?: number;
51+
/**
52+
* The number of milliseconds since the Unix epoch (January 1, 1970).
53+
*
54+
* @see {@link https://www.rfc-editor.org/rfc/rfc4122#section-4.1.4}
55+
*/
56+
msecs?: number;
57+
/**
58+
* The number of nanoseconds to add to {@linkcode GenerateOptions.msecs}
59+
* (0 - 10,000).
60+
*
61+
* @see {@link https://www.rfc-editor.org/rfc/rfc4122#section-4.1.4}
62+
*/
63+
nsecs?: number;
64+
/** An array of 16 random bytes (0 - 255). */
65+
random?: number[];
66+
/**
67+
* A function that returns an array of 16 random bytes (0 - 255).
68+
* Alternative to {@linkcode GenerateOptions.random}.
69+
*/
70+
rng?: () => number[];
71+
}
72+
73+
/**
74+
* Generates a
75+
* {@link https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-6 | UUIDv6}.
76+
*
77+
* @param options Can use RFC time sequence values as overwrites.
78+
*
79+
* @returns Returns a UUIDv6 string.
80+
*
81+
* @example Usage
82+
* ```ts
83+
* import { generate, validate } from "@std/uuid/unstable-v6";
84+
* import { assert } from "@std/assert";
85+
*
86+
* const options = {
87+
* node: [0x01, 0x23, 0x45, 0x67, 0x89, 0xab],
88+
* clockseq: 0x1234,
89+
* msecs: new Date("2011-11-01").getTime(),
90+
* nsecs: 5678,
91+
* };
92+
*
93+
* const uuid = generate(options);
94+
* assert(validate(uuid as string));
95+
* ```
96+
*/
97+
export function generate(options: GenerateOptions = {}): string {
98+
let i = 0;
99+
const b: number[] = [];
100+
101+
let { node = _nodeId, clockseq = _clockseq } = options;
102+
103+
if (node === undefined || clockseq === undefined) {
104+
// deno-lint-ignore no-explicit-any
105+
const seedBytes: any = options.random ??
106+
options.rng ??
107+
crypto.getRandomValues(new Uint8Array(16));
108+
109+
if (node === undefined) {
110+
node = _nodeId = [
111+
seedBytes[0] | 0x01,
112+
seedBytes[1],
113+
seedBytes[2],
114+
seedBytes[3],
115+
seedBytes[4],
116+
seedBytes[5],
117+
];
118+
}
119+
120+
if (clockseq === undefined) {
121+
clockseq = _clockseq = ((seedBytes[6] << 8) | seedBytes[7]) & 0x3fff;
122+
}
123+
}
124+
125+
let { msecs = new Date().getTime(), nsecs = _lastNSecs + 1 } = options;
126+
127+
const dt = msecs - _lastMSecs + (nsecs - _lastNSecs) / 10000;
128+
129+
if (dt < 0 && options.clockseq === undefined) {
130+
clockseq = (clockseq + 1) & 0x3fff;
131+
}
132+
133+
if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) {
134+
nsecs = 0;
135+
}
136+
137+
if (nsecs > 10000) {
138+
throw new Error("Can't create more than 10M uuids/sec");
139+
}
140+
141+
if (node.length !== 6) {
142+
throw new Error(
143+
"Cannot create UUID: the node option must be an array of 6 bytes",
144+
);
145+
}
146+
147+
_lastMSecs = msecs;
148+
_lastNSecs = nsecs;
149+
_clockseq = clockseq;
150+
151+
// We have to add this value because "msecs" here is the number of
152+
// milliseconds since January 1, 1970, not since October 15, 1582.
153+
// This is also the milliseconds from October 15, 1582 to January 1, 1970.
154+
msecs += 12219292800000;
155+
156+
const th = ((msecs / 0x10000000) * 10000) & 0xffffffff;
157+
b[i++] = (th >>> 24) & 0xff;
158+
b[i++] = (th >>> 16) & 0xff;
159+
b[i++] = (th >>> 8) & 0xff;
160+
b[i++] = th & 0xff;
161+
162+
const tml = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x10000000;
163+
b[i++] = (tml >>> 20) & 0xff;
164+
b[i++] = (tml >>> 12) & 0xff;
165+
166+
b[i++] = (tml >>> 8) & 0xf | 0x60;
167+
b[i++] = tml & 0xff;
168+
169+
b[i++] = (clockseq >>> 8) | 0x80;
170+
171+
b[i++] = clockseq & 0xff;
172+
173+
for (let n = 0; n < 6; ++n) {
174+
b[i + n] = node[n]!;
175+
}
176+
177+
return bytesToUuid(b);
178+
}

uuid/unstable_v6_test.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2018-2025 the Deno authors. MIT license.
2+
import { assert, assertEquals, assertThrows } from "@std/assert";
3+
import { generate, validate } from "./unstable_v6.ts";
4+
5+
Deno.test("validate() checks if a string is a valid v6 UUID", () => {
6+
const u = generate();
7+
const t = "1efed67d-d966-6490-8b9a-755015853480";
8+
const n = "1efed67d-d966-1490-8b9a-755015853480";
9+
10+
assert(validate(u as string), `generated ${u} should be valid`);
11+
assert(validate(t), `${t} should be valid`);
12+
assert(!validate(n), `${n} should not be valid`);
13+
});
14+
15+
Deno.test("generate() generates a non-empty string", () => {
16+
const u1 = generate();
17+
const u2 = generate({
18+
msecs: new Date("2011-11-01").getTime(),
19+
nsecs: 10000,
20+
});
21+
22+
assertEquals(typeof u1, "string", "returns a string");
23+
assert(u1 !== "", "return string is not empty");
24+
assertEquals(typeof u2, "string", "returns a string");
25+
assert(u2 !== "", "return string is not empty");
26+
});
27+
28+
Deno.test("generate() generates UUIDs in version 6 format", () => {
29+
for (let i = 0; i < 10000; i++) {
30+
const u = generate() as string;
31+
assert(validate(u), `${u} is not a valid uuid v6`);
32+
}
33+
});
34+
35+
Deno.test("generate() can generate a static v6 UUID", () => {
36+
const v6options = {
37+
node: [0x01, 0x23, 0x45, 0x67, 0x89, 0xab],
38+
clockseq: 0x385c,
39+
msecs: new Date("2011-11-18T21:25:33.573+00:00").getTime(),
40+
nsecs: 5442,
41+
};
42+
const u = generate(v6options);
43+
assertEquals(u, "1e1122bd-9428-6892-b85c-0123456789ab");
44+
});
45+
46+
Deno.test("generate() throws when node is passed with less than 6 numbers", () => {
47+
assertThrows(
48+
() => {
49+
generate({ node: [0x01, 0x23, 0x45, 0x67, 0x89] });
50+
},
51+
Error,
52+
"Cannot create UUID: the node option must be an array of 6 bytes",
53+
);
54+
});
55+
56+
Deno.test("generate() throws when node is passed with more than 6 numbers", () => {
57+
assertThrows(
58+
() => {
59+
generate({ node: [0x01, 0x23, 0x45, 0x67, 0x89, 0x89, 0x89] });
60+
},
61+
Error,
62+
"Cannot create UUID: the node option must be an array of 6 bytes",
63+
);
64+
});
65+
66+
Deno.test("generate() throws when create more than 10M uuids/sec", () => {
67+
assertThrows(
68+
() => {
69+
generate({ nsecs: 10001 });
70+
},
71+
Error,
72+
"Can't create more than 10M uuids/sec",
73+
);
74+
});

0 commit comments

Comments
 (0)