Cloudillo uses Yjs for real-time collaborative editing with CRDT (Conflict-Free Replicated Data Types).
CRDT vs RTDB
CRDT is best for collaborative editing where multiple users edit simultaneously. For structured data with queries (todos, settings, lists), see RTDB. Compare all storage types in Data Storage & Access.
Installation
pnpm add yjs y-websocket
Quick Start
import*ascloudillofrom'@cloudillo/core'import*asYfrom'yjs'// Initialize
awaitcloudillo.init('my-app')
// Create Yjs document
constyDoc=newY.Doc()
// Open collaborative document
const { provider } =awaitcloudillo.openYDoc(yDoc, 'my-document-id')
// Use shared text
constyText=yDoc.getText('content')
yText.insert(0, 'Hello, collaborative world!')
// Listen for changes
yText.observe(() => {
console.log('Text updated:', yText.toString())
})
Shared Types
Yjs provides several shared data types:
YText - Shared Text
Best for plain text or rich text content.
constyText=yDoc.getText('content')
// Insert text
yText.insert(0, 'Hello ')
yText.insert(6, 'world!')
// Delete text
yText.delete(0, 5) // Delete 5 characters from position 0
// Format text (for rich text)
yText.format(0, 5, { bold: true })
// Get text content
console.log(yText.toString()) // "world!"
// Observe changes
yText.observe((event) => {
event.changes.delta.forEach(change=> {
if (change.insert) {
console.log('Inserted:', change.insert)
}
if (change.delete) {
console.log('Deleted', change.delete, 'characters')
}
})
})
YMap - Shared Object
Best for key-value data like form fields or settings.
Yjs documents work offline and sync when reconnected.
import { IndexeddbPersistence } from'y-indexeddb'constyDoc=newY.Doc()
// Persist to IndexedDB
constindexeddbProvider=newIndexeddbPersistence('my-doc-id', yDoc)
indexeddbProvider.on('synced', () => {
console.log('Loaded from IndexedDB')
})
// Also connect to server
const { provider } =awaitcloudillo.openYDoc(yDoc, 'my-doc-id')
// Now works offline with local persistence
// Syncs to server when connection available
Transactions
Group multiple changes into a single transaction:
yDoc.transact(() => {
constyText=yDoc.getText('content')
yText.insert(0, 'Hello ')
yText.insert(6, 'world!')
yText.format(0, 11, { bold: true })
})
// All changes sync as one update
// Only one observer event fired
Undo/Redo
import { UndoManager } from'yjs'constyText=yDoc.getText('content')
constundoManager=newUndoManager(yText)
// Make changes
yText.insert(0, 'Hello')
// Undo
undoManager.undo()
// Redo
undoManager.redo()
// Track who made changes
undoManager.on('stack-item-added', (event) => {
console.log('Change by:', event.origin)
})
Document Lifecycle
// Create document
constyDoc=newY.Doc()
// Open collaborative connection
const { provider } =awaitcloudillo.openYDoc(yDoc, 'doc_123')
// Use document...
// Close connection
provider.destroy()
// Destroy document
yDoc.destroy()
Best Practices
1. Use Subdocs for Large Documents
constyDoc=newY.Doc()
constyMap=yDoc.getMap('pages')
// Create subdocument for each page
constpage1=newY.Doc()
yMap.set('page1', page1)
constpage1Text=page1.getText('content')
page1Text.insert(0, 'Page 1 content')
2. Batch Operations in Transactions
// ✅ Single transaction
yDoc.transact(() => {
for (leti=0; i<100; i++) {
yText.insert(i, 'x')
}
})
// ❌ Many transactions
for (leti=0; i<100; i++) {
yText.insert(i, 'x') // Sends 100 updates!
}