mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-03-21 01:43:08 +00:00
Compare commits
2 Commits
v2.37.4
...
feat/creat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a9e3c7ec0 | ||
|
|
47a1cb135d |
36
CHANGELOG.md
36
CHANGELOG.md
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2.39.0] - 2026-03-20
|
||||
|
||||
### Added
|
||||
|
||||
- **`n8n_create_data_table` MCP tool** (Issue #640): Create data tables in n8n via the REST API
|
||||
- `N8nApiClient.createDataTable()` calling `POST /data-tables`
|
||||
- Zod-validated handler with `N8nApiError` handling for structured error responses
|
||||
- TypeScript interfaces matching the n8n OpenAPI spec (`DataTableColumn`, `DataTableColumnResponse`, `DataTable`)
|
||||
- Column types per spec: `string | number | boolean | date | json`
|
||||
- Input validation: `.min(1)` on table name and column names
|
||||
- Tool documentation with examples, use cases, and pitfalls
|
||||
- Requires n8n enterprise or cloud with data tables feature enabled
|
||||
- **`projectId` parameter for `n8n_create_workflow`**: Create workflows directly in a specific team project (enterprise feature)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Health check management tool count**: Updated from 13 to 14 to include `n8n_create_data_table`
|
||||
|
||||
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||
|
||||
## [2.38.0] - 2026-03-20
|
||||
|
||||
### Added
|
||||
|
||||
- **`transferWorkflow` diff operation** (Issue #644): Move workflows between projects via `n8n_update_partial_workflow`
|
||||
- New `transferWorkflow` operation type with `destinationProjectId` parameter
|
||||
- Calls `PUT /workflows/{id}/transfer` via dedicated API after workflow update
|
||||
- Proper error handling: returns `{ success: false, saved: true }` when transfer fails after update
|
||||
- Transfer executes before activation so workflow is in target project first
|
||||
- Zod schema validates `destinationProjectId` is non-empty
|
||||
- Updated tool description and documentation to list the new operation
|
||||
- `inferIntentFromOperations` returns descriptive intent for transfer operations
|
||||
- `N8nApiClient.transferWorkflow()` method added
|
||||
|
||||
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||
|
||||
## [2.37.4] - 2026-03-18
|
||||
|
||||
### Changed
|
||||
|
||||
2
dist/mcp/handlers-workflow-diff.d.ts.map
vendored
2
dist/mcp/handlers-workflow-diff.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"handlers-workflow-diff.d.ts","sourceRoot":"","sources":["../../src/mcp/handlers-workflow-diff.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAMnD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAgF7D,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,eAAe,CAAC,CA6Z1B"}
|
||||
{"version":3,"file":"handlers-workflow-diff.d.ts","sourceRoot":"","sources":["../../src/mcp/handlers-workflow-diff.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAMnD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAkF7D,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,eAAe,CAAC,CAib1B"}
|
||||
24
dist/mcp/handlers-workflow-diff.js
vendored
24
dist/mcp/handlers-workflow-diff.js
vendored
@@ -79,6 +79,7 @@ const workflowDiffSchema = zod_1.z.object({
|
||||
settings: zod_1.z.any().optional(),
|
||||
name: zod_1.z.string().optional(),
|
||||
tag: zod_1.z.string().optional(),
|
||||
destinationProjectId: zod_1.z.string().min(1).optional(),
|
||||
id: zod_1.z.string().optional(),
|
||||
}).transform((op) => {
|
||||
if (NODE_TARGETING_OPERATIONS.has(op.type)) {
|
||||
@@ -329,6 +330,25 @@ async function handleUpdatePartialWorkflow(args, repository, context) {
|
||||
logger_1.logger.warn('Tag operations failed (non-blocking)', tagError);
|
||||
}
|
||||
}
|
||||
let transferMessage = '';
|
||||
if (diffResult.transferToProjectId) {
|
||||
try {
|
||||
await client.transferWorkflow(input.id, diffResult.transferToProjectId);
|
||||
transferMessage = ` Workflow transferred to project ${diffResult.transferToProjectId}.`;
|
||||
}
|
||||
catch (transferError) {
|
||||
logger_1.logger.error('Failed to transfer workflow to project', transferError);
|
||||
return {
|
||||
success: false,
|
||||
saved: true,
|
||||
error: 'Workflow updated successfully but project transfer failed',
|
||||
details: {
|
||||
workflowUpdated: true,
|
||||
transferError: transferError instanceof Error ? transferError.message : 'Unknown error'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
let finalWorkflow = updatedWorkflow;
|
||||
let activationMessage = '';
|
||||
try {
|
||||
@@ -409,7 +429,7 @@ async function handleUpdatePartialWorkflow(args, repository, context) {
|
||||
nodeCount: finalWorkflow.nodes?.length || 0,
|
||||
operationsApplied: diffResult.operationsApplied
|
||||
},
|
||||
message: `Workflow "${finalWorkflow.name}" updated successfully. Applied ${diffResult.operationsApplied} operations.${activationMessage} Use n8n_get_workflow with mode 'structure' to verify current state.`,
|
||||
message: `Workflow "${finalWorkflow.name}" updated successfully. Applied ${diffResult.operationsApplied} operations.${transferMessage}${activationMessage} Use n8n_get_workflow with mode 'structure' to verify current state.`,
|
||||
details: {
|
||||
applied: diffResult.applied,
|
||||
failed: diffResult.failed,
|
||||
@@ -498,6 +518,8 @@ function inferIntentFromOperations(operations) {
|
||||
return 'Activate workflow';
|
||||
case 'deactivateWorkflow':
|
||||
return 'Deactivate workflow';
|
||||
case 'transferWorkflow':
|
||||
return `Transfer workflow to project ${op.destinationProjectId || ''}`.trim();
|
||||
default:
|
||||
return `Workflow ${op.type}`;
|
||||
}
|
||||
|
||||
2
dist/mcp/handlers-workflow-diff.js.map
vendored
2
dist/mcp/handlers-workflow-diff.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
{"version":3,"file":"n8n-update-partial-workflow.d.ts","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE7C,eAAO,MAAM,2BAA2B,EAAE,iBA+ZzC,CAAC"}
|
||||
{"version":3,"file":"n8n-update-partial-workflow.d.ts","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE7C,eAAO,MAAM,2BAA2B,EAAE,iBAuazC,CAAC"}
|
||||
@@ -5,7 +5,7 @@ exports.n8nUpdatePartialWorkflowDoc = {
|
||||
name: 'n8n_update_partial_workflow',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, rewireConnection, cleanStaleConnections, replaceConnections, updateSettings, updateName, add/removeTag, activateWorkflow, deactivateWorkflow. Supports smart parameters (branch, case) for multi-output nodes. Full support for AI connections (ai_languageModel, ai_tool, ai_memory, ai_embedding, ai_vectorStore, ai_document, ai_textSplitter, ai_outputParser).',
|
||||
description: 'Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, rewireConnection, cleanStaleConnections, replaceConnections, updateSettings, updateName, add/removeTag, activateWorkflow, deactivateWorkflow, transferWorkflow. Supports smart parameters (branch, case) for multi-output nodes. Full support for AI connections (ai_languageModel, ai_tool, ai_memory, ai_embedding, ai_vectorStore, ai_document, ai_textSplitter, ai_outputParser).',
|
||||
keyParameters: ['id', 'operations', 'continueOnError'],
|
||||
example: 'n8n_update_partial_workflow({id: "wf_123", operations: [{type: "rewireConnection", source: "IF", from: "Old", to: "New", branch: "true"}]})',
|
||||
performance: 'Fast (50-200ms)',
|
||||
@@ -23,7 +23,8 @@ exports.n8nUpdatePartialWorkflowDoc = {
|
||||
'Batch AI component connections for atomic updates',
|
||||
'Auto-sanitization: ALL nodes auto-fixed during updates (operator structures, missing metadata)',
|
||||
'Node renames automatically update all connection references - no manual connection operations needed',
|
||||
'Activate/deactivate workflows: Use activateWorkflow/deactivateWorkflow operations (requires activatable triggers like webhook/schedule)'
|
||||
'Activate/deactivate workflows: Use activateWorkflow/deactivateWorkflow operations (requires activatable triggers like webhook/schedule)',
|
||||
'Transfer workflows between projects: Use transferWorkflow with destinationProjectId (enterprise feature)'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
@@ -56,6 +57,9 @@ exports.n8nUpdatePartialWorkflowDoc = {
|
||||
- **activateWorkflow**: Activate the workflow to enable automatic execution via triggers
|
||||
- **deactivateWorkflow**: Deactivate the workflow to prevent automatic execution
|
||||
|
||||
### Project Management Operations (1 type):
|
||||
- **transferWorkflow**: Transfer the workflow to a different project. Requires \`destinationProjectId\`. Enterprise/cloud feature.
|
||||
|
||||
## Smart Parameters for Multi-Output Nodes
|
||||
|
||||
For **IF nodes**, use semantic 'branch' parameter instead of technical sourceIndex:
|
||||
@@ -346,7 +350,10 @@ n8n_update_partial_workflow({
|
||||
'// Migrate from deprecated continueOnFail to onError\nn8n_update_partial_workflow({id: "rm2", operations: [{type: "updateNode", nodeName: "HTTP Request", updates: {continueOnFail: null, onError: "continueErrorOutput"}}]})',
|
||||
'// Remove nested property\nn8n_update_partial_workflow({id: "rm3", operations: [{type: "updateNode", nodeName: "API Request", updates: {"parameters.authentication": null}}]})',
|
||||
'// Remove multiple properties\nn8n_update_partial_workflow({id: "rm4", operations: [{type: "updateNode", nodeName: "Data Processor", updates: {continueOnFail: null, alwaysOutputData: null, "parameters.legacy_option": null}}]})',
|
||||
'// Remove entire array property\nn8n_update_partial_workflow({id: "rm5", operations: [{type: "updateNode", nodeName: "HTTP Request", updates: {"parameters.headers": null}}]})'
|
||||
'// Remove entire array property\nn8n_update_partial_workflow({id: "rm5", operations: [{type: "updateNode", nodeName: "HTTP Request", updates: {"parameters.headers": null}}]})',
|
||||
'\n// ============ PROJECT TRANSFER EXAMPLES ============',
|
||||
'// Transfer workflow to a different project\nn8n_update_partial_workflow({id: "tf1", operations: [{type: "transferWorkflow", destinationProjectId: "project-abc-123"}]})',
|
||||
'// Transfer and activate in one call\nn8n_update_partial_workflow({id: "tf2", operations: [{type: "transferWorkflow", destinationProjectId: "project-abc-123"}, {type: "activateWorkflow"}]})'
|
||||
],
|
||||
useCases: [
|
||||
'Rewire connections when replacing nodes',
|
||||
@@ -364,7 +371,8 @@ n8n_update_partial_workflow({
|
||||
'Add fallback language models to AI Agents',
|
||||
'Configure Vector Store retrieval systems',
|
||||
'Swap language models in existing AI workflows',
|
||||
'Batch-update AI tool connections'
|
||||
'Batch-update AI tool connections',
|
||||
'Transfer workflows between team projects (enterprise)'
|
||||
],
|
||||
performance: 'Very fast - typically 50-200ms. Much faster than full updates as only changes are processed.',
|
||||
bestPractices: [
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"n8n-update-partial-workflow.js","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"n8n-update-partial-workflow.js","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.ts"],"names":[],"mappings":"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"}
|
||||
2
dist/mcp/tools-n8n-manager.js
vendored
2
dist/mcp/tools-n8n-manager.js
vendored
@@ -137,7 +137,7 @@ exports.n8nManagementTools = [
|
||||
},
|
||||
{
|
||||
name: 'n8n_update_partial_workflow',
|
||||
description: `Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, updateSettings, updateName, add/removeTag. See tools_documentation("n8n_update_partial_workflow", "full") for details.`,
|
||||
description: `Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, updateSettings, updateName, add/removeTag, activate/deactivateWorkflow, transferWorkflow. See tools_documentation("n8n_update_partial_workflow", "full") for details.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: true,
|
||||
|
||||
2
dist/mcp/tools-n8n-manager.js.map
vendored
2
dist/mcp/tools-n8n-manager.js.map
vendored
File diff suppressed because one or more lines are too long
1
dist/services/n8n-api-client.d.ts
vendored
1
dist/services/n8n-api-client.d.ts
vendored
@@ -20,6 +20,7 @@ export declare class N8nApiClient {
|
||||
getWorkflow(id: string): Promise<Workflow>;
|
||||
updateWorkflow(id: string, workflow: Partial<Workflow>): Promise<Workflow>;
|
||||
deleteWorkflow(id: string): Promise<Workflow>;
|
||||
transferWorkflow(id: string, destinationProjectId: string): Promise<void>;
|
||||
activateWorkflow(id: string): Promise<Workflow>;
|
||||
deactivateWorkflow(id: string): Promise<Workflow>;
|
||||
listWorkflows(params?: WorkflowListParams): Promise<WorkflowListResponse>;
|
||||
|
||||
2
dist/services/n8n-api-client.d.ts.map
vendored
2
dist/services/n8n-api-client.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"n8n-api-client.d.ts","sourceRoot":"","sources":["../../src/services/n8n-api-client.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"n8n-api-client.d.ts","sourceRoot":"","sources":["../../src/services/n8n-api-client.ts"],"names":[],"mappings":"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"}
|
||||
8
dist/services/n8n-api-client.js
vendored
8
dist/services/n8n-api-client.js
vendored
@@ -194,6 +194,14 @@ class N8nApiClient {
|
||||
throw (0, n8n_errors_1.handleN8nApiError)(error);
|
||||
}
|
||||
}
|
||||
async transferWorkflow(id, destinationProjectId) {
|
||||
try {
|
||||
await this.client.put(`/workflows/${id}/transfer`, { destinationProjectId });
|
||||
}
|
||||
catch (error) {
|
||||
throw (0, n8n_errors_1.handleN8nApiError)(error);
|
||||
}
|
||||
}
|
||||
async activateWorkflow(id) {
|
||||
try {
|
||||
const response = await this.client.post(`/workflows/${id}/activate`, {});
|
||||
|
||||
2
dist/services/n8n-api-client.js.map
vendored
2
dist/services/n8n-api-client.js.map
vendored
File diff suppressed because one or more lines are too long
3
dist/services/workflow-diff-engine.d.ts
vendored
3
dist/services/workflow-diff-engine.d.ts
vendored
@@ -7,6 +7,7 @@ export declare class WorkflowDiffEngine {
|
||||
private removedNodeNames;
|
||||
private tagsToAdd;
|
||||
private tagsToRemove;
|
||||
private transferToProjectId;
|
||||
applyDiff(workflow: Workflow, request: WorkflowDiffRequest): Promise<WorkflowDiffResult>;
|
||||
private validateOperation;
|
||||
private applyOperation;
|
||||
@@ -36,6 +37,8 @@ export declare class WorkflowDiffEngine {
|
||||
private validateDeactivateWorkflow;
|
||||
private applyActivateWorkflow;
|
||||
private applyDeactivateWorkflow;
|
||||
private validateTransferWorkflow;
|
||||
private applyTransferWorkflow;
|
||||
private validateCleanStaleConnections;
|
||||
private validateReplaceConnections;
|
||||
private applyCleanStaleConnections;
|
||||
|
||||
2
dist/services/workflow-diff-engine.d.ts.map
vendored
2
dist/services/workflow-diff-engine.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"workflow-diff-engine.d.ts","sourceRoot":"","sources":["../../src/services/workflow-diff-engine.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,mBAAmB,EACnB,kBAAkB,EAsBnB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAoC,MAAM,kBAAkB,CAAC;AAY9E,qBAAa,kBAAkB;IAE7B,OAAO,CAAC,SAAS,CAAkC;IAEnD,OAAO,CAAC,QAAQ,CAAqC;IAErD,OAAO,CAAC,eAAe,CAAqB;IAE5C,OAAO,CAAC,gBAAgB,CAAqB;IAE7C,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,YAAY,CAAgB;IAK9B,SAAS,CACb,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC;IAqN9B,OAAO,CAAC,iBAAiB;IAwCzB,OAAO,CAAC,cAAc;IAyDtB,OAAO,CAAC,eAAe;IAwBvB,OAAO,CAAC,kBAAkB;IAuB1B,OAAO,CAAC,kBAAkB;IAoC1B,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,qBAAqB;IAkD7B,OAAO,CAAC,wBAAwB;IA6ChC,OAAO,CAAC,wBAAwB;IAmDhC,OAAO,CAAC,YAAY;IA4BpB,OAAO,CAAC,eAAe;IAwCvB,OAAO,CAAC,eAAe;IA0BvB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,sBAAsB;IAwD9B,OAAO,CAAC,kBAAkB;IA6C1B,OAAO,CAAC,qBAAqB;IAuC7B,OAAO,CAAC,qBAAqB;IA0B7B,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,wBAAwB;IAchC,OAAO,CAAC,0BAA0B;IAMlC,OAAO,CAAC,qBAAqB;IAM7B,OAAO,CAAC,uBAAuB;IAO/B,OAAO,CAAC,6BAA6B;IAKrC,OAAO,CAAC,0BAA0B;IA0BlC,OAAO,CAAC,0BAA0B;IA+ElC,OAAO,CAAC,uBAAuB;IAe/B,OAAO,CAAC,0BAA0B;IAkElC,OAAO,CAAC,iBAAiB;IAkBzB,OAAO,CAAC,QAAQ;IAsChB,OAAO,CAAC,uBAAuB;IAW/B,OAAO,CAAC,iBAAiB;CAoB1B"}
|
||||
{"version":3,"file":"workflow-diff-engine.d.ts","sourceRoot":"","sources":["../../src/services/workflow-diff-engine.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,mBAAmB,EACnB,kBAAkB,EAuBnB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAoC,MAAM,kBAAkB,CAAC;AAY9E,qBAAa,kBAAkB;IAE7B,OAAO,CAAC,SAAS,CAAkC;IAEnD,OAAO,CAAC,QAAQ,CAAqC;IAErD,OAAO,CAAC,eAAe,CAAqB;IAE5C,OAAO,CAAC,gBAAgB,CAAqB;IAE7C,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,YAAY,CAAgB;IAEpC,OAAO,CAAC,mBAAmB,CAAqB;IAK1C,SAAS,CACb,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC;IAgO9B,OAAO,CAAC,iBAAiB;IA0CzB,OAAO,CAAC,cAAc;IA4DtB,OAAO,CAAC,eAAe;IAwBvB,OAAO,CAAC,kBAAkB;IAuB1B,OAAO,CAAC,kBAAkB;IAoC1B,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,qBAAqB;IAkD7B,OAAO,CAAC,wBAAwB;IA6ChC,OAAO,CAAC,wBAAwB;IAmDhC,OAAO,CAAC,YAAY;IA4BpB,OAAO,CAAC,eAAe;IAwCvB,OAAO,CAAC,eAAe;IA0BvB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,sBAAsB;IAwD9B,OAAO,CAAC,kBAAkB;IA6C1B,OAAO,CAAC,qBAAqB;IAuC7B,OAAO,CAAC,qBAAqB;IA0B7B,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,wBAAwB;IAchC,OAAO,CAAC,0BAA0B;IAMlC,OAAO,CAAC,qBAAqB;IAM7B,OAAO,CAAC,uBAAuB;IAO/B,OAAO,CAAC,wBAAwB;IAOhC,OAAO,CAAC,qBAAqB;IAK7B,OAAO,CAAC,6BAA6B;IAKrC,OAAO,CAAC,0BAA0B;IA0BlC,OAAO,CAAC,0BAA0B;IA+ElC,OAAO,CAAC,uBAAuB;IAe/B,OAAO,CAAC,0BAA0B;IAmElC,OAAO,CAAC,iBAAiB;IAkBzB,OAAO,CAAC,QAAQ;IAsChB,OAAO,CAAC,uBAAuB;IAW/B,OAAO,CAAC,iBAAiB;CAoB1B"}
|
||||
30
dist/services/workflow-diff-engine.js
vendored
30
dist/services/workflow-diff-engine.js
vendored
@@ -23,6 +23,7 @@ class WorkflowDiffEngine {
|
||||
this.removedNodeNames.clear();
|
||||
this.tagsToAdd = [];
|
||||
this.tagsToRemove = [];
|
||||
this.transferToProjectId = undefined;
|
||||
const workflowCopy = JSON.parse(JSON.stringify(workflow));
|
||||
const nodeOperationTypes = ['addNode', 'removeNode', 'updateNode', 'moveNode', 'enableNode', 'disableNode'];
|
||||
const nodeOperations = [];
|
||||
@@ -81,6 +82,10 @@ class WorkflowDiffEngine {
|
||||
failed: failedIndices
|
||||
};
|
||||
}
|
||||
const shouldActivate = workflowCopy._shouldActivate === true;
|
||||
const shouldDeactivate = workflowCopy._shouldDeactivate === true;
|
||||
delete workflowCopy._shouldActivate;
|
||||
delete workflowCopy._shouldDeactivate;
|
||||
const success = appliedIndices.length > 0;
|
||||
return {
|
||||
success,
|
||||
@@ -91,8 +96,11 @@ class WorkflowDiffEngine {
|
||||
warnings: this.warnings.length > 0 ? this.warnings : undefined,
|
||||
applied: appliedIndices,
|
||||
failed: failedIndices,
|
||||
shouldActivate: shouldActivate || undefined,
|
||||
shouldDeactivate: shouldDeactivate || undefined,
|
||||
tagsToAdd: this.tagsToAdd.length > 0 ? this.tagsToAdd : undefined,
|
||||
tagsToRemove: this.tagsToRemove.length > 0 ? this.tagsToRemove : undefined
|
||||
tagsToRemove: this.tagsToRemove.length > 0 ? this.tagsToRemove : undefined,
|
||||
transferToProjectId: this.transferToProjectId || undefined
|
||||
};
|
||||
}
|
||||
else {
|
||||
@@ -181,7 +189,8 @@ class WorkflowDiffEngine {
|
||||
shouldActivate: shouldActivate || undefined,
|
||||
shouldDeactivate: shouldDeactivate || undefined,
|
||||
tagsToAdd: this.tagsToAdd.length > 0 ? this.tagsToAdd : undefined,
|
||||
tagsToRemove: this.tagsToRemove.length > 0 ? this.tagsToRemove : undefined
|
||||
tagsToRemove: this.tagsToRemove.length > 0 ? this.tagsToRemove : undefined,
|
||||
transferToProjectId: this.transferToProjectId || undefined
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -220,6 +229,8 @@ class WorkflowDiffEngine {
|
||||
case 'addTag':
|
||||
case 'removeTag':
|
||||
return null;
|
||||
case 'transferWorkflow':
|
||||
return this.validateTransferWorkflow(workflow, operation);
|
||||
case 'activateWorkflow':
|
||||
return this.validateActivateWorkflow(workflow, operation);
|
||||
case 'deactivateWorkflow':
|
||||
@@ -285,6 +296,9 @@ class WorkflowDiffEngine {
|
||||
case 'replaceConnections':
|
||||
this.applyReplaceConnections(workflow, operation);
|
||||
break;
|
||||
case 'transferWorkflow':
|
||||
this.applyTransferWorkflow(workflow, operation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
validateAddNode(workflow, operation) {
|
||||
@@ -699,6 +713,15 @@ class WorkflowDiffEngine {
|
||||
applyDeactivateWorkflow(workflow, operation) {
|
||||
workflow._shouldDeactivate = true;
|
||||
}
|
||||
validateTransferWorkflow(_workflow, operation) {
|
||||
if (!operation.destinationProjectId) {
|
||||
return 'transferWorkflow requires a non-empty destinationProjectId string';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
applyTransferWorkflow(_workflow, operation) {
|
||||
this.transferToProjectId = operation.destinationProjectId;
|
||||
}
|
||||
validateCleanStaleConnections(workflow, operation) {
|
||||
return null;
|
||||
}
|
||||
@@ -806,9 +829,10 @@ class WorkflowDiffEngine {
|
||||
for (let connIndex = 0; connIndex < connectionsAtIndex.length; connIndex++) {
|
||||
const connection = connectionsAtIndex[connIndex];
|
||||
if (renames.has(connection.node)) {
|
||||
const oldTargetName = connection.node;
|
||||
const newTargetName = renames.get(connection.node);
|
||||
connection.node = newTargetName;
|
||||
logger.debug(`Updated connection: ${sourceName}[${outputType}][${outputIndex}][${connIndex}].node: "${connection.node}" → "${newTargetName}"`);
|
||||
logger.debug(`Updated connection: ${sourceName}[${outputType}][${outputIndex}][${connIndex}].node: "${oldTargetName}" → "${newTargetName}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
dist/services/workflow-diff-engine.js.map
vendored
2
dist/services/workflow-diff-engine.js.map
vendored
File diff suppressed because one or more lines are too long
7
dist/types/workflow-diff.d.ts
vendored
7
dist/types/workflow-diff.d.ts
vendored
@@ -94,6 +94,10 @@ export interface ActivateWorkflowOperation extends DiffOperation {
|
||||
export interface DeactivateWorkflowOperation extends DiffOperation {
|
||||
type: 'deactivateWorkflow';
|
||||
}
|
||||
export interface TransferWorkflowOperation extends DiffOperation {
|
||||
type: 'transferWorkflow';
|
||||
destinationProjectId: string;
|
||||
}
|
||||
export interface CleanStaleConnectionsOperation extends DiffOperation {
|
||||
type: 'cleanStaleConnections';
|
||||
dryRun?: boolean;
|
||||
@@ -110,7 +114,7 @@ export interface ReplaceConnectionsOperation extends DiffOperation {
|
||||
};
|
||||
};
|
||||
}
|
||||
export type WorkflowDiffOperation = AddNodeOperation | RemoveNodeOperation | UpdateNodeOperation | MoveNodeOperation | EnableNodeOperation | DisableNodeOperation | AddConnectionOperation | RemoveConnectionOperation | RewireConnectionOperation | UpdateSettingsOperation | UpdateNameOperation | AddTagOperation | RemoveTagOperation | ActivateWorkflowOperation | DeactivateWorkflowOperation | CleanStaleConnectionsOperation | ReplaceConnectionsOperation;
|
||||
export type WorkflowDiffOperation = AddNodeOperation | RemoveNodeOperation | UpdateNodeOperation | MoveNodeOperation | EnableNodeOperation | DisableNodeOperation | AddConnectionOperation | RemoveConnectionOperation | RewireConnectionOperation | UpdateSettingsOperation | UpdateNameOperation | AddTagOperation | RemoveTagOperation | ActivateWorkflowOperation | DeactivateWorkflowOperation | CleanStaleConnectionsOperation | ReplaceConnectionsOperation | TransferWorkflowOperation;
|
||||
export interface WorkflowDiffRequest {
|
||||
id: string;
|
||||
operations: WorkflowDiffOperation[];
|
||||
@@ -139,6 +143,7 @@ export interface WorkflowDiffResult {
|
||||
shouldDeactivate?: boolean;
|
||||
tagsToAdd?: string[];
|
||||
tagsToRemove?: string[];
|
||||
transferToProjectId?: string;
|
||||
}
|
||||
export interface NodeReference {
|
||||
id?: string;
|
||||
|
||||
2
dist/types/workflow-diff.d.ts.map
vendored
2
dist/types/workflow-diff.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"workflow-diff.d.ts","sourceRoot":"","sources":["../../src/types/workflow-diff.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAsB,MAAM,WAAW,CAAC;AAG7D,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAGD,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG;QAC5B,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAC5B,CAAC;CACH;AAED,MAAM,WAAW,mBAAoB,SAAQ,aAAa;IACxD,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAoB,SAAQ,aAAa;IACxD,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE;QACP,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC;KACrB,CAAC;CACH;AAED,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAoB,SAAQ,aAAa;IACxD,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,WAAW,sBAAuB,SAAQ,aAAa;IAC3D,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,yBAA0B,SAAQ,aAAa;IAC9D,IAAI,EAAE,kBAAkB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,yBAA0B,SAAQ,aAAa;IAC9D,IAAI,EAAE,kBAAkB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAGD,MAAM,WAAW,uBAAwB,SAAQ,aAAa;IAC5D,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE;QACR,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,mBAAoB,SAAQ,aAAa;IACxD,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,IAAI,EAAE,QAAQ,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACvD,IAAI,EAAE,WAAW,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,yBAA0B,SAAQ,aAAa;IAC9D,IAAI,EAAE,kBAAkB,CAAC;CAE1B;AAED,MAAM,WAAW,2BAA4B,SAAQ,aAAa;IAChE,IAAI,EAAE,oBAAoB,CAAC;CAE5B;AAGD,MAAM,WAAW,8BAA+B,SAAQ,aAAa;IACnE,IAAI,EAAE,uBAAuB,CAAC;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,2BAA4B,SAAQ,aAAa;IAChE,IAAI,EAAE,oBAAoB,CAAC;IAC3B,WAAW,EAAE;QACX,CAAC,QAAQ,EAAE,MAAM,GAAG;YAClB,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC;gBAChC,IAAI,EAAE,MAAM,CAAC;gBACb,IAAI,EAAE,MAAM,CAAC;gBACb,KAAK,EAAE,MAAM,CAAC;aACf,CAAC,CAAC,CAAC;SACL,CAAC;KACH,CAAC;CACH;AAGD,MAAM,MAAM,qBAAqB,GAC7B,gBAAgB,GAChB,mBAAmB,GACnB,mBAAmB,GACnB,iBAAiB,GACjB,mBAAmB,GACnB,oBAAoB,GACpB,sBAAsB,GACtB,yBAAyB,GACzB,yBAAyB,GACzB,uBAAuB,GACvB,mBAAmB,GACnB,eAAe,GACf,kBAAkB,GAClB,yBAAyB,GACzB,2BAA2B,GAC3B,8BAA8B,GAC9B,2BAA2B,CAAC;AAGhC,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,qBAAqB,EAAE,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAGD,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,GAAG,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,MAAM,CAAC,EAAE,2BAA2B,EAAE,CAAC;IACvC,QAAQ,CAAC,EAAE,2BAA2B,EAAE,CAAC;IACzC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,uBAAuB,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAGD,MAAM,WAAW,aAAa;IAC5B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAGD,wBAAgB,eAAe,CAAC,EAAE,EAAE,qBAAqB,GAAG,EAAE,IAC5D,gBAAgB,GAAG,mBAAmB,GAAG,mBAAmB,GAC5D,iBAAiB,GAAG,mBAAmB,GAAG,oBAAoB,CAE/D;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,qBAAqB,GAAG,EAAE,IAClE,sBAAsB,GAAG,yBAAyB,GAAG,yBAAyB,GAAG,8BAA8B,GAAG,2BAA2B,CAE9I;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,qBAAqB,GAAG,EAAE,IAChE,uBAAuB,GAAG,mBAAmB,GAAG,eAAe,GAAG,kBAAkB,CAErF"}
|
||||
{"version":3,"file":"workflow-diff.d.ts","sourceRoot":"","sources":["../../src/types/workflow-diff.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAsB,MAAM,WAAW,CAAC;AAG7D,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAGD,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG;QAC5B,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAC5B,CAAC;CACH;AAED,MAAM,WAAW,mBAAoB,SAAQ,aAAa;IACxD,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAoB,SAAQ,aAAa;IACxD,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE;QACP,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC;KACrB,CAAC;CACH;AAED,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAoB,SAAQ,aAAa;IACxD,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,WAAW,sBAAuB,SAAQ,aAAa;IAC3D,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,yBAA0B,SAAQ,aAAa;IAC9D,IAAI,EAAE,kBAAkB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,yBAA0B,SAAQ,aAAa;IAC9D,IAAI,EAAE,kBAAkB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAGD,MAAM,WAAW,uBAAwB,SAAQ,aAAa;IAC5D,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE;QACR,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,mBAAoB,SAAQ,aAAa;IACxD,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,IAAI,EAAE,QAAQ,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACvD,IAAI,EAAE,WAAW,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,yBAA0B,SAAQ,aAAa;IAC9D,IAAI,EAAE,kBAAkB,CAAC;CAE1B;AAED,MAAM,WAAW,2BAA4B,SAAQ,aAAa;IAChE,IAAI,EAAE,oBAAoB,CAAC;CAE5B;AAED,MAAM,WAAW,yBAA0B,SAAQ,aAAa;IAC9D,IAAI,EAAE,kBAAkB,CAAC;IACzB,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAGD,MAAM,WAAW,8BAA+B,SAAQ,aAAa;IACnE,IAAI,EAAE,uBAAuB,CAAC;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,2BAA4B,SAAQ,aAAa;IAChE,IAAI,EAAE,oBAAoB,CAAC;IAC3B,WAAW,EAAE;QACX,CAAC,QAAQ,EAAE,MAAM,GAAG;YAClB,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC;gBAChC,IAAI,EAAE,MAAM,CAAC;gBACb,IAAI,EAAE,MAAM,CAAC;gBACb,KAAK,EAAE,MAAM,CAAC;aACf,CAAC,CAAC,CAAC;SACL,CAAC;KACH,CAAC;CACH;AAGD,MAAM,MAAM,qBAAqB,GAC7B,gBAAgB,GAChB,mBAAmB,GACnB,mBAAmB,GACnB,iBAAiB,GACjB,mBAAmB,GACnB,oBAAoB,GACpB,sBAAsB,GACtB,yBAAyB,GACzB,yBAAyB,GACzB,uBAAuB,GACvB,mBAAmB,GACnB,eAAe,GACf,kBAAkB,GAClB,yBAAyB,GACzB,2BAA2B,GAC3B,8BAA8B,GAC9B,2BAA2B,GAC3B,yBAAyB,CAAC;AAG9B,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,qBAAqB,EAAE,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAGD,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,GAAG,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,MAAM,CAAC,EAAE,2BAA2B,EAAE,CAAC;IACvC,QAAQ,CAAC,EAAE,2BAA2B,EAAE,CAAC;IACzC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,uBAAuB,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAGD,MAAM,WAAW,aAAa;IAC5B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAGD,wBAAgB,eAAe,CAAC,EAAE,EAAE,qBAAqB,GAAG,EAAE,IAC5D,gBAAgB,GAAG,mBAAmB,GAAG,mBAAmB,GAC5D,iBAAiB,GAAG,mBAAmB,GAAG,oBAAoB,CAE/D;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,qBAAqB,GAAG,EAAE,IAClE,sBAAsB,GAAG,yBAAyB,GAAG,yBAAyB,GAAG,8BAA8B,GAAG,2BAA2B,CAE9I;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,qBAAqB,GAAG,EAAE,IAChE,uBAAuB,GAAG,mBAAmB,GAAG,eAAe,GAAG,kBAAkB,CAErF"}
|
||||
2
dist/types/workflow-diff.js.map
vendored
2
dist/types/workflow-diff.js.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"workflow-diff.js","sourceRoot":"","sources":["../../src/types/workflow-diff.ts"],"names":[],"mappings":";;AA2MA,0CAIC;AAED,sDAGC;AAED,kDAGC;AAdD,SAAgB,eAAe,CAAC,EAAyB;IAGvD,OAAO,CAAC,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;AAC5G,CAAC;AAED,SAAgB,qBAAqB,CAAC,EAAyB;IAE7D,OAAO,CAAC,eAAe,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,oBAAoB,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;AACpI,CAAC;AAED,SAAgB,mBAAmB,CAAC,EAAyB;IAE3D,OAAO,CAAC,gBAAgB,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;AACnF,CAAC"}
|
||||
{"version":3,"file":"workflow-diff.js","sourceRoot":"","sources":["../../src/types/workflow-diff.ts"],"names":[],"mappings":";;AAkNA,0CAIC;AAED,sDAGC;AAED,kDAGC;AAdD,SAAgB,eAAe,CAAC,EAAyB;IAGvD,OAAO,CAAC,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;AAC5G,CAAC;AAED,SAAgB,qBAAqB,CAAC,EAAyB;IAE7D,OAAO,CAAC,eAAe,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,oBAAoB,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;AACpI,CAAC;AAED,SAAgB,mBAAmB,CAAC,EAAyB;IAE3D,OAAO,CAAC,gBAAgB,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;AACnF,CAAC"}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-mcp",
|
||||
"version": "2.37.4",
|
||||
"version": "2.39.0",
|
||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
WebhookRequest,
|
||||
McpToolResponse,
|
||||
ExecutionFilterOptions,
|
||||
ExecutionMode
|
||||
ExecutionMode,
|
||||
} from '../types/n8n-api';
|
||||
import type { TriggerType, TestWorkflowInput } from '../triggers/types';
|
||||
import {
|
||||
@@ -383,6 +383,7 @@ const createWorkflowSchema = z.object({
|
||||
executionTimeout: z.number().optional(),
|
||||
errorWorkflow: z.string().optional(),
|
||||
}).optional(),
|
||||
projectId: z.string().optional(),
|
||||
});
|
||||
|
||||
const updateWorkflowSchema = z.object({
|
||||
@@ -1974,7 +1975,7 @@ export async function handleDiagnostic(request: any, context?: InstanceContext):
|
||||
|
||||
// Check which tools are available
|
||||
const documentationTools = 7; // Base documentation tools (after v2.26.0 consolidation)
|
||||
const managementTools = apiConfigured ? 13 : 0; // Management tools requiring API (includes n8n_deploy_template)
|
||||
const managementTools = apiConfigured ? 14 : 0; // Management tools requiring API (includes n8n_create_data_table)
|
||||
const totalTools = documentationTools + managementTools;
|
||||
|
||||
// Check npm version
|
||||
@@ -2688,3 +2689,58 @@ export async function handleTriggerWebhookWorkflow(args: unknown, context?: Inst
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Data Table Handler
|
||||
// ========================================================================
|
||||
|
||||
const createDataTableSchema = z.object({
|
||||
name: z.string().min(1, 'Table name cannot be empty'),
|
||||
columns: z.array(z.object({
|
||||
name: z.string().min(1, 'Column name cannot be empty'),
|
||||
type: z.enum(['string', 'number', 'boolean', 'date', 'json']).optional(),
|
||||
})).optional(),
|
||||
});
|
||||
|
||||
export async function handleCreateDataTable(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const input = createDataTableSchema.parse(args);
|
||||
const dataTable = await client.createDataTable(input);
|
||||
|
||||
if (!dataTable || !dataTable.id) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Data table creation failed: n8n API returned an empty or invalid response'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: { id: dataTable.id, name: dataTable.name },
|
||||
message: `Data table "${dataTable.name}" created with ID: ${dataTable.id}`
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid input',
|
||||
details: { errors: error.errors }
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof N8nApiError) {
|
||||
return {
|
||||
success: false,
|
||||
error: getUserFriendlyErrorMessage(error),
|
||||
code: error.code,
|
||||
details: error.details as Record<string, unknown> | undefined
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,8 @@ const workflowDiffSchema = z.object({
|
||||
settings: z.any().optional(),
|
||||
name: z.string().optional(),
|
||||
tag: z.string().optional(),
|
||||
// Transfer operation
|
||||
destinationProjectId: z.string().min(1).optional(),
|
||||
// Aliases: LLMs often use "id" instead of "nodeId" — accept both
|
||||
id: z.string().optional(),
|
||||
}).transform((op) => {
|
||||
@@ -370,6 +372,26 @@ export async function handleUpdatePartialWorkflow(
|
||||
}
|
||||
}
|
||||
|
||||
// Handle project transfer if requested (before activation so workflow is in target project first)
|
||||
let transferMessage = '';
|
||||
if (diffResult.transferToProjectId) {
|
||||
try {
|
||||
await client.transferWorkflow(input.id, diffResult.transferToProjectId);
|
||||
transferMessage = ` Workflow transferred to project ${diffResult.transferToProjectId}.`;
|
||||
} catch (transferError) {
|
||||
logger.error('Failed to transfer workflow to project', transferError);
|
||||
return {
|
||||
success: false,
|
||||
saved: true,
|
||||
error: 'Workflow updated successfully but project transfer failed',
|
||||
details: {
|
||||
workflowUpdated: true,
|
||||
transferError: transferError instanceof Error ? transferError.message : 'Unknown error'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Handle activation/deactivation if requested
|
||||
let finalWorkflow = updatedWorkflow;
|
||||
let activationMessage = '';
|
||||
@@ -454,7 +476,7 @@ export async function handleUpdatePartialWorkflow(
|
||||
nodeCount: finalWorkflow.nodes?.length || 0,
|
||||
operationsApplied: diffResult.operationsApplied
|
||||
},
|
||||
message: `Workflow "${finalWorkflow.name}" updated successfully. Applied ${diffResult.operationsApplied} operations.${activationMessage} Use n8n_get_workflow with mode 'structure' to verify current state.`,
|
||||
message: `Workflow "${finalWorkflow.name}" updated successfully. Applied ${diffResult.operationsApplied} operations.${transferMessage}${activationMessage} Use n8n_get_workflow with mode 'structure' to verify current state.`,
|
||||
details: {
|
||||
applied: diffResult.applied,
|
||||
failed: diffResult.failed,
|
||||
@@ -559,6 +581,8 @@ function inferIntentFromOperations(operations: any[]): string {
|
||||
return 'Activate workflow';
|
||||
case 'deactivateWorkflow':
|
||||
return 'Deactivate workflow';
|
||||
case 'transferWorkflow':
|
||||
return `Transfer workflow to project ${op.destinationProjectId || ''}`.trim();
|
||||
default:
|
||||
return `Workflow ${op.type}`;
|
||||
}
|
||||
|
||||
@@ -1496,6 +1496,10 @@ export class N8NDocumentationMCPServer {
|
||||
if (!this.repository) throw new Error('Repository not initialized');
|
||||
return n8nHandlers.handleDeployTemplate(args, this.templateService, this.repository, this.instanceContext);
|
||||
|
||||
case 'n8n_create_data_table':
|
||||
this.validateToolParams(name, args, ['name']);
|
||||
return n8nHandlers.handleCreateDataTable(args, this.instanceContext);
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ import {
|
||||
n8nTestWorkflowDoc,
|
||||
n8nExecutionsDoc,
|
||||
n8nWorkflowVersionsDoc,
|
||||
n8nDeployTemplateDoc
|
||||
n8nDeployTemplateDoc,
|
||||
n8nCreateDataTableDoc
|
||||
} from './workflow_management';
|
||||
|
||||
// Combine all tool documentations into a single object
|
||||
@@ -60,7 +61,8 @@ export const toolsDocumentation: Record<string, ToolDocumentation> = {
|
||||
n8n_test_workflow: n8nTestWorkflowDoc,
|
||||
n8n_executions: n8nExecutionsDoc,
|
||||
n8n_workflow_versions: n8nWorkflowVersionsDoc,
|
||||
n8n_deploy_template: n8nDeployTemplateDoc
|
||||
n8n_deploy_template: n8nDeployTemplateDoc,
|
||||
n8n_create_data_table: n8nCreateDataTableDoc
|
||||
};
|
||||
|
||||
// Re-export types
|
||||
|
||||
@@ -10,3 +10,4 @@ export { n8nTestWorkflowDoc } from './n8n-test-workflow';
|
||||
export { n8nExecutionsDoc } from './n8n-executions';
|
||||
export { n8nWorkflowVersionsDoc } from './n8n-workflow-versions';
|
||||
export { n8nDeployTemplateDoc } from './n8n-deploy-template';
|
||||
export { n8nCreateDataTableDoc } from './n8n-create-data-table';
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const n8nCreateDataTableDoc: ToolDocumentation = {
|
||||
name: 'n8n_create_data_table',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'Create a new data table in n8n. Requires n8n enterprise or cloud with the data tables feature enabled.',
|
||||
keyParameters: ['name', 'columns'],
|
||||
example: 'n8n_create_data_table({name: "Contacts", columns: [{name: "email", type: "string"}]})',
|
||||
performance: 'Fast (100-300ms)',
|
||||
tips: [
|
||||
'Available column types: string, number, boolean, date, json',
|
||||
'Columns are optional — a table can be created without columns',
|
||||
'Requires n8n enterprise or cloud plan with data tables feature',
|
||||
'projectId cannot be set via the public API — use the n8n UI for project assignment'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: 'Creates a new data table in n8n. Data tables are structured, persistent storage for workflow data. Each table can have typed columns (string, number, boolean, date, json). Requires the data tables feature to be enabled on the n8n instance.',
|
||||
parameters: {
|
||||
name: { type: 'string', required: true, description: 'Name for the new data table' },
|
||||
columns: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
description: 'Column definitions. Each column has a name and optional type (string, number, boolean, date, json). Defaults to string if type is omitted.'
|
||||
}
|
||||
},
|
||||
returns: 'Object with id and name of the created data table on success.',
|
||||
examples: [
|
||||
'n8n_create_data_table({name: "Orders"}) - Create table without columns',
|
||||
'n8n_create_data_table({name: "Contacts", columns: [{name: "email", type: "string"}, {name: "score", type: "number"}]}) - Create table with typed columns',
|
||||
'n8n_create_data_table({name: "Events", columns: [{name: "payload", type: "json"}, {name: "occurred_at", type: "date"}]})'
|
||||
],
|
||||
useCases: [
|
||||
'Persist structured workflow data across executions',
|
||||
'Store lookup tables for workflow logic',
|
||||
'Accumulate records from multiple workflow runs',
|
||||
'Share data between different workflows'
|
||||
],
|
||||
performance: 'Fast operation — typically 100-300ms.',
|
||||
bestPractices: [
|
||||
'Define columns upfront to enforce schema consistency',
|
||||
'Use typed columns for numeric or date data to enable proper filtering',
|
||||
'Use json type for nested or variable-structure data'
|
||||
],
|
||||
pitfalls: [
|
||||
'Requires N8N_API_URL and N8N_API_KEY configured',
|
||||
'Feature only available on n8n enterprise or cloud plans',
|
||||
'projectId cannot be set via the public API',
|
||||
'Column types cannot be changed after table creation via API'
|
||||
],
|
||||
relatedTools: ['n8n_create_workflow', 'n8n_list_workflows', 'n8n_health_check']
|
||||
}
|
||||
};
|
||||
@@ -4,7 +4,7 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = {
|
||||
name: 'n8n_update_partial_workflow',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, rewireConnection, cleanStaleConnections, replaceConnections, updateSettings, updateName, add/removeTag, activateWorkflow, deactivateWorkflow. Supports smart parameters (branch, case) for multi-output nodes. Full support for AI connections (ai_languageModel, ai_tool, ai_memory, ai_embedding, ai_vectorStore, ai_document, ai_textSplitter, ai_outputParser).',
|
||||
description: 'Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, rewireConnection, cleanStaleConnections, replaceConnections, updateSettings, updateName, add/removeTag, activateWorkflow, deactivateWorkflow, transferWorkflow. Supports smart parameters (branch, case) for multi-output nodes. Full support for AI connections (ai_languageModel, ai_tool, ai_memory, ai_embedding, ai_vectorStore, ai_document, ai_textSplitter, ai_outputParser).',
|
||||
keyParameters: ['id', 'operations', 'continueOnError'],
|
||||
example: 'n8n_update_partial_workflow({id: "wf_123", operations: [{type: "rewireConnection", source: "IF", from: "Old", to: "New", branch: "true"}]})',
|
||||
performance: 'Fast (50-200ms)',
|
||||
@@ -22,7 +22,8 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = {
|
||||
'Batch AI component connections for atomic updates',
|
||||
'Auto-sanitization: ALL nodes auto-fixed during updates (operator structures, missing metadata)',
|
||||
'Node renames automatically update all connection references - no manual connection operations needed',
|
||||
'Activate/deactivate workflows: Use activateWorkflow/deactivateWorkflow operations (requires activatable triggers like webhook/schedule)'
|
||||
'Activate/deactivate workflows: Use activateWorkflow/deactivateWorkflow operations (requires activatable triggers like webhook/schedule)',
|
||||
'Transfer workflows between projects: Use transferWorkflow with destinationProjectId (enterprise feature)'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
@@ -55,6 +56,9 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = {
|
||||
- **activateWorkflow**: Activate the workflow to enable automatic execution via triggers
|
||||
- **deactivateWorkflow**: Deactivate the workflow to prevent automatic execution
|
||||
|
||||
### Project Management Operations (1 type):
|
||||
- **transferWorkflow**: Transfer the workflow to a different project. Requires \`destinationProjectId\`. Enterprise/cloud feature.
|
||||
|
||||
## Smart Parameters for Multi-Output Nodes
|
||||
|
||||
For **IF nodes**, use semantic 'branch' parameter instead of technical sourceIndex:
|
||||
@@ -345,7 +349,10 @@ n8n_update_partial_workflow({
|
||||
'// Migrate from deprecated continueOnFail to onError\nn8n_update_partial_workflow({id: "rm2", operations: [{type: "updateNode", nodeName: "HTTP Request", updates: {continueOnFail: null, onError: "continueErrorOutput"}}]})',
|
||||
'// Remove nested property\nn8n_update_partial_workflow({id: "rm3", operations: [{type: "updateNode", nodeName: "API Request", updates: {"parameters.authentication": null}}]})',
|
||||
'// Remove multiple properties\nn8n_update_partial_workflow({id: "rm4", operations: [{type: "updateNode", nodeName: "Data Processor", updates: {continueOnFail: null, alwaysOutputData: null, "parameters.legacy_option": null}}]})',
|
||||
'// Remove entire array property\nn8n_update_partial_workflow({id: "rm5", operations: [{type: "updateNode", nodeName: "HTTP Request", updates: {"parameters.headers": null}}]})'
|
||||
'// Remove entire array property\nn8n_update_partial_workflow({id: "rm5", operations: [{type: "updateNode", nodeName: "HTTP Request", updates: {"parameters.headers": null}}]})',
|
||||
'\n// ============ PROJECT TRANSFER EXAMPLES ============',
|
||||
'// Transfer workflow to a different project\nn8n_update_partial_workflow({id: "tf1", operations: [{type: "transferWorkflow", destinationProjectId: "project-abc-123"}]})',
|
||||
'// Transfer and activate in one call\nn8n_update_partial_workflow({id: "tf2", operations: [{type: "transferWorkflow", destinationProjectId: "project-abc-123"}, {type: "activateWorkflow"}]})'
|
||||
],
|
||||
useCases: [
|
||||
'Rewire connections when replacing nodes',
|
||||
@@ -363,7 +370,8 @@ n8n_update_partial_workflow({
|
||||
'Add fallback language models to AI Agents',
|
||||
'Configure Vector Store retrieval systems',
|
||||
'Swap language models in existing AI workflows',
|
||||
'Batch-update AI tool connections'
|
||||
'Batch-update AI tool connections',
|
||||
'Transfer workflows between team projects (enterprise)'
|
||||
],
|
||||
performance: 'Very fast - typically 50-200ms. Much faster than full updates as only changes are processed.',
|
||||
bestPractices: [
|
||||
|
||||
@@ -63,6 +63,10 @@ export const n8nManagementTools: ToolDefinition[] = [
|
||||
executionTimeout: { type: 'number' },
|
||||
errorWorkflow: { type: 'string' }
|
||||
}
|
||||
},
|
||||
projectId: {
|
||||
type: 'string',
|
||||
description: 'Optional project ID to create the workflow in (enterprise feature)'
|
||||
}
|
||||
},
|
||||
required: ['name', 'nodes', 'connections']
|
||||
@@ -143,7 +147,7 @@ export const n8nManagementTools: ToolDefinition[] = [
|
||||
},
|
||||
{
|
||||
name: 'n8n_update_partial_workflow',
|
||||
description: `Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, updateSettings, updateName, add/removeTag. See tools_documentation("n8n_update_partial_workflow", "full") for details.`,
|
||||
description: `Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, updateSettings, updateName, add/removeTag, activate/deactivateWorkflow, transferWorkflow. See tools_documentation("n8n_update_partial_workflow", "full") for details.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: true, // Allow any extra properties Claude Desktop might add
|
||||
@@ -602,5 +606,38 @@ export const n8nManagementTools: ToolDefinition[] = [
|
||||
destructiveHint: false,
|
||||
openWorldHint: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'n8n_create_data_table',
|
||||
description: 'Create a new data table in n8n. Requires n8n enterprise or cloud with data tables feature.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Name for the data table' },
|
||||
columns: {
|
||||
type: 'array',
|
||||
description: 'Column definitions',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Column name' },
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['string', 'number', 'boolean', 'date', 'json'],
|
||||
description: 'Column data type',
|
||||
},
|
||||
},
|
||||
required: ['name'],
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ['name'],
|
||||
},
|
||||
annotations: {
|
||||
title: 'Create Data Table',
|
||||
readOnlyHint: false,
|
||||
destructiveHint: false,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -22,6 +22,8 @@ import {
|
||||
SourceControlStatus,
|
||||
SourceControlPullResult,
|
||||
SourceControlPushResult,
|
||||
DataTable,
|
||||
DataTableColumn,
|
||||
} from '../types/n8n-api';
|
||||
import { handleN8nApiError, logN8nError } from '../utils/n8n-errors';
|
||||
import { cleanWorkflowForCreate, cleanWorkflowForUpdate } from './n8n-validation';
|
||||
@@ -252,6 +254,14 @@ export class N8nApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
async transferWorkflow(id: string, destinationProjectId: string): Promise<void> {
|
||||
try {
|
||||
await this.client.put(`/workflows/${id}/transfer`, { destinationProjectId });
|
||||
} catch (error) {
|
||||
throw handleN8nApiError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async activateWorkflow(id: string): Promise<Workflow> {
|
||||
try {
|
||||
const response = await this.client.post(`/workflows/${id}/activate`, {});
|
||||
@@ -574,6 +584,15 @@ export class N8nApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
async createDataTable(params: { name: string; columns?: DataTableColumn[] }): Promise<DataTable> {
|
||||
try {
|
||||
const response = await this.client.post('/data-tables', params);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleN8nApiError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and normalizes n8n API list responses.
|
||||
* Handles both modern format {data: [], nextCursor?: string} and legacy array format.
|
||||
|
||||
@@ -28,7 +28,8 @@ import {
|
||||
ActivateWorkflowOperation,
|
||||
DeactivateWorkflowOperation,
|
||||
CleanStaleConnectionsOperation,
|
||||
ReplaceConnectionsOperation
|
||||
ReplaceConnectionsOperation,
|
||||
TransferWorkflowOperation
|
||||
} from '../types/workflow-diff';
|
||||
import { Workflow, WorkflowNode, WorkflowConnection } from '../types/n8n-api';
|
||||
import { Logger } from '../utils/logger';
|
||||
@@ -54,6 +55,8 @@ export class WorkflowDiffEngine {
|
||||
// Track tag operations for dedicated API calls
|
||||
private tagsToAdd: string[] = [];
|
||||
private tagsToRemove: string[] = [];
|
||||
// Track transfer operation for dedicated API call
|
||||
private transferToProjectId: string | undefined;
|
||||
|
||||
/**
|
||||
* Apply diff operations to a workflow
|
||||
@@ -70,6 +73,7 @@ export class WorkflowDiffEngine {
|
||||
this.removedNodeNames.clear();
|
||||
this.tagsToAdd = [];
|
||||
this.tagsToRemove = [];
|
||||
this.transferToProjectId = undefined;
|
||||
|
||||
// Clone workflow to avoid modifying original
|
||||
const workflowCopy = JSON.parse(JSON.stringify(workflow));
|
||||
@@ -141,6 +145,12 @@ export class WorkflowDiffEngine {
|
||||
};
|
||||
}
|
||||
|
||||
// Extract and clean up activation flags (same as atomic mode)
|
||||
const shouldActivate = (workflowCopy as any)._shouldActivate === true;
|
||||
const shouldDeactivate = (workflowCopy as any)._shouldDeactivate === true;
|
||||
delete (workflowCopy as any)._shouldActivate;
|
||||
delete (workflowCopy as any)._shouldDeactivate;
|
||||
|
||||
const success = appliedIndices.length > 0;
|
||||
return {
|
||||
success,
|
||||
@@ -151,8 +161,11 @@ export class WorkflowDiffEngine {
|
||||
warnings: this.warnings.length > 0 ? this.warnings : undefined,
|
||||
applied: appliedIndices,
|
||||
failed: failedIndices,
|
||||
shouldActivate: shouldActivate || undefined,
|
||||
shouldDeactivate: shouldDeactivate || undefined,
|
||||
tagsToAdd: this.tagsToAdd.length > 0 ? this.tagsToAdd : undefined,
|
||||
tagsToRemove: this.tagsToRemove.length > 0 ? this.tagsToRemove : undefined
|
||||
tagsToRemove: this.tagsToRemove.length > 0 ? this.tagsToRemove : undefined,
|
||||
transferToProjectId: this.transferToProjectId || undefined
|
||||
};
|
||||
} else {
|
||||
// Atomic mode: all operations must succeed
|
||||
@@ -256,7 +269,8 @@ export class WorkflowDiffEngine {
|
||||
shouldActivate: shouldActivate || undefined,
|
||||
shouldDeactivate: shouldDeactivate || undefined,
|
||||
tagsToAdd: this.tagsToAdd.length > 0 ? this.tagsToAdd : undefined,
|
||||
tagsToRemove: this.tagsToRemove.length > 0 ? this.tagsToRemove : undefined
|
||||
tagsToRemove: this.tagsToRemove.length > 0 ? this.tagsToRemove : undefined,
|
||||
transferToProjectId: this.transferToProjectId || undefined
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -298,6 +312,8 @@ export class WorkflowDiffEngine {
|
||||
case 'addTag':
|
||||
case 'removeTag':
|
||||
return null; // These are always valid
|
||||
case 'transferWorkflow':
|
||||
return this.validateTransferWorkflow(workflow, operation as TransferWorkflowOperation);
|
||||
case 'activateWorkflow':
|
||||
return this.validateActivateWorkflow(workflow, operation);
|
||||
case 'deactivateWorkflow':
|
||||
@@ -367,6 +383,9 @@ export class WorkflowDiffEngine {
|
||||
case 'replaceConnections':
|
||||
this.applyReplaceConnections(workflow, operation);
|
||||
break;
|
||||
case 'transferWorkflow':
|
||||
this.applyTransferWorkflow(workflow, operation as TransferWorkflowOperation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -975,6 +994,18 @@ export class WorkflowDiffEngine {
|
||||
(workflow as any)._shouldDeactivate = true;
|
||||
}
|
||||
|
||||
// Transfer operation — uses dedicated API call (PUT /workflows/{id}/transfer)
|
||||
private validateTransferWorkflow(_workflow: Workflow, operation: TransferWorkflowOperation): string | null {
|
||||
if (!operation.destinationProjectId) {
|
||||
return 'transferWorkflow requires a non-empty destinationProjectId string';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private applyTransferWorkflow(_workflow: Workflow, operation: TransferWorkflowOperation): void {
|
||||
this.transferToProjectId = operation.destinationProjectId;
|
||||
}
|
||||
|
||||
// Connection cleanup operation validators
|
||||
private validateCleanStaleConnections(workflow: Workflow, operation: CleanStaleConnectionsOperation): string | null {
|
||||
// This operation is always valid - it just cleans up what it finds
|
||||
@@ -1128,9 +1159,10 @@ export class WorkflowDiffEngine {
|
||||
const connection = connectionsAtIndex[connIndex];
|
||||
// Check if target node was renamed
|
||||
if (renames.has(connection.node)) {
|
||||
const oldTargetName = connection.node;
|
||||
const newTargetName = renames.get(connection.node)!;
|
||||
connection.node = newTargetName;
|
||||
logger.debug(`Updated connection: ${sourceName}[${outputType}][${outputIndex}][${connIndex}].node: "${connection.node}" → "${newTargetName}"`);
|
||||
logger.debug(`Updated connection: ${sourceName}[${outputType}][${outputIndex}][${connIndex}].node: "${oldTargetName}" → "${newTargetName}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,4 +454,26 @@ export interface ErrorSuggestion {
|
||||
title: string;
|
||||
description: string;
|
||||
confidence: 'high' | 'medium' | 'low';
|
||||
}
|
||||
|
||||
// Data Table types
|
||||
export interface DataTableColumn {
|
||||
name: string;
|
||||
type?: 'string' | 'number' | 'boolean' | 'date' | 'json';
|
||||
}
|
||||
|
||||
export interface DataTableColumnResponse {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'string' | 'number' | 'boolean' | 'date' | 'json';
|
||||
index: number;
|
||||
}
|
||||
|
||||
export interface DataTable {
|
||||
id: string;
|
||||
name: string;
|
||||
columns?: DataTableColumnResponse[];
|
||||
projectId?: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
@@ -124,6 +124,11 @@ export interface DeactivateWorkflowOperation extends DiffOperation {
|
||||
// No additional properties needed - just deactivates the workflow
|
||||
}
|
||||
|
||||
export interface TransferWorkflowOperation extends DiffOperation {
|
||||
type: 'transferWorkflow';
|
||||
destinationProjectId: string;
|
||||
}
|
||||
|
||||
// Connection Cleanup Operations
|
||||
export interface CleanStaleConnectionsOperation extends DiffOperation {
|
||||
type: 'cleanStaleConnections';
|
||||
@@ -161,7 +166,8 @@ export type WorkflowDiffOperation =
|
||||
| ActivateWorkflowOperation
|
||||
| DeactivateWorkflowOperation
|
||||
| CleanStaleConnectionsOperation
|
||||
| ReplaceConnectionsOperation;
|
||||
| ReplaceConnectionsOperation
|
||||
| TransferWorkflowOperation;
|
||||
|
||||
// Main diff request structure
|
||||
export interface WorkflowDiffRequest {
|
||||
@@ -192,6 +198,7 @@ export interface WorkflowDiffResult {
|
||||
shouldDeactivate?: boolean; // Flag to deactivate workflow after update (for deactivateWorkflow operation)
|
||||
tagsToAdd?: string[];
|
||||
tagsToRemove?: string[];
|
||||
transferToProjectId?: string; // For transferWorkflow operation - uses dedicated API call
|
||||
}
|
||||
|
||||
// Helper type for node reference (supports both ID and name)
|
||||
|
||||
251
tests/unit/mcp/handlers-create-data-table.test.ts
Normal file
251
tests/unit/mcp/handlers-create-data-table.test.ts
Normal file
@@ -0,0 +1,251 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { N8nApiClient } from '@/services/n8n-api-client';
|
||||
import { N8nApiError } from '@/utils/n8n-errors';
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('@/services/n8n-api-client');
|
||||
vi.mock('@/config/n8n-api', () => ({
|
||||
getN8nApiConfig: vi.fn(),
|
||||
}));
|
||||
vi.mock('@/services/n8n-validation', () => ({
|
||||
validateWorkflowStructure: vi.fn(),
|
||||
hasWebhookTrigger: vi.fn(),
|
||||
getWebhookUrl: vi.fn(),
|
||||
}));
|
||||
vi.mock('@/utils/logger', () => ({
|
||||
logger: {
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
},
|
||||
Logger: vi.fn().mockImplementation(() => ({
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
})),
|
||||
LogLevel: {
|
||||
ERROR: 0,
|
||||
WARN: 1,
|
||||
INFO: 2,
|
||||
DEBUG: 3,
|
||||
},
|
||||
}));
|
||||
|
||||
describe('handleCreateDataTable', () => {
|
||||
let mockApiClient: any;
|
||||
let handlers: any;
|
||||
let getN8nApiConfig: any;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Setup mock API client
|
||||
mockApiClient = {
|
||||
createWorkflow: vi.fn(),
|
||||
getWorkflow: vi.fn(),
|
||||
updateWorkflow: vi.fn(),
|
||||
deleteWorkflow: vi.fn(),
|
||||
listWorkflows: vi.fn(),
|
||||
triggerWebhook: vi.fn(),
|
||||
getExecution: vi.fn(),
|
||||
listExecutions: vi.fn(),
|
||||
deleteExecution: vi.fn(),
|
||||
healthCheck: vi.fn(),
|
||||
createDataTable: vi.fn(),
|
||||
};
|
||||
|
||||
// Import mocked modules
|
||||
getN8nApiConfig = (await import('@/config/n8n-api')).getN8nApiConfig;
|
||||
|
||||
// Mock the API config
|
||||
vi.mocked(getN8nApiConfig).mockReturnValue({
|
||||
baseUrl: 'https://n8n.test.com',
|
||||
apiKey: 'test-key',
|
||||
timeout: 30000,
|
||||
maxRetries: 3,
|
||||
});
|
||||
|
||||
// Mock the N8nApiClient constructor
|
||||
vi.mocked(N8nApiClient).mockImplementation(() => mockApiClient);
|
||||
|
||||
// Import handlers module after setting up mocks
|
||||
handlers = await import('@/mcp/handlers-n8n-manager');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (handlers) {
|
||||
const clientGetter = handlers.getN8nApiClient;
|
||||
if (clientGetter) {
|
||||
vi.mocked(getN8nApiConfig).mockReturnValue(null);
|
||||
clientGetter();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should create data table with name and columns successfully', async () => {
|
||||
const createdTable = {
|
||||
id: 'dt-123',
|
||||
name: 'My Data Table',
|
||||
columns: [
|
||||
{ id: 'col-1', name: 'email', type: 'string', index: 0 },
|
||||
{ id: 'col-2', name: 'age', type: 'number', index: 1 },
|
||||
],
|
||||
};
|
||||
|
||||
mockApiClient.createDataTable.mockResolvedValue(createdTable);
|
||||
|
||||
const result = await handlers.handleCreateDataTable({
|
||||
name: 'My Data Table',
|
||||
columns: [
|
||||
{ name: 'email', type: 'string' },
|
||||
{ name: 'age', type: 'number' },
|
||||
],
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: true,
|
||||
data: { id: 'dt-123', name: 'My Data Table' },
|
||||
message: 'Data table "My Data Table" created with ID: dt-123',
|
||||
});
|
||||
|
||||
expect(mockApiClient.createDataTable).toHaveBeenCalledWith({
|
||||
name: 'My Data Table',
|
||||
columns: [
|
||||
{ name: 'email', type: 'string' },
|
||||
{ name: 'age', type: 'number' },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should create data table with name only (no columns)', async () => {
|
||||
const createdTable = {
|
||||
id: 'dt-456',
|
||||
name: 'Empty Table',
|
||||
};
|
||||
|
||||
mockApiClient.createDataTable.mockResolvedValue(createdTable);
|
||||
|
||||
const result = await handlers.handleCreateDataTable({
|
||||
name: 'Empty Table',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: true,
|
||||
data: { id: 'dt-456', name: 'Empty Table' },
|
||||
message: 'Data table "Empty Table" created with ID: dt-456',
|
||||
});
|
||||
|
||||
expect(mockApiClient.createDataTable).toHaveBeenCalledWith({
|
||||
name: 'Empty Table',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return error when n8n API is not configured', async () => {
|
||||
vi.mocked(getN8nApiConfig).mockReturnValue(null);
|
||||
|
||||
const result = await handlers.handleCreateDataTable({
|
||||
name: 'Test Table',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'n8n API not configured. Please set N8N_API_URL and N8N_API_KEY environment variables.',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return Zod validation error when name is missing', async () => {
|
||||
const result = await handlers.handleCreateDataTable({});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Invalid input');
|
||||
expect(result.details).toHaveProperty('errors');
|
||||
});
|
||||
|
||||
it('should return Zod validation error when name is empty string', async () => {
|
||||
const result = await handlers.handleCreateDataTable({ name: '' });
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Invalid input');
|
||||
expect(result.details).toHaveProperty('errors');
|
||||
});
|
||||
|
||||
it('should return Zod validation error when column name is empty string', async () => {
|
||||
const result = await handlers.handleCreateDataTable({
|
||||
name: 'Valid Table',
|
||||
columns: [{ name: '' }],
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Invalid input');
|
||||
expect(result.details).toHaveProperty('errors');
|
||||
});
|
||||
|
||||
it('should return error when API call fails', async () => {
|
||||
const apiError = new Error('Data table creation failed on the server');
|
||||
mockApiClient.createDataTable.mockRejectedValue(apiError);
|
||||
|
||||
const result = await handlers.handleCreateDataTable({
|
||||
name: 'Duplicate Table',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'Data table creation failed on the server',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return structured error for N8nApiError', async () => {
|
||||
const apiError = new N8nApiError('Feature not available', 402, 'PAYMENT_REQUIRED', { plan: 'enterprise' });
|
||||
mockApiClient.createDataTable.mockRejectedValue(apiError);
|
||||
|
||||
const result = await handlers.handleCreateDataTable({
|
||||
name: 'Enterprise Table',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.code).toBe('PAYMENT_REQUIRED');
|
||||
expect(result.details).toEqual({ plan: 'enterprise' });
|
||||
});
|
||||
|
||||
it('should return error when API returns empty response (null)', async () => {
|
||||
mockApiClient.createDataTable.mockResolvedValue(null);
|
||||
|
||||
const result = await handlers.handleCreateDataTable({
|
||||
name: 'Ghost Table',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'Data table creation failed: n8n API returned an empty or invalid response',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return error when API returns response without id', async () => {
|
||||
mockApiClient.createDataTable.mockResolvedValue({ name: 'No ID Table' });
|
||||
|
||||
const result = await handlers.handleCreateDataTable({
|
||||
name: 'No ID Table',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'Data table creation failed: n8n API returned an empty or invalid response',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return Unknown error when a non-Error value is thrown', async () => {
|
||||
mockApiClient.createDataTable.mockRejectedValue('string-error');
|
||||
|
||||
const result = await handlers.handleCreateDataTable({
|
||||
name: 'Error Table',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'Unknown error occurred',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -631,6 +631,27 @@ describe('handlers-n8n-manager', () => {
|
||||
expect(result.details.errors[0]).toContain('Webhook');
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass projectId to API when provided', async () => {
|
||||
const testWorkflow = createTestWorkflow();
|
||||
const input = {
|
||||
name: 'Test Workflow',
|
||||
nodes: testWorkflow.nodes,
|
||||
connections: testWorkflow.connections,
|
||||
projectId: 'project-abc-123',
|
||||
};
|
||||
|
||||
mockApiClient.createWorkflow.mockResolvedValue(testWorkflow);
|
||||
|
||||
const result = await handlers.handleCreateWorkflow(input);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(mockApiClient.createWorkflow).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
projectId: 'project-abc-123',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleGetWorkflow', () => {
|
||||
|
||||
@@ -76,6 +76,7 @@ describe('handlers-workflow-diff', () => {
|
||||
listTags: vi.fn().mockResolvedValue({ data: [] }),
|
||||
createTag: vi.fn(),
|
||||
updateWorkflowTags: vi.fn().mockResolvedValue([]),
|
||||
transferWorkflow: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
// Setup mock diff engine
|
||||
@@ -1002,6 +1003,186 @@ describe('handlers-workflow-diff', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Project Transfer via Dedicated API', () => {
|
||||
it('should call transferWorkflow when diffResult has transferToProjectId', async () => {
|
||||
const testWorkflow = createTestWorkflow();
|
||||
const updatedWorkflow = { ...testWorkflow };
|
||||
|
||||
mockApiClient.getWorkflow.mockResolvedValue(testWorkflow);
|
||||
mockDiffEngine.applyDiff.mockResolvedValue({
|
||||
success: true,
|
||||
workflow: updatedWorkflow,
|
||||
operationsApplied: 1,
|
||||
message: 'Success',
|
||||
errors: [],
|
||||
transferToProjectId: 'project-abc-123',
|
||||
});
|
||||
mockApiClient.updateWorkflow.mockResolvedValue(updatedWorkflow);
|
||||
|
||||
const result = await handleUpdatePartialWorkflow({
|
||||
id: 'test-workflow-id',
|
||||
operations: [{ type: 'transferWorkflow', destinationProjectId: 'project-abc-123' }],
|
||||
}, mockRepository);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(mockApiClient.transferWorkflow).toHaveBeenCalledWith('test-workflow-id', 'project-abc-123');
|
||||
expect(result.message).toContain('transferred to project');
|
||||
});
|
||||
|
||||
it('should NOT call transferWorkflow when transferToProjectId is absent', async () => {
|
||||
const testWorkflow = createTestWorkflow();
|
||||
const updatedWorkflow = { ...testWorkflow };
|
||||
|
||||
mockApiClient.getWorkflow.mockResolvedValue(testWorkflow);
|
||||
mockDiffEngine.applyDiff.mockResolvedValue({
|
||||
success: true,
|
||||
workflow: updatedWorkflow,
|
||||
operationsApplied: 1,
|
||||
message: 'Success',
|
||||
errors: [],
|
||||
});
|
||||
mockApiClient.updateWorkflow.mockResolvedValue(updatedWorkflow);
|
||||
|
||||
await handleUpdatePartialWorkflow({
|
||||
id: 'test-workflow-id',
|
||||
operations: [{ type: 'updateName', name: 'New Name' }],
|
||||
}, mockRepository);
|
||||
|
||||
expect(mockApiClient.transferWorkflow).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return success false with saved true when transfer fails', async () => {
|
||||
const testWorkflow = createTestWorkflow();
|
||||
const updatedWorkflow = { ...testWorkflow };
|
||||
|
||||
mockApiClient.getWorkflow.mockResolvedValue(testWorkflow);
|
||||
mockDiffEngine.applyDiff.mockResolvedValue({
|
||||
success: true,
|
||||
workflow: updatedWorkflow,
|
||||
operationsApplied: 1,
|
||||
message: 'Success',
|
||||
errors: [],
|
||||
transferToProjectId: 'project-bad-id',
|
||||
});
|
||||
mockApiClient.updateWorkflow.mockResolvedValue(updatedWorkflow);
|
||||
mockApiClient.transferWorkflow.mockRejectedValue(new Error('Project not found'));
|
||||
|
||||
const result = await handleUpdatePartialWorkflow({
|
||||
id: 'test-workflow-id',
|
||||
operations: [{ type: 'transferWorkflow', destinationProjectId: 'project-bad-id' }],
|
||||
}, mockRepository);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.saved).toBe(true);
|
||||
expect(result.error).toBe('Workflow updated successfully but project transfer failed');
|
||||
expect(result.details).toEqual({
|
||||
workflowUpdated: true,
|
||||
transferError: 'Project not found',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return Unknown error when non-Error value is thrown during transfer', async () => {
|
||||
const testWorkflow = createTestWorkflow();
|
||||
const updatedWorkflow = { ...testWorkflow };
|
||||
|
||||
mockApiClient.getWorkflow.mockResolvedValue(testWorkflow);
|
||||
mockDiffEngine.applyDiff.mockResolvedValue({
|
||||
success: true,
|
||||
workflow: updatedWorkflow,
|
||||
operationsApplied: 1,
|
||||
message: 'Success',
|
||||
errors: [],
|
||||
transferToProjectId: 'project-unknown',
|
||||
});
|
||||
mockApiClient.updateWorkflow.mockResolvedValue(updatedWorkflow);
|
||||
mockApiClient.transferWorkflow.mockRejectedValue('string error');
|
||||
|
||||
const result = await handleUpdatePartialWorkflow({
|
||||
id: 'test-workflow-id',
|
||||
operations: [{ type: 'transferWorkflow', destinationProjectId: 'project-unknown' }],
|
||||
}, mockRepository);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.saved).toBe(true);
|
||||
expect(result.details).toEqual({
|
||||
workflowUpdated: true,
|
||||
transferError: 'Unknown error',
|
||||
});
|
||||
});
|
||||
|
||||
it('should call transferWorkflow BEFORE activateWorkflow', async () => {
|
||||
const testWorkflow = createTestWorkflow({ active: false });
|
||||
const updatedWorkflow = { ...testWorkflow, active: false };
|
||||
const activatedWorkflow = { ...testWorkflow, active: true };
|
||||
|
||||
const callOrder: string[] = [];
|
||||
|
||||
mockApiClient.getWorkflow.mockResolvedValue(testWorkflow);
|
||||
mockDiffEngine.applyDiff.mockResolvedValue({
|
||||
success: true,
|
||||
workflow: updatedWorkflow,
|
||||
operationsApplied: 2,
|
||||
message: 'Success',
|
||||
errors: [],
|
||||
transferToProjectId: 'project-target',
|
||||
shouldActivate: true,
|
||||
});
|
||||
mockApiClient.updateWorkflow.mockResolvedValue(updatedWorkflow);
|
||||
mockApiClient.transferWorkflow.mockImplementation(async () => {
|
||||
callOrder.push('transfer');
|
||||
});
|
||||
mockApiClient.activateWorkflow = vi.fn().mockImplementation(async () => {
|
||||
callOrder.push('activate');
|
||||
return activatedWorkflow;
|
||||
});
|
||||
|
||||
const result = await handleUpdatePartialWorkflow({
|
||||
id: 'test-workflow-id',
|
||||
operations: [
|
||||
{ type: 'transferWorkflow', destinationProjectId: 'project-target' },
|
||||
{ type: 'activateWorkflow' },
|
||||
],
|
||||
}, mockRepository);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(mockApiClient.transferWorkflow).toHaveBeenCalledWith('test-workflow-id', 'project-target');
|
||||
expect(mockApiClient.activateWorkflow).toHaveBeenCalledWith('test-workflow-id');
|
||||
expect(callOrder).toEqual(['transfer', 'activate']);
|
||||
});
|
||||
|
||||
it('should skip activation when transfer fails', async () => {
|
||||
const testWorkflow = createTestWorkflow({ active: false });
|
||||
const updatedWorkflow = { ...testWorkflow, active: false };
|
||||
|
||||
mockApiClient.getWorkflow.mockResolvedValue(testWorkflow);
|
||||
mockDiffEngine.applyDiff.mockResolvedValue({
|
||||
success: true,
|
||||
workflow: updatedWorkflow,
|
||||
operationsApplied: 2,
|
||||
message: 'Success',
|
||||
errors: [],
|
||||
transferToProjectId: 'project-fail',
|
||||
shouldActivate: true,
|
||||
});
|
||||
mockApiClient.updateWorkflow.mockResolvedValue(updatedWorkflow);
|
||||
mockApiClient.transferWorkflow.mockRejectedValue(new Error('Transfer denied'));
|
||||
mockApiClient.activateWorkflow = vi.fn();
|
||||
|
||||
const result = await handleUpdatePartialWorkflow({
|
||||
id: 'test-workflow-id',
|
||||
operations: [
|
||||
{ type: 'transferWorkflow', destinationProjectId: 'project-fail' },
|
||||
{ type: 'activateWorkflow' },
|
||||
],
|
||||
}, mockRepository);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.saved).toBe(true);
|
||||
expect(result.error).toBe('Workflow updated successfully but project transfer failed');
|
||||
expect(mockApiClient.activateWorkflow).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('field name normalization', () => {
|
||||
it('should normalize "name" to "nodeName" for updateNode operations', async () => {
|
||||
const testWorkflow = createTestWorkflow();
|
||||
@@ -1119,4 +1300,4 @@ describe('handlers-workflow-diff', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -542,6 +542,9 @@ describe('Parameter Validation', () => {
|
||||
await expect(server.testExecuteTool('n8n_test_workflow', {}))
|
||||
.rejects.toThrow('Missing required parameters for n8n_test_workflow: workflowId');
|
||||
|
||||
await expect(server.testExecuteTool('n8n_create_data_table', {}))
|
||||
.rejects.toThrow('Missing required parameters for n8n_create_data_table: name');
|
||||
|
||||
for (const tool of n8nToolsWithRequiredParams) {
|
||||
await expect(server.testExecuteTool(tool.name, tool.args))
|
||||
.rejects.toThrow(tool.expected);
|
||||
|
||||
@@ -1250,6 +1250,109 @@ describe('N8nApiClient', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('transferWorkflow', () => {
|
||||
beforeEach(() => {
|
||||
client = new N8nApiClient(defaultConfig);
|
||||
});
|
||||
|
||||
it('should transfer workflow successfully via PUT', async () => {
|
||||
mockAxiosInstance.put.mockResolvedValue({ data: undefined });
|
||||
|
||||
await client.transferWorkflow('123', 'project-456');
|
||||
|
||||
expect(mockAxiosInstance.put).toHaveBeenCalledWith(
|
||||
'/workflows/123/transfer',
|
||||
{ destinationProjectId: 'project-456' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw N8nNotFoundError on 404', async () => {
|
||||
const error = {
|
||||
message: 'Request failed',
|
||||
response: { status: 404, data: { message: 'Workflow not found' } }
|
||||
};
|
||||
await mockAxiosInstance.simulateError('put', error);
|
||||
|
||||
try {
|
||||
await client.transferWorkflow('123', 'project-456');
|
||||
expect.fail('Should have thrown an error');
|
||||
} catch (err) {
|
||||
expect(err).toBeInstanceOf(N8nNotFoundError);
|
||||
expect((err as N8nNotFoundError).message).toContain('not found');
|
||||
expect((err as N8nNotFoundError).statusCode).toBe(404);
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw appropriate error on 403 forbidden', async () => {
|
||||
const error = {
|
||||
message: 'Request failed',
|
||||
response: { status: 403, data: { message: 'Forbidden' } }
|
||||
};
|
||||
await mockAxiosInstance.simulateError('put', error);
|
||||
|
||||
try {
|
||||
await client.transferWorkflow('123', 'project-456');
|
||||
expect.fail('Should have thrown an error');
|
||||
} catch (err) {
|
||||
expect(err).toBeInstanceOf(N8nApiError);
|
||||
expect((err as N8nApiError).statusCode).toBe(403);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('createDataTable', () => {
|
||||
beforeEach(() => {
|
||||
client = new N8nApiClient(defaultConfig);
|
||||
});
|
||||
|
||||
it('should create data table with name and columns', async () => {
|
||||
const params = {
|
||||
name: 'My Table',
|
||||
columns: [
|
||||
{ name: 'email', type: 'string' as const },
|
||||
{ name: 'count', type: 'number' as const },
|
||||
],
|
||||
};
|
||||
const createdTable = { id: 'dt-1', name: 'My Table', columns: [] };
|
||||
|
||||
mockAxiosInstance.post.mockResolvedValue({ data: createdTable });
|
||||
|
||||
const result = await client.createDataTable(params);
|
||||
|
||||
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/data-tables', params);
|
||||
expect(result).toEqual(createdTable);
|
||||
});
|
||||
|
||||
it('should create data table without columns', async () => {
|
||||
const params = { name: 'Empty Table' };
|
||||
const createdTable = { id: 'dt-2', name: 'Empty Table' };
|
||||
|
||||
mockAxiosInstance.post.mockResolvedValue({ data: createdTable });
|
||||
|
||||
const result = await client.createDataTable(params);
|
||||
|
||||
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/data-tables', params);
|
||||
expect(result).toEqual(createdTable);
|
||||
});
|
||||
|
||||
it('should handle 400 error', async () => {
|
||||
const error = {
|
||||
message: 'Request failed',
|
||||
response: { status: 400, data: { message: 'Invalid table name' } },
|
||||
};
|
||||
await mockAxiosInstance.simulateError('post', error);
|
||||
|
||||
try {
|
||||
await client.createDataTable({ name: '' });
|
||||
expect.fail('Should have thrown an error');
|
||||
} catch (err) {
|
||||
expect(err).toBeInstanceOf(N8nValidationError);
|
||||
expect((err as N8nValidationError).message).toBe('Invalid table name');
|
||||
expect((err as N8nValidationError).statusCode).toBe(400);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('interceptors', () => {
|
||||
let requestInterceptor: any;
|
||||
let responseInterceptor: any;
|
||||
@@ -1317,4 +1420,4 @@ describe('N8nApiClient', () => {
|
||||
expect(result.message).toBe('Bad request');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,7 +17,8 @@ import {
|
||||
AddTagOperation,
|
||||
RemoveTagOperation,
|
||||
CleanStaleConnectionsOperation,
|
||||
ReplaceConnectionsOperation
|
||||
ReplaceConnectionsOperation,
|
||||
TransferWorkflowOperation
|
||||
} from '@/types/workflow-diff';
|
||||
import { Workflow } from '@/types/n8n-api';
|
||||
|
||||
@@ -4989,4 +4990,151 @@ describe('WorkflowDiffEngine', () => {
|
||||
expect('nonExistent' in updatedNode).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('transferWorkflow operation', () => {
|
||||
it('should set transferToProjectId in result for valid transferWorkflow', async () => {
|
||||
const operation: TransferWorkflowOperation = {
|
||||
type: 'transferWorkflow',
|
||||
destinationProjectId: 'project-abc-123'
|
||||
};
|
||||
|
||||
const request: WorkflowDiffRequest = {
|
||||
id: 'test-workflow',
|
||||
operations: [operation]
|
||||
};
|
||||
|
||||
const result = await diffEngine.applyDiff(baseWorkflow, request);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.transferToProjectId).toBe('project-abc-123');
|
||||
});
|
||||
|
||||
it('should fail validation when destinationProjectId is empty', async () => {
|
||||
const operation: TransferWorkflowOperation = {
|
||||
type: 'transferWorkflow',
|
||||
destinationProjectId: ''
|
||||
};
|
||||
|
||||
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('destinationProjectId');
|
||||
});
|
||||
|
||||
it('should fail validation when destinationProjectId is undefined', async () => {
|
||||
const operation = {
|
||||
type: 'transferWorkflow',
|
||||
destinationProjectId: undefined
|
||||
} as any as TransferWorkflowOperation;
|
||||
|
||||
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('destinationProjectId');
|
||||
});
|
||||
|
||||
it('should not include transferToProjectId when no transferWorkflow operation is present', async () => {
|
||||
const operation: UpdateNameOperation = {
|
||||
type: 'updateName',
|
||||
name: 'Renamed Workflow'
|
||||
};
|
||||
|
||||
const request: WorkflowDiffRequest = {
|
||||
id: 'test-workflow',
|
||||
operations: [operation]
|
||||
};
|
||||
|
||||
const result = await diffEngine.applyDiff(baseWorkflow, request);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.transferToProjectId).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should combine updateName and transferWorkflow operations', async () => {
|
||||
const operations: WorkflowDiffOperation[] = [
|
||||
{
|
||||
type: 'updateName',
|
||||
name: 'Transferred Workflow'
|
||||
} as UpdateNameOperation,
|
||||
{
|
||||
type: 'transferWorkflow',
|
||||
destinationProjectId: 'project-xyz-789'
|
||||
} as TransferWorkflowOperation
|
||||
];
|
||||
|
||||
const request: WorkflowDiffRequest = {
|
||||
id: 'test-workflow',
|
||||
operations
|
||||
};
|
||||
|
||||
const result = await diffEngine.applyDiff(baseWorkflow, request);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.operationsApplied).toBe(2);
|
||||
expect(result.workflow!.name).toBe('Transferred Workflow');
|
||||
expect(result.transferToProjectId).toBe('project-xyz-789');
|
||||
});
|
||||
|
||||
it('should combine removeTag and transferWorkflow in continueOnError mode', async () => {
|
||||
const operations: WorkflowDiffOperation[] = [
|
||||
{
|
||||
type: 'removeTag',
|
||||
tag: 'non-existent-tag'
|
||||
} as RemoveTagOperation,
|
||||
{
|
||||
type: 'transferWorkflow',
|
||||
destinationProjectId: 'project-target-456'
|
||||
} as TransferWorkflowOperation
|
||||
];
|
||||
|
||||
const request: WorkflowDiffRequest = {
|
||||
id: 'test-workflow',
|
||||
operations,
|
||||
continueOnError: true
|
||||
};
|
||||
|
||||
const result = await diffEngine.applyDiff(baseWorkflow, request);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.transferToProjectId).toBe('project-target-456');
|
||||
});
|
||||
|
||||
it('should fail entire batch in atomic mode when transferWorkflow has empty destinationProjectId alongside updateName', async () => {
|
||||
const operations: WorkflowDiffOperation[] = [
|
||||
{
|
||||
type: 'updateName',
|
||||
name: 'Should Not Apply'
|
||||
} as UpdateNameOperation,
|
||||
{
|
||||
type: 'transferWorkflow',
|
||||
destinationProjectId: ''
|
||||
} as TransferWorkflowOperation
|
||||
];
|
||||
|
||||
const request: WorkflowDiffRequest = {
|
||||
id: 'test-workflow',
|
||||
operations
|
||||
};
|
||||
|
||||
const result = await diffEngine.applyDiff(baseWorkflow, request);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.errors).toBeDefined();
|
||||
expect(result.errors![0].message).toContain('destinationProjectId');
|
||||
// In atomic mode, the workflow should not be returned since the batch failed
|
||||
expect(result.workflow).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user