src/util/ulid.ts — ULID generator
Purpose
Section titled “Purpose”Stamp every vx run invocation with a sortable, collision-resistant
id (run_id) that’s shared across every task in that invocation.
Lets analytics queries group by run without needing a separate
“runs” parent table.
Public surface
Section titled “Public surface”export function ulid(now: number = Date.now()): stringReturns a 26-character ULID:
- 48-bit ms timestamp prefix → first 10 base32 chars
- 80 bits of
crypto.getRandomValuesentropy → next 16 base32 chars
Encoding: Crockford base32 (0123456789ABCDEFGHJKMNPQRSTVWXYZ).
Properties
Section titled “Properties”- Lexicographically sortable by time (millisecond resolution).
- Collision-resistant under parallelism — 80 bits of randomness
is plenty for the “two
vx runinvocations within the same ms” case. - No dependencies. Hand-rolled to keep the dep tree slim — the
ulidnpm package is ~12 KB with a browser/node split for features we don’t need.
Why not crypto.randomUUID()
Section titled “Why not crypto.randomUUID()”UUIDs aren’t lexicographically sortable, so grouping runs table
rows by time-window or “the latest run” would require a separate
timestamp column AND join. ULID does both jobs in one column.
tests/ulid.test.ts:
- 26 chars total.
- First 10 chars sort with time.
- Two ULIDs taken in the same millisecond differ (entropy verified).
- Character set is Crockford base32 only.