examples/live-notes Public
# 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 page lists who else has the note open, each with an animal name and a color, and showswhen one of them is typing. The room works this out from its open sockets and never stores it.The room keeps the note and its last 200 operations in the Durable Object's SQLite databaseand accepts sockets with the Hibernation API. 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 |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), `op` (someone else's edit, at this version, with who made it) and`presence` (who is here, whenever someone joins or leaves).## 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 ops.ts insert, delete, transform, compose, diff protocol.ts the socket messages and their validation presence.ts names and colors for the editors on a noteclient/ 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