Skip to content

Commit 0159c94

Browse files
bmuschkotimtebeekknutwannheden
authored
UUID generation fallback for Node version pre-14.17.0 (#6495)
* UUID generation fallback for Node version pre-14.17.0 * Apply suggestions from code review * Use module-load-time selection for performance * Polish --------- Co-authored-by: Tim te Beek <tim@moderne.io> Co-authored-by: Knut Wannheden <knut@moderne.io>
1 parent fe57f2f commit 0159c94

2 files changed

Lines changed: 76 additions & 3 deletions

File tree

rewrite-javascript/rewrite/src/uuid.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,34 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import {randomUUID} from "crypto";
16+
import * as crypto from "crypto";
1717

1818
export type UUID = string;
1919

20-
export function randomId(): UUID {
21-
return randomUUID();
20+
/**
21+
* Fallback UUID v4 generator for Node versions before 14.17.0
22+
*/
23+
function fallbackRandomId(): UUID {
24+
const bytes = crypto.randomBytes(16);
25+
26+
// Set version 4 bits (0100xxxx in byte 6)
27+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
28+
29+
// Set variant bits (10xxxxxx in byte 8)
30+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
31+
32+
// Convert to hex string with dashes
33+
const hex = bytes.toString('hex');
34+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
2235
}
36+
37+
/**
38+
* Generate a random UUID v4.
39+
*
40+
* Uses native crypto.randomUUID() on Node 14.17.0+, falls back to
41+
* crypto.randomBytes() on older versions. The implementation is
42+
* selected once at module load time to avoid per-call overhead.
43+
*/
44+
export const randomId: () => UUID = typeof crypto.randomUUID === 'function'
45+
? crypto.randomUUID
46+
: fallbackRandomId;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2026 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {describe} from "@jest/globals";
18+
import {randomId} from "../src/uuid";
19+
20+
describe("randomId", () => {
21+
test("generates valid UUID v4 format", () => {
22+
const uuid = randomId();
23+
// UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
24+
// where y is one of 8, 9, a, or b
25+
const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
26+
expect(uuid).toMatch(uuidV4Regex);
27+
});
28+
29+
test("generates unique IDs on each call", () => {
30+
const ids = new Set<string>();
31+
const iterations = 1000;
32+
33+
for (let i = 0; i < iterations; i++) {
34+
ids.add(randomId());
35+
}
36+
37+
expect(ids.size).toBe(iterations);
38+
});
39+
40+
test("returns string type", () => {
41+
const uuid = randomId();
42+
expect(typeof uuid).toBe("string");
43+
});
44+
45+
test("returns 36 character string", () => {
46+
const uuid = randomId();
47+
expect(uuid.length).toBe(36);
48+
});
49+
});

0 commit comments

Comments
 (0)