splitwise / src /lib /domain /ulid.ts
assafvayner's picture
assafvayner HF Staff
fix(domain): harden schemas, ULID monotonicity and amount parsing
7f9220c
Raw History Blame Contribute Delete
1.29 kB
import { randomBytes } from 'node:crypto';
const ENCODING = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
const TIME_LEN = 10;
const RANDOM_LEN = 16;
function encodeTime(ms: number): string {
let out = '';
let t = ms;
for (let i = 0; i < TIME_LEN; i++) {
out = ENCODING[t % 32] + out;
t = Math.floor(t / 32);
}
return out;
}
function randomDigits(): number[] {
const bytes = randomBytes(RANDOM_LEN);
return Array.from(bytes, (b) => b % 32);
}
function incrementDigits(digits: number[]): void {
for (let i = digits.length - 1; i >= 0; i--) {
if (digits[i] < 31) {
digits[i]++;
return;
}
digits[i] = 0;
}
throw new Error('ULID random component overflow');
}
/**
* Returns a monotonic ULID generator: ids created in the same millisecond
* strictly increase, so lexical order equals creation order within a process.
*/
export function createUlidFactory(now: () => number = Date.now): () => string {
let lastTime = -1;
let lastRandom: number[] | null = null;
return () => {
const t = Math.floor(now());
if (lastRandom !== null && t <= lastTime) {
incrementDigits(lastRandom);
} else {
lastTime = t;
lastRandom = randomDigits();
}
return encodeTime(lastTime) + lastRandom.map((d) => ENCODING[d]).join('');
};
}
export const ulid = createUlidFactory();