// Deterministic atproto `rev` (TID). The marriage REQUIRES rev to be a pure function of the log —
// NEVER Date.now() — so every co-owner computes the identical commit chain (see DESIGN.md §hard
// requirements). A TID is a 13-char base32-sortable encoding of (unixMicros<<10 | clockId). We use
// clockId=0 and drive `micros` from a deterministic per-commit counter, so `rev` is monotonic AND
// identical across co-owners given the same prev.
const S32 = '234567abcdefghijklmnopqrstuvwxyz' // atproto base32-sortable alphabet
export function encodeTid (micros, clock = 0) {
let n = (BigInt(micros) << 10n) | BigInt(clock & 0x3ff)
let s = ''
for (let i = 0; i < 13; i++) { s = S32[Number(n & 31n)] + s; n >>= 5n }
return s
}
export function decodeTidMicros (tid) {
let n = 0n
for (const ch of tid) n = (n << 5n) | BigInt(S32.indexOf(ch))
return Number(n >> 10n)
}
// The next rev after `prevRev` — deterministic monotonic increment. Genesis (prevRev null) → rev 0's
// TID from a fixed epoch. Because prevRev is itself deterministic, the whole chain is deterministic.
export function nextRev (prevRev, baseMicros = 0) {
const micros = prevRev ? decodeTidMicros(prevRev) : baseMicros
return encodeTid(prevRev ? micros + 1 : micros)
}
// A DETERMINISTIC ISO timestamp derived from a rev — used for the firehose frame `time` (cosmetic,
// not signed), so co-owners emit byte-identical frames. NOT wall-clock.
export function revTime (rev) {
return new Date(Math.floor(decodeTidMicros(rev) / 1000)).toISOString()
}