/** * UUIDv7, zeitlich sortiert. * * Selbst geschrieben statt als Abhängigkeit: das Paket wog 2,6 kB im Bündel * der Fallakte, und die eine Eigenschaft, auf die sich das System verlässt, * steht so ausdrücklich hier statt in fremdem Code. * * Die Eigenschaft ist: **die Kennungen sind lexikografisch sortierbar wie die * Zeit.** Darauf beruhen zwei Dinge - die Nachlieferung beim Wiederverbinden * (`id > seit`) und die Reihenfolge im Chat, ohne dass eine Sortierspalte * nötig wäre. Siehe docs/06-chat-und-realtime.md. * * Aufbau nach RFC 9562: * * 48 Bit Zeitstempel (ms) | 4 Bit Version 7 | 12 Bit Zähler * 2 Bit Variante | 62 Bit Zufall * * Der Zähler in `rand_a` sichert die Reihenfolge auch innerhalb derselben * Millisekunde. Ohne ihn kämen zwei schnell hintereinander gesendete * Nachrichten in zufälliger Reihenfolge an. */ let letzteZeit = 0 let zaehler = 0 /** 12 Bit, danach wird auf die nächste Millisekunde gewartet. */ const ZAEHLER_MAX = 0xfff export function uuidv7(now: number = Date.now()): string { if (now === letzteZeit) { zaehler += 1 if (zaehler > ZAEHLER_MAX) { // Mehr als 4096 Kennungen in einer Millisekunde: lieber eine // Millisekunde vorgreifen als die Sortierung verlieren. letzteZeit += 1 zaehler = 0 now = letzteZeit } } else if (now > letzteZeit) { letzteZeit = now zaehler = 0 } else { // Die Uhr ist zurückgesprungen. Die Reihenfolge ist wichtiger als der // genaue Zeitpunkt, also weiterzählen statt zurückfallen. letzteZeit += 1 zaehler = 0 now = letzteZeit } const bytes = new Uint8Array(16) // 48 Bit Zeitstempel, big endian. const zeit = BigInt(now) for (let i = 0; i < 6; i++) { bytes[i] = Number((zeit >> BigInt(8 * (5 - i))) & 0xffn) } // Version 7 in den oberen vier Bit von Byte 6, darunter der Zähler. bytes[6] = 0x70 | ((zaehler >> 8) & 0x0f) bytes[7] = zaehler & 0xff const zufall = new Uint8Array(8) crypto.getRandomValues(zufall) bytes.set(zufall, 8) // Variante 10xx in den oberen zwei Bit von Byte 8. bytes[8] = (bytes[8]! & 0x3f) | 0x80 const hex = [...bytes].map((b) => b.toString(16).padStart(2, '0')).join('') return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}` }