You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
33 lines
1.4 KiB
TypeScript
33 lines
1.4 KiB
TypeScript
/**
|
|
* UUIDv7: 48-bit unix-ms timestamp + random. Time-ordered so ids sort roughly
|
|
* by creation across devices — the property the sync design (02-ARCHITECTURE §3)
|
|
* relies on for cheap cursor pagination. Client-generated, never server-assigned.
|
|
* Web Crypto keeps this package loadable in both Node and the POS renderer.
|
|
*/
|
|
let lastMs = 0
|
|
export function uuidv7(now: number = Date.now()): string {
|
|
// Monotonic within a process: two ids minted in the same millisecond (or an earlier
|
|
// one, e.g. a passed-in timestamp) still strictly increase — so `ORDER BY id` is genuine
|
|
// creation order, the property pagination and the sync cursor both rely on. Without this,
|
|
// sub-millisecond ties fall back to the random tail and the order is undefined.
|
|
lastMs = now > lastMs ? now : lastMs + 1
|
|
now = lastMs
|
|
const b = new Uint8Array(16)
|
|
globalThis.crypto.getRandomValues(b)
|
|
b[0] = (now / 2 ** 40) & 0xff
|
|
b[1] = (now / 2 ** 32) & 0xff
|
|
b[2] = (now / 2 ** 24) & 0xff
|
|
b[3] = (now / 2 ** 16) & 0xff
|
|
b[4] = (now / 2 ** 8) & 0xff
|
|
b[5] = now & 0xff
|
|
b[6] = 0x70 | (b[6]! & 0x0f)
|
|
b[8] = 0x80 | (b[8]! & 0x3f)
|
|
const h = Array.from(b, (x) => x.toString(16).padStart(2, '0')).join('')
|
|
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`
|
|
}
|
|
|
|
export function timestampOfUuidv7(id: string): number {
|
|
const hex = id.replaceAll('-', '').slice(0, 12)
|
|
return Number.parseInt(hex, 16)
|
|
}
|