source: Klonkt/src/services/CRDTService.js@ 7bc636b

main
Last change on this file since 7bc636b was 7bc636b, checked in by Robin <robin@…>, 4 months ago

Initial commit — PrutFolio v1 source (pulled from Hetzner /srv/prutfolio)

  • Property mode set to 100644
File size: 3.4 KB
Line 
1import * as Y from 'yjs';
2
3// Global CRDT document cache
4// Key: "siteId:postId"
5// Value: { ydoc, lastSaved, updateCallbacks }
6const documents = new Map();
7
8export class CRDTService {
9 /**
10 * Get or create a Yjs document for a post
11 */
12 static getDocument(siteId, postId, initialBinary = null) {
13 const key = `${siteId}:${postId}`;
14
15 if (documents.has(key)) {
16 return documents.get(key).ydoc;
17 }
18
19 // Create new document
20 const ydoc = new Y.Doc();
21 const ytext = ydoc.getText('content');
22 const ymeta = ydoc.getMap('metadata');
23
24 // If we have stored binary, restore it
25 if (initialBinary && initialBinary.length > 0) {
26 try {
27 Y.applyUpdate(ydoc, initialBinary);
28 } catch (err) {
29 console.error(`⚠️ Failed to restore CRDT for ${key}:`, err);
30 }
31 }
32
33 documents.set(key, {
34 ydoc,
35 lastSaved: Date.now(),
36 updateCallbacks: []
37 });
38
39 return ydoc;
40 }
41
42 /**
43 * Get plain text content from document (for FTS5 + display)
44 */
45 static getPlainText(siteId, postId) {
46 const ydoc = this.getDocument(siteId, postId);
47 const ytext = ydoc.getText('content');
48 return ytext.toString();
49 }
50
51 /**
52 * Apply an update to a document
53 */
54 static applyUpdate(siteId, postId, updateBinary) {
55 const key = `${siteId}:${postId}`;
56 const ydoc = this.getDocument(siteId, postId);
57
58 try {
59 Y.applyUpdate(ydoc, updateBinary);
60 if (documents.has(key)) {
61 documents.get(key).lastSaved = Date.now();
62 }
63 return true;
64 } catch (err) {
65 console.error(`⚠️ Failed to apply update for ${key}:`, err);
66 return false;
67 }
68 }
69
70 /**
71 * Get full state as binary (for persistence to SQLite)
72 */
73 static encodeState(siteId, postId) {
74 const ydoc = this.getDocument(siteId, postId);
75 return Y.encodeStateAsUpdate(ydoc);
76 }
77
78 /**
79 * Register a callback for document updates
80 */
81 static onUpdate(siteId, postId, callback) {
82 const key = `${siteId}:${postId}`;
83 const ydoc = this.getDocument(siteId, postId);
84
85 // Store callback for later cleanup
86 if (!documents.has(key)) {
87 documents.set(key, { ydoc, lastSaved: Date.now(), updateCallbacks: [] });
88 }
89 documents.get(key).updateCallbacks.push(callback);
90
91 // Trigger callback on every update
92 const listener = (update, origin) => {
93 if (origin !== 'local') {
94 callback(update);
95 }
96 };
97
98 ydoc.on('update', listener);
99
100 // Return unsubscribe function
101 return () => {
102 ydoc.off('update', listener);
103 };
104 }
105
106 /**
107 * Clean up old documents from memory
108 */
109 static cleanup(olderThanMinutes = 60) {
110 const cutoff = Date.now() - (olderThanMinutes * 60 * 1000);
111 let cleaned = 0;
112
113 for (const [key, value] of documents.entries()) {
114 if (value.lastSaved < cutoff) {
115 value.ydoc.destroy();
116 documents.delete(key);
117 cleaned++;
118 }
119 }
120
121 if (cleaned > 0) {
122 console.log(`🧹 CRDT: Cleaned ${cleaned} old documents`);
123 }
124 }
125
126 /**
127 * Destroy a document
128 */
129 static destroy(siteId, postId) {
130 const key = `${siteId}:${postId}`;
131 if (documents.has(key)) {
132 documents.get(key).ydoc.destroy();
133 documents.delete(key);
134 }
135 }
136
137 /**
138 * Get stats (for debugging)
139 */
140 static getStats() {
141 return {
142 documentsInMemory: documents.size,
143 documents: Array.from(documents.keys())
144 };
145 }
146}
147
148export default CRDTService;
Note: See TracBrowser for help on using the repository browser.