pr_01m47d15m3e54sn21z27rpy5n9/packages/contracts/src/ids.ts
| 1 | const ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz"; |
| 2 | |
| 3 | export type IdPrefix = "usr" | "ses" | "tok" | "key" | "rep" | "int" | "att" | "evt" | "dpl" | "prj" | "dom"; |
| 4 | |
| 5 | let lastMs = 0; |
| 6 | let lastCounter = 0; |
| 7 | |
| 8 | /** |
| 9 | * A new id in TypeID format (https://github.com/jetify-com/typeid): a type |
| 10 | * prefix, then a UUIDv7 in lowercase Crockford base32, such as |
| 11 | * `att_01jb2k7x9hfq0b3zj0f5s2m8ra`. Sorting ids as strings sorts them by |
| 12 | * creation time. Must stay identical to `new_id` in crates/contracts. |
| 13 | */ |
| 14 | export function newId(prefix: IdPrefix, now: number = Date.now()): string { |
| 15 | const bytes = crypto.getRandomValues(new Uint8Array(16)); |
| 16 | |
| 17 | // Ids made in the same millisecond count up in the 12-bit rand_a field. |
| 18 | const counter = |
| 19 | now === lastMs |
| 20 | ? (lastCounter + 1) & 0x0fff |
| 21 | : ((bytes[6] << 8) | bytes[7]) & 0x07ff; |
| 22 | lastMs = now; |
| 23 | lastCounter = counter; |
| 24 | |
| 25 | const time = BigInt(now); |
| 26 | for (let i = 0; i < 6; i++) { |
| 27 | bytes[i] = Number((time >> BigInt(40 - 8 * i)) & 0xffn); |
| 28 | } |
| 29 | bytes[6] = 0x70 | (counter >> 8); // version 7 |
| 30 | bytes[7] = counter & 0xff; |
| 31 | bytes[8] = 0x80 | (bytes[8] & 0x3f); // RFC 9562 variant |
| 32 | |
| 33 | let value = 0n; |
| 34 | for (const byte of bytes) value = (value << 8n) | BigInt(byte); |
| 35 | let suffix = ""; |
| 36 | // 128 bits in 26 characters of 5 bits; the first carries only 3. |
| 37 | for (let i = 0; i < 26; i++) { |
| 38 | suffix += ALPHABET[Number((value >> BigInt(125 - 5 * i)) & 31n)]; |
| 39 | } |
| 40 | return `${prefix}_${suffix}`; |
| 41 | } |