UNPKG

cs-element

Version:

Advanced reactive data management library with state machines, blueprints, persistence, compression, networking, and multithreading support

892 lines (738 loc) 30.5 kB
# 🔍 Diff Engine и слияние данных CSElement включает мощный движок для вычисления различий между элементами и выполнения трёхстороннего слияния данных. ## 📋 Содержание - [Основы Diff Engine](#основы-diff-engine) - [Алгоритмы вычисления различий](#алгоритмы-вычисления-различий) - [Трёхстороннее слияние](#трёхстороннее-слияние) - [Визуализация различий](#визуализация-различий) - [Полный пример](#полный-пример-система-версионирования) ## Основы Diff Engine ### Создание и использование ```typescript import { DiffEngine, DiffAlgorithm, MergeStrategy, DiffResult, ThreeWayMergeResult } from 'cs-element'; // Создание движка const diffEngine = new DiffEngine(); // Создание элементов для сравнения const sourceElement = new CSElement('doc-v1'); await sourceElement.setData('title', 'Документ версии 1'); await sourceElement.setData('content', 'Исходное содержимое документа'); await sourceElement.setData('tags', ['draft', 'important']); const targetElement = new CSElement('doc-v2'); await targetElement.setData('title', 'Документ версии 2'); await targetElement.setData('content', 'Обновленное содержимое документа'); await targetElement.setData('tags', ['draft', 'important', 'reviewed']); await targetElement.setData('author', 'John Doe'); // Вычисление различий const diffResult = diffEngine.computeDiff(sourceElement, targetElement, { algorithm: DiffAlgorithm.MYERS, ignoreWhitespace: false, ignoreCase: false, contextLines: 3, includeVisualization: true }); console.log('📊 Результат сравнения:', { additions: diffResult.summary.additions, deletions: diffResult.summary.deletions, modifications: diffResult.summary.modifications }); ``` ### Анализ изменений ```typescript // Подробный анализ изменений for (const change of diffResult.changes) { console.log(`📝 Изменение в ${change.path.join('.')}:`); console.log(` Тип: ${change.type}`); console.log(` Было: ${JSON.stringify(change.oldValue)}`); console.log(` Стало: ${JSON.stringify(change.newValue)}`); console.log(` Уверенность: ${change.confidence}`); } // Фильтрация изменений по типу const additions = diffResult.changes.filter(c => c.type === 'addition'); const deletions = diffResult.changes.filter(c => c.type === 'deletion'); const modifications = diffResult.changes.filter(c => c.type === 'modification'); console.log(`➕ Добавлено: ${additions.length}`); console.log(`➖ Удалено: ${deletions.length}`); console.log(`✏️ Изменено: ${modifications.length}`); ``` ## Алгоритмы вычисления различий ### Myers Algorithm ```typescript // Алгоритм Myers (по умолчанию) const myersDiff = diffEngine.computeDiff(sourceElement, targetElement, { algorithm: DiffAlgorithm.MYERS, contextLines: 5 }); console.log('Myers Algorithm результат:', myersDiff.summary); ``` ### Patience Algorithm ```typescript // Алгоритм Patience - лучше для структурированных данных const patienceDiff = diffEngine.computeDiff(sourceElement, targetElement, { algorithm: DiffAlgorithm.PATIENCE, contextLines: 3 }); console.log('Patience Algorithm результат:', patienceDiff.summary); ``` ### Histogram Algorithm ```typescript // Алгоритм Histogram - оптимизированная версия Patience const histogramDiff = diffEngine.computeDiff(sourceElement, targetElement, { algorithm: DiffAlgorithm.HISTOGRAM, contextLines: 3 }); console.log('Histogram Algorithm результат:', histogramDiff.summary); ``` ### Сравнение алгоритмов ```typescript class DiffAlgorithmComparator { private diffEngine: DiffEngine; constructor() { this.diffEngine = new DiffEngine(); } async compareAlgorithms( sourceElement: CSElement, targetElement: CSElement ): Promise<{ myers: DiffResult; patience: DiffResult; histogram: DiffResult; simple: DiffResult; comparison: { algorithm: string; executionTime: number; changesCount: number; accuracy: number; }[]; }> { const algorithms = [ DiffAlgorithm.MYERS, DiffAlgorithm.PATIENCE, DiffAlgorithm.HISTOGRAM, DiffAlgorithm.SIMPLE ]; const results: Record<string, DiffResult> = {}; const comparison = []; for (const algorithm of algorithms) { const startTime = Date.now(); const result = this.diffEngine.computeDiff(sourceElement, targetElement, { algorithm, contextLines: 3, includeVisualization: false }); const executionTime = Date.now() - startTime; const changesCount = result.changes.length; const accuracy = this.calculateAccuracy(result); results[algorithm] = result; comparison.push({ algorithm, executionTime, changesCount, accuracy }); } return { myers: results[DiffAlgorithm.MYERS], patience: results[DiffAlgorithm.PATIENCE], histogram: results[DiffAlgorithm.HISTOGRAM], simple: results[DiffAlgorithm.SIMPLE], comparison }; } private calculateAccuracy(result: DiffResult): number { // Простая метрика точности на основе уверенности в изменениях const totalConfidence = result.changes.reduce((sum, change) => sum + change.confidence, 0); return result.changes.length > 0 ? totalConfidence / result.changes.length : 1; } } // Использование сравнения const comparator = new DiffAlgorithmComparator(); const comparison = await comparator.compareAlgorithms(sourceElement, targetElement); console.log('📊 Сравнение алгоритмов:', comparison.comparison); ``` ## Трёхстороннее слияние ### Основы слияния ```typescript // Создание базовой версии const baseElement = new CSElement('doc-base'); await baseElement.setData('title', 'Базовый документ'); await baseElement.setData('content', 'Базовое содержимое'); await baseElement.setData('version', 1); // Создание ветки A const branchA = new CSElement('doc-branch-a'); await branchA.setData('title', 'Документ ветки A'); await branchA.setData('content', 'Содержимое изменено в ветке A'); await branchA.setData('version', 2); await branchA.setData('author', 'Alice'); // Создание ветки B const branchB = new CSElement('doc-branch-b'); await branchB.setData('title', 'Базовый документ'); await branchB.setData('content', 'Базовое содержимое'); await branchB.setData('version', 2); await branchB.setData('reviewer', 'Bob'); // Выполнение трёхстороннего слияния const mergeResult = diffEngine.threeWayMerge(baseElement, branchA, branchB, { strategy: MergeStrategy.AUTO, conflictResolution: 'auto', autoResolveThreshold: 0.8 }); console.log('🔀 Результат слияния:', { success: mergeResult.success, autoResolved: mergeResult.autoResolved, manualRequired: mergeResult.manualRequired, conflicts: mergeResult.conflicts.length }); ``` ### Стратегии слияния ```typescript // Автоматическое слияние const autoMerge = diffEngine.threeWayMerge(baseElement, branchA, branchB, { strategy: MergeStrategy.AUTO }); // Приоритет нашей ветки const oursMerge = diffEngine.threeWayMerge(baseElement, branchA, branchB, { strategy: MergeStrategy.OURS }); // Приоритет их ветки const theirsMerge = diffEngine.threeWayMerge(baseElement, branchA, branchB, { strategy: MergeStrategy.THEIRS }); // Объединение всех изменений const unionMerge = diffEngine.threeWayMerge(baseElement, branchA, branchB, { strategy: MergeStrategy.UNION }); console.log('📊 Сравнение стратегий:', { auto: autoMerge.success, ours: oursMerge.success, theirs: theirsMerge.success, union: unionMerge.success }); ``` ### Разрешение конфликтов ```typescript class ConflictResolver { private diffEngine: DiffEngine; constructor() { this.diffEngine = new DiffEngine(); } async resolveConflicts( baseElement: CSElement, sourceElement: CSElement, targetElement: CSElement, userPreferences: { preferSource?: boolean; preferTarget?: boolean; autoResolveSimple?: boolean; customRules?: Array<{ path: string[]; strategy: 'source' | 'target' | 'merge' | 'custom'; customResolver?: (sourceValue: any, targetValue: any, baseValue: any) => any; }>; } = {} ): Promise<{ mergedElement: CSElement; resolvedConflicts: number; remainingConflicts: number; resolutionLog: string[]; }> { const mergeResult = this.diffEngine.threeWayMerge(baseElement, sourceElement, targetElement, { strategy: MergeStrategy.MANUAL }); const resolutionLog: string[] = []; let resolvedConflicts = 0; let remainingConflicts = 0; // Создаем результирующий элемент const mergedElement = new CSElement(`merged-${Date.now()}`); // Копируем базовые данные const baseData = baseElement.serialize({ includeChildren: true, includeData: true }); await this.copyDataToElement(mergedElement, baseData); // Обрабатываем конфликты for (const conflict of mergeResult.conflicts) { const resolved = await this.resolveConflict( conflict, mergedElement, userPreferences, resolutionLog ); if (resolved) { resolvedConflicts++; } else { remainingConflicts++; } } return { mergedElement, resolvedConflicts, remainingConflicts, resolutionLog }; } private async resolveConflict( conflict: any, mergedElement: CSElement, userPreferences: any, resolutionLog: string[] ): Promise<boolean> { const path = conflict.path; const pathStr = path.join('.'); // Проверяем пользовательские правила const customRule = userPreferences.customRules?.find( (rule: any) => rule.path.join('.') === pathStr ); if (customRule) { return await this.applyCustomRule(conflict, mergedElement, customRule, resolutionLog); } // Автоматическое разрешение простых конфликтов if (userPreferences.autoResolveSimple && conflict.severity === 'low') { return await this.autoResolveSimpleConflict(conflict, mergedElement, resolutionLog); } // Применяем общие предпочтения if (userPreferences.preferSource) { await this.setValueByPath(mergedElement, path, conflict.sourceValue); resolutionLog.push(`✅ Конфликт в ${pathStr} разрешен в пользу источника`); return true; } if (userPreferences.preferTarget) { await this.setValueByPath(mergedElement, path, conflict.targetValue); resolutionLog.push(`✅ Конфликт в ${pathStr} разрешен в пользу цели`); return true; } // Конфликт не разрешен resolutionLog.push(`⚠️ Конфликт в ${pathStr} требует ручного разрешения`); return false; } private async applyCustomRule( conflict: any, mergedElement: CSElement, rule: any, resolutionLog: string[] ): Promise<boolean> { const pathStr = conflict.path.join('.'); try { let resolvedValue; switch (rule.strategy) { case 'source': resolvedValue = conflict.sourceValue; break; case 'target': resolvedValue = conflict.targetValue; break; case 'merge': resolvedValue = this.mergeValues(conflict.sourceValue, conflict.targetValue); break; case 'custom': if (rule.customResolver) { resolvedValue = rule.customResolver( conflict.sourceValue, conflict.targetValue, conflict.baseValue ); } else { return false; } break; default: return false; } await this.setValueByPath(mergedElement, conflict.path, resolvedValue); resolutionLog.push(`✅ Конфликт в ${pathStr} разрешен пользовательским правилом (${rule.strategy})`); return true; } catch (error) { resolutionLog.push(`❌ Ошибка применения правила для ${pathStr}: ${error}`); return false; } } private async autoResolveSimpleConflict( conflict: any, mergedElement: CSElement, resolutionLog: string[] ): Promise<boolean> { const pathStr = conflict.path.join('.'); // Простые стратегии автоматического разрешения if (conflict.type === 'content' && typeof conflict.sourceValue === 'string' && typeof conflict.targetValue === 'string') { // Объединяем строки const mergedValue = `${conflict.sourceValue} ${conflict.targetValue}`; await this.setValueByPath(mergedElement, conflict.path, mergedValue); resolutionLog.push(`✅ Строковый конфликт в ${pathStr} разрешен объединением`); return true; } if (conflict.type === 'structure' && Array.isArray(conflict.sourceValue) && Array.isArray(conflict.targetValue)) { // Объединяем массивы const mergedArray = [...new Set([...conflict.sourceValue, ...conflict.targetValue])]; await this.setValueByPath(mergedElement, conflict.path, mergedArray); resolutionLog.push(`✅ Конфликт массивов в ${pathStr} разрешен объединением`); return true; } return false; } private mergeValues(sourceValue: any, targetValue: any): any { if (Array.isArray(sourceValue) && Array.isArray(targetValue)) { return [...new Set([...sourceValue, ...targetValue])]; } if (typeof sourceValue === 'object' && typeof targetValue === 'object') { return { ...sourceValue, ...targetValue }; } if (typeof sourceValue === 'string' && typeof targetValue === 'string') { return `${sourceValue} ${targetValue}`; } // По умолчанию возвращаем значение цели return targetValue; } private async copyDataToElement(element: CSElement, data: any): Promise<void> { if (data.data) { for (const [key, value] of Object.entries(data.data)) { await element.setData(key, value); } } } private async setValueByPath(element: CSElement, path: string[], value: any): Promise<void> { if (path.length === 1) { await element.setData(path[0], value); } else { // Для вложенных путей нужна более сложная логика // В данном примере упрощено await element.setData(path.join('.'), value); } } } ``` ## Визуализация различий ### Генерация визуального представления ```typescript // Получение визуализации различий const diffWithVisualization = diffEngine.computeDiff(sourceElement, targetElement, { algorithm: DiffAlgorithm.MYERS, includeVisualization: true }); if (diffWithVisualization.visualization) { console.log('📊 Визуализация различий:'); console.log(diffWithVisualization.visualization); } // Создание HTML отчета class DiffReportGenerator { generateHtmlReport(diffResult: DiffResult): string { const html = ` <!DOCTYPE html> <html> <head> <title>Отчет о различиях</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } .summary { background: #f5f5f5; padding: 15px; border-radius: 5px; margin-bottom: 20px; } .change { margin: 10px 0; padding: 10px; border-radius: 3px; } .addition { background: #d4edda; border-left: 4px solid #28a745; } .deletion { background: #f8d7da; border-left: 4px solid #dc3545; } .modification { background: #fff3cd; border-left: 4px solid #ffc107; } .path { font-weight: bold; color: #495057; } .value { font-family: monospace; background: #e9ecef; padding: 2px 4px; border-radius: 2px; } </style> </head> <body> <h1>Отчет о различиях</h1> <div class="summary"> <h2>Сводка</h2> <p>Добавлено: ${diffResult.summary.additions}</p> <p>Удалено: ${diffResult.summary.deletions}</p> <p>Изменено: ${diffResult.summary.modifications}</p> </div> <div class="changes"> <h2>Изменения</h2> ${diffResult.changes.map(change => ` <div class="change ${change.type}"> <div class="path">${change.path.join('.')}</div> <div>Тип: ${change.type}</div> ${change.oldValue !== undefined ? `<div>Было: <span class="value">${JSON.stringify(change.oldValue)}</span></div>` : ''} ${change.newValue !== undefined ? `<div>Стало: <span class="value">${JSON.stringify(change.newValue)}</span></div>` : ''} <div>Уверенность: ${(change.confidence * 100).toFixed(1)}%</div> </div> `).join('')} </div> </body> </html> `; return html; } generateMarkdownReport(diffResult: DiffResult): string { const markdown = ` # Отчет о различиях ## Сводка - **Добавлено:** ${diffResult.summary.additions} - **Удалено:** ${diffResult.summary.deletions} - **Изменено:** ${diffResult.summary.modifications} ## Изменения ${diffResult.changes.map(change => ` ### ${change.path.join('.')} - **Тип:** ${change.type} ${change.oldValue !== undefined ? `- **Было:** \`${JSON.stringify(change.oldValue)}\`` : ''} ${change.newValue !== undefined ? `- **Стало:** \`${JSON.stringify(change.newValue)}\`` : ''} - **Уверенность:** ${(change.confidence * 100).toFixed(1)}% `).join('')} `; return markdown.trim(); } } // Использование генератора отчетов const reportGenerator = new DiffReportGenerator(); const htmlReport = reportGenerator.generateHtmlReport(diffResult); const markdownReport = reportGenerator.generateMarkdownReport(diffResult); console.log('📄 HTML отчет создан'); console.log('📝 Markdown отчет создан'); ``` ## Полный пример: Система версионирования ```typescript class VersionControlSystem { private diffEngine: DiffEngine; private conflictResolver: ConflictResolver; private versions: Map<string, CSElement> = new Map(); private branches: Map<string, string[]> = new Map(); // branch -> version ids constructor() { this.diffEngine = new DiffEngine(); this.conflictResolver = new ConflictResolver(); this.branches.set('main', []); } async createVersion( documentId: string, data: any, parentVersionId?: string ): Promise<string> { const versionId = `${documentId}-v${Date.now()}`; const element = new CSElement(versionId); await element.setData('documentId', documentId); await element.setData('versionId', versionId); await element.setData('parentVersionId', parentVersionId); await element.setData('createdAt', new Date().toISOString()); await element.setData('data', data); this.versions.set(versionId, element); // Добавляем в основную ветку if (!this.branches.get('main')?.includes(versionId)) { this.branches.get('main')?.push(versionId); } console.log(`📄 Создана версия ${versionId}`); return versionId; } async createBranch(branchName: string, fromVersionId: string): Promise<void> { if (this.branches.has(branchName)) { throw new Error(`Ветка ${branchName} уже существует`); } this.branches.set(branchName, [fromVersionId]); console.log(`🌿 Создана ветка ${branchName} от версии ${fromVersionId}`); } async commitToBranch( branchName: string, documentId: string, data: any ): Promise<string> { const branch = this.branches.get(branchName); if (!branch) { throw new Error(`Ветка ${branchName} не найдена`); } const parentVersionId = branch[branch.length - 1]; const versionId = await this.createVersion(documentId, data, parentVersionId); branch.push(versionId); console.log(`💾 Коммит ${versionId} в ветку ${branchName}`); return versionId; } async compareVersions( version1Id: string, version2Id: string ): Promise<{ diffResult: DiffResult; htmlReport: string; markdownReport: string; }> { const version1 = this.versions.get(version1Id); const version2 = this.versions.get(version2Id); if (!version1 || !version2) { throw new Error('Одна из версий не найдена'); } const diffResult = this.diffEngine.computeDiff(version1, version2, { algorithm: DiffAlgorithm.MYERS, includeVisualization: true }); const reportGenerator = new DiffReportGenerator(); const htmlReport = reportGenerator.generateHtmlReport(diffResult); const markdownReport = reportGenerator.generateMarkdownReport(diffResult); return { diffResult, htmlReport, markdownReport }; } async mergeBranches( sourceBranch: string, targetBranch: string, conflictResolutionStrategy: { preferSource?: boolean; preferTarget?: boolean; autoResolveSimple?: boolean; } = {} ): Promise<{ success: boolean; mergedVersionId?: string; conflicts: number; resolutionLog: string[]; }> { const sourceBranchVersions = this.branches.get(sourceBranch); const targetBranchVersions = this.branches.get(targetBranch); if (!sourceBranchVersions || !targetBranchVersions) { throw new Error('Одна из веток не найдена'); } // Находим общего предка const commonAncestor = this.findCommonAncestor(sourceBranchVersions, targetBranchVersions); if (!commonAncestor) { throw new Error('Не найден общий предок веток'); } const baseElement = this.versions.get(commonAncestor)!; const sourceElement = this.versions.get(sourceBranchVersions[sourceBranchVersions.length - 1])!; const targetElement = this.versions.get(targetBranchVersions[targetBranchVersions.length - 1])!; // Выполняем слияние const mergeResult = await this.conflictResolver.resolveConflicts( baseElement, sourceElement, targetElement, conflictResolutionStrategy ); if (mergeResult.remainingConflicts === 0) { // Создаем новую версию с результатом слияния const documentId = await sourceElement.getData('documentId') as string; const mergedData = await mergeResult.mergedElement.getData('data'); const mergedVersionId = await this.createVersion( documentId, mergedData, targetElement.id ); // Добавляем в целевую ветку targetBranchVersions.push(mergedVersionId); return { success: true, mergedVersionId, conflicts: mergeResult.resolvedConflicts, resolutionLog: mergeResult.resolutionLog }; } else { return { success: false, conflicts: mergeResult.remainingConflicts, resolutionLog: mergeResult.resolutionLog }; } } private findCommonAncestor(branch1: string[], branch2: string[]): string | null { // Упрощенная реализация - находим первую общую версию for (const version1 of branch1) { if (branch2.includes(version1)) { return version1; } } return null; } async getBranchHistory(branchName: string): Promise<Array<{ versionId: string; createdAt: string; parentVersionId?: string; summary: string; }>> { const branch = this.branches.get(branchName); if (!branch) { throw new Error(`Ветка ${branchName} не найдена`); } const history = []; for (const versionId of branch) { const version = this.versions.get(versionId); if (version) { const createdAt = await version.getData('createdAt') as string; const parentVersionId = await version.getData('parentVersionId') as string; const data = await version.getData('data') as any; history.push({ versionId, createdAt, parentVersionId, summary: this.generateVersionSummary(data) }); } } return history; } private generateVersionSummary(data: any): string { if (data.title) { return `Документ: ${data.title}`; } return `Версия с ${Object.keys(data).length} полями`; } async exportBranch(branchName: string): Promise<Blob> { const history = await this.getBranchHistory(branchName); const exportData = { branchName, history, exportedAt: new Date().toISOString() }; return new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }); } } // Демонстрация использования async function demonstrateVersionControl() { const vcs = new VersionControlSystem(); try { // Создаем начальную версию const v1 = await vcs.createVersion('doc-1', { title: 'Мой документ', content: 'Исходное содержимое', tags: ['draft'] }); // Создаем ветку для разработки await vcs.createBranch('feature', v1); // Делаем изменения в основной ветке const v2 = await vcs.commitToBranch('main', 'doc-1', { title: 'Мой документ', content: 'Обновленное содержимое', tags: ['draft', 'updated'], author: 'Alice' }); // Делаем изменения в ветке feature const v3 = await vcs.commitToBranch('feature', 'doc-1', { title: 'Мой документ (feature)', content: 'Исходное содержимое с новой функцией', tags: ['draft', 'feature'], feature: 'new-feature' }); // Сравниваем версии const comparison = await vcs.compareVersions(v2, v3); console.log('📊 Сравнение версий:', comparison.diffResult.summary); // Пытаемся слить ветки const mergeResult = await vcs.mergeBranches('feature', 'main', { autoResolveSimple: true, preferTarget: false }); console.log('🔀 Результат слияния:', { success: mergeResult.success, conflicts: mergeResult.conflicts, mergedVersionId: mergeResult.mergedVersionId }); // Получаем историю const mainHistory = await vcs.getBranchHistory('main'); console.log('📋 История main:', mainHistory); // Экспортируем ветку const exportBlob = await vcs.exportBranch('main'); console.log(`📦 Экспорт ветки готов, размер: ${exportBlob.size} байт`); } catch (error) { console.error('❌ Ошибка в системе версионирования:', error); } } demonstrateVersionControl().catch(console.error); ``` ## 🎯 Заключение Diff Engine в CSElement обеспечивает: - **🔍 Точное сравнение** - множество алгоритмов для различных сценариев - **🔀 Умное слияние** - автоматическое и ручное разрешение конфликтов - **📊 Визуализация** - наглядное представление различий - **⚡ Производительность** - оптимизированные алгоритмы для больших данных - **🛠️ Гибкость** - настраиваемые стратегии слияния --- **Следующий раздел:** [Blueprint система шаблонов →](13-blueprint-system.md)