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
Markdown
# 🔍 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)