Skip to content

Time travel

Nothing is overwritten, so the past is a query away. This page is for anyone who saw Reef’s time-travel slider and wants the same thing in their app.

Reef board under a blue Time travel bar with the slider at db.asOf(27) of 42, the header pill reading paused, and fewer cards than the live board
The board read at version 27 of 42. Same board query; the slider picks the version. · src/app/screens/BoardScreen.tsx:448-454

The slider’s ceiling is the current version number t — the number every write gets, in order (glossary). Its value picks a view of the database:

examples/reef/src/app/screens/BoardScreen.tsx:448-454
const maxT = useBasis(db);
const [scrubbed, setScrubbed] = useState<number | null>(null);
const t = scrubbed ?? maxT;
// Until the basis lands, read the live view — the same rows the board
// already shows — so the hook order never varies.
const past = useQuery(t === undefined ? db : db.asOf(t), boardQuery);
const everything = useQuery(db.history, everyIssueEverQuery);

boardQuery is the same value the live board uses. Nothing was copied or exported.

db.asOf(t) is a read-only view of the database exactly as it was right after version t (glossary). Queries and pulls run over it unchanged. It takes a version number, not a date; and you cannot write into it — the view has no transact.

examples/kv-style/app.ts:108-111
// …and the same query as of a past transaction. `asOf` is pure.
const beforeRows = yield* db
.asOf(report.t - 1)
.q(Ramose.query(User).select({ name: User.name }));

Calling asOf costs nothing; the read happens when you run the query.

  • From a write. report.t is the version your write produced.
  • From the database. db.basis() answers { t } for the newest version; in React, useBasis(db) keeps it current, which is how Reef’s slider knows its ceiling.
  • From your own records. Store a t you care about and read at it later.

db.history is a view that also returns facts later removed — one statement about one record’s field (glossary). Reef uses it for the “Deleted, still in history” strip: run a plain query over db.history, subtract what the live board shows, and what is left was deleted.

examples/reef/src/domain/queries.ts:84-88
/** Over `db.history` this also returns issues that no longer exist. */
export const everyIssueEverQuery = Ramose.query(Issue).select({
id: Issue.id,
title: Issue.title,
});

Use it for audit trails, “who changed this”, and debugging state that no longer exists.

A live query over asOf(t) or history emits once and stops — a pinned view has no news.

Reads over asOf and history are filtered by the current policy: a field you may not read now is missing from the past too, and a removed permission cannot re-grant access to what it once allowed.

History is kept as version numbers, not dates: asOf takes a t. Nothing prunes history — RAMOSE_RETAIN_ROOTS bounds how much storage the index costs, not how far back asOf reads. A read opens the current index and hides facts newer than your t, so asOf reaches the database’s very first version no matter how many snapshots have been swept — see Retention.