import { Hocuspocus, type Configuration } from '@hocuspocus/server' import { Database } from '@hocuspocus/extension-database' import * as Y from 'yjs' import { DEFAULT_DECK } from './sample-deck' import { createStorage, type DeckStorage } from './storage' import { authRequired, verifySessionToken } from './auth' /** * The realtime (Yjs) layer with durable, debounced persistence. * * - New decks are seeded once (in `fetch`, when storage has nothing yet) so clients * never race to seed and existing decks are never overwritten. * - Edits are debounced, then stored as a binary Yjs snapshot + readable markdown. * * M4 adds onAuthenticate (Hugging Face identity). */ export function createCollabServer(): { hocuspocus: ReturnType; storage: DeckStorage } { const storage = createStorage() const configuration: Partial = { debounce: 5000, maxDebounce: 30000, extensions: [ new Database({ fetch: async ({ documentName }) => { const stored = await storage.load(documentName) if (stored) return stored // Brand-new deck: seed it. const seed = new Y.Doc() seed.getText('content').insert(0, DEFAULT_DECK) return Y.encodeStateAsUpdate(seed) }, store: async ({ documentName, state, document }) => { const markdown = document.getText('content').toString() await storage.save(documentName, new Uint8Array(state), markdown) }, }), ], } // Honor a session token (attaches the signed-in identity) but allow guests through // unless AUTH_REQUIRED is set. configuration.onAuthenticate = async ({ token }) => { const user = await verifySessionToken(token) if (user) return { user } if (authRequired) throw new Error('Unauthorized') return {} } const hocuspocus = new Hocuspocus().configure(configuration) return { hocuspocus, storage } }