feat(workspace): implement Yjs CRDT real-time collaborative editing (resolves FLAW-01)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Failing after 29s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Failing after 29s
This commit is contained in:
1 parent
0bdeb5f320
commit
ef8cf8f022
13 files changed
+1028
-85
No files matched your search
@@ -232,6 +232,25 @@ func (s *Store) migrate() error {
|
||||
PRIMARY KEY(user_id, tag)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS document_crdt_state (
|
||||
doc_id TEXT PRIMARY KEY,
|
||||
state_blob BLOB NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
updated_by TEXT NOT NULL DEFAULT '',
|
||||
FOREIGN KEY(doc_id) REFERENCES documents(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS document_crdt_updates (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
doc_id TEXT NOT NULL,
|
||||
update_blob BLOB NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
created_by TEXT NOT NULL DEFAULT '',
|
||||
FOREIGN KEY(doc_id) REFERENCES documents(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_doc_crdt_updates_doc ON document_crdt_updates(doc_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS lists (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
@@ -1559,6 +1578,59 @@ func (s *Store) PatchDocumentBlocks(userID, docID string, req domain.DocBlocksPa
|
||||
return doc, mergedOrder, nil
|
||||
}
|
||||
|
||||
// GetDocumentCRDTState returns the compacted CRDT state blob and any pending updates for a document.
|
||||
func (s *Store) GetDocumentCRDTState(docID string) ([]byte, [][]byte, error) {
|
||||
var stateBlob []byte
|
||||
err := s.db.QueryRow(`SELECT state_blob FROM document_crdt_state WHERE doc_id = ?`, docID).Scan(&stateBlob)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rows, err := s.db.Query(`SELECT update_blob FROM document_crdt_updates WHERE doc_id = ? ORDER BY id ASC`, docID)
|
||||
if err != nil {
|
||||
return stateBlob, nil, nil
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var updates [][]byte
|
||||
for rows.Next() {
|
||||
var u []byte
|
||||
if err := rows.Scan(&u); err == nil && len(u) > 0 {
|
||||
updates = append(updates, u)
|
||||
}
|
||||
}
|
||||
return stateBlob, updates, nil
|
||||
}
|
||||
|
||||
// AppendDocumentCRDTUpdate appends a new CRDT update delta for a document.
|
||||
func (s *Store) AppendDocumentCRDTUpdate(docID string, update []byte, userID string) error {
|
||||
now := domain.NowISO()
|
||||
_, err := s.db.Exec(`
|
||||
INSERT INTO document_crdt_updates (doc_id, update_blob, created_at, created_by)
|
||||
VALUES (?, ?, ?, ?)
|
||||
`, docID, update, now, userID)
|
||||
return err
|
||||
}
|
||||
|
||||
// SaveDocumentCRDTState saves a compacted snapshot of the document CRDT state and prunes older incremental updates.
|
||||
func (s *Store) SaveDocumentCRDTState(docID string, stateBlob []byte, userID string) error {
|
||||
now := domain.NowISO()
|
||||
_, err := s.db.Exec(`
|
||||
INSERT INTO document_crdt_state (doc_id, state_blob, updated_at, updated_by)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(doc_id) DO UPDATE SET
|
||||
state_blob = excluded.state_blob,
|
||||
updated_at = excluded.updated_at,
|
||||
updated_by = excluded.updated_by
|
||||
`, docID, stateBlob, now, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Prune incremental updates older than the snapshot
|
||||
_, _ = s.db.Exec(`DELETE FROM document_crdt_updates WHERE doc_id = ?`, docID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAuthorizedDocUsers returns all user IDs who have read/write/owner access to docID.
|
||||
func (s *Store) GetAuthorizedDocUsers(docID string) ([]string, error) {
|
||||
userSet := make(map[string]bool)
|
||||
|
||||
Reference in new issue
Block a user