Skip to content

jkomyno/uniku

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

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors