- Owner
- justin
- Reviewed
- 2026-10-14
- Component
- note-room
- Describes
- src/note-room.ts, src/room.ts, src/index.ts
Each note is one Durable Object of class NoteRoom, found with idFromName(noteId) through the
NOTES binding. Everyone who opens the same note id reaches the same object, wherever they
are, and that object is the single sequencer described in Conflict strategy.
Two layers
NoteRoom (src/note-room.ts) is the part that knows about the runtime: it accepts sockets,
reads frames, sends replies and broadcasts, and owns the SQLite storage. Everything it decides
is delegated to Room (src/room.ts), which takes a NoteStore and returns, for each
message, a reply for the sender and an optional broadcast for the others. The tests drive
Room with an in-memory store, so they run in plain Node without workerd.
Storage
Two tables in the object's SQLite database:
note: one row with the text and its version.Roomcaps the text at 100,000 UTF-16 code units, so the row and every snapshot frame stay small.ops: the op log, keyed by the version each op produced, trimmed to the last 200.
commit() writes the new text, appends the op and trims the log inside one transactionSync,
so a note and its log never disagree. The room only updates its in-memory copy after the
write.
Hibernation
Sockets are accepted with ctx.acceptWebSocket, the Hibernation API. When nobody has typed for
a while the runtime evicts the object but keeps the sockets open; the next frame wakes it,
runs the constructor, and calls webSocketMessage. The constructor creates the tables if
needed and loads the note, so waking up is the same as starting fresh. Nothing the room needs
lives only in memory.
Broadcasting uses ctx.getWebSockets(), which lists every accepted socket even after a wake,
so there is no connection list to rebuild.
Reads without a socket
GET /notes/:id calls the read() RPC method and returns { id, text, version }. It reads the
room's in-memory copy, which is always the last committed state.