Files
AfiS-Aktensystem/web/lib/uuidv7.ts
T
Harlekin e0b03ea336
Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
Gedankenstriche durch Bindestriche ersetzt
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die
README; an keiner Stelle Bedeutung, nur Zeichensetzung.
2026-09-04 08:54:52 +02:00

72 lines
2.3 KiB
TypeScript

/**
* 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)}`
}