diff --git a/src/services/workflow-diff-engine.ts b/src/services/workflow-diff-engine.ts index 441a3ed..f046580 100644 --- a/src/services/workflow-diff-engine.ts +++ b/src/services/workflow-diff-engine.ts @@ -20,7 +20,6 @@ import { DisableNodeOperation, AddConnectionOperation, RemoveConnectionOperation, - UpdateConnectionOperation, RewireConnectionOperation, UpdateSettingsOperation, UpdateNameOperation, @@ -224,8 +223,6 @@ export class WorkflowDiffEngine { return this.validateAddConnection(workflow, operation); case 'removeConnection': return this.validateRemoveConnection(workflow, operation); - case 'updateConnection': - return this.validateUpdateConnection(workflow, operation); case 'rewireConnection': return this.validateRewireConnection(workflow, operation as RewireConnectionOperation); case 'updateSettings': @@ -271,9 +268,6 @@ export class WorkflowDiffEngine { case 'removeConnection': this.applyRemoveConnection(workflow, operation); break; - case 'updateConnection': - this.applyUpdateConnection(workflow, operation); - break; case 'rewireConnection': this.applyRewireConnection(workflow, operation as RewireConnectionOperation); break; @@ -461,40 +455,6 @@ export class WorkflowDiffEngine { return null; } - private validateUpdateConnection(workflow: Workflow, operation: UpdateConnectionOperation): string | null { - const sourceNode = this.findNode(workflow, operation.source, operation.source); - const targetNode = this.findNode(workflow, operation.target, operation.target); - - if (!sourceNode) { - return `Source node not found: ${operation.source}`; - } - if (!targetNode) { - return `Target node not found: ${operation.target}`; - } - - // Check if connection exists to update - const existingConnections = workflow.connections[sourceNode.name]; - if (!existingConnections) { - return `No connections found from "${sourceNode.name}"`; - } - - // Check if any connection to target exists - let hasConnection = false; - Object.values(existingConnections).forEach(outputs => { - outputs.forEach(connections => { - if (connections.some(c => c.node === targetNode.name)) { - hasConnection = true; - } - }); - }); - - if (!hasConnection) { - return `No connection exists from "${sourceNode.name}" to "${targetNode.name}"`; - } - - return null; - } - private validateRewireConnection(workflow: Workflow, operation: RewireConnectionOperation): string | null { // Validate source node exists const sourceNode = this.findNode(workflow, operation.source, operation.source); @@ -743,53 +703,6 @@ export class WorkflowDiffEngine { } } - private applyUpdateConnection(workflow: Workflow, operation: UpdateConnectionOperation): void { - // Validate that updates object exists and is an object - if (!operation.updates || typeof operation.updates !== 'object') { - throw new Error( - `updateConnection operation requires 'updates' object.\n\n` + - `You provided: ${JSON.stringify(operation, null, 2)}\n\n` + - `The 'updates' property is missing or invalid. ` + - `This operation modifies connection properties (output type, input type, indices), ` + - `not connection targets.\n\n` + - `Correct format:\n` + - `{\n` + - ` type: "updateConnection",\n` + - ` source: "IF",\n` + - ` target: "EmailNode",\n` + - ` updates: {\n` + - ` sourceOutput: "false" // Change from one output to another\n` + - ` }\n` + - `}\n\n` + - `💡 Note: If you want to change which node a connection goes to, ` + - `use removeConnection + addConnection instead:\n` + - `[\n` + - ` {type: "removeConnection", source: "${operation.source}", target: "${operation.target}"},\n` + - ` {type: "addConnection", source: "${operation.source}", target: "NewTarget"}\n` + - `]` - ); - } - - // Implement as remove + add with the updated properties - this.applyRemoveConnection(workflow, { - type: 'removeConnection', - source: operation.source, - target: operation.target, - sourceOutput: operation.updates.sourceOutput, - targetInput: operation.updates.targetInput - }); - - this.applyAddConnection(workflow, { - type: 'addConnection', - source: operation.source, - target: operation.target, - sourceOutput: operation.updates.sourceOutput, - targetInput: operation.updates.targetInput, - sourceIndex: operation.updates.sourceIndex, - targetIndex: operation.updates.targetIndex - }); - } - /** * Rewire a connection from one target to another * This is a semantic wrapper around removeConnection + addConnection diff --git a/src/types/workflow-diff.ts b/src/types/workflow-diff.ts index 3ca3639..e76b757 100644 --- a/src/types/workflow-diff.ts +++ b/src/types/workflow-diff.ts @@ -78,18 +78,6 @@ export interface RemoveConnectionOperation extends DiffOperation { ignoreErrors?: boolean; // If true, don't fail when connection doesn't exist (useful for cleanup) } -export interface UpdateConnectionOperation extends DiffOperation { - type: 'updateConnection'; - source: string; - target: string; - updates: { - sourceOutput?: string; - targetInput?: string; - sourceIndex?: number; - targetIndex?: number; - }; -} - export interface RewireConnectionOperation extends DiffOperation { type: 'rewireConnection'; source: string; // Source node name or ID @@ -155,7 +143,6 @@ export type WorkflowDiffOperation = | DisableNodeOperation | AddConnectionOperation | RemoveConnectionOperation - | UpdateConnectionOperation | RewireConnectionOperation | UpdateSettingsOperation | UpdateNameOperation @@ -204,8 +191,8 @@ export function isNodeOperation(op: WorkflowDiffOperation): op is } export function isConnectionOperation(op: WorkflowDiffOperation): op is - AddConnectionOperation | RemoveConnectionOperation | UpdateConnectionOperation | RewireConnectionOperation | CleanStaleConnectionsOperation | ReplaceConnectionsOperation { - return ['addConnection', 'removeConnection', 'updateConnection', 'rewireConnection', 'cleanStaleConnections', 'replaceConnections'].includes(op.type); + AddConnectionOperation | RemoveConnectionOperation | RewireConnectionOperation | CleanStaleConnectionsOperation | ReplaceConnectionsOperation { + return ['addConnection', 'removeConnection', 'rewireConnection', 'cleanStaleConnections', 'replaceConnections'].includes(op.type); } export function isMetadataOperation(op: WorkflowDiffOperation): op is diff --git a/tests/unit/services/workflow-diff-engine.test.ts b/tests/unit/services/workflow-diff-engine.test.ts index ca1657b..df4cb47 100644 --- a/tests/unit/services/workflow-diff-engine.test.ts +++ b/tests/unit/services/workflow-diff-engine.test.ts @@ -12,7 +12,6 @@ import { DisableNodeOperation, AddConnectionOperation, RemoveConnectionOperation, - UpdateConnectionOperation, UpdateSettingsOperation, UpdateNameOperation, AddTagOperation, @@ -774,98 +773,6 @@ describe('WorkflowDiffEngine', () => { }); }); - describe('UpdateConnection Operation', () => { - it('should update connection properties', async () => { - // Add an IF node with multiple outputs - const addNodeOp: AddNodeOperation = { - type: 'addNode', - node: { - name: 'IF', - type: 'n8n-nodes-base.if', - position: [600, 300] - } - }; - - const addConnectionOp: AddConnectionOperation = { - type: 'addConnection', - source: 'IF', - target: 'slack-1', - sourceOutput: 'true' - }; - - const updateConnectionOp: UpdateConnectionOperation = { - type: 'updateConnection', - source: 'IF', - target: 'slack-1', - updates: { - sourceOutput: 'false', - sourceIndex: 0, - targetIndex: 0 - } - }; - - const request: WorkflowDiffRequest = { - id: 'test-workflow', - operations: [addNodeOp, addConnectionOp, updateConnectionOp] - }; - - const result = await diffEngine.applyDiff(baseWorkflow, request); - - expect(result.success).toBe(true); - // After update, the connection should be on 'false' output only - expect(result.workflow!.connections['IF'].false).toBeDefined(); - expect(result.workflow!.connections['IF'].false[0][0].node).toBe('Slack'); - // The 'true' output should still have the original connection - // because updateConnection removes using the NEW output values, not the old ones - expect(result.workflow!.connections['IF'].true).toBeDefined(); - expect(result.workflow!.connections['IF'].true[0][0].node).toBe('Slack'); - }); - - it('should reject updateConnection without updates object (Issue #272, #204)', async () => { - const operation: any = { - type: 'updateConnection', - source: 'Webhook', - target: 'HTTP Request' - // Missing updates object - should fail with helpful error - }; - - const request: WorkflowDiffRequest = { - id: 'test-workflow', - operations: [operation] - }; - - const result = await diffEngine.applyDiff(baseWorkflow, request); - - expect(result.success).toBe(false); - expect(result.errors).toBeDefined(); - expect(result.errors![0].message).toContain('updates'); - expect(result.errors![0].message).toContain('object'); - // Should include helpful guidance - expect(result.errors![0].message).toContain('removeConnection'); - expect(result.errors![0].message).toContain('addConnection'); - }); - - it('should reject updateConnection with invalid updates type', async () => { - const operation: any = { - type: 'updateConnection', - source: 'Webhook', - target: 'HTTP Request', - updates: 'invalid' // Should be object, not string - }; - - const request: WorkflowDiffRequest = { - id: 'test-workflow', - operations: [operation] - }; - - const result = await diffEngine.applyDiff(baseWorkflow, request); - - expect(result.success).toBe(false); - expect(result.errors).toBeDefined(); - expect(result.errors![0].message).toContain('updates'); - expect(result.errors![0].message).toContain('object'); - }); - }); describe('RewireConnection Operation (Phase 1)', () => { it('should rewire connection from one target to another', async () => { @@ -3673,47 +3580,6 @@ describe('WorkflowDiffEngine', () => { expect(result.workflow).toBeUndefined(); }); - it('should handle updateConnection with complex output configurations', async () => { - const workflow = JSON.parse(JSON.stringify(baseWorkflow)); - - // Add IF node - workflow.nodes.push({ - id: 'if-1', - name: 'IF', - type: 'n8n-nodes-base.if', - typeVersion: 1, - position: [600, 400], - parameters: {} - }); - - // Add connection on 'true' output - workflow.connections['IF'] = { - 'true': [[ - { node: 'Slack', type: 'main', index: 0 } - ]] - }; - - const operations: UpdateConnectionOperation[] = [{ - type: 'updateConnection', - source: 'IF', - target: 'Slack', - updates: { - sourceOutput: 'false', - targetInput: 'main', - sourceIndex: 0, - targetIndex: 0 - } - }]; - - const request: WorkflowDiffRequest = { - id: 'test-workflow', - operations - }; - - const result = await diffEngine.applyDiff(workflow, request); - - expect(result.success).toBe(true); - }); it('should handle addConnection with all optional parameters specified', async () => { const workflow = JSON.parse(JSON.stringify(baseWorkflow));