From 1536a29b49b0b5bb1ae9a840b42cfc7307cc0488 Mon Sep 17 00:00:00 2001 From: Denis mgaya Date: Sat, 11 Oct 2025 09:02:38 +0300 Subject: [PATCH 1/2] featmake uuidv4 synchronous (BREAKING CHANGE) --- README.md | 26 +++++++++++--------------- package.json | 8 ++++++++ src/random/secure-random.ts | 29 +++++++++++++++-------------- src/uuid/v4.ts | 25 +++++++++++-------------- test/random.test.ts | 18 +++++++++--------- test/uuid.v4.test.ts | 16 ++++++++-------- 6 files changed, 62 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 73abc62..e0e91dc 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ [![GitHub license](https://img.shields.io/github/license/dnspascal/core-uuid.svg)](https://github.com/dnspascal/core-uuid/blob/main/LICENSE) [![CI](https://github.com/dnspascal/core-uuid/actions/workflows/test.yml/badge.svg)](https://github.com/dnspascal/core-uuid/actions) - > A lightweight, zero-dependency, cryptographically secure UUID generator written in TypeScript. `core-uuid` provides a fast, reliable, and modern way to generate **UUIDs (v4)** and optionally gives you access to **cryptographically secure random bytes** for other use cases. Perfect for Node.js and browser environments. @@ -40,11 +39,10 @@ yarn add core-uuid ```ts import { uuidv4 } from "core-uuid"; -(async () => { - const id = await uuidv4(); + + const id = uuidv4(); console.log("Generated UUID v4:", id); // Example output: "1757c02d-0aff-4b21-884e-0e5a0c22384d" -})(); ``` ### 2️⃣ Generate multiple UUIDs @@ -52,10 +50,9 @@ import { uuidv4 } from "core-uuid"; ```ts import { uuidv4 } from "core-uuid"; -(async () => { - const uuids = await Promise.all(Array.from({ length: 5 }, () => uuidv4())); + const uuids = Array.from({ length: 5 }, () => uuidv4()); console.log("5 UUIDs:", uuids); -})(); +; ``` ### 3️⃣ Advanced: Secure Random Bytes @@ -63,18 +60,18 @@ import { uuidv4 } from "core-uuid"; ```ts import { getSecureRandomBytes, UUID_BYTES, TOKEN_BYTES } from "core-uuid"; -(async () => { + // Default 16 bytes (same as UUID size) - const uuidBytes = await getSecureRandomBytes(); + const uuidBytes = getSecureRandomBytes(); console.log("Secure 16 bytes (Uint8Array):", uuidBytes); // 32-byte random token - const tokenBytes = await getSecureRandomBytes(TOKEN_BYTES); + const tokenBytes = getSecureRandomBytes(TOKEN_BYTES); const tokenHex = Array.from(tokenBytes) .map(b => b.toString(16).padStart(2, "0")) .join(""); console.log("Secure 32-byte token (hex):", tokenHex); -})(); + ``` **Output example:** @@ -88,13 +85,12 @@ Secure 32-byte token (hex): c1f5e9a0dce43f2a9b4e7f1a8d12e3c4b5a6d7e8f9a0b1c2d3e4 ## API -### `uuidv4(): Promise` +### `uuidv4(): string` * Returns a **UUID v4 string**. -* Asynchronous because it uses crypto APIs under the hood. -* Always RFC 4122 compliant. +* Synchronous and RFC 4122 compliant. -### `getSecureRandomBytes(length?: number): Promise` +### `getSecureRandomBytes(length?: number): Uint8Array` * Returns a cryptographically secure `Uint8Array` of specified length. * Default: `16` bytes. diff --git a/package.json b/package.json index c018d18..b70bd01 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,14 @@ "license": "MIT", "main": "./dist/index.js", "types": "./dist/index.d.ts", + "engines": { + "node": ">=15.0.0" + }, + "browserslist": [ + "defaults", + "not IE 11", + "maintained node versions" + ], "exports": { ".": { "import": "./dist/index.js", diff --git a/src/random/secure-random.ts b/src/random/secure-random.ts index f177281..f6edff8 100644 --- a/src/random/secure-random.ts +++ b/src/random/secure-random.ts @@ -1,26 +1,27 @@ +/** + * Default main consts + */ + export const UUID_BYTES = 16; export const TOKEN_BYTES = 32; /** - * - * - * @param length - * @returns - * - * Generates random secured bytes given length + * Generates cryptographically secure random bytes + * @param length - Number of bytes to generate (default: 16) + * @returns Uint8Array of random bytes */ -export async function getSecureRandomBytes(length = 16): Promise { - const bytes = new Uint8Array(length); - - if (typeof globalThis.crypto !== "undefined") { - globalThis.crypto.getRandomValues(bytes); - } else { - const { webcrypto } = await import("node:crypto"); - webcrypto.getRandomValues(bytes); +export function getSecureRandomBytes(length = 16): Uint8Array { + if (!globalThis.crypto?.getRandomValues) { + throw new Error( + "crypto.getRandomValues is not available. " + + "Requires Node.js 15+, Deno, Bun, or a modern browser." + ); } + const bytes = new Uint8Array(length); + globalThis.crypto.getRandomValues(bytes); return bytes; } \ No newline at end of file diff --git a/src/uuid/v4.ts b/src/uuid/v4.ts index 28585b9..17332e5 100644 --- a/src/uuid/v4.ts +++ b/src/uuid/v4.ts @@ -1,27 +1,24 @@ import { getSecureRandomBytes } from "../random/secure-random.js"; /** - * Generate a UUID v4 (RFC 4122 compliant) - * Uses cryptographically secure random bytes. + * Generate a RFC 4122 compliant UUID v4 + * @returns UUID v4 string (e.g., "f47ac10b-58cc-4372-a567-0e02b2c3d479") */ -export async function uuidv4(): Promise { - const bytes = await getSecureRandomBytes(16); +export function uuidv4(): string { + const bytes = getSecureRandomBytes(16); - // Set version (4) and variant bits + // Set version (4) and variant (RFC 4122) bits bytes[6] = (bytes[6] & 0x0f) | 0x40; bytes[8] = (bytes[8] & 0x3f) | 0x80; - // Convert bytes → hex → UUID string - const hex = Array.from(bytes, (b: number) => b.toString(16).padStart(2, "0")); + // Convert to hex string + const hex: string[] = []; + for (let i = 0; i < 16; i++) { + hex.push(bytes[i].toString(16).padStart(2, "0")); + } - return [ - hex.slice(0, 4).join(""), - hex.slice(4, 6).join(""), - hex.slice(6, 8).join(""), - hex.slice(8, 10).join(""), - hex.slice(10, 16).join("") - ].join("-"); + return `${hex[0]}${hex[1]}${hex[2]}${hex[3]}-${hex[4]}${hex[5]}-${hex[6]}${hex[7]}-${hex[8]}${hex[9]}-${hex[10]}${hex[11]}${hex[12]}${hex[13]}${hex[14]}${hex[15]}`; } export default uuidv4; diff --git a/test/random.test.ts b/test/random.test.ts index 95c5311..7691d63 100644 --- a/test/random.test.ts +++ b/test/random.test.ts @@ -2,26 +2,26 @@ import { describe, it, expect } from 'vitest'; import { getSecureRandomBytes } from '../src/index.js'; describe('getSecureRandomBytes', () => { - it('should return a Uint8Array', async () => { - const bytes = await getSecureRandomBytes(16); + it('should return a Uint8Array', () => { + const bytes = getSecureRandomBytes(16); expect(bytes).toBeInstanceOf(Uint8Array); }); - it('should return correct length', async () => { + it('should return correct length', () => { const length = 32; - const bytes = await getSecureRandomBytes(length); + const bytes = getSecureRandomBytes(length); expect(bytes.length).toBe(length); }); - it('should return different values each time', async () => { - const bytes1 = await getSecureRandomBytes(16); - const bytes2 = await getSecureRandomBytes(16); + it('should return different values each time', () => { + const bytes1 = getSecureRandomBytes(16); + const bytes2 = getSecureRandomBytes(16); // Chances of collision extremely low expect(bytes1).not.toEqual(bytes2); }); - it('should default to 16 bytes if no length is provided', async () => { - const bytes = await getSecureRandomBytes(); + it('should default to 16 bytes if no length is provided', () => { + const bytes = getSecureRandomBytes(); expect(bytes.length).toBe(16); }); }); diff --git a/test/uuid.v4.test.ts b/test/uuid.v4.test.ts index d78ff7c..9a72c32 100644 --- a/test/uuid.v4.test.ts +++ b/test/uuid.v4.test.ts @@ -2,25 +2,25 @@ import { describe, it, expect } from 'vitest'; import { uuidv4 } from '../src/index.js'; describe('UUID v4', () => { - it('should generate a string', async () => { - const id = await uuidv4(); + it('should generate a string', () => { + const id = uuidv4(); expect(typeof id).toBe('string'); }); - it('should have correct length (36 chars)', async () => { - const id = await uuidv4(); + it('should have correct length (36 chars)', () => { + const id = uuidv4(); expect(id.length).toBe(36); }); - it('should match UUID v4 format', async () => { - const id = await uuidv4(); + it('should match UUID v4 format', () => { + const id = uuidv4(); 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; expect(uuidv4Regex.test(id)).toBe(true); }); - it('should generate unique IDs', async () => { - const ids = await Promise.all([uuidv4(), uuidv4(), uuidv4()]); + it('should generate unique IDs', () => { + const ids = [uuidv4(), uuidv4(), uuidv4()]; const uniqueIds = new Set(ids); expect(uniqueIds.size).toBe(ids.length); }); From 6227b2b6ca97675a13d79ef0247621a36ed9d5a0 Mon Sep 17 00:00:00 2001 From: Denis mgaya Date: Sat, 11 Oct 2025 09:04:52 +0300 Subject: [PATCH 2/2] 2.0.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d26388b..1b85d64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "core-uuid", - "version": "1.0.0", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "core-uuid", - "version": "1.0.0", + "version": "2.0.0", "license": "MIT", "devDependencies": { "@types/node": "^24.7.1", diff --git a/package.json b/package.json index b70bd01..2b32f57 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "core-uuid", - "version": "1.0.0", + "version": "2.0.0", "type": "module", "description": "A zero-dependency, secure, and lightweight UUID v4 generator written in TypeScript.", "author": "Denis Mgaya",