Skip to content
Merged

V2 #3

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 11 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -40,41 +39,39 @@ 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

```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

```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:**
Expand All @@ -88,13 +85,12 @@ Secure 32-byte token (hex): c1f5e9a0dce43f2a9b4e7f1a8d12e3c4b5a6d7e8f9a0b1c2d3e4

## API

### `uuidv4(): Promise<string>`
### `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<Uint8Array>`
### `getSecureRandomBytes(length?: number): Uint8Array`

* Returns a cryptographically secure `Uint8Array` of specified length.
* Default: `16` bytes.
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
{
"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",
"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",
Expand Down
29 changes: 15 additions & 14 deletions src/random/secure-random.ts
Original file line number Diff line number Diff line change
@@ -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<Uint8Array> {
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;
}
25 changes: 11 additions & 14 deletions src/uuid/v4.ts
Original file line number Diff line number Diff line change
@@ -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<string> {
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;
18 changes: 9 additions & 9 deletions test/random.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
16 changes: 8 additions & 8 deletions test/uuid.v4.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down