// atproto TID — the 13-char, lexicographically-sortable timestamp id used for `rev`
// (and default rkeys). value = (unixMicros << 10) | clockId(10 bits), top bit 0,
// encoded in the sortable base32 alphabet. Monotonic within a process.
const S32 = '234567abcdefghijklmnopqrstuvwxyz' // atproto sortable base32
function s32encode (n) {
let s = ''
for (let i = 0; i < 13; i++) { s = S32[Number(n % 32n)] + s; n /= 32n }
return s
}
let _last = 0n
// clock() returns micros; injectable for deterministic tests.
export function tidNow (clockId = 0, clock = () => BigInt(Date.now()) * 1000n) {
let micros = clock()
const val = (micros << 10n) | (BigInt(clockId) & 0x3ffn)
const t = val > _last ? val : _last + 1n // strictly monotonic
_last = t
return s32encode(t)
}
export function isValidTid (s) {
return typeof s === 'string' && s.length === 13 && [...s].every(c => S32.includes(c))
}
// The micros component of a TID (drops the 10-bit clock id).
export function tidMicros (tid) {
let n = 0n
for (const ch of String(tid)) n = (n << 5n) | BigInt(S32.indexOf(ch))
return n >> 10n
}
// The DETERMINISTIC next rev after `prevRev` — a pure function of the previous rev, never the
// wall clock. This is what makes the atproto chain indexer-agnostic: two co-owners continuing
// from the SAME head over the SAME finalized record set produce byte-identical commits (RFC6979
// signing is deterministic), so concurrent signers are idempotent rather than forking. Mirrors
// atproto-base/lib/rev.js byte-for-byte (clockId 0) so both implementations agree exactly.
export function nextRev (prevRev) {
return s32encode((tidMicros(prevRev) + 1n) << 10n)
}