Rebasely Beta: data may be purged

examples/live-notes Public

Markdown · 92 lines · 4.1 KiB · at 70b1506

# live-notesPlain-text notes that several people can edit at the same time, on Cloudflare Workers andDurable Objects. Open `/#groceries` in two browsers and type in both: each sees the other'sedits as they happen, and both end up with the same text.## How it worksEvery note is one Durable Object, `NoteRoom`, found by name. Everyone editing "groceries"connects to the same object over a WebSocket, and that object is the only place where edits tothe note are put in order.Editors do not send the whole text. They send operations (insert some text at an offset, ordelete some characters at an offset) together with the version of the note they were lookingat. If other edits landed in the meantime, the room moves the operation past them with a smalltransform function, applies it, saves it, and sends it on to the other editors. Each editordoes the same transform on its side for edits it has not had acknowledged yet, so its caretstays where it was while others type.The room keeps the note and its last 200 operations in the Durable Object's SQLite databaseand accepts sockets with the Hibernation API. Every 100 versions it also saves a copy of thetext to R2, so older versions can still be read. A note nobody is typing in is evicted frommemory without closing anyone's connection, and wakes up on the next keystroke.## Running it locallyYou need Node 24.```bashnpm installnpm test            # the transform, the room and simulated editorsnpm run typecheck   # the Worker and the page's scriptnpm run dev         # builds the page script, then runs wrangler dev on :8787```Then open http://localhost:8787/#groceries in two windows.## API| Request                             | Response                                                 || ----------------------------------- | -------------------------------------------------------- || `GET /`                             | the editor page; the part after `#` is the note id       || `GET /notes/:id`                    | the note as JSON: `{ "id", "text", "version" }`          || `GET /notes/:id/ws`                 | a WebSocket to the note's room                           || `GET /notes/:id/snapshots`          | the note's saved versions, oldest first; `?after=` pages || `GET /notes/:id/snapshots/:version` | the note's text at that version, as plain text           |Note ids are 1 to 64 letters, digits, dashes or underscores. On the socket, the editor sends```json{ "type": "op", "base": 41, "op": { "type": "insert", "pos": 4, "text": "oat " } }```and the room answers with `snapshot` (on connect, or when it can't use an op), `ack` (your opis in, at this version) and `op` (someone else's edit, at this version).## Conflicts in brief- The room is the single sequencer for its note. It handles one message at a time, so there is  always an answer to "which edit came first".- An op written against an older version is transformed past the ops it missed, in order.- Two inserts at the same offset: the one the room saw first goes first.- Overlapping deletes remove the text once.- Typing inside text that someone else deleted at the same moment is deleted along with it.  This keeps every transform to a single op.- An editor more than 200 versions behind gets a fresh snapshot and drops its unsent edits.The knowledge base in `.rebasely/kb` explains each of these and why we chose them.## Layout```src/  index.ts        the Worker: routes /notes/:id to its NoteRoom  routes.ts       path matching for the Worker  note-room.ts    the Durable Object: sockets, hibernation, SQLite  room.ts         the sequencer, with storage behind an interface  snapshots.ts    copies of the note in R2: keys, saving, listing  ops.ts          insert, delete, transform, compose, diff  protocol.ts     the socket messages and their validationclient/  session.ts      one editor's state: pending ops, remote ops, caret  editor.ts       the page script, compiled to public/jspublic/  index.html      the editor pagetest/             node --test, no Workers runtime needed.rebasely/        architecture, flows, knowledge base and CI```## LicenseMIT