Architecture
Jspreadsheet Server is an authoritative sequencer. For every document (identified by its guid) the server keeps exactly one live, headless spreadsheet instance in memory. Every change, whether from a browser over WebSocket or from your backend over the REST API, is applied to that instance as one ordered operation, then broadcast to every connected peer.
The server never broadcasts its own computed side effects. It re-sends the original operation, and every peer replays it with the same spreadsheet engine, so formulas, cascades and structural shifts are recomputed identically everywhere.
The life of a change
Step 1: local optimistic apply
A peer applies the change locally and emits the operation (method name and arguments) through the socket. REST API calls skip this step; they start at the server.
Step 2: sequencing
The server applies the operation to the live instance for that guid and increments the document's revision counter (rev) in the same synchronous block, so revision order always equals apply order.
Step 3: persistence
Your change handler (or a ready-made adapter) receives the operation, not the whole document, so storage cost is proportional to the change. The handler runs fire-and-forget and does not block the acknowledgement or the broadcast. If it fails, it reports the failure and the server broadcasts a forceRefresh so clients reload from storage instead of drifting.
Step 4: broadcast
Every other peer in the document's room receives the operation, stamped with its rev, and replays it locally.
Operation flow diagram
browser A ──┐ ┌──▶ browser B (replays op)
│ op: setValue C5=10 │
▼ │
┌─────────────────────────────┐ │
│ live instance for guid │─┘
│ apply → rev++ → persist │──▶ your database (change handler)
└─────────────────────────────┘
One document, one instance, one node
A document's guid is the atomic unit of the system. All peers of a document must reach the same server process, because that process holds the only live instance. This is the basis for Scaling, and it means the server needs no distributed locks, operational transform matrix or CRDT metadata: a single event loop per document provides the ordering.
The in-memory cache
Lazy instance creation
The first access to a document (socket load or any REST call) builds the live instance from your load handler; creating a document does not. From then on, operations and REST requests are served from the warm instance without calling load again.
Garbage collection and eviction
A garbage collector reclaims cached instances. Both of its policies only evict documents with zero connected sockets, so an active collaboration is never torn down:
server({
// ...
gc: {
// Sweep cadence and idle threshold, in minutes.
// A document untouched for this long (writes and reads both
// count as activity) is eligible for eviction.
interval: 30,
// Maximum number of cached documents. Beyond this, the
// least-recently-used idle documents are evicted immediately,
// protection against a burst of documents arriving faster
// than the timed sweep. 0 disables the cap.
max: 100,
},
});
Sizing the cache from available memory
A cached document holds heap proportional to its serialized size, so size gc.max by measuring your own representative documents (see Scaling). Evicted documents are rebuilt transparently on their next access, at a cost proportional to the document size.
Revisions
Every applied change bumps the document's monotonic rev.
Where the revision travels
- the socket acknowledgement of the operation (
{ status: true, rev }), - every operation broadcast to peers,
- the
loadacknowledgement, and - REST write responses (
{ message: 'Done', rev }).
Border operations (setBorder, resetBorders) are exempt: they are presence indicators, relayed to peers without being persisted, and do not bump rev; they carry the current revision instead.
How clients use the revision
The revision defines the authoritative order of operations and is available to custom clients that consume the broadcast stream (Real-time sync). The official client instead uses the document's version signature on reconnection to check whether anything happened while it was away. The counter lives with the cached instance and restarts when a document is rebuilt, which is safe because it only orders operations within one cache lifetime.
What you own
The server owns sequencing, spreadsheet logic and fan-out. You own two sets of plain async handlers:
Authentication hooks
beforeConnect, beforeLoad, beforeChange decide who can connect, read and write. See Authentication.
Persistence handlers
load, change, create, destroy, replace move documents in and out of your storage. Ready-made adapters exist for MongoDB, PostgreSQL and Redis.
Keep the handlers cheap
The hooks run before every operation is applied, and change after every one. Fetch only the fields you need: an ownership check should read the owner and privacy flags, never the whole document.