Persistence

Jspreadsheet Server provides an abstract, event-driven layer for handling data persistence. You implement five handlers (load, create, change, destroy and replace) backed by any database, version-control scheme or queue. The server never talks to storage directly.

The handlers

The exact payloads, ordering guarantees and failure semantics are specified in the persistence contract.

load

load(guid, auth, cachedConfiguration) returns the document configuration for a GUID, as an object or a JSON string, or false for not found. When the document is already live in the server cache, its current configuration is passed as cachedConfiguration and can be returned directly, skipping the database read.

create

create(guid, config, auth) persists a new spreadsheet. The configuration arrives parsed and validated: every worksheet has a worksheetName and a minted worksheetId, and document-level namespace and version are set.

change

change(guid, changes, auth, onerror) is called once per applied operation, in order, with { instance, worksheet, method, args }. On persistence failure, call onerror(error): the server then emits forceRefresh to the document's room.

destroy

destroy(guid, auth) deletes the spreadsheet. When the handler returns truthy, the server also drops the document from its memory cache, emits forceRefresh to the room and disconnects every connected socket.

replace

replace(guid, config, auth) performs a full-document overwrite. The snapshot-restore route calls it to write a restored configuration wholesale; when no replace handler is defined, the server falls back to destroy followed by create. The incremental adapters also use their own replace internally for failure resynchronization.

destroyCache (optional)

destroyCache(guid, config) is called when the server removes a document from its memory cache (deletion, garbage collection or cache-size eviction). The final serialized configuration is only computed, an expensive operation on large documents, when this handler is defined. Adapters that keep a hot copy outside the server process use it to drop that copy.

Example: raw handlers against Redis

Complete implementation against a Redis client

The event layer does not require an adapter. The following is a complete implementation directly against a Redis client, with no access control, to show the structure:

const server = require('@jspreadsheet/server');
const { createClient } = require("redis");

require('dotenv').config();

const client = createClient({
    socket: {
        host: 'localhost',
        port: 6379
    },
});

// Connect to Redis
client.connect();

server({
    port: 3000,
    // Socket.IO server configuration
    config: {
        cors: {
            origin: "*"
        },
    },
    error: async function(e) {
        console.log(e);
    },
    beforeConnect: async function(auth) {
        return true;
    },
    load: async function(guid) {
        return await client.get(guid);
    },
    create: async function(guid, config) {
        const result = await client.exists(guid);

        if (result) {
            // A spreadsheet already exists
            return false;
        } else {
            // Create a new spreadsheet
            await client.set(guid, JSON.stringify(config));

            return true;
        }
    },
    destroy: async function(guid) {
        return await client.del(guid)
            .then(() => true)
            .catch(() => false);
    },
    change: async function(guid, changes) {
        // Get the live configuration from the server cache
        let config = changes.instance.getConfig();
        // Save it wholesale on Redis
        await client.set(guid, JSON.stringify(config));
    },
    license: {
        clientId: process.env.JSS_CLIENT,
        licenseKey: process.env.JSS_LICENSE,
    },
});

Full-document write caveat

This example re-serializes the full document on every update, regardless of size. That is acceptable for small documents; for larger documents, batch the writes or persist the operations themselves. The upgrade path is described step by step in Build your own adapter.

Adapters

For convenience, prebuilt implementations of this event layer exist for MongoDB, PostgreSQL and Redis. The MongoDB and PostgreSQL adapters provide incremental per-operation writes, per-document write queues and failure recovery; the Redis adapter is a snapshot store, as covered in Adapters. The PostgreSQL adapter is documented in depth as a reference implementation.