/** * SyncServer singleton — shared across server/index.ts and modules. * * Participant mode: server maintains its own Automerge docs. * On any doc change, debounced-save to disk via doc-persistence. * * When a doc belongs to an encrypted space (meta.encrypted === true), * the save is encrypted at rest using the space's encryptionKeyId. * * Relay mode: for encrypted spaces, the server stores opaque blobs * it cannot decrypt, enabling cross-device restore. */ import { SyncServer } from "./local-first/sync-server"; import { saveDoc, saveDocImmediate, saveEncryptedBlob, loadEncryptedBlob } from "./local-first/doc-persistence"; import { getDocumentData } from "./community-store"; import { spaceKnowledgeIndex } from "./space-knowledge"; /** * Look up the encryption key ID for a doc's space. * DocIds are formatted as "spaceSlug:module:collection[:itemId]". * Returns the encryptionKeyId if the space has encryption enabled, else undefined. */ function getEncryptionKeyId(docId: string): string | undefined { const spaceSlug = docId.split(":")[0]; if (!spaceSlug || spaceSlug === "global") return undefined; const data = getDocumentData(spaceSlug); if (data?.meta?.encrypted && data.meta.encryptionKeyId) { return data.meta.encryptionKeyId; } return undefined; } export const syncServer = new SyncServer({ participantMode: true, maxDocs: 500, onDocChange: (docId, doc) => { const spaceSlug = docId.split(":")[0]; if (spaceSlug && spaceSlug !== "global") spaceKnowledgeIndex.invalidate(spaceSlug); const encryptionKeyId = getEncryptionKeyId(docId); saveDoc(docId, doc, encryptionKeyId); }, onDocEvict: (docId, doc) => { // Persist to disk immediately before evicting from memory (no debounce!) const encryptionKeyId = getEncryptionKeyId(docId); saveDocImmediate(docId, doc, encryptionKeyId).catch(e => { console.error(`[SyncInstance] Eviction save failed for ${docId}:`, e); }); }, onRelayBackup: (docId, blob) => { saveEncryptedBlob(docId, blob); }, onRelayLoad: (docId) => { return loadEncryptedBlob(docId); }, }); /** * Flush all in-memory docs to disk immediately, then resolve. * Called on SIGTERM/SIGINT before process exit. */ export async function flushAndShutdown(): Promise { console.log("[SyncInstance] Flushing all docs to disk before shutdown..."); const start = Date.now(); await syncServer.flushAll(async (docId, doc) => { const encryptionKeyId = getEncryptionKeyId(docId); await saveDocImmediate(docId, doc, encryptionKeyId); }); console.log(`[SyncInstance] Flush complete (${Date.now() - start}ms)`); }