import { z } from "zod"; import { randomBytes } from "crypto"; export namespace Identifier { const prefixes = { session: "ses", message: "msg", } as const; export function schema(prefix: keyof typeof prefixes) { return z.string().startsWith(prefixes[prefix]); } const LENGTH = 26; // State for monotonic ID generation let lastTimestamp = 0; let counter = 0; export function ascending(prefix: keyof typeof prefixes, given?: string) { return generateID(prefix, false, given); } export function descending(prefix: keyof typeof prefixes, given?: string) { return generateID(prefix, true, given); } function generateID( prefix: keyof typeof prefixes, descending: boolean, given?: string, ): string { if (!given) { return generateNewID(prefix, descending); } if (!given.startsWith(prefixes[prefix])) { throw new Error(`ID ${given} does not start with ${prefixes[prefix]}`); } return given; } function generateNewID( prefix: keyof typeof prefixes, descending: boolean, ): string { const currentTimestamp = Date.now(); if (currentTimestamp !== lastTimestamp) { lastTimestamp = currentTimestamp; counter = 0; } counter++; let now = BigInt(currentTimestamp) * BigInt(0x1000) + BigInt(counter); now = descending ? ~now : now; const timeBytes = Buffer.alloc(6); for (let i = 0; i < 6; i++) { timeBytes[i] = Number((now >> BigInt(40 - 8 * i)) & BigInt(0xff)); } const randLength = (LENGTH - 12) / 2; const random = randomBytes(randLength); return ( prefixes[prefix] + "_" + timeBytes.toString("hex") + random.toString("hex") ); } }