Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

101 linesCodeBlameRaw
1const ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz";
2
3export type IdPrefix =
4 | "usr"
5 | "ses"
6 | "tok"
7 | "key"
8 | "rep"
9 | "int"
10 | "att"
11 | "evt"
12 | "dpl"
13 | "prj"
14 | "dom"
15 | "dep"
16 | "dst"
17 | "chn"
18 | "msg"
19 | "agt"
20 | "arp"
21 | "asn"
22 | "mem"
23 | "rtn"
24 | "drf"
25 | "spc"
26 | "pag"
27 | "ver"
28 | "thr"
29 | "cmt"
30 | "sug"
31 | "tpl"
32 | "fil"
33 | "rds"
34 | "fol"
35 | "prp"
36 | "ins"
37 | "skl"
38 | "ska"
39 | "abr"
40 | "mcp"
41 | "mkt"
42 | "plg"
43 | "con"
44 | "cgr"
45 | "civ"
46 | "crq";
47
48let lastMs = 0;
49let lastCounter = 0;
50
51/**
52 * A new id in TypeID format (https://github.com/jetify-com/typeid): a type
53 * prefix, then a UUIDv7 in lowercase Crockford base32, such as
54 * `att_01jb2k7x9hfq0b3zj0f5s2m8ra`. Sorting ids as strings sorts them by
55 * creation time. Must stay identical to `new_id` in crates/contracts.
56 */
57export function newId(prefix: IdPrefix, now: number = Date.now()): string {
58 const bytes = crypto.getRandomValues(new Uint8Array(16));
59
60 // Ids made in the same millisecond count up in the 12-bit rand_a field.
61 const counter = now === lastMs ? (lastCounter + 1) & 0x0fff : ((bytes[6] << 8) | bytes[7]) & 0x07ff;
62 lastMs = now;
63 lastCounter = counter;
64
65 const time = BigInt(now);
66 for (let i = 0; i < 6; i++) {
67 bytes[i] = Number((time >> BigInt(40 - 8 * i)) & 0xffn);
68 }
69 bytes[6] = 0x70 | (counter >> 8); // version 7
70 bytes[7] = counter & 0xff;
71 bytes[8] = 0x80 | (bytes[8] & 0x3f); // RFC 9562 variant
72 return encode(prefix, bytes);
73}
74
75/**
76 * The smallest id with `prefix` that could have been made at or after
77 * `now`: its time, and every other bit zero. Ids sort by time, so
78 * `id >= idFloor(p, from) AND id < idFloor(p, until)` picks the ids made in
79 * `[from, until)` as a range of any index that ends in the id, without a
80 * time column. No real id ever equals one: a real id's version bits are
81 * set. The same as `id_floor` in crates/contracts.
82 */
83export function idFloor(prefix: IdPrefix, now: number): string {
84 const bytes = new Uint8Array(16);
85 const time = BigInt(Math.max(0, Math.floor(now)));
86 for (let i = 0; i < 6; i++) {
87 bytes[i] = Number((time >> BigInt(40 - 8 * i)) & 0xffn);
88 }
89 return encode(prefix, bytes);
90}
91
92function encode(prefix: string, bytes: Uint8Array): string {
93 let value = 0n;
94 for (const byte of bytes) value = (value << 8n) | BigInt(byte);
95 let suffix = "";
96 // 128 bits in 26 characters of 5 bits; the first carries only 3.
97 for (let i = 0; i < 26; i++) {
98 suffix += ALPHABET[Number((value >> BigInt(125 - 5 * i)) & 31n)];
99 }
100 return `${prefix}_${suffix}`;
101}