examples/live-notes Public
---id: snapshotstype: conceptowner: justincomponent: snapshotsdescribes: - src/snapshots.tsreviewed: 2026-10-16---# Snapshots in R2A room keeps only the latest text and its last 200 ops ([[note-room]]). Every 100 versions italso writes the note's text to the `SNAPSHOTS` bucket, so what a note said last week can stillbe read after the op log has moved on. [[0003-snapshots-in-r2]] has the reasoning.These are the same thing as the `snapshot` message on the socket ([[wire-protocol]]), the textat a version. The difference is that these are kept.## When the room saves one`Room.receive()` (`src/room.ts`) marks an outcome with `saveSnapshot` when the new version is atleast `SNAPSHOT_EVERY` (100) past the last saved one. `NoteRoom` sends the ack and the broadcastfirst, then puts the text in R2, then records the version in its SQLite `snapshot` table. Theeditors never wait for R2.If the put fails, nothing is recorded, so the next edit is due again and tries once more. Onlyone save runs at a time per room; edits that arrive while one is in flight do not start another.A failed save is logged as `saving a snapshot failed` with the version.Edits that change nothing, and edits the room refuses, never trigger a save.## Keys```snapshots/<object id>/<version, 12 digits>.txtsnapshots/6f1c...e2/000000000300.txt```The object id is `idFromName(noteId).toString()`. The room knows it as `ctx.id`, and the Workercan compute it from the URL, so both sides agree on the prefix without the room ever being toldits name. The zero padding makes R2 list keys in version order.An object is never overwritten: a version is only saved once, and a note's text at a versionnever changes.## Reading themThe Worker serves them from R2 without touching the room:```GET /notes/groceries/snapshots{ "id": "groceries", "snapshots": [{ "version": 100, "saved_at": "2026-10-15T09:00:00.000Z", "size": 412 }], "next": null }GET /notes/groceries/snapshots?after=10000GET /notes/groceries/snapshots/100```The list is oldest first, 100 at a time; pass `next` back as `after` for the following page.`saved_at` is when R2 stored the object. One snapshot comes back as `text/plain` with a year of`cache-control`, because it can never change. A version that was never saved is a 404.## Tests`test/snapshots.test.ts` covers when the room asks for a save, retries after a failed one, therecorded version surviving a wake from hibernation, and the key format, saving, reading andpaging against an in-memory bucket (`test/helpers/memory-bucket.ts`).