ulidx

A lightweight, cryptographically-secure ULID generator for NodeJS, browsers, and edge runtimes.

Library
npm
v2.4.1
342stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
35/100Needs Attention
Development Activity0
Maintenance0
Community52
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
66/100Good
Architecture78
Code Quality72
Innovation70
Learning Curve45

ulidx is a TypeScript-first ULID (Universally Unique Lexicographically Sortable Identifier) generator for Node.js, browsers, web workers, and edge runtimes. It’s a maintained continuation of the original ulid library, built to fix compatibility gaps around cryptographically-secure random number generation across environments like Cloudflare Workers, Vercel Edge, and React Native.

Beyond basic ID generation, ulidx provides a monotonic factory for strictly ordered IDs sharing the same timestamp, bidirectional conversion between ULIDs and UUIDs, ULID validation, and typo-correcting Base32 decoding, all shipped as dual ESM/CommonJS builds with full TypeScript declarations.

What You Get

  • Core ulid() generator using the environment’s native crypto (Node crypto.randomBytes or Web Crypto getRandomValues), never falling back to Math.random()
  • A monotonicFactory() for producing strictly increasing ULIDs even when generated within the same millisecond
  • Bidirectional ulidToUUID() / uuidToULID() conversion functions for interop with UUID-based systems
  • isValid(), decodeTime(), and fixULIDBase32() helpers for validating, inspecting, and repairing typo’d ULIDs
  • Dual ESM and CommonJS builds for Node, browser, and React Native, plus full TypeScript type declarations

Common Use Cases

  • Generating sortable primary keys for database rows so IDs and insertion order stay aligned without a separate created_at index
  • Replacing UUIDv4 primary keys in Postgres/MySQL schemas to get better index locality from time-ordered IDs
  • Producing request/trace IDs in edge functions (Cloudflare Workers, Vercel Edge) where Math.random() alone isn’t a safe entropy source
  • Migrating existing UUID-keyed data to ULIDs, or vice versa, using the built-in conversion helpers

Under The Hood

Architecture The library is small and flat: source/index.ts re-exports named functions from ulid.ts, constants.ts, and types.ts, with crockford.ts providing the low-level Base32 encode/decode used by ulidToUUID/uuidToULID. There’s no class hierarchy or dependency injection; it’s a pure-function module with a single mutable-closure exception in monotonicFactory, which holds lastTime/lastRandom in closure scope across calls. PRNG detection (detectPRNG/detectRoot) is the only environment-branching logic, abstracting Node’s crypto.randomBytes versus Web Crypto’s getRandomValues versus a Web Worker’s self.crypto behind one function signature so callers can pass an optional prng without knowing the runtime. Rollup config keyed by FMT/ENV environment variables fans this single source tree out into four physical builds (node/browser times cjs/esm), so changing the core encodeTime/encodeRandom pair touches every one of ulid(), monotonicFactory(), and the UUID conversion helpers directly, since all depend on it.

Tech Stack Written in TypeScript, compiled to declaration files via tsc, and bundled via Rollup 4 using @rollup/plugin-typescript, node-resolve, commonjs, and alias plugins. The only runtime dependency is layerr, a lightweight error-wrapping library used purely for structured error info objects. There’s no web framework, ORM, or database involved, this is a browser/Node dual-target utility library targeting Node 16+ (per package.json engines), with a react-native export condition mapping to the browser CJS bundle for polyfilled environments. CI additionally runs @arethetypeswrong/cli to verify the package’s exports map resolves correctly for consumers across module systems.

Code Quality Tests live in test/node-esm, test/node-cjs, and test/browser-cjs, each exercising the compiled dist output directly with Mocha and Chai, plus a benchmark-based perf script. Coverage is enforced via c8 with explicit thresholds (60% lines/functions/statements, 65% branches), a real gate, though a modest one, and measured against dist rather than source. Error handling is explicit and typed: every thrown error uses Layerr with a structured {code, source} info object, giving callers a stable machine-readable error code (e.g. DEC_TIME_MALFORMED, INVALID_ULID) instead of a bare message. Naming is consistent camelCase throughout, Prettier enforces formatting via a pre-commit hook (simple-git-hooks + lint-staged), and CI runs a three-way Node version matrix (16/18/20) plus dedicated browser-test and type-correctness jobs. No standalone linter configuration was found; formatting and type-checking substitute for that role.

API Design ulidx’s public API is intentionally minimal and functional: a single ulid() call returns a valid identifier with zero configuration, and every other capability (monotonic ordering, decoding, conversion, validation) is a separate top-level export rather than a class or chained builder, so there’s no boilerplate beyond import { ulid } from "ulidx". Naming is consistent and predictable (ulidToUUID/uuidToULID, isValid, decodeTime), and every exported function carries a JSDoc comment with a runnable usage example that the README mirrors directly. The one API wrinkle is environment-dependent PRNG detection: detectPRNG() throws if no crypto source is found, so consumers on unusual runtimes get a clear runtime error rather than silent misbehavior, at the cost of requiring an explicit polyfill import first on environments like older React Native. This drop-in design is inherited largely from the original ulid API rather than reimagined, but it is executed cleanly and documented thoroughly.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search