Download lib/shared-documents.ts from ayushkokande/distributed-collaborative-editor: direct link, hf CLI and curl.
- Browser
- Download file 4.37 kB
-
https://huggingface.co/spaces/ayushkokande/distributed-collaborative-editor/resolve/main/lib/shared-documents.ts
- Command line
-
hf download hf://spaces/ayushkokande/distributed-collaborative-editor/lib/shared-documents.ts
-
curl -L -o shared-documents.ts https://huggingface.co/spaces/ayushkokande/distributed-collaborative-editor/resolve/main/lib/shared-documents.ts
4.37 kB
| import fs from "node:fs/promises"; | |
| import path from "node:path"; | |
| import * as Y from "yjs"; | |
| export interface SharedDocumentSummary { | |
| id: string; | |
| title: string; | |
| createdAt: number; | |
| updatedAt: number; | |
| } | |
| interface SharedDocumentRecord extends SharedDocumentSummary { | |
| stateBase64: string; | |
| } | |
| interface SharedDocumentsFile { | |
| documents: SharedDocumentRecord[]; | |
| } | |
| const DATA_DIR = path.join(process.cwd(), "data"); | |
| const DOCUMENTS_FILE = path.join(DATA_DIR, "shared-documents.json"); | |
| const pendingStates = new Map<string, Uint8Array>(); | |
| const pendingTimers = new Map<string, ReturnType<typeof setTimeout>>(); | |
| let writeQueue: Promise<void> = Promise.resolve(); | |
| function defaultFile(): SharedDocumentsFile { | |
| return { documents: [] }; | |
| } | |
| async function ensureDataDir() { | |
| await fs.mkdir(DATA_DIR, { recursive: true }); | |
| } | |
| async function readFile(): Promise<SharedDocumentsFile> { | |
| try { | |
| const raw = await fs.readFile(DOCUMENTS_FILE, "utf8"); | |
| const parsed = JSON.parse(raw) as Partial<SharedDocumentsFile>; | |
| if (!parsed.documents || !Array.isArray(parsed.documents)) { | |
| return defaultFile(); | |
| } | |
| return { | |
| documents: parsed.documents | |
| .filter((document): document is SharedDocumentRecord => { | |
| return ( | |
| typeof document?.id === "string" && | |
| typeof document?.title === "string" && | |
| typeof document?.createdAt === "number" && | |
| typeof document?.updatedAt === "number" && | |
| typeof document?.stateBase64 === "string" | |
| ); | |
| }) | |
| .map((document) => ({ ...document })), | |
| }; | |
| } catch { | |
| return defaultFile(); | |
| } | |
| } | |
| async function writeFile(file: SharedDocumentsFile) { | |
| await ensureDataDir(); | |
| await fs.writeFile(DOCUMENTS_FILE, JSON.stringify(file, null, 2), "utf8"); | |
| } | |
| async function queueWrite(task: () => Promise<void>) { | |
| writeQueue = writeQueue.then(task, task); | |
| return writeQueue; | |
| } | |
| function summarizeDocument( | |
| id: string, | |
| state: Uint8Array, | |
| existing?: SharedDocumentRecord | |
| ): SharedDocumentRecord { | |
| const ydoc = new Y.Doc(); | |
| let title = existing?.title || "Untitled Document"; | |
| try { | |
| Y.applyUpdate(ydoc, state); | |
| const nextTitle = ydoc.getText("title").toString().trim(); | |
| if (nextTitle) { | |
| title = nextTitle; | |
| } | |
| } catch { | |
| // Keep the previous title if the state cannot be parsed. | |
| } finally { | |
| ydoc.destroy(); | |
| } | |
| const now = Date.now(); | |
| return { | |
| id, | |
| title, | |
| createdAt: existing?.createdAt ?? now, | |
| updatedAt: now, | |
| stateBase64: Buffer.from(state).toString("base64"), | |
| }; | |
| } | |
| function scheduleFlush(documentId: string) { | |
| const existingTimer = pendingTimers.get(documentId); | |
| if (existingTimer) { | |
| clearTimeout(existingTimer); | |
| } | |
| pendingTimers.set( | |
| documentId, | |
| setTimeout(() => { | |
| void flushDocument(documentId); | |
| }, 250) | |
| ); | |
| } | |
| async function flushDocument(documentId: string) { | |
| const state = pendingStates.get(documentId); | |
| if (!state) { | |
| return; | |
| } | |
| pendingStates.delete(documentId); | |
| const existingTimer = pendingTimers.get(documentId); | |
| if (existingTimer) { | |
| clearTimeout(existingTimer); | |
| pendingTimers.delete(documentId); | |
| } | |
| await queueWrite(async () => { | |
| const file = await readFile(); | |
| const existing = file.documents.find((document) => document.id === documentId); | |
| const record = summarizeDocument(documentId, state, existing); | |
| const documents = file.documents.filter((document) => document.id !== documentId); | |
| documents.push(record); | |
| documents.sort((a, b) => b.updatedAt - a.updatedAt); | |
| await writeFile({ documents }); | |
| }); | |
| } | |
| export async function listSharedDocuments(): Promise<SharedDocumentSummary[]> { | |
| const file = await readFile(); | |
| return file.documents | |
| .slice() | |
| .sort((a, b) => b.updatedAt - a.updatedAt) | |
| .map(({ stateBase64: _stateBase64, ...summary }) => summary); | |
| } | |
| export async function getSharedDocumentState( | |
| documentId: string | |
| ): Promise<Uint8Array | null> { | |
| const file = await readFile(); | |
| const record = file.documents.find((document) => document.id === documentId); | |
| if (!record) { | |
| return null; | |
| } | |
| return new Uint8Array(Buffer.from(record.stateBase64, "base64")); | |
| } | |
| export function scheduleSharedDocumentUpdate( | |
| documentId: string, | |
| state: Uint8Array | |
| ) { | |
| pendingStates.set(documentId, state); | |
| scheduleFlush(documentId); | |
| } | |