/** * Real-Time Code Synchronization Engine * KILLER FEATURE: Operational Transform-based conflict resolution * Google Docs-level synchronization for code - Cursor can't do this! */ import { EventEmitter } from 'events'; import { v4 as uuidv4 } from 'uuid'; import { Logger } from '../utils/logger'; export interface CodeOperation { id: string; type: 'insert' | 'delete' | 'retain'; position: number; content?: string; length?: number; author: string; timestamp: Date; sessionId: string; fileId: string; } export interface CodeDocument { id: string; sessionId: string; filePath: string; content: string; version: number; lastModified: Date; lastModifiedBy: string; operations: CodeOperation[]; collaborators: string[]; // user IDs currently editing } export interface ConflictResolution { strategy: 'operational_transform' | 'last_writer_wins' | 'manual_merge' | 'ai_assisted'; resolvedBy?: string; originalOperations: CodeOperation[]; resolvedOperations: CodeOperation[]; conflictReason: string; resolutionTime: Date; } export interface SyncState { documentId: string; userId: string; localVersion: number; serverVersion: number; pendingOperations: CodeOperation[]; acknowledgedOperations: string[]; // operation IDs lastSyncTime: Date; } export class CodeSyncEngine extends EventEmitter { private documents: Map = new Map(); private syncStates: Map = new Map(); // userId -> SyncState private operationQueues: Map = new Map(); // documentId -> operations private conflictHistory: Map = new Map(); constructor() { super(); this.startSyncProcessor(); } // Document Management createDocument( sessionId: string, filePath: string, initialContent: string = '', createdBy: string ): CodeDocument { const documentId = uuidv4(); const document: CodeDocument = { id: documentId, sessionId, filePath, content: initialContent, version: 0, lastModified: new Date(), lastModifiedBy: createdBy, operations: [], collaborators: [createdBy] }; this.documents.set(documentId, document); this.operationQueues.set(documentId, []); this.emit('document-created', { document }); Logger.info(`Code document created: ${filePath} in session ${sessionId}`); return document; } getDocument(documentId: string): CodeDocument | null { return this.documents.get(documentId) || null; } joinDocument(documentId: string, userId: string): boolean { const document = this.documents.get(documentId); if (!document) return false; if (!document.collaborators.includes(userId)) { document.collaborators.push(userId); } // Initialize sync state for user const syncState: SyncState = { documentId, userId, localVersion: document.version, serverVersion: document.version, pendingOperations: [], acknowledgedOperations: [], lastSyncTime: new Date() }; this.syncStates.set(`${documentId}:${userId}`, syncState); this.emit('user-joined-document', { documentId, userId, document }); return true; } leaveDocument(documentId: string, userId: string): boolean { const document = this.documents.get(documentId); if (!document) return false; document.collaborators = document.collaborators.filter(id => id !== userId); this.syncStates.delete(`${documentId}:${userId}`); this.emit('user-left-document', { documentId, userId, document }); return true; } // Operational Transform Implementation submitOperation(operation: CodeOperation): void { const document = this.documents.get(operation.fileId); if (!document) { Logger.error(`Document not found for operation: ${operation.fileId}`); return; } // Add operation to queue for processing const queue = this.operationQueues.get(operation.fileId) || []; queue.push(operation); this.operationQueues.set(operation.fileId, queue); Logger.debug(`Operation queued: ${operation.type} at ${operation.position} by ${operation.author}`); } private processOperations(documentId: string): void { const document = this.documents.get(documentId); const queue = this.operationQueues.get(documentId); if (!document || !queue || queue.length === 0) return; // Sort operations by timestamp for consistent processing queue.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); const processedOps: CodeOperation[] = []; const conflicts: { op1: CodeOperation; op2: CodeOperation; reason: string }[] = []; // Process operations with conflict detection for (let i = 0; i < queue.length; i++) { const currentOp = queue[i]; let transformedOp = { ...currentOp }; // Transform against all previously processed operations for (const processedOp of processedOps) { if (this.operationsConflict(transformedOp, processedOp)) { conflicts.push({ op1: transformedOp, op2: processedOp, reason: this.getConflictReason(transformedOp, processedOp) }); // Apply operational transform transformedOp = this.transformOperation(transformedOp, processedOp); } } processedOps.push(transformedOp); } // Apply operations to document let newContent = document.content; for (const op of processedOps) { newContent = this.applyOperation(newContent, op); document.operations.push(op); } // Update document document.content = newContent; document.version += processedOps.length; document.lastModified = new Date(); document.lastModifiedBy = processedOps[processedOps.length - 1]?.author || 'unknown'; // Clear queue this.operationQueues.set(documentId, []); // Handle conflicts if any if (conflicts.length > 0) { this.handleConflicts(documentId, conflicts); } // Broadcast changes to all collaborators this.broadcastDocumentChange(document, processedOps); } private operationsConflict(op1: CodeOperation, op2: CodeOperation): boolean { if (op1.author === op2.author) return false; // Same author, no conflict if (op1.fileId !== op2.fileId) return false; // Different files, no conflict // Check for overlapping positions const op1End = op1.position + (op1.content?.length || op1.length || 0); const op2End = op2.position + (op2.content?.length || op2.length || 0); return !(op1End <= op2.position || op2End <= op1.position); } private getConflictReason(op1: CodeOperation, op2: CodeOperation): string { if (op1.type === 'insert' && op2.type === 'insert' && op1.position === op2.position) { return 'Simultaneous insertion at same position'; } if (op1.type === 'delete' && op2.type === 'delete') { return 'Overlapping deletions'; } if ((op1.type === 'insert' && op2.type === 'delete') || (op1.type === 'delete' && op2.type === 'insert')) { return 'Insert-delete conflict at overlapping positions'; } return 'Unknown conflict type'; } private transformOperation(op: CodeOperation, against: CodeOperation): CodeOperation { const transformed = { ...op }; if (against.type === 'insert') { // If the other operation inserted text before this operation's position if (against.position <= op.position) { transformed.position += against.content?.length || 0; } } else if (against.type === 'delete') { // If the other operation deleted text before this operation's position if (against.position < op.position) { const deletedLength = against.length || 0; if (against.position + deletedLength <= op.position) { // Deletion is completely before our operation transformed.position -= deletedLength; } else { // Deletion overlaps with or encompasses our position transformed.position = against.position; // Additional logic needed for complex overlap scenarios } } } return transformed; } private applyOperation(content: string, operation: CodeOperation): string { switch (operation.type) { case 'insert': return content.slice(0, operation.position) + (operation.content || '') + content.slice(operation.position); case 'delete': const deleteEnd = operation.position + (operation.length || 0); return content.slice(0, operation.position) + content.slice(deleteEnd); case 'retain': return content; // No change for retain operations default: Logger.warn(`Unknown operation type: ${operation.type}`); return content; } } private handleConflicts( documentId: string, conflicts: { op1: CodeOperation; op2: CodeOperation; reason: string }[] ): void { const resolution: ConflictResolution = { strategy: 'operational_transform', originalOperations: conflicts.flatMap(c => [c.op1, c.op2]), resolvedOperations: [], // Would contain the transformed operations conflictReason: conflicts.map(c => c.reason).join('; '), resolutionTime: new Date() }; // Store conflict resolution history const history = this.conflictHistory.get(documentId) || []; history.push(resolution); this.conflictHistory.set(documentId, history); this.emit('conflicts-resolved', { documentId, resolution, conflicts }); Logger.info(`Resolved ${conflicts.length} conflicts in document ${documentId}`); } private broadcastDocumentChange(document: CodeDocument, operations: CodeOperation[]): void { this.emit('document-changed', { document, operations, collaborators: document.collaborators }); // Update sync states for all collaborators document.collaborators.forEach(userId => { const syncStateKey = `${document.id}:${userId}`; const syncState = this.syncStates.get(syncStateKey); if (syncState) { syncState.serverVersion = document.version; syncState.lastSyncTime = new Date(); } }); } private startSyncProcessor(): void { // Process operations every 100ms for real-time feel setInterval(() => { for (const documentId of this.operationQueues.keys()) { this.processOperations(documentId); } }, 100); } // Advanced Conflict Resolution async resolveConflictWithAI( documentId: string, conflictingOperations: CodeOperation[] ): Promise { // This would integrate with the AI system to intelligently resolve conflicts // For now, return a mock resolution const resolution: ConflictResolution = { strategy: 'ai_assisted', resolvedBy: 'ai-assistant', originalOperations: conflictingOperations, resolvedOperations: conflictingOperations, // AI would provide better resolution conflictReason: 'AI-assisted automatic resolution', resolutionTime: new Date() }; this.emit('ai-conflict-resolution', { documentId, resolution }); return resolution; } manualResolveConflict( documentId: string, resolution: ConflictResolution ): void { const document = this.documents.get(documentId); if (!document) return; // Apply the manual resolution let newContent = document.content; for (const op of resolution.resolvedOperations) { newContent = this.applyOperation(newContent, op); } document.content = newContent; document.version++; document.lastModified = new Date(); // Store resolution const history = this.conflictHistory.get(documentId) || []; history.push(resolution); this.conflictHistory.set(documentId, history); this.broadcastDocumentChange(document, resolution.resolvedOperations); this.emit('manual-conflict-resolved', { documentId, resolution }); } // Synchronization State Management getSyncState(documentId: string, userId: string): SyncState | null { return this.syncStates.get(`${documentId}:${userId}`) || null; } updateSyncState(documentId: string, userId: string, updates: Partial): void { const key = `${documentId}:${userId}`; const current = this.syncStates.get(key); if (current) { Object.assign(current, updates); this.syncStates.set(key, current); } } // Analytics and Monitoring getDocumentAnalytics(documentId: string) { const document = this.documents.get(documentId); const conflicts = this.conflictHistory.get(documentId) || []; if (!document) return null; return { documentId, filePath: document.filePath, version: document.version, collaborators: document.collaborators.length, totalOperations: document.operations.length, totalConflicts: conflicts.length, lastModified: document.lastModified, operationsByType: this.groupOperationsByType(document.operations), operationsByAuthor: this.groupOperationsByAuthor(document.operations), conflictResolutionStrategies: this.groupConflictsByStrategy(conflicts), averageConflictResolutionTime: this.calculateAverageResolutionTime(conflicts) }; } private groupOperationsByType(operations: CodeOperation[]) { return operations.reduce((acc, op) => { acc[op.type] = (acc[op.type] || 0) + 1; return acc; }, {} as Record); } private groupOperationsByAuthor(operations: CodeOperation[]) { return operations.reduce((acc, op) => { acc[op.author] = (acc[op.author] || 0) + 1; return acc; }, {} as Record); } private groupConflictsByStrategy(conflicts: ConflictResolution[]) { return conflicts.reduce((acc, conflict) => { acc[conflict.strategy] = (acc[conflict.strategy] || 0) + 1; return acc; }, {} as Record); } private calculateAverageResolutionTime(conflicts: ConflictResolution[]): number { if (conflicts.length === 0) return 0; // This is simplified - in reality, you'd track conflict detection time const totalTime = conflicts.reduce((sum, conflict) => { return sum + (conflict.resolutionTime.getTime() - conflict.resolutionTime.getTime()); }, 0); return totalTime / conflicts.length; } // Cleanup and Shutdown async shutdown(): Promise { Logger.info('Shutting down code sync engine...'); this.documents.clear(); this.syncStates.clear(); this.operationQueues.clear(); this.conflictHistory.clear(); Logger.info('Code sync engine shut down'); } } // Export types // Types are already exported as interfaces above