From 1cf3bb60b4f579b93159f92349199f288c13b954 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Fri, 19 Sep 2025 17:20:01 +0000 Subject: [PATCH 1/7] feat(uuid): add '@smithy/uuid' with v4 polyfill --- packages/uuid/.eslintrc.json | 6 + packages/uuid/.gitignore | 8 + packages/uuid/CHANGELOG.md | 1 + packages/uuid/LICENSE | 201 ++++++++++++++++++++++++ packages/uuid/README.md | 10 ++ packages/uuid/package.json | 66 ++++++++ packages/uuid/src/index.ts | 1 + packages/uuid/src/randomUUID.browser.ts | 1 + packages/uuid/src/randomUUID.native.ts | 2 + packages/uuid/src/randomUUID.ts | 4 + packages/uuid/src/v4.spec.ts | 46 ++++++ packages/uuid/src/v4.ts | 19 +++ packages/uuid/tsconfig.cjs.json | 9 ++ packages/uuid/tsconfig.es.json | 10 ++ packages/uuid/tsconfig.types.json | 9 ++ packages/uuid/vitest.config.mts | 9 ++ yarn.lock | 13 ++ 17 files changed, 415 insertions(+) create mode 100644 packages/uuid/.eslintrc.json create mode 100644 packages/uuid/.gitignore create mode 100644 packages/uuid/CHANGELOG.md create mode 100644 packages/uuid/LICENSE create mode 100644 packages/uuid/README.md create mode 100644 packages/uuid/package.json create mode 100644 packages/uuid/src/index.ts create mode 100644 packages/uuid/src/randomUUID.browser.ts create mode 100644 packages/uuid/src/randomUUID.native.ts create mode 100644 packages/uuid/src/randomUUID.ts create mode 100644 packages/uuid/src/v4.spec.ts create mode 100644 packages/uuid/src/v4.ts create mode 100644 packages/uuid/tsconfig.cjs.json create mode 100644 packages/uuid/tsconfig.es.json create mode 100644 packages/uuid/tsconfig.types.json create mode 100644 packages/uuid/vitest.config.mts diff --git a/packages/uuid/.eslintrc.json b/packages/uuid/.eslintrc.json new file mode 100644 index 00000000000..a4ae1380eb4 --- /dev/null +++ b/packages/uuid/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "plugins": ["eslint-plugin-tsdoc"], + "rules": { + "tsdoc/syntax": "warn" + } +} diff --git a/packages/uuid/.gitignore b/packages/uuid/.gitignore new file mode 100644 index 00000000000..3d1714c9806 --- /dev/null +++ b/packages/uuid/.gitignore @@ -0,0 +1,8 @@ +/node_modules/ +/build/ +/coverage/ +/docs/ +*.tsbuildinfo +*.tgz +*.log +package-lock.json diff --git a/packages/uuid/CHANGELOG.md b/packages/uuid/CHANGELOG.md new file mode 100644 index 00000000000..420e6f23d0e --- /dev/null +++ b/packages/uuid/CHANGELOG.md @@ -0,0 +1 @@ +# Change Log diff --git a/packages/uuid/LICENSE b/packages/uuid/LICENSE new file mode 100644 index 00000000000..7b6491ba787 --- /dev/null +++ b/packages/uuid/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/packages/uuid/README.md b/packages/uuid/README.md new file mode 100644 index 00000000000..d7b3d4ea2ba --- /dev/null +++ b/packages/uuid/README.md @@ -0,0 +1,10 @@ +# @smithy/uuid + +[![NPM version](https://img.shields.io/npm/v/@smithy/uuid/latest.svg)](https://www.npmjs.com/package/@smithy/uuid) +[![NPM downloads](https://img.shields.io/npm/dm/@smithy/uuid.svg)](https://www.npmjs.com/package/@smithy/uuid) + +> An internal package + +## Usage + +You probably shouldn't, at least directly. diff --git a/packages/uuid/package.json b/packages/uuid/package.json new file mode 100644 index 00000000000..b3f8a00e36a --- /dev/null +++ b/packages/uuid/package.json @@ -0,0 +1,66 @@ +{ + "name": "@smithy/uuid", + "version": "1.0.0", + "description": "Polyfill for generating UUID v4", + "dependencies": { + "tslib": "^2.6.2" + }, + "scripts": { + "build": "concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types && yarn build:types:downlevel'", + "build:cjs": "node ../../scripts/inline uuid", + "build:es": "yarn g:tsc -p tsconfig.es.json", + "build:types": "yarn g:tsc -p tsconfig.types.json", + "build:types:downlevel": "rimraf dist-types/ts3.4 && downlevel-dts dist-types dist-types/ts3.4", + "stage-release": "rimraf ./.release && yarn pack && mkdir ./.release && tar zxvf ./package.tgz --directory ./.release && rm ./package.tgz", + "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo || exit 0", + "lint": "eslint -c ../../.eslintrc.js \"src/**/*.ts\"", + "format": "prettier --config ../../prettier.config.js --ignore-path ../../.prettierignore --write \"**/*.{ts,md,json}\"", + "test": "yarn g:vitest run", + "test:watch": "yarn g:vitest watch" + }, + "author": { + "name": "AWS SDK for JavaScript Team", + "url": "https://aws.amazon.com/javascript/" + }, + "license": "Apache-2.0", + "sideEffects": false, + "main": "./dist-cjs/index.js", + "module": "./dist-es/index.js", + "types": "./dist-types/index.d.ts", + "engines": { + "node": ">=18.0.0" + }, + "typesVersions": { + "<4.0": { + "dist-types/*": [ + "dist-types/ts3.4/*" + ] + } + }, + "files": [ + "dist-*/**" + ], + "homepage": "https://github.com/smithy-lang/smithy-typescript/tree/main/packages/uuid", + "repository": { + "type": "git", + "url": "https://github.com/smithy-lang/smithy-typescript.git", + "directory": "packages/uuid" + }, + "browser": { + "./dist-es/randomUUID": "./dist-es/randomUUID.browser" + }, + "react-native": {}, + "devDependencies": { + "@types/node": "^18.11.9", + "concurrently": "7.0.0", + "downlevel-dts": "0.10.1", + "rimraf": "3.0.2", + "typedoc": "0.23.23" + }, + "typedoc": { + "entryPoint": "src/index.ts" + }, + "publishConfig": { + "directory": ".release/package" + } +} diff --git a/packages/uuid/src/index.ts b/packages/uuid/src/index.ts new file mode 100644 index 00000000000..7d673783113 --- /dev/null +++ b/packages/uuid/src/index.ts @@ -0,0 +1 @@ +export * from "./v4"; diff --git a/packages/uuid/src/randomUUID.browser.ts b/packages/uuid/src/randomUUID.browser.ts new file mode 100644 index 00000000000..c2760b418cd --- /dev/null +++ b/packages/uuid/src/randomUUID.browser.ts @@ -0,0 +1 @@ +export const randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto); diff --git a/packages/uuid/src/randomUUID.native.ts b/packages/uuid/src/randomUUID.native.ts new file mode 100644 index 00000000000..01da2fdda2d --- /dev/null +++ b/packages/uuid/src/randomUUID.native.ts @@ -0,0 +1,2 @@ +// If user has provided their polyfill, like "react-native-random-uuid" +export const randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto); diff --git a/packages/uuid/src/randomUUID.ts b/packages/uuid/src/randomUUID.ts new file mode 100644 index 00000000000..98cb6455670 --- /dev/null +++ b/packages/uuid/src/randomUUID.ts @@ -0,0 +1,4 @@ +// ToDo: Merge Node.js and browser implementations after dropping support for Node.js 22.x +import crypto from "crypto"; + +export const randomUUID = crypto.randomUUID.bind(crypto); diff --git a/packages/uuid/src/v4.spec.ts b/packages/uuid/src/v4.spec.ts new file mode 100644 index 00000000000..938e7ca0a34 --- /dev/null +++ b/packages/uuid/src/v4.spec.ts @@ -0,0 +1,46 @@ +import { getRandomValues } from "crypto"; +import { afterEach, beforeEach, describe, expect, test as it, vi } from "vitest"; + +describe("randomUUID", () => { + afterEach(() => { + vi.resetModules(); + }); + + it("should call native randomUUID when available", async () => { + const mockUUID = "mocked-uuid"; + const nativeRandomUUID = vi.fn(() => mockUUID); + vi.doMock("./randomUUID", () => ({ randomUUID: nativeRandomUUID })); + + const { v4 } = await import("./v4"); + const uuid = v4(); + + expect(nativeRandomUUID).toHaveBeenCalled(); + expect(uuid).toBe(mockUUID); + }); + + describe("when native randomUUID is not available", () => { + let v4: any; + 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; + + beforeEach(async () => { + vi.doMock("./randomUUID", () => ({ randomUUID: undefined })); + v4 = (await import("./v4")).v4; + + // Simulate crypto.getRandomValues in test, as it's expected to be available + global.crypto = { + getRandomValues: getRandomValues, + } as any; + }); + + it("each generation is unique and matches regex", () => { + const uuids = new Set(); + const iterations = 10_000; + for (let i = 0; i < iterations; i++) { + const uuid = v4(); + expect(uuid).toMatch(UUID_REGEX); + uuids.add(uuid); + } + expect(uuids.size).toBe(iterations); + }); + }); +}); diff --git a/packages/uuid/src/v4.ts b/packages/uuid/src/v4.ts new file mode 100644 index 00000000000..d5e45f372ff --- /dev/null +++ b/packages/uuid/src/v4.ts @@ -0,0 +1,19 @@ +import { randomUUID } from "./randomUUID"; + +export const v4 = () => { + if (randomUUID) { + return randomUUID(); + } + + const rnds = new Uint8Array(16); + crypto.getRandomValues(rnds); + + // Set version (4) and variant (RFC4122) + rnds[6] = (rnds[6] & 0x0f) | 0x40; // version 4 + rnds[8] = (rnds[8] & 0x3f) | 0x80; // variant + + return Array.from(rnds.slice(0, 16)) + .map((b) => b.toString(16).padStart(2, "0")) + .join("") + .replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, "$1-$2-$3-$4-$5"); +}; diff --git a/packages/uuid/tsconfig.cjs.json b/packages/uuid/tsconfig.cjs.json new file mode 100644 index 00000000000..96198be8164 --- /dev/null +++ b/packages/uuid/tsconfig.cjs.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "outDir": "dist-cjs", + "rootDir": "src" + }, + "extends": "../../tsconfig.cjs.json", + "include": ["src/"] +} diff --git a/packages/uuid/tsconfig.es.json b/packages/uuid/tsconfig.es.json new file mode 100644 index 00000000000..7f162b266e2 --- /dev/null +++ b/packages/uuid/tsconfig.es.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "lib": [], + "outDir": "dist-es", + "rootDir": "src" + }, + "extends": "../../tsconfig.es.json", + "include": ["src/"] +} diff --git a/packages/uuid/tsconfig.types.json b/packages/uuid/tsconfig.types.json new file mode 100644 index 00000000000..6cdf9f52ea0 --- /dev/null +++ b/packages/uuid/tsconfig.types.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "declarationDir": "dist-types", + "rootDir": "src" + }, + "extends": "../../tsconfig.types.json", + "include": ["src/"] +} diff --git a/packages/uuid/vitest.config.mts b/packages/uuid/vitest.config.mts new file mode 100644 index 00000000000..4e46707824a --- /dev/null +++ b/packages/uuid/vitest.config.mts @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + exclude: ["**/*.{integ,e2e,browser}.spec.ts"], + include: ["**/*.spec.ts"], + environment: "node", + }, +}); diff --git a/yarn.lock b/yarn.lock index f70a296d615..0fa265a3beb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3283,6 +3283,19 @@ __metadata: languageName: unknown linkType: soft +"@smithy/uuid@workspace:packages/uuid": + version: 0.0.0-use.local + resolution: "@smithy/uuid@workspace:packages/uuid" + dependencies: + "@types/node": "npm:^18.11.9" + concurrently: "npm:7.0.0" + downlevel-dts: "npm:0.10.1" + rimraf: "npm:3.0.2" + tslib: "npm:^2.6.2" + typedoc: "npm:0.23.23" + languageName: unknown + linkType: soft + "@tootallnate/once@npm:2": version: 2.0.0 resolution: "@tootallnate/once@npm:2.0.0" From 865b29f50bb9d02b0c809bfcccac19585e071015 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 22 Sep 2025 16:29:27 +0000 Subject: [PATCH 2/7] chore(uuid): set initial version as 0.0.1 --- packages/uuid/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/uuid/package.json b/packages/uuid/package.json index b3f8a00e36a..cd689ee8003 100644 --- a/packages/uuid/package.json +++ b/packages/uuid/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/uuid", - "version": "1.0.0", + "version": "0.0.1", "description": "Polyfill for generating UUID v4", "dependencies": { "tslib": "^2.6.2" From 25fb1808d8a794a8f73b31a0a77255ff90d47905 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 22 Sep 2025 16:30:40 +0000 Subject: [PATCH 3/7] chore: yarn changeset --- .changeset/beige-kangaroos-raise.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/beige-kangaroos-raise.md diff --git a/.changeset/beige-kangaroos-raise.md b/.changeset/beige-kangaroos-raise.md new file mode 100644 index 00000000000..2617db05510 --- /dev/null +++ b/.changeset/beige-kangaroos-raise.md @@ -0,0 +1,5 @@ +--- +"@smithy/uuid": major +--- + +Add polyfill for uuid v4 with preference for native implementations From 01eb8c540f92c3be4da1e144e9f926527fe5e617 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 22 Sep 2025 16:46:03 +0000 Subject: [PATCH 4/7] fix: use Object.defineProperty for providing crypto.getRandomValues --- packages/uuid/src/v4.spec.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/uuid/src/v4.spec.ts b/packages/uuid/src/v4.spec.ts index 938e7ca0a34..a8ca7e83555 100644 --- a/packages/uuid/src/v4.spec.ts +++ b/packages/uuid/src/v4.spec.ts @@ -27,9 +27,12 @@ describe("randomUUID", () => { v4 = (await import("./v4")).v4; // Simulate crypto.getRandomValues in test, as it's expected to be available - global.crypto = { - getRandomValues: getRandomValues, - } as any; + Object.defineProperty(global, "crypto", { + value: { + getRandomValues: getRandomValues, + }, + configurable: true, + }); }); it("each generation is unique and matches regex", () => { From 1823fd975219b2ac9e0238c12e0a6dd2369da9ee Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 22 Sep 2025 17:09:40 +0000 Subject: [PATCH 5/7] chore(uuid): use decimalToHex array --- packages/uuid/src/v4.ts | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/uuid/src/v4.ts b/packages/uuid/src/v4.ts index d5e45f372ff..e3350def68e 100644 --- a/packages/uuid/src/v4.ts +++ b/packages/uuid/src/v4.ts @@ -1,5 +1,7 @@ import { randomUUID } from "./randomUUID"; +const decimalToHex = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0")); + export const v4 = () => { if (randomUUID) { return randomUUID(); @@ -12,8 +14,26 @@ export const v4 = () => { rnds[6] = (rnds[6] & 0x0f) | 0x40; // version 4 rnds[8] = (rnds[8] & 0x3f) | 0x80; // variant - return Array.from(rnds.slice(0, 16)) - .map((b) => b.toString(16).padStart(2, "0")) - .join("") - .replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, "$1-$2-$3-$4-$5"); + return ( + decimalToHex[rnds[0]] + + decimalToHex[rnds[1]] + + decimalToHex[rnds[2]] + + decimalToHex[rnds[3]] + + "-" + + decimalToHex[rnds[4]] + + decimalToHex[rnds[5]] + + "-" + + decimalToHex[rnds[6]] + + decimalToHex[rnds[7]] + + "-" + + decimalToHex[rnds[8]] + + decimalToHex[rnds[9]] + + "-" + + decimalToHex[rnds[10]] + + decimalToHex[rnds[11]] + + decimalToHex[rnds[12]] + + decimalToHex[rnds[13]] + + decimalToHex[rnds[14]] + + decimalToHex[rnds[15]] + ); }; From d5d77fee2fca1f2b385462d29eaa62d09439e1ce Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:45:11 -0700 Subject: [PATCH 6/7] docs: update 'usage' section --- packages/uuid/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/uuid/README.md b/packages/uuid/README.md index d7b3d4ea2ba..3e085df3141 100644 --- a/packages/uuid/README.md +++ b/packages/uuid/README.md @@ -7,4 +7,4 @@ ## Usage -You probably shouldn't, at least directly. +This is an internal `@smithy` package. You should not install it as a direct dependency in your applications. From f4cb486b71cb62650146eadc9218173fac3f4597 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:49:55 -0700 Subject: [PATCH 7/7] docs: add documentation for v4 function --- packages/uuid/src/v4.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/uuid/src/v4.ts b/packages/uuid/src/v4.ts index e3350def68e..33f699b1b62 100644 --- a/packages/uuid/src/v4.ts +++ b/packages/uuid/src/v4.ts @@ -2,6 +2,24 @@ import { randomUUID } from "./randomUUID"; const decimalToHex = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0")); +/** + * Generates a RFC4122 version 4 UUID + * + * This function generates a random UUID using one of two methods: + * 1. The native randomUUID() function if available + * 2. A fallback implementation using crypto.getRandomValues() + * + * The fallback implementation: + * - Generates 16 random bytes using crypto.getRandomValues() + * - Sets the version bits to indicate version 4 + * - Sets the variant bits to indicate RFC4122 + * - Formats the bytes as a UUID string with dashes + * + * @returns A version 4 UUID string in the format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx + * where x is any hexadecimal digit and y is one of 8, 9, a, or b. + * + * @internal + */ export const v4 = () => { if (randomUUID) { return randomUUID();