Skip to content

Reverse Timestamp codec

Most KV stores (DynamoDB, Cloud Datastore, range-scan KV) only support forward lexicographic scans natively. The Reverse Timestamp codec bitwise-inverts the 48-bit timestamp field before encoding, so newer IDs sort lexicographically before older ones — reading the most recent entries first becomes a forward scan.

import { createReverseTimestampId } from "@smonn/ids/reverse";
const events = createReverseTimestampId("evt");
const id = events.generate(); // "evt_…", sorts newest-first
events.extractTimestamp(id); // Date — inversion is reversed to recover the ms

No key material is required, and the inversion is a deterministic byte transform — generate, generateAt, and extractTimestamp are fully synchronous, exactly like the Timestamp codec.

Method Description
generate() Produces a new canonical ID using the codec’s now and rng.
generateAt(date) Produces a new canonical ID at date; throws on invalid dates.
is(value) Strict type guard — true only for already-canonical strings. For untrusted input, use safeParse().
parse(value) Lenient parse that normalises case and Crockford aliases; throws on failure.
safeParse(value) Lenient parse that returns { ok: true, id } or { ok: false, error } without throwing.
extractTimestamp(id) Inverts the timestamp bytes back to recover the original creation Date. Trusts the type — use safeParse() at boundaries first.
minIdForTime(date) Lexicographically smallest ID for any ID generated at date (random portion 0x00). Throws on invalid dates.
maxIdForTime(date) Lexicographically largest ID for any ID generated at date (random portion 0xff). Throws on invalid dates.
toJsonSchema() Returns a JSON Schema object for the canonical wire form.
~standard Standard Schema v1 validate entry point.

IdsError, isIdsError, and IdsErrorCode are available from @smonn/ids.

minIdForTime(date) and maxIdForTime(date) build synthetic IDs at the tight lower and upper bounds of the given millisecond — same timestamp bytes, random portion all 0x00 (min) or all 0xff (max). Because timestamps are inverted, a newer date yields a lexicographically smaller result, and an older date yields a lexicographically larger one. That flips the range-scan bounds relative to the Timestamp codec:

const start = new Date("2026-01-01T00:00:00Z"); // older
const end = new Date("2026-02-01T00:00:00Z"); // newer
// Reverse Timestamp: lower bound = newer time, upper bound = older time
sql`SELECT * FROM events WHERE id BETWEEN ${events.minIdForTime(end)} AND ${events.maxIdForTime(start)}`;

See ADR-0010.

All date-taking methods (generateAt, minIdForTime, maxIdForTime) throw if passed a pre-epoch date (before 1 January 1970 UTC), a date past the 48-bit ceiling (~10 889 AD), or an Invalid Date.

The injection contract is identical to the Timestamp codec — pass a fixed now and a no-op rng for snapshot-friendly output:

const events = createReverseTimestampId("evt", {
now: () => new Date("2026-01-01T00:00:00Z").getTime(),
rng: (target) => {}, // leave target as zero-filled
});
events.generate(); // deterministic output

See the Testing guide for the full pattern across every codec.

The bitwise inversion (~ts & 0xFFFFFFFFFFFF) is a key-free, deterministic transform. Creation time is always recoverable from the wire form by anyone — extractTimestamp just inverts the bytes back. Like the Timestamp codec, the Reverse Timestamp codec is not a tool for hiding creation time. If you need the timestamp to be confidential, use the Opaque Timestamp codec instead.