"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WorkflowVersioningService = void 0;
const workflow_validator_1 = require("./workflow-validator");
const enhanced_config_validator_1 = require("./enhanced-config-validator");
class WorkflowVersioningService {
constructor(nodeRepository, apiClient) {
this.nodeRepository = nodeRepository;
this.apiClient = apiClient;
this.DEFAULT_MAX_VERSIONS = 10;
}
async createBackup(workflowId, workflow, context) {
const versions = this.nodeRepository.getWorkflowVersions(workflowId, 1);
const nextVersion = versions.length > 0 ? versions[0].versionNumber + 1 : 1;
const versionId = this.nodeRepository.createWorkflowVersion({
workflowId,
versionNumber: nextVersion,
workflowName: workflow.name || 'Unnamed Workflow',
workflowSnapshot: workflow,
trigger: context.trigger,
operations: context.operations,
fixTypes: context.fixTypes,
metadata: context.metadata
});
const pruned = this.nodeRepository.pruneWorkflowVersions(workflowId, this.DEFAULT_MAX_VERSIONS);
return {
versionId,
versionNumber: nextVersion,
pruned,
message: pruned > 0
? `Backup created (version ${nextVersion}), pruned ${pruned} old version(s)`
: `Backup created (version ${nextVersion})`
};
}
async getVersionHistory(workflowId, limit = 10) {
const versions = this.nodeRepository.getWorkflowVersions(workflowId, limit);
return versions.map(v => ({
id: v.id,
workflowId: v.workflowId,
versionNumber: v.versionNumber,
workflowName: v.workflowName,
trigger: v.trigger,
operationCount: v.operations ? v.operations.length : undefined,
fixTypesApplied: v.fixTypes || undefined,
createdAt: v.createdAt,
size: JSON.stringify(v.workflowSnapshot).length
}));
}
async getVersion(versionId) {
return this.nodeRepository.getWorkflowVersion(versionId);
}
async restoreVersion(workflowId, versionId, validateBefore = true) {
if (!this.apiClient) {
return {
success: false,
message: 'API client not configured - cannot restore workflow',
workflowId,
toVersionId: versionId || 0,
backupCreated: false
};
}
let versionToRestore = null;
if (versionId) {
versionToRestore = this.nodeRepository.getWorkflowVersion(versionId);
}
else {
versionToRestore = this.nodeRepository.getLatestWorkflowVersion(workflowId);
}
if (!versionToRestore) {
return {
success: false,
message: versionId
? `Version ${versionId} not found`
: `No backup versions found for workflow ${workflowId}`,
workflowId,
toVersionId: versionId || 0,
backupCreated: false
};
}
if (validateBefore) {
const validator = new workflow_validator_1.WorkflowValidator(this.nodeRepository, enhanced_config_validator_1.EnhancedConfigValidator);
const validationResult = await validator.validateWorkflow(versionToRestore.workflowSnapshot, {
validateNodes: true,
validateConnections: true,
validateExpressions: false,
profile: 'runtime'
});
if (validationResult.errors.length > 0) {
return {
success: false,
message: `Cannot restore - version ${versionToRestore.versionNumber} has validation errors`,
workflowId,
toVersionId: versionToRestore.id,
backupCreated: false,
validationErrors: validationResult.errors.map(e => e.message || 'Unknown error')
};
}
}
let backupResult;
try {
const currentWorkflow = await this.apiClient.getWorkflow(workflowId);
backupResult = await this.createBackup(workflowId, currentWorkflow, {
trigger: 'partial_update',
metadata: {
reason: 'Backup before rollback',
restoringToVersion: versionToRestore.versionNumber
}
});
}
catch (error) {
return {
success: false,
message: `Failed to create backup before restore: ${error.message}`,
workflowId,
toVersionId: versionToRestore.id,
backupCreated: false
};
}
try {
await this.apiClient.updateWorkflow(workflowId, versionToRestore.workflowSnapshot);
return {
success: true,
message: `Successfully restored workflow to version ${versionToRestore.versionNumber}`,
workflowId,
fromVersion: backupResult.versionNumber,
toVersionId: versionToRestore.id,
backupCreated: true,
backupVersionId: backupResult.versionId
};
}
catch (error) {
return {
success: false,
message: `Failed to restore workflow: ${error.message}`,
workflowId,
toVersionId: versionToRestore.id,
backupCreated: true,
backupVersionId: backupResult.versionId
};
}
}
async deleteVersion(versionId) {
const version = this.nodeRepository.getWorkflowVersion(versionId);
if (!version) {
return {
success: false,
message: `Version ${versionId} not found`
};
}
this.nodeRepository.deleteWorkflowVersion(versionId);
return {
success: true,
message: `Deleted version ${version.versionNumber} for workflow ${version.workflowId}`
};
}
async deleteAllVersions(workflowId) {
const count = this.nodeRepository.getWorkflowVersionCount(workflowId);
if (count === 0) {
return {
deleted: 0,
message: `No versions found for workflow ${workflowId}`
};
}
const deleted = this.nodeRepository.deleteWorkflowVersionsByWorkflowId(workflowId);
return {
deleted,
message: `Deleted ${deleted} version(s) for workflow ${workflowId}`
};
}
async pruneVersions(workflowId, maxVersions = 10) {
const pruned = this.nodeRepository.pruneWorkflowVersions(workflowId, maxVersions);
const remaining = this.nodeRepository.getWorkflowVersionCount(workflowId);
return { pruned, remaining };
}
async truncateAllVersions(confirm) {
if (!confirm) {
return {
deleted: 0,
message: 'Truncate operation not confirmed - no action taken'
};
}
const deleted = this.nodeRepository.truncateWorkflowVersions();
return {
deleted,
message: `Truncated workflow_versions table - deleted ${deleted} version(s)`
};
}
async getStorageStats() {
const stats = this.nodeRepository.getVersionStorageStats();
return {
totalVersions: stats.totalVersions,
totalSize: stats.totalSize,
totalSizeFormatted: this.formatBytes(stats.totalSize),
byWorkflow: stats.byWorkflow.map((w) => ({
workflowId: w.workflowId,
workflowName: w.workflowName,
versionCount: w.versionCount,
totalSize: w.totalSize,
totalSizeFormatted: this.formatBytes(w.totalSize),
lastBackup: w.lastBackup
}))
};
}
async compareVersions(versionId1, versionId2) {
const v1 = this.nodeRepository.getWorkflowVersion(versionId1);
const v2 = this.nodeRepository.getWorkflowVersion(versionId2);
if (!v1 || !v2) {
throw new Error(`One or both versions not found: ${versionId1}, ${versionId2}`);
}
const nodes1 = new Set(v1.workflowSnapshot.nodes?.map((n) => n.id) || []);
const nodes2 = new Set(v2.workflowSnapshot.nodes?.map((n) => n.id) || []);
const addedNodes = [...nodes2].filter(id => !nodes1.has(id));
const removedNodes = [...nodes1].filter(id => !nodes2.has(id));
const commonNodes = [...nodes1].filter(id => nodes2.has(id));
const modifiedNodes = [];
for (const nodeId of commonNodes) {
const node1 = v1.workflowSnapshot.nodes?.find((n) => n.id === nodeId);
const node2 = v2.workflowSnapshot.nodes?.find((n) => n.id === nodeId);
if (JSON.stringify(node1) !== JSON.stringify(node2)) {
modifiedNodes.push(nodeId);
}
}
const conn1Str = JSON.stringify(v1.workflowSnapshot.connections || {});
const conn2Str = JSON.stringify(v2.workflowSnapshot.connections || {});
const connectionChanges = conn1Str !== conn2Str ? 1 : 0;
const settings1 = v1.workflowSnapshot.settings || {};
const settings2 = v2.workflowSnapshot.settings || {};
const settingChanges = this.diffObjects(settings1, settings2);
return {
versionId1,
versionId2,
version1Number: v1.versionNumber,
version2Number: v2.versionNumber,
addedNodes,
removedNodes,
modifiedNodes,
connectionChanges,
settingChanges
};
}
formatBytes(bytes) {
if (bytes === 0)
return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
}
diffObjects(obj1, obj2) {
const changes = {};
const allKeys = new Set([...Object.keys(obj1), ...Object.keys(obj2)]);
for (const key of allKeys) {
if (JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])) {
changes[key] = {
before: obj1[key],
after: obj2[key]
};
}
}
return changes;
}
}
exports.WorkflowVersioningService = WorkflowVersioningService;
//# sourceMappingURL=workflow-versioning-service.js.map