pr_01m47d24b0e6n91zwymwxg0vpx/packages/contracts/src/ids.ts

41 lines1,436 bytesCodeBlame
1const ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz";
2
3export type IdPrefix = "usr" | "ses" | "tok" | "key" | "rep" | "int" | "att" | "evt" | "dpl" | "prj";
4
5let lastMs = 0;
6let 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 */
14export 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}