uniku

Getting started

Install ten type-safe ID strategies, then import only the generator your application needs.

Install

npm install uniku

uniku is ESM-only. Import the exact generator you want rather than a package-root barrel:

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

const id = uuidv7()

For a Deno project without a package import map, use Deno's npm specifier directly:

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

const id = uuidv7()

The usual shape

The named generator import has two roles. Call it to generate an ID. Use the same value as a dictionary of methods and constants for that strategy.

If you are used to class APIs, the attached helpers resemble "static methods." The generator is still a function, so there is nothing to instantiate:

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

// Call the imported value to generate a UUID v7 string.
const id = uuidv7()

// Use that same value to access the UUID v7 API.
const bytes = uuidv7.toBytes(id)
const restored = uuidv7.fromBytes(bytes)
const createdAt = new Date(uuidv7.timestamp(id))
const valid = uuidv7.isValid(restored)

const nil = uuidv7.NIL
const max = uuidv7.MAX

You do not import toBytes() or timestamp() separately. They stay attached to uuidv7 and grouped with that strategy.

Every generator value exposes isValid(). The remaining API matches the strategy. Time-ordered formats expose timestamp(). Formats with a canonical binary representation expose toBytes() and fromBytes(). CUID v2 and Nanoid expose a smaller API because they have no canonical byte representation.

Each generator reference lists the exact call signatures, methods, constants, and overloads available from its imported value.

Every strategy

Each format is independently importable and tree-shakeable. Install one package, then ship only the strategy your application imports.

StrategyDirect importUsual function callPrimary value
UUID v4uniku/uuid/v4uuidv4()string
UUID v7uniku/uuid/v7uuidv7()string
ULIDuniku/ulidulid()string
TypeIDuniku/typeidtypeid('user')string
CUID v2uniku/cuid/v2cuidv2()string
Nanoiduniku/nanoidnanoid()string
KSUIDuniku/ksuidksuid()string
ObjectIDuniku/objectidobjectid()string
XIDuniku/xidxid()string
TSIDuniku/tsidtsid()bigint

See Choosing an ID for the trade-offs, or open a generator reference when you already know which format you need.

Why byte helpers exist

UUIDs, ULIDs, TypeIDs, KSUIDs, ObjectIDs, XIDs, and TSIDs have a canonical binary representation. Their toBytes() and fromBytes() helpers let you store and transport that representation without inventing a second codec.

This matters when an ID lives in a binary database column, a compact protocol payload, or a cache key. A UUID, for instance, is 16 bytes instead of its 36-character string form. The helpers also make the string boundary explicit: convert when data leaves your application, preserve bytes where the system already handles bytes.

String-native formats do not pretend otherwise. Nanoid and CUID v2 have no canonical byte encoding, so they expose a smaller API.

Write into a caller-owned buffer

Binary formats can also write directly into a buffer when an integration already owns the allocation:

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

// 32 bytes: room for the 16-byte UUID plus other fields the caller packs alongside it.
const destination = new Uint8Array(32)

// Offset 8: write the UUID starting at byte 8, after whatever precedes it in the buffer.
uuidv7(undefined, destination, 8)

Public inputs are validated. Explicit random bytes and buffer offsets make tests and boundary integrations deterministic without changing the generator's default monotonic state.

On this page