From a65a9e631b13bbe70ad64727fb1109ae7cd014eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Mon, 3 Feb 2025 14:00:53 +0100 Subject: [PATCH] feat(core): Handle Declarative nodes more like regular nodes (#13007) --- packages/cli/src/__tests__/node-types.test.ts | 53 ++++- packages/cli/src/node-types.ts | 16 ++ .../services/credentials-tester.service.ts | 30 +-- .../dynamic-node-parameters.service.ts | 32 +-- .../__tests__/routing-node.test.ts | 38 ++-- .../__tests__/shared-tests.ts | 1 - .../base-execute-context.ts | 10 +- .../credentials-test-context.ts | 26 +++ .../node-execution-context/index.ts | 2 + .../node-execution-context.ts | 16 +- .../core/src/execution-engine/routing-node.ts | 196 ++++++++---------- .../src/execution-engine/workflow-execute.ts | 23 +- packages/core/src/node-execute-functions.ts | 13 -- 13 files changed, 245 insertions(+), 211 deletions(-) create mode 100644 packages/core/src/execution-engine/node-execution-context/credentials-test-context.ts diff --git a/packages/cli/src/__tests__/node-types.test.ts b/packages/cli/src/__tests__/node-types.test.ts index 78d0c5e18a..4868364d03 100644 --- a/packages/cli/src/__tests__/node-types.test.ts +++ b/packages/cli/src/__tests__/node-types.test.ts @@ -1,5 +1,5 @@ import { mock } from 'jest-mock-extended'; -import { UnrecognizedNodeTypeError } from 'n8n-core'; +import { RoutingNode, UnrecognizedNodeTypeError } from 'n8n-core'; import type { LoadedClass, INodeType, @@ -11,7 +11,9 @@ import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials'; import { NodeTypes } from '@/node-types'; describe('NodeTypes', () => { - const loadNodesAndCredentials = mock(); + const loadNodesAndCredentials = mock({ + convertNodeToAiTool: LoadNodesAndCredentials.prototype.convertNodeToAiTool, + }); const nodeTypes: NodeTypes = new NodeTypes(loadNodesAndCredentials); @@ -54,12 +56,29 @@ describe('NodeTypes', () => { }), }, }; + const declarativeNode: LoadedClass = { + sourcePath: '', + type: { + description: mock({ + name: 'n8n-nodes-base.declarativeNode', + displayName: 'Declarative Node', + usableAsTool: true, + properties: [], + }), + execute: undefined, + poll: undefined, + trigger: undefined, + webhook: undefined, + methods: undefined, + }, + }; loadNodesAndCredentials.getNode.mockImplementation((fullNodeType) => { const [packageName, nodeType] = fullNodeType.split('.'); if (nodeType === 'nonVersioned') return nonVersionedNode; if (nodeType === 'versioned') return versionedNode; if (nodeType === 'testNode') return toolSupportingNode; + if (nodeType === 'declarativeNode') return declarativeNode; throw new UnrecognizedNodeTypeError(packageName, nodeType); }); @@ -104,17 +123,39 @@ describe('NodeTypes', () => { }); it('should return the tool node-type when requested as tool', () => { - // @ts-expect-error don't mock convertNodeToAiTool for now - loadNodesAndCredentials.convertNodeToAiTool = - LoadNodesAndCredentials.prototype.convertNodeToAiTool; const result = nodeTypes.getByNameAndVersion('n8n-nodes-base.testNodeTool'); - expect(result).not.toEqual(toolSupportingNode); + expect(result).not.toEqual(toolSupportingNode.type); expect(result.description.name).toEqual('n8n-nodes-base.testNodeTool'); expect(result.description.displayName).toEqual('TestNode Tool'); expect(result.description.codex?.categories).toContain('AI'); expect(result.description.inputs).toEqual([]); expect(result.description.outputs).toEqual(['ai_tool']); }); + + it('should return a declarative node-type with an `.execute` method', () => { + const result = nodeTypes.getByNameAndVersion('n8n-nodes-base.declarativeNode'); + expect(result).toBe(declarativeNode.type); + expect(result.execute).toBeDefined(); + + const runNodeSpy = jest.spyOn(RoutingNode.prototype, 'runNode').mockResolvedValue([]); + result.execute!.call(mock()); + expect(runNodeSpy).toHaveBeenCalled(); + }); + + it('should return a declarative node-type as a tool with an `.execute` method', () => { + const result = nodeTypes.getByNameAndVersion('n8n-nodes-base.declarativeNodeTool'); + expect(result).not.toEqual(declarativeNode.type); + expect(result.description.name).toEqual('n8n-nodes-base.declarativeNodeTool'); + expect(result.description.displayName).toEqual('Declarative Node Tool'); + expect(result.description.codex?.categories).toContain('AI'); + expect(result.description.inputs).toEqual([]); + expect(result.description.outputs).toEqual(['ai_tool']); + expect(result.execute).toBeDefined(); + + const runNodeSpy = jest.spyOn(RoutingNode.prototype, 'runNode').mockResolvedValue([]); + result.execute!.call(mock()); + expect(runNodeSpy).toHaveBeenCalled(); + }); }); describe('getWithSourcePath', () => { diff --git a/packages/cli/src/node-types.ts b/packages/cli/src/node-types.ts index 77d872dc26..80ae9850f1 100644 --- a/packages/cli/src/node-types.ts +++ b/packages/cli/src/node-types.ts @@ -2,6 +2,8 @@ import { Service } from '@n8n/di'; import type { NeededNodeType } from '@n8n/task-runner'; import type { Dirent } from 'fs'; import { readdir } from 'fs/promises'; +import { RoutingNode } from 'n8n-core'; +import type { ExecuteContext } from 'n8n-core'; import type { INodeType, INodeTypeDescription, INodeTypes, IVersionedNodeType } from 'n8n-workflow'; import { ApplicationError, NodeHelpers } from 'n8n-workflow'; import { join, dirname } from 'path'; @@ -39,6 +41,20 @@ export class NodeTypes implements INodeTypes { const node = this.loadNodesAndCredentials.getNode(nodeType); const versionedNodeType = NodeHelpers.getVersionedNodeType(node.type, version); + if ( + !versionedNodeType.execute && + !versionedNodeType.poll && + !versionedNodeType.trigger && + !versionedNodeType.webhook && + !versionedNodeType.methods + ) { + versionedNodeType.execute = async function (this: ExecuteContext) { + const routingNode = new RoutingNode(this, versionedNodeType); + const data = await routingNode.runNode(); + return data ?? []; + }; + } + if (!toolRequested) return versionedNodeType; if (!versionedNodeType.description.usableAsTool) diff --git a/packages/cli/src/services/credentials-tester.service.ts b/packages/cli/src/services/credentials-tester.service.ts index 1954923481..a45443a3bc 100644 --- a/packages/cli/src/services/credentials-tester.service.ts +++ b/packages/cli/src/services/credentials-tester.service.ts @@ -6,9 +6,10 @@ import { Service } from '@n8n/di'; import get from 'lodash/get'; import { + CredentialTestContext, ErrorReporter, + ExecuteContext, Logger, - NodeExecuteFunctions, RoutingNode, isObjectLiteral, } from 'n8n-core'; @@ -29,6 +30,7 @@ import type { INodeTypes, ICredentialTestFunctions, IDataObject, + IExecuteData, } from 'n8n-workflow'; import { VersionedNodeType, NodeHelpers, Workflow, ApplicationError } from 'n8n-workflow'; @@ -205,9 +207,8 @@ export class CredentialsTester { if (typeof credentialTestFunction === 'function') { // The credentials get tested via a function that is defined on the node - const credentialTestFunctions = NodeExecuteFunctions.getCredentialTestFunctions(); - - return credentialTestFunction.call(credentialTestFunctions, credentialsDecrypted); + const context = new CredentialTestContext(); + return credentialTestFunction.call(context, credentialsDecrypted); } // Credentials get tested via request instructions @@ -293,25 +294,24 @@ export class CredentialsTester { const additionalData = await WorkflowExecuteAdditionalData.getBase(user.id, node.parameters); - const routingNode = new RoutingNode( + const executeData: IExecuteData = { node, data: {}, source: null }; + const executeFunctions = new ExecuteContext( workflow, node, - connectionInputData, - runExecutionData ?? null, additionalData, mode, + runExecutionData, + runIndex, + connectionInputData, + inputData, + executeData, + [], ); + const routingNode = new RoutingNode(executeFunctions, nodeTypeCopy, credentialsDecrypted); let response: INodeExecutionData[][] | null | undefined; - try { - response = await routingNode.runNode( - inputData, - runIndex, - nodeTypeCopy, - { node, data: {}, source: null }, - credentialsDecrypted, - ); + response = await routingNode.runNode(); } catch (error) { this.errorReporter.error(error); // Do not fail any requests to allow custom error messages and diff --git a/packages/cli/src/services/dynamic-node-parameters.service.ts b/packages/cli/src/services/dynamic-node-parameters.service.ts index 0cde1c8489..1cbaf42363 100644 --- a/packages/cli/src/services/dynamic-node-parameters.service.ts +++ b/packages/cli/src/services/dynamic-node-parameters.service.ts @@ -1,5 +1,5 @@ import { Service } from '@n8n/di'; -import { LoadOptionsContext, RoutingNode, LocalLoadOptionsContext } from 'n8n-core'; +import { LoadOptionsContext, RoutingNode, LocalLoadOptionsContext, ExecuteContext } from 'n8n-core'; import type { ILoadOptions, ILoadOptionsFunctions, @@ -19,6 +19,7 @@ import type { NodeParameterValueType, IDataObject, ILocalLoadOptionsFunctions, + IExecuteData, } from 'n8n-workflow'; import { Workflow, ApplicationError } from 'n8n-workflow'; @@ -103,17 +104,8 @@ export class DynamicNodeParametersService { const workflow = this.getWorkflow(nodeTypeAndVersion, currentNodeParameters, credentials); const node = workflow.nodes['Temp-Node']; - const routingNode = new RoutingNode( - workflow, - node, - connectionInputData, - runExecutionData ?? null, - additionalData, - mode, - ); - // Create copy of node-type with the single property we want to get the data off - const tempNode: INodeType = { + const tempNodeType: INodeType = { ...nodeType, ...{ description: { @@ -135,11 +127,25 @@ export class DynamicNodeParametersService { main: [[{ json: {} }]], }; - const optionsData = await routingNode.runNode(inputData, runIndex, tempNode, { + const executeData: IExecuteData = { node, source: null, data: {}, - }); + }; + const executeFunctions = new ExecuteContext( + workflow, + node, + additionalData, + mode, + runExecutionData, + runIndex, + connectionInputData, + inputData, + executeData, + [], + ); + const routingNode = new RoutingNode(executeFunctions, tempNodeType); + const optionsData = await routingNode.runNode(); if (optionsData?.length === 0) { return []; diff --git a/packages/core/src/execution-engine/__tests__/routing-node.test.ts b/packages/core/src/execution-engine/__tests__/routing-node.test.ts index 5203cff3da..a35ad24a8a 100644 --- a/packages/core/src/execution-engine/__tests__/routing-node.test.ts +++ b/packages/core/src/execution-engine/__tests__/routing-node.test.ts @@ -744,14 +744,17 @@ describe('RoutingNode', () => { nodeTypes, }); - const routingNode = new RoutingNode( + const executeFunctions = mock(); + Object.assign(executeFunctions, { + runIndex, + additionalData, workflow, node, - connectionInputData, - runExecutionData ?? null, - additionalData, mode, - ); + connectionInputData, + runExecutionData, + }); + const routingNode = new RoutingNode(executeFunctions, nodeType); const executeSingleFunctions = getExecuteSingleFunctions( workflow, @@ -1947,15 +1950,6 @@ describe('RoutingNode', () => { nodeTypes, }); - const routingNode = new RoutingNode( - workflow, - node, - connectionInputData, - runExecutionData ?? null, - additionalData, - mode, - ); - const executeData = { data: {}, node, @@ -1963,6 +1957,18 @@ describe('RoutingNode', () => { } as IExecuteData; const executeFunctions = mock(); + Object.assign(executeFunctions, { + executeData, + inputData, + runIndex, + additionalData, + workflow, + node, + mode, + connectionInputData, + runExecutionData, + }); + const executeSingleFunctions = getExecuteSingleFunctions( workflow, runExecutionData, @@ -1971,7 +1977,6 @@ describe('RoutingNode', () => { itemIndex, ); - jest.spyOn(executionContexts, 'ExecuteContext').mockReturnValue(executeFunctions); jest .spyOn(executionContexts, 'ExecuteSingleContext') .mockReturnValue(executeSingleFunctions); @@ -2004,7 +2009,8 @@ describe('RoutingNode', () => { ? testData.input.node.parameters[parameterName] : (getNodeParameter(parameterName) ?? {}); - const result = await routingNode.runNode(inputData, runIndex, nodeType, executeData); + const routingNode = new RoutingNode(executeFunctions, nodeType); + const result = await routingNode.runNode(); if (testData.input.specialTestOptions?.sleepCalls) { expect(spy.mock.calls).toEqual(testData.input.specialTestOptions?.sleepCalls); diff --git a/packages/core/src/execution-engine/node-execution-context/__tests__/shared-tests.ts b/packages/core/src/execution-engine/node-execution-context/__tests__/shared-tests.ts index 601b49fe4c..de5edaad96 100644 --- a/packages/core/src/execution-engine/node-execution-context/__tests__/shared-tests.ts +++ b/packages/core/src/execution-engine/node-execution-context/__tests__/shared-tests.ts @@ -40,7 +40,6 @@ export const describeCommonTests = ( executeData: IExecuteData; }, ) => { - // @ts-expect-error `additionalData` is private const additionalData = context.additionalData as MockProxy; describe('getExecutionCancelSignal', () => { diff --git a/packages/core/src/execution-engine/node-execution-context/base-execute-context.ts b/packages/core/src/execution-engine/node-execution-context/base-execute-context.ts index 1c4bba34e3..f2d0aa8653 100644 --- a/packages/core/src/execution-engine/node-execution-context/base-execute-context.ts +++ b/packages/core/src/execution-engine/node-execution-context/base-execute-context.ts @@ -41,12 +41,12 @@ export class BaseExecuteContext extends NodeExecutionContext { node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, - protected readonly runExecutionData: IRunExecutionData, + readonly runExecutionData: IRunExecutionData, runIndex: number, - protected readonly connectionInputData: INodeExecutionData[], - protected readonly inputData: ITaskDataConnections, - protected readonly executeData: IExecuteData, - protected readonly abortSignal?: AbortSignal, + readonly connectionInputData: INodeExecutionData[], + readonly inputData: ITaskDataConnections, + readonly executeData: IExecuteData, + readonly abortSignal?: AbortSignal, ) { super(workflow, node, additionalData, mode, runExecutionData, runIndex); } diff --git a/packages/core/src/execution-engine/node-execution-context/credentials-test-context.ts b/packages/core/src/execution-engine/node-execution-context/credentials-test-context.ts new file mode 100644 index 0000000000..9aa99683da --- /dev/null +++ b/packages/core/src/execution-engine/node-execution-context/credentials-test-context.ts @@ -0,0 +1,26 @@ +import { Container } from '@n8n/di'; +import type { ICredentialTestFunctions } from 'n8n-workflow'; + +import { Memoized } from '@/decorators'; +import { Logger } from '@/logging'; +// eslint-disable-next-line import/no-cycle +import { getSSHTunnelFunctions, proxyRequestToAxios } from '@/node-execute-functions'; + +export class CredentialTestContext implements ICredentialTestFunctions { + readonly helpers: ICredentialTestFunctions['helpers']; + + constructor() { + this.helpers = { + ...getSSHTunnelFunctions(), + request: async (uriOrObject: string | object, options?: object) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return await proxyRequestToAxios(undefined, undefined, undefined, uriOrObject, options); + }, + }; + } + + @Memoized + get logger() { + return Container.get(Logger); + } +} diff --git a/packages/core/src/execution-engine/node-execution-context/index.ts b/packages/core/src/execution-engine/node-execution-context/index.ts index 4c96322211..800e539346 100644 --- a/packages/core/src/execution-engine/node-execution-context/index.ts +++ b/packages/core/src/execution-engine/node-execution-context/index.ts @@ -1,4 +1,6 @@ // eslint-disable-next-line import/no-cycle +export { CredentialTestContext } from './credentials-test-context'; +// eslint-disable-next-line import/no-cycle export { ExecuteContext } from './execute-context'; export { ExecuteSingleContext } from './execute-single-context'; export { HookContext } from './hook-context'; diff --git a/packages/core/src/execution-engine/node-execution-context/node-execution-context.ts b/packages/core/src/execution-engine/node-execution-context/node-execution-context.ts index 79225554d6..662e24d6b6 100644 --- a/packages/core/src/execution-engine/node-execution-context/node-execution-context.ts +++ b/packages/core/src/execution-engine/node-execution-context/node-execution-context.ts @@ -43,14 +43,14 @@ export abstract class NodeExecutionContext implements Omit { - const items = inputData.main[0] as INodeExecutionData[]; - const returnData: INodeExecutionData[] = []; - - const closeFunctions: CloseFunction[] = []; - const executeFunctions = new ExecuteContext( - this.workflow, - this.node, - this.additionalData, - this.mode, - this.runExecutionData, - runIndex, - this.connectionInputData, - inputData, + async runNode(): Promise { + const { context, nodeType, credentialsDecrypted } = this; + const { + additionalData, executeData, - closeFunctions, - abortSignal, - ); + inputData, + node, + workflow, + mode, + runIndex, + connectionInputData, + runExecutionData, + } = context; + const abortSignal = context.getExecutionCancelSignal(); + + const items = (inputData[NodeConnectionType.Main] ?? + inputData[NodeConnectionType.AiTool])[0] as INodeExecutionData[]; + const returnData: INodeExecutionData[] = []; let credentialDescription: INodeCredentialDescription | undefined; @@ -104,17 +74,14 @@ export class RoutingNode { if (nodeType.description.credentials.length === 1) { credentialDescription = nodeType.description.credentials[0]; } else { - const authenticationMethod = executeFunctions.getNodeParameter( - 'authentication', - 0, - ) as string; + const authenticationMethod = context.getNodeParameter('authentication', 0) as string; credentialDescription = nodeType.description.credentials.find((x) => x.displayOptions?.show?.authentication?.includes(authenticationMethod), ); if (!credentialDescription) { throw new NodeOperationError( - this.node, - `Node type "${this.node.type}" does not have any credentials of type "${authenticationMethod}" defined`, + node, + `Node type "${node.type}" does not have any credentials of type "${authenticationMethod}" defined`, { level: 'warning' }, ); } @@ -127,7 +94,7 @@ export class RoutingNode { } else if (credentialDescription) { try { credentials = - (await executeFunctions.getCredentials( + (await context.getCredentials( credentialDescription.name, 0, )) || {}; @@ -142,7 +109,7 @@ export class RoutingNode { } } - const { batching } = executeFunctions.getNodeParameter('requestOptions', 0, {}) as { + const { batching } = context.getNodeParameter('requestOptions', 0, {}) as { batching: { batch: { batchSize: number; batchInterval: number } }; }; @@ -163,13 +130,13 @@ export class RoutingNode { } const thisArgs = new ExecuteSingleContext( - this.workflow, - this.node, - this.additionalData, - this.mode, - this.runExecutionData, + workflow, + node, + additionalData, + mode, + runExecutionData, runIndex, - this.connectionInputData, + connectionInputData, inputData, itemIndex, executeData, @@ -214,7 +181,7 @@ export class RoutingNode { itemIndex, runIndex, executeData, - { $credentials: credentials, $version: this.node.typeVersion }, + { $credentials: credentials, $version: node.typeVersion }, false, ) as string; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -223,14 +190,14 @@ export class RoutingNode { } for (const property of nodeType.description.properties) { - let value = get(this.node.parameters, property.name, []) as string | NodeParameterValue; + let value = get(node.parameters, property.name, []) as string | NodeParameterValue; // If the value is an expression resolve it value = this.getParameterValue( value, itemIndex, runIndex, executeData, - { $credentials: credentials, $version: this.node.typeVersion }, + { $credentials: credentials, $version: node.typeVersion }, false, ) as string | NodeParameterValue; @@ -240,7 +207,7 @@ export class RoutingNode { itemIndex, runIndex, '', - { $credentials: credentials, $value: value, $version: this.node.typeVersion }, + { $credentials: credentials, $value: value, $version: node.typeVersion }, ); this.mergeOptions(itemContext[itemIndex].requestData, tempOptions); @@ -255,7 +222,7 @@ export class RoutingNode { !(property in proxyParsed) || proxyParsed[property as keyof typeof proxyParsed] === null ) { - throw new NodeOperationError(this.node, 'The proxy is not value', { + throw new NodeOperationError(node, 'The proxy is not value', { runIndex, itemIndex, description: `The proxy URL does not contain a valid value for "${property}"`, @@ -291,7 +258,7 @@ export class RoutingNode { } requestPromises.push( - this.makeRoutingRequest( + this.makeRequest( itemContext[itemIndex].requestData, itemContext[itemIndex].thisArgs, itemIndex, @@ -327,7 +294,7 @@ export class RoutingNode { throw error; } - throw new NodeApiError(this.node, error as JsonObject, { + throw new NodeApiError(node, error as JsonObject, { runIndex, itemIndex, message: error?.message, @@ -347,7 +314,7 @@ export class RoutingNode { return [returnData]; } - mergeOptions( + private mergeOptions( destinationOptions: DeclarativeRestApiSettings.ResultOptions, sourceOptions?: DeclarativeRestApiSettings.ResultOptions, ): void { @@ -368,7 +335,7 @@ export class RoutingNode { } } - async runPostReceiveAction( + private async runPostReceiveAction( executeSingleFunctions: IExecuteSingleFunctions, action: PostReceiveAction, inputData: INodeExecutionData[], @@ -380,6 +347,9 @@ export class RoutingNode { if (typeof action === 'function') { return await action.call(executeSingleFunctions, inputData, responseData); } + + const { node } = this.context; + if (action.type === 'rootProperty') { try { return inputData.flatMap((item) => { @@ -395,13 +365,14 @@ export class RoutingNode { }); }); } catch (error) { - throw new NodeOperationError(this.node, error as Error, { + throw new NodeOperationError(node, error as Error, { runIndex, itemIndex, description: `The rootProperty "${action.properties.property}" could not be found on item.`, }); } } + if (action.type === 'filter') { const passValue = action.properties.pass; @@ -416,7 +387,7 @@ export class RoutingNode { $response: responseData, $responseItem: item.json, $value: parameterValue, - $version: this.node.typeVersion, + $version: node.typeVersion, }, false, ) as boolean; @@ -424,17 +395,19 @@ export class RoutingNode { return inputData; } + if (action.type === 'limit') { const maxResults = this.getParameterValue( action.properties.maxResults, itemIndex, runIndex, executeSingleFunctions.getExecuteData(), - { $response: responseData, $value: parameterValue, $version: this.node.typeVersion }, + { $response: responseData, $value: parameterValue, $version: node.typeVersion }, false, ) as string; return inputData.slice(0, parseInt(maxResults, 10)); } + if (action.type === 'set') { const { value } = action.properties; // If the value is an expression resolve it @@ -445,12 +418,13 @@ export class RoutingNode { itemIndex, runIndex, executeSingleFunctions.getExecuteData(), - { $response: responseData, $value: parameterValue, $version: this.node.typeVersion }, + { $response: responseData, $value: parameterValue, $version: node.typeVersion }, false, ) as IDataObject, }, ]; } + if (action.type === 'sort') { // Sort the returned options const sortKey = action.properties.key; @@ -468,6 +442,7 @@ export class RoutingNode { return inputData; } + if (action.type === 'setKeyValue') { const returnData: INodeExecutionData[] = []; @@ -491,7 +466,7 @@ export class RoutingNode { $response: responseData, $responseItem: item.json, $value: parameterValue, - $version: this.node.typeVersion, + $version: node.typeVersion, }, false, ) as string; @@ -503,6 +478,7 @@ export class RoutingNode { return returnData; } + if (action.type === 'binaryData') { const body = (responseData.body = Buffer.from(responseData.body as string)); let { destinationProperty } = action.properties; @@ -512,7 +488,7 @@ export class RoutingNode { itemIndex, runIndex, executeSingleFunctions.getExecuteData(), - { $response: responseData, $value: parameterValue, $version: this.node.typeVersion }, + { $response: responseData, $value: parameterValue, $version: node.typeVersion }, false, ) as string; @@ -535,7 +511,7 @@ export class RoutingNode { return []; } - async postProcessResponseData( + private async postProcessResponseData( executeSingleFunctions: IExecuteSingleFunctions, responseData: IN8nHttpFullResponse, requestData: DeclarativeRestApiSettings.ResultOptions, @@ -580,7 +556,7 @@ export class RoutingNode { return returnData; } - async rawRoutingRequest( + private async rawRoutingRequest( executeSingleFunctions: IExecuteSingleFunctions, requestData: DeclarativeRestApiSettings.ResultOptions, credentialType?: string, @@ -604,7 +580,7 @@ export class RoutingNode { return responseData; } - async makeRoutingRequest( + private async makeRequest( requestData: DeclarativeRestApiSettings.ResultOptions, executeSingleFunctions: IExecuteSingleFunctions, itemIndex: number, @@ -639,6 +615,7 @@ export class RoutingNode { ); }; + const { node } = this.context; const executePaginationFunctions = Object.create(executeSingleFunctions, { makeRoutingRequest: { value: makeRoutingRequest }, }) as IExecutePaginationFunctions; @@ -669,7 +646,7 @@ export class RoutingNode { const additionalKeys = { $request: requestData.options, $response: {} as IN8nHttpFullResponse, - $version: this.node.typeVersion, + $version: node.typeVersion, }; do { @@ -763,7 +740,7 @@ export class RoutingNode { | undefined; if (tempResponseValue === undefined) { throw new NodeOperationError( - this.node, + node, `The rootProperty "${properties.rootProperty}" could not be found on item.`, { runIndex, itemIndex }, ); @@ -801,7 +778,7 @@ export class RoutingNode { return responseData; } - getParameterValue( + private getParameterValue( parameterValue: NodeParameterValueType, itemIndex: number, runIndex: number, @@ -813,14 +790,15 @@ export class RoutingNode { typeof parameterValue === 'object' || (typeof parameterValue === 'string' && parameterValue.charAt(0) === '=') ) { - return this.workflow.expression.getParameterValue( + const { node, workflow, mode, connectionInputData, runExecutionData } = this.context; + return workflow.expression.getParameterValue( parameterValue, - this.runExecutionData ?? null, + runExecutionData ?? null, runIndex, itemIndex, - this.node.name, - this.connectionInputData, - this.mode, + node.name, + connectionInputData, + mode, additionalKeys ?? {}, executeData, returnObjectAsString, @@ -851,14 +829,8 @@ export class RoutingNode { }; let basePath = path ? `${path}.` : ''; - if ( - !NodeHelpers.displayParameter( - this.node.parameters, - nodeProperties, - this.node, - this.node.parameters, - ) - ) { + const { node } = this.context; + if (!NodeHelpers.displayParameter(node.parameters, nodeProperties, node, node.parameters)) { return undefined; } if (nodeProperties.routing) { @@ -1032,7 +1004,7 @@ export class RoutingNode { let value; if (nodeProperties.type === 'options') { const optionValue = NodeHelpers.getParameterValueByPath( - this.node.parameters, + node.parameters, nodeProperties.name, basePath.slice(0, -1), ); @@ -1050,14 +1022,14 @@ export class RoutingNode { itemIndex, runIndex, `${basePath}${nodeProperties.name}`, - { $value: optionValue, $version: this.node.typeVersion }, + { $value: optionValue, $version: node.typeVersion }, ); this.mergeOptions(returnData, tempOptions); } } else if (nodeProperties.type === 'collection') { value = NodeHelpers.getParameterValueByPath( - this.node.parameters, + node.parameters, nodeProperties.name, basePath.slice(0, -1), ); @@ -1074,7 +1046,7 @@ export class RoutingNode { itemIndex, runIndex, `${basePath}${nodeProperties.name}`, - { $version: this.node.typeVersion }, + { $version: node.typeVersion }, ); this.mergeOptions(returnData, tempOptions); @@ -1085,7 +1057,7 @@ export class RoutingNode { for (const propertyOptions of nodeProperties.options as INodePropertyCollection[]) { // Check if the option got set and if not skip it value = NodeHelpers.getParameterValueByPath( - this.node.parameters, + node.parameters, propertyOptions.name, basePath.slice(0, -1), ); diff --git a/packages/core/src/execution-engine/workflow-execute.ts b/packages/core/src/execution-engine/workflow-execute.ts index 4f9361f6b5..9b20a7bd2a 100644 --- a/packages/core/src/execution-engine/workflow-execute.ts +++ b/packages/core/src/execution-engine/workflow-execute.ts @@ -69,7 +69,6 @@ import { handleCycles, filterDisabledNodes, } from './partial-execution-utils'; -import { RoutingNode } from './routing-node'; import { TriggersAndPollers } from './triggers-and-pollers'; export class WorkflowExecute { @@ -1171,27 +1170,7 @@ export class WorkflowExecute { // For webhook nodes always simply pass the data through return { data: inputData.main as INodeExecutionData[][] }; } else { - // For nodes which have routing information on properties - - const routingNode = new RoutingNode( - workflow, - node, - connectionInputData, - runExecutionData ?? null, - additionalData, - mode, - ); - - return { - data: await routingNode.runNode( - inputData, - runIndex, - nodeType, - executionData, - undefined, - abortSignal, - ), - }; + throw new ApplicationError('Declarative nodes should been handled as regular nodes'); } } diff --git a/packages/core/src/node-execute-functions.ts b/packages/core/src/node-execute-functions.ts index 0ecac85f9d..4dbc2540a1 100644 --- a/packages/core/src/node-execute-functions.ts +++ b/packages/core/src/node-execute-functions.ts @@ -38,7 +38,6 @@ import type { IAllExecuteFunctions, IBinaryData, ICredentialDataDecryptedObject, - ICredentialTestFunctions, IDataObject, IExecuteData, IExecuteFunctions, @@ -2555,15 +2554,3 @@ export function getExecuteTriggerFunctions( ): ITriggerFunctions { return new TriggerContext(workflow, node, additionalData, mode, activation); } - -export function getCredentialTestFunctions(): ICredentialTestFunctions { - return { - logger: Container.get(Logger), - helpers: { - ...getSSHTunnelFunctions(), - request: async (uriOrObject: string | object, options?: object) => { - return await proxyRequestToAxios(undefined, undefined, undefined, uriOrObject, options); - }, - }, - }; -}