Skip to content

Commit d500a31

Browse files
committed
feat(core): add crypto.randomUUID polyfill
1 parent 6a17b95 commit d500a31

File tree

11 files changed

+104
-3
lines changed

11 files changed

+104
-3
lines changed

packages/core/package.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@
6767
"node": "./dist-cjs/submodules/event-streams/index.js",
6868
"import": "./dist-es/submodules/event-streams/index.js",
6969
"require": "./dist-cjs/submodules/event-streams/index.js"
70+
},
71+
"./uuid": {
72+
"types": "./dist-types/submodules/uuid/index.d.ts",
73+
"module": "./dist-es/submodules/uuid/index.js",
74+
"node": "./dist-cjs/submodules/uuid/index.js",
75+
"import": "./dist-es/submodules/uuid/index.js",
76+
"require": "./dist-cjs/submodules/uuid/index.js"
7077
}
7178
},
7279
"author": {
@@ -110,6 +117,8 @@
110117
"./schema.js",
111118
"./serde.d.ts",
112119
"./serde.js",
120+
"./uuid.d.ts",
121+
"./uuid.js",
113122
"dist-*/**"
114123
],
115124
"homepage": "https://github.com/smithy-lang/smithy-typescript/tree/main/packages/core",
@@ -127,6 +136,10 @@
127136
"rimraf": "3.0.2",
128137
"typedoc": "0.23.23"
129138
},
139+
"browser": {
140+
"./dist-es/submodules/uuid/randomUUID": "./dist-es/submodules/uuid/randomUUID.browser"
141+
},
142+
"react-native": {},
130143
"typedoc": {
131144
"entryPoint": "src/index.ts"
132145
},
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { getRandomValues } from "crypto";
2+
import { afterEach, beforeEach, describe, expect, test as it, vi } from "vitest";
3+
4+
describe("randomUUID", () => {
5+
afterEach(() => {
6+
vi.resetModules();
7+
});
8+
9+
it("should call native randomUUID when available", async () => {
10+
const mockUUID = "mocked-uuid";
11+
const nativeRandomUUID = vi.fn(() => mockUUID);
12+
vi.doMock("./randomUUID", () => ({ randomUUID: nativeRandomUUID }));
13+
14+
const { randomUUID } = await import("./index");
15+
const uuid = randomUUID();
16+
17+
expect(nativeRandomUUID).toHaveBeenCalled();
18+
expect(uuid).toBe(mockUUID);
19+
});
20+
21+
describe("when native randomUUID is not available", () => {
22+
let randomUUID: any;
23+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
24+
25+
beforeEach(async () => {
26+
vi.doMock("./randomUUID", () => ({ randomUUID: undefined }));
27+
randomUUID = (await import("./index")).randomUUID;
28+
29+
// Simulate crypto.getRandomValues in test, as it's expected to be available
30+
global.crypto = {
31+
getRandomValues: getRandomValues,
32+
} as any;
33+
});
34+
35+
it("each generation is unique and matches regex", () => {
36+
const uuids = new Set();
37+
const iterations = 10_000;
38+
for (let i = 0; i < iterations; i++) {
39+
const uuid = randomUUID();
40+
expect(uuid).toMatch(UUID_REGEX);
41+
uuids.add(uuid);
42+
}
43+
expect(uuids.size).toBe(iterations);
44+
});
45+
});
46+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { randomUUID as nativeRandomUUID } from "./randomUUID";
2+
3+
export const randomUUID = () => {
4+
if (nativeRandomUUID) {
5+
return nativeRandomUUID();
6+
}
7+
8+
const rnds = new Uint8Array(16);
9+
crypto.getRandomValues(rnds);
10+
11+
// Set version (4) and variant (RFC4122)
12+
rnds[6] = (rnds[6] & 0x0f) | 0x40; // version 4
13+
rnds[8] = (rnds[8] & 0x3f) | 0x80; // variant
14+
15+
return Array.from(rnds.slice(0, 16))
16+
.map((b) => b.toString(16).padStart(2, "0"))
17+
.join("")
18+
.replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, "$1-$2-$3-$4-$5");
19+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// If user has provided their polyfill, like "react-native-random-uuid"
2+
export const randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// ToDo: Merge Node.js and browser implementations after dropping support for Node.js 22.x
2+
import crypto from "crypto";
3+
4+
export const randomUUID = crypto.randomUUID.bind(crypto);

packages/core/tsconfig.cjs.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"@smithy/core/protocols": ["./src/submodules/protocols/index.ts"],
99
"@smithy/core/serde": ["./src/submodules/serde/index.ts"],
1010
"@smithy/core/schema": ["./src/submodules/schema/index.ts"],
11-
"@smithy/core/event-streams": ["./src/submodules/event-streams/index.ts"]
11+
"@smithy/core/event-streams": ["./src/submodules/event-streams/index.ts"],
12+
"@smithy/core/uuid": ["./src/submodules/uuid/index.ts"]
1213
}
1314
},
1415
"extends": "../../tsconfig.cjs.json",

packages/core/tsconfig.es.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"@smithy/core/protocols": ["./src/submodules/protocols/index.ts"],
1010
"@smithy/core/serde": ["./src/submodules/serde/index.ts"],
1111
"@smithy/core/schema": ["./src/submodules/schema/index.ts"],
12-
"@smithy/core/event-streams": ["./src/submodules/event-streams/index.ts"]
12+
"@smithy/core/event-streams": ["./src/submodules/event-streams/index.ts"],
13+
"@smithy/core/uuid": ["./src/submodules/uuid/index.ts"]
1314
}
1415
},
1516
"extends": "../../tsconfig.es.json",

packages/core/tsconfig.types.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"@smithy/core/protocols": ["./src/submodules/protocols/index.ts"],
99
"@smithy/core/serde": ["./src/submodules/serde/index.ts"],
1010
"@smithy/core/schema": ["./src/submodules/schema/index.ts"],
11-
"@smithy/core/event-streams": ["./src/submodules/event-streams/index.ts"]
11+
"@smithy/core/event-streams": ["./src/submodules/event-streams/index.ts"],
12+
"@smithy/core/uuid": ["./src/submodules/uuid/index.ts"]
1213
}
1314
},
1415
"extends": "../../tsconfig.types.json",

packages/core/uuid.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* Do not edit:
3+
* This is a compatibility redirect for contexts that do not understand package.json exports field.
4+
*/
5+
declare module "@smithy/core/uuid" {
6+
export * from "@smithy/core/dist-types/submodules/uuid/index.d";
7+
}

0 commit comments

Comments
 (0)