OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-store / lib / tid.js

43 lines · javascript ref:
// 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)
}