Skip to content
 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

121 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

uniku

npm version npm downloads Bundle Size CI TypeScript License: MIT

One library. Every ID format. Every runtime. Zero dependencies.

uniku /uˈniːku/ — Maltese for "unique"

import { uuidv7 } from 'uniku/uuid/v7'

const id = uuidv7()
// => "018e5e5c-7c8a-7000-8000-000000000000"

// Time-ordered: IDs sort by creation time
const [first, second, third] = [uuidv7(), uuidv7(), uuidv7()]
console.log(first < second && second < third) // true

At a Glance

uniku uuid nanoid ulid cuid2 ksuid
UUID v4
UUID v7
ULID
CUID2
Nanoid
KSUID
Tree-shakeable
ESM-only ✅¹
Edge/Workers ⚠️ ⚠️
Byte ↔ String - ⚠️² -

Notes:

  • Byte ↔ String conversion doesn't make sense for nanoid and cuid2, since they are string-native formats with no canonical binary representation.
  • ¹ uuid@13 is ESM-only; earlier versions support CommonJS.
  • ² ulid only provides timestamp encoding/decoding, not full binary serialization.

Works Everywhere

Node.js Deno Bun Cloudflare Workers Vercel Edge Browsers

Uses globalThis.crypto (Web Crypto API) — no Node.js-specific APIs.

Why uniku?

  • ESM-only — No CommonJS or legacy support.
  • Universal runtime support — Works everywhere: Node.js, Cloudflare Workers, Vercel Edge, Deno, browsers.
  • Truly tree-shakeable — Separate entry points mean you only bundle what you import. No barrel index.ts re-exports.
  • Byte-level access — Convert between UUID strings and Uint8Array with built-in toBytes() and fromBytes().
  • Modern standards — Includes UUID v7 (RFC 9562) for time-ordered, sortable identifiers with monotonic sequencing.
  • Lightweight — Designed for use in performance-sensitive contexts like ORMs.

Performance

Benchmarks comparing uniku string ID generation with equivalent npm packages:

Generator uniku vs npm
ULID 85× faster
CUID2 8× faster
KSUID 1.5× faster
UUID v7 1.1× faster
Nanoid ~comparable speed
UUID v4 npm is 1.1× faster

Which ID Should I Use?

Use Case Recommended Why
Database primary keys UUID v7 or ULID Time-ordered for index efficiency
URL shorteners Nanoid Compact, URL-safe characters
Prevent enumeration CUID2 Non-sequential, secure
Maximum compatibility UUID v4 Universal standard
Distributed systems ULID Sortable + high entropy

Installation

# pnpm (recommended)
pnpm add uniku

# bun
bun add uniku

# npm
npm install uniku

# yarn
yarn add uniku

# deno
deno install npm:uniku

Bundle Impact

Only import what you use — each entry point is independently tree-shakeable:

Import Minified + gzipped
uniku/uuid/v4 ~940 B
uniku/uuid/v7 ~1.1 KB
uniku/ulid ~1.5 KB
uniku/cuid2 ~1.1 KB*
uniku/nanoid ~938 B
uniku/ksuid ~1.0 KB

Preview Releases

Every pull request gets a preview release via pkg.pr.new. Install directly from a PR:

pnpm add https://pkg.pr.new/jkomyno/uniku@<pr-number>

Quick Start

UUID v4 (random)

import { uuidv4 } from 'uniku/uuid/v4'

// Generate a UUID string
const id = uuidv4()
// => "550e8400-e29b-41d4-a716-446655440000"

// Convert to bytes
const bytes = uuidv4.toBytes(id)
// => Uint8Array(16)

// Convert back to string
const str = uuidv4.fromBytes(bytes)
// => "550e8400-e29b-41d4-a716-446655440000"

For advanced use cases, you can provide custom random bytes or write directly to a buffer:

import { uuidv4 } from 'uniku/uuid/v4'

// Custom random source (note: bytes at index 6 and 8 will be modified for version/variant bits)
const id = uuidv4({ random: myRandomBytes })

// Write to existing buffer at offset
const buffer = new Uint8Array(32)
uuidv4(undefined, buffer, 8) // writes 16 bytes starting at offset 8

UUID v7 (time-ordered)

UUID v7 embeds a timestamp, making IDs sortable by creation time — ideal for database primary keys:

import { uuidv7 } from 'uniku/uuid/v7'

// Generate a time-ordered UUID
const id = uuidv7()
// => "018e5e5c-7c8a-7000-8000-000000000000"

// IDs generated in sequence are lexicographically sortable
const ids = [uuidv7(), uuidv7(), uuidv7()]
ids.sort() // Already in creation order

// Byte conversions work the same way
const bytes = uuidv7.toBytes(id)
const str = uuidv7.fromBytes(bytes)

For testing or advanced use cases:

import { uuidv7 } from 'uniku/uuid/v7'

// Specify timestamp and sequence
const id = uuidv7({
  msecs: 1702387456789,
  seq: 0,
  random: new Uint8Array(16)
})

// Write to buffer
const buffer = new Uint8Array(32)
uuidv7(undefined, buffer, 8)

ULID (time-ordered)

ULID is a 26-character, lexicographically sortable identifier:

import { ulid } from 'uniku/ulid'

// Generate a ULID string
const id = ulid()
// => "01HW9T2W9W9YJ3JZ1H4P4M2T8Q"

// Convert to bytes
const bytes = ulid.toBytes(id)

// Convert back to string
const str = ulid.fromBytes(bytes)

CUID2 (secure, non-time-ordered)

CUID2 generates secure, collision-resistant identifiers using SHA3-512 hashing. Unlike time-ordered IDs, CUID2 prevents enumeration attacks:

import { cuid2 } from 'uniku/cuid2'

// Generate a CUID2 string
const id = cuid2()
// => "pfh0haxfpzowht3oi213cqos"

// Custom length (2-32 characters)
const shortId = cuid2({ length: 10 })
// => "tz4a98xxat"

// Validation (type guard)
const maybeId: unknown = getUserInput()
if (cuid2.isValid(maybeId)) {
  console.log(maybeId) // TypeScript knows this is a string
}

Nanoid (URL-friendly, custom alphabet)

Nanoid generates compact, URL-friendly unique string IDs with 126 bits of entropy (comparable to UUID):

import { nanoid } from 'uniku/nanoid'

// Generate a 21-character URL-safe ID
const id = nanoid()
// => "V1StGXR8_Z5jdHi6B-myT"

// Custom size
const shortId = nanoid(10)
// => "IRFa-VaY2b"

// Custom alphabet and size via options
const hexId = nanoid({ alphabet: '0123456789abcdef', size: 12 })
// => "4f90d13a42bc"

// Validation (default alphabet only)
nanoid.isValid(id)
// => true

KSUID (time-ordered, high entropy)

KSUID is a 27-character, K-Sortable Unique Identifier with second-precision timestamps:

import { ksuid } from 'uniku/ksuid'

// Generate a KSUID string
const id = ksuid()
// => "2QnJjKLvpSfpZqGiPPxVwWLMy2p"

// Convert to bytes
const bytes = ksuid.toBytes(id)

// Convert back to string
const str = ksuid.fromBytes(bytes)

// Extract timestamp (returns milliseconds for API consistency)
const ts = ksuid.timestamp(id)
// => 1702387456000

// Validate
ksuid.isValid(id)
// => true

Migrating to uniku

From uuid

- import { v4 as uuidv4 } from 'uuid'
+ import { uuidv4 } from 'uniku/uuid/v4'

- import { v7 as uuidv7 } from 'uuid'
+ import { uuidv7 } from 'uniku/uuid/v7'

From nanoid

- import { nanoid } from 'nanoid'
+ import { nanoid } from 'uniku/nanoid'

API is identical — drop-in replacement.

From ulid

- import { ulid } from 'ulid'
+ import { ulid } from 'uniku/ulid'

API is identical — drop-in replacement.

From @paralleldrive/cuid2

- import { createId } from '@paralleldrive/cuid2'
+ import { cuid2 } from 'uniku/cuid2'

- const id = createId()
+ const id = cuid2()

From @owpz/ksuid

- import { KSUID } from '@owpz/ksuid'
+ import { ksuid } from 'uniku/ksuid'

- const id = KSUID.random().toString()
+ const id = ksuid()

- const bytes = KSUID.random().toBuffer()
+ const bytes = ksuid(undefined, new Uint8Array(20))

- const parsed = KSUID.parse(str)
- const timestamp = parsed.timestamp
+ const timestamp = ksuid.timestamp(str)

- const fromBuf = KSUID.fromBytes(buffer).toString()
+ const fromStr = ksuid.fromBytes(bytes)

Key differences:

  • uniku uses a functional API (ksuid()) vs class-based API (KSUID.random())
  • uniku uses standard Uint8Array instead of Node.js Buffer
  • uniku's timestamp() returns milliseconds (for API consistency with ulid/uuidv7)
  • uniku doesn't include Sequence, CompressedSet, or sorting utilities

API

uuidv4 (from uniku/uuid/v4)

uuidv4(options?: UuidV4Options): string
uuidv4(options: UuidV4Options | undefined, buf: Uint8Array, offset?: number): Uint8Array

uuidv4.toBytes(id: string): Uint8Array
uuidv4.fromBytes(bytes: Uint8Array): string
uuidv4.isValid(id: unknown): id is string
uuidv4.NIL  // "00000000-0000-0000-0000-000000000000"
uuidv4.MAX  // "ffffffff-ffff-ffff-ffff-ffffffffffff"

Options:

  • random?: Uint8Array — 16 bytes of random data (note: bytes at index 6 and 8 will be modified in-place)

uuidv7 (from uniku/uuid/v7)

uuidv7(options?: UuidV7Options): string
uuidv7(options: UuidV7Options | undefined, buf: Uint8Array, offset?: number): Uint8Array

uuidv7.toBytes(id: string): Uint8Array
uuidv7.fromBytes(bytes: Uint8Array): string
uuidv7.timestamp(id: string): number
uuidv7.isValid(id: unknown): id is string
uuidv7.NIL  // "00000000-0000-0000-0000-000000000000"
uuidv7.MAX  // "ffffffff-ffff-ffff-ffff-ffffffffffff"

Options:

  • msecs?: number — Timestamp in milliseconds (defaults to Date.now())
  • seq?: number — Sequence number for monotonicity
  • random?: Uint8Array — 16 bytes of random data

ulid (from uniku/ulid)

ulid(options?: UlidOptions): string
ulid(options: UlidOptions | undefined, buf: Uint8Array, offset?: number): Uint8Array

ulid.toBytes(id: string): Uint8Array
ulid.fromBytes(bytes: Uint8Array): string
ulid.timestamp(id: string): number
ulid.isValid(id: unknown): id is string
ulid.NIL  // "00000000000000000000000000"
ulid.MAX  // "7ZZZZZZZZZZZZZZZZZZZZZZZZZ"

Options:

  • msecs?: number — Timestamp in milliseconds (defaults to Date.now())
  • random?: Uint8Array — 16 bytes of random data (only first 10 bytes used)

cuid2 (from uniku/cuid2)

cuid2(options?: Cuid2Options): string
cuid2.isValid(id: unknown): id is string

Options:

  • length?: number — ID length (2-32, default 24)
  • random?: Uint8Array — Custom random bytes for testing

Note: Unlike UUID and ULID, CUID2 does not provide toBytes/fromBytes because it is a string-native format with no canonical binary representation.

nanoid (from uniku/nanoid)

nanoid(): string
nanoid(size: number): string
nanoid(options: NanoidOptions): string
nanoid.isValid(id: unknown): id is string

// Constant
URL_ALPHABET  // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"

Options:

  • size?: number — ID length (default 21, max 2048)
  • alphabet?: string — Custom alphabet (2-256 printable ASCII characters)
  • random?: Uint8Array — Custom random bytes for testing

Note: isValid() only validates IDs against the default URL-safe alphabet (A-Za-z0-9_-). IDs generated with custom alphabets cannot be validated with this method.

ksuid (from uniku/ksuid)

ksuid(options?: KsuidOptions): string
ksuid(options: KsuidOptions | undefined, buf: Uint8Array, offset?: number): Uint8Array

ksuid.toBytes(id: string): Uint8Array
ksuid.fromBytes(bytes: Uint8Array): string
ksuid.timestamp(id: string): number
ksuid.isValid(id: unknown): id is string
ksuid.NIL  // "000000000000000000000000000"
ksuid.MAX  // "aWgEPTl1tmebfsQzFP4bxwgy80V"

Options:

  • secs?: number — Timestamp in seconds since Unix epoch (defaults to Math.floor(Date.now() / 1000))
  • random?: Uint8Array — 16 bytes of random data for the payload

Note: KSUID uses secs (seconds) while UUID v7 and ULID use msecs (milliseconds). This reflects KSUID's native second-precision timestamps.

CLI

Generate, validate, and inspect IDs from the command line with @uniku/cli:

# Install pre-built binary (macOS, Linux)
curl -fsSL https://raw.githubusercontent.com/jkomyno/uniku/main/install.sh | sh

# Or install via npm
pnpm add -g @uniku/cli

# Generate IDs
uniku uuid --version 7 --count 3
uniku nanoid --size 12 --alphabet hex

# Validate an ID (type auto-detected)
uniku validate 018e5e5c-7c8a-7000-8000-000000000000

# Inspect an ID's metadata
uniku inspect 018e5e5c-7c8a-7000-8000-000000000000

See the CLI README for the full list of commands and options.

Related Projects

Third-party libraries that inspired this project:

  • uuid: the most popular UUID library for JavaScript
  • ulid: the reference ULID implementation for JavaScript
  • @paralleldrive/cuid2: secure, collision-resistant IDs
  • @owpz/ksuid: K-Sortable Unique Identifier
  • nanoid: tiny, URL-friendly unique string ID generator

Other:

Author

Hi, I'm Alberto Schiabel, aka @jkomyno. You can follow me on:

Show Your Support

Give a star if this project helped or inspired you!

License

MIT — see LICENSE

About

TypeScript ID generator, compatible with every modern JS runtime

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages