import { Database } from"bun:sqlite"; const db = newDatabase("collab.sqlite");
// 启用 WAL 模式以提高并发性能 db.exec("PRAGMA journal_mode = WAL;"); // 房间表,包含 content BLOB 字段存储 Yjs 二进制数据 db.run(` CREATE TABLE IF NOT EXISTS rooms ( id TEXT PRIMARY KEY, name TEXT NOT NULL, creator_id TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, content BLOB, -- Yjs 二进制数据 FOREIGN KEY (creator_id) REFERENCES users(id) ) `);
// Hocuspocus 数据库扩展实现 (ALD_Backend/src/collab.ts)
const dbExtension = newHocuspocusDB({ fetch: async ({ documentName }) => { const roomId = getRoomId(documentName); console.log( `[Yjs] Fetching data for RoomID: ${roomId}, Original documentName: ${documentName}` );
const query = db.query("SELECT content FROM rooms WHERE id = $id"); const row = query.get({ $id: roomId }) as { content: Uint8Array | null; } | null;
if (row && row.content !== null && row.content !== undefined) { if (row.content.length > 0) { console.log( `[Yjs] Returning data with size: ${row.content.length} bytes` ); returnnewUint8Array(row.content); } else { console.log(`[Yjs] content is empty, creating new Yjs document`); const ydoc = new Y.Doc(); ydoc.getMap("elements"); // 存储图形元素 return Y.encodeStateAsUpdate(ydoc); } }
console.log(`[Yjs] No valid data found, creating new Yjs document`); const ydoc = new Y.Doc(); ydoc.getMap("elements"); // 存储图形元素 return Y.encodeStateAsUpdate(ydoc); },
store: async ({ documentName, state }) => { const roomId = getRoomId(documentName); try { console.log( `[Yjs] Saving data for RoomID: ${roomId}, State size: ${state.length} bytes, Original documentName: ${documentName}` );
if (state.length > 0) { const roomCheck = db.query("SELECT id FROM rooms WHERE id = $id");
if (!roomExists) { console.error( `[Yjs] Room ${roomId} does not exist, cannot save data` );
return; }
const update = db.query( "UPDATE rooms SET content = $blob WHERE id = $id" );
update.run({ $blob: state, $id: roomId });
console.log(`[Yjs] Data saved successfully for RoomID: ${roomId}`); } else { console.log( `[Yjs] Skipping save for RoomID: ${roomId} as state is empty` ); } } catch (error) { console.error(`[Yjs] Save failed for ${roomId}:`, error); } }, });
graph TB subgraph "Frontend Application (React/Vue)" A[Canvas UI Components] B[State Management<br/>Zustand/Pinia] C[Yjs Document<br/>Shared Data] D[IndexedDB<br/>Local Persistence] end subgraph "Backend Services" E[Hono Server<br/>Port 3000] F[RESTful API<br/>Auth/RM] G[SQLite DB<br/>Persistence] end subgraph "WebSocket Services" H[Hocuspocus Server<br/>Port 1234] I[Yjs Extensions<br/>DB/Storage] end A <--> C B <--> C C <--> D C <--> H E <--> F E <--> G F <--> H H <--> I I <--> G style A fill:#87CEEB style B fill:#98FB98 style C fill:#FFD700 style D fill:#DDA0DD style E fill:#F0E68C style F fill:#FFA07A style G fill:#BA55D3 style H fill:#20B2AA style I fill:#FF69B4
graph LR subgraph "Client A" A[Y.Map<br/>Shared Data] B[IndexedDB<br/>Local Persistence] end subgraph "Server" C[Hocuspocus<br/>Server] D[SQLite DB<br/>Persistence] end subgraph "Client B" E[Y.Map<br/>Shared Data] F[IndexedDB<br/>Local Persistence] end A <--> C E <--> C B <--> D F <--> D C <--> D style A fill:#FFD700 style B fill:#DDA0DD style C fill:#20B2AA style D fill:#BA55D3 style E fill:#FFD700 style F fill:#DDA0DD