// Job ids are ULIDs: a 48-bit millisecond timestamp followed by 80 bits of // randomness, Crockford base32-encoded to a fixed 26 chars. Two properties earn // their keep on the /jobs screen: // 1. Lexicographic order == chronological order, so the on-disk `.jobs` // directory can be sorted and paginated by filename alone. // 2. The timestamp is recoverable from the id itself (jobIdTime), so listing a // page never has to stat/read every file to know when a job ran. // Hand-rolled to avoid a dependency; strict within-millisecond monotonicity is // not needed here (tie-break order between jobs sharing a ms is irrelevant). // Crockford base32 (no I, L, O, U). const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; const ENCODING_LEN = ENCODING.length; // 32 const TIME_LEN = 10; // 48 bits -> 10 base32 chars const RANDOM_LEN = 16; // 80 bits -> 16 base32 chars function encodeTime(now: number): string { let str = ""; for (let i = TIME_LEN; i > 0; i--) { const mod = now % ENCODING_LEN; str = ENCODING[mod] + str; now = Math.floor(now / ENCODING_LEN); } return str; } function encodeRandom(): string { let str = ""; for (let i = RANDOM_LEN; i > 0; i--) { str += ENCODING[Math.floor(Math.random() * ENCODING_LEN)]; } return str; } export function ulid(now: number = Date.now()): string { return encodeTime(now) + encodeRandom(); } // Decode the millisecond timestamp carried by a job id. Handles both id schemes // so on-disk logs written before the ULID switch still sort/paginate correctly: // - Legacy `Date.now().toString(36)-` (contains a "-"). // - ULID (26 chars, no "-"): the first 10 chars are the base32 timestamp. // Returns 0 when the id is unparseable; callers fall back to the file mtime. export function jobIdTime(id: string): number { const dash = id.indexOf("-"); if (dash >= 0) { const t = parseInt(id.slice(0, dash), 36); return Number.isFinite(t) ? t : 0; } if (id.length < TIME_LEN) return 0; let time = 0; for (let i = 0; i < TIME_LEN; i++) { const idx = ENCODING.indexOf(id[i].toUpperCase()); if (idx < 0) return 0; time = time * ENCODING_LEN + idx; } return time; }