ulidx
A lightweight, cryptographically-secure ULID generator for NodeJS, browsers, and edge runtimes.
Repository Health
Technical Analysis
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 (Nodecrypto.randomBytesor Web CryptogetRandomValues), never falling back toMath.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(), andfixULIDBase32()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.
Used by 3 apps in this directory
flue
AI Agents · Developer Tools
Build autonomous AI agents and powerful workflows with a programmable TypeScript harness that gives any model sessions, tools, sandboxes, and durable execution.
Jan
AI Assistants
Run LLMs 100% locally with full privacy, or connect to cloud AI — your machine, your data, your control.
Unleash
Developer Tools · Devops · Ab Testing Experimentation
The open-source feature management platform that lets you ship code to production and control who sees it — without redeploying.