| | import { Service } from '@n8n/di'; |
| | import { LoadOptionsContext, RoutingNode, LocalLoadOptionsContext, ExecuteContext } from 'n8n-core'; |
| | import type { |
| | ILoadOptions, |
| | ILoadOptionsFunctions, |
| | INode, |
| | INodeExecutionData, |
| | INodeListSearchResult, |
| | INodeProperties, |
| | INodePropertyOptions, |
| | INodeType, |
| | IRunExecutionData, |
| | ITaskDataConnections, |
| | IWorkflowExecuteAdditionalData, |
| | ResourceMapperFields, |
| | INodeCredentials, |
| | INodeParameters, |
| | INodeTypeNameVersion, |
| | NodeParameterValueType, |
| | IDataObject, |
| | ILocalLoadOptionsFunctions, |
| | IExecuteData, |
| | } from 'n8n-workflow'; |
| | import { Workflow, UnexpectedError } from 'n8n-workflow'; |
| |
|
| | import { NodeTypes } from '@/node-types'; |
| |
|
| | import { WorkflowLoaderService } from './workflow-loader.service'; |
| |
|
| | type LocalResourceMappingMethod = ( |
| | this: ILocalLoadOptionsFunctions, |
| | ) => Promise<ResourceMapperFields>; |
| | type ListSearchMethod = ( |
| | this: ILoadOptionsFunctions, |
| | filter?: string, |
| | paginationToken?: string, |
| | ) => Promise<INodeListSearchResult>; |
| | type LoadOptionsMethod = (this: ILoadOptionsFunctions) => Promise<INodePropertyOptions[]>; |
| | type ActionHandlerMethod = ( |
| | this: ILoadOptionsFunctions, |
| | payload?: string, |
| | ) => Promise<NodeParameterValueType>; |
| | type ResourceMappingMethod = (this: ILoadOptionsFunctions) => Promise<ResourceMapperFields>; |
| |
|
| | type NodeMethod = |
| | | LocalResourceMappingMethod |
| | | ListSearchMethod |
| | | LoadOptionsMethod |
| | | ActionHandlerMethod |
| | | ResourceMappingMethod; |
| |
|
| | @Service() |
| | export class DynamicNodeParametersService { |
| | constructor( |
| | private nodeTypes: NodeTypes, |
| | private workflowLoaderService: WorkflowLoaderService, |
| | ) {} |
| |
|
| | |
| | async getOptionsViaMethodName( |
| | methodName: string, |
| | path: string, |
| | additionalData: IWorkflowExecuteAdditionalData, |
| | nodeTypeAndVersion: INodeTypeNameVersion, |
| | currentNodeParameters: INodeParameters, |
| | credentials?: INodeCredentials, |
| | ): Promise<INodePropertyOptions[]> { |
| | const nodeType = this.getNodeType(nodeTypeAndVersion); |
| | const method = this.getMethod('loadOptions', methodName, nodeType); |
| | const workflow = this.getWorkflow(nodeTypeAndVersion, currentNodeParameters, credentials); |
| | const thisArgs = this.getThisArg(path, additionalData, workflow); |
| | |
| | |
| | |
| | return method.call(thisArgs); |
| | } |
| |
|
| | |
| | async getOptionsViaLoadOptions( |
| | loadOptions: ILoadOptions, |
| | additionalData: IWorkflowExecuteAdditionalData, |
| | nodeTypeAndVersion: INodeTypeNameVersion, |
| | currentNodeParameters: INodeParameters, |
| | credentials?: INodeCredentials, |
| | ): Promise<INodePropertyOptions[]> { |
| | const nodeType = this.getNodeType(nodeTypeAndVersion); |
| | if (!nodeType.description.requestDefaults?.baseURL) { |
| | |
| | |
| | |
| | |
| | |
| | |
| | throw new UnexpectedError( |
| | 'Node type does not exist or does not have "requestDefaults.baseURL" defined!', |
| | { tags: { nodeType: nodeType.description.name } }, |
| | ); |
| | } |
| |
|
| | const mode = 'internal'; |
| | const runIndex = 0; |
| | const connectionInputData: INodeExecutionData[] = []; |
| | const runExecutionData: IRunExecutionData = { resultData: { runData: {} } }; |
| | const workflow = this.getWorkflow(nodeTypeAndVersion, currentNodeParameters, credentials); |
| | const node = workflow.nodes['Temp-Node']; |
| |
|
| | |
| | const tempNodeType: INodeType = { |
| | ...nodeType, |
| | ...{ |
| | description: { |
| | ...nodeType.description, |
| | properties: [ |
| | { |
| | displayName: '', |
| | type: 'string', |
| | name: '', |
| | default: '', |
| | routing: loadOptions.routing, |
| | } as INodeProperties, |
| | ], |
| | }, |
| | }, |
| | }; |
| |
|
| | const inputData: ITaskDataConnections = { |
| | main: [[{ json: {} }]], |
| | }; |
| |
|
| | 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 []; |
| | } |
| |
|
| | if (!Array.isArray(optionsData)) { |
| | throw new UnexpectedError('The returned data is not an array'); |
| | } |
| |
|
| | return optionsData[0].map((item) => item.json) as unknown as INodePropertyOptions[]; |
| | } |
| |
|
| | async getResourceLocatorResults( |
| | methodName: string, |
| | path: string, |
| | additionalData: IWorkflowExecuteAdditionalData, |
| | nodeTypeAndVersion: INodeTypeNameVersion, |
| | currentNodeParameters: INodeParameters, |
| | credentials?: INodeCredentials, |
| | filter?: string, |
| | paginationToken?: string, |
| | ): Promise<INodeListSearchResult> { |
| | const nodeType = this.getNodeType(nodeTypeAndVersion); |
| | const method = this.getMethod('listSearch', methodName, nodeType); |
| | const workflow = this.getWorkflow(nodeTypeAndVersion, currentNodeParameters, credentials); |
| | const thisArgs = this.getThisArg(path, additionalData, workflow); |
| | |
| | return method.call(thisArgs, filter, paginationToken); |
| | } |
| |
|
| | |
| | async getResourceMappingFields( |
| | methodName: string, |
| | path: string, |
| | additionalData: IWorkflowExecuteAdditionalData, |
| | nodeTypeAndVersion: INodeTypeNameVersion, |
| | currentNodeParameters: INodeParameters, |
| | credentials?: INodeCredentials, |
| | ): Promise<ResourceMapperFields> { |
| | const nodeType = this.getNodeType(nodeTypeAndVersion); |
| | const method = this.getMethod('resourceMapping', methodName, nodeType); |
| | const workflow = this.getWorkflow(nodeTypeAndVersion, currentNodeParameters, credentials); |
| | const thisArgs = this.getThisArg(path, additionalData, workflow); |
| | |
| | return method.call(thisArgs); |
| | } |
| |
|
| | |
| | async getLocalResourceMappingFields( |
| | methodName: string, |
| | path: string, |
| | additionalData: IWorkflowExecuteAdditionalData, |
| | nodeTypeAndVersion: INodeTypeNameVersion, |
| | ): Promise<ResourceMapperFields> { |
| | const nodeType = this.getNodeType(nodeTypeAndVersion); |
| | const method = this.getMethod('localResourceMapping', methodName, nodeType); |
| | const thisArgs = this.getLocalLoadOptionsContext(path, additionalData); |
| | |
| | return method.call(thisArgs); |
| | } |
| |
|
| | |
| | async getActionResult( |
| | handler: string, |
| | path: string, |
| | additionalData: IWorkflowExecuteAdditionalData, |
| | nodeTypeAndVersion: INodeTypeNameVersion, |
| | currentNodeParameters: INodeParameters, |
| | payload: IDataObject | string | undefined, |
| | credentials?: INodeCredentials, |
| | ): Promise<NodeParameterValueType> { |
| | const nodeType = this.getNodeType(nodeTypeAndVersion); |
| | const method = this.getMethod('actionHandler', handler, nodeType); |
| | const workflow = this.getWorkflow(nodeTypeAndVersion, currentNodeParameters, credentials); |
| | const thisArgs = this.getThisArg(path, additionalData, workflow); |
| | |
| | return method.call(thisArgs, payload); |
| | } |
| |
|
| | private getMethod( |
| | type: 'resourceMapping', |
| | methodName: string, |
| | nodeType: INodeType, |
| | ): ResourceMappingMethod; |
| | private getMethod( |
| | type: 'localResourceMapping', |
| | methodName: string, |
| | nodeType: INodeType, |
| | ): LocalResourceMappingMethod; |
| | private getMethod(type: 'listSearch', methodName: string, nodeType: INodeType): ListSearchMethod; |
| | private getMethod( |
| | type: 'loadOptions', |
| | methodName: string, |
| | nodeType: INodeType, |
| | ): LoadOptionsMethod; |
| | private getMethod( |
| | type: 'actionHandler', |
| | methodName: string, |
| | nodeType: INodeType, |
| | ): ActionHandlerMethod; |
| | private getMethod( |
| | type: |
| | | 'resourceMapping' |
| | | 'localResourceMapping' |
| | | 'listSearch' |
| | | 'loadOptions' |
| | | 'actionHandler', |
| | methodName: string, |
| | nodeType: INodeType, |
| | ): NodeMethod { |
| | const method = nodeType.methods?.[type]?.[methodName] as NodeMethod; |
| | if (typeof method !== 'function') { |
| | throw new UnexpectedError('Node type does not have method defined', { |
| | tags: { nodeType: nodeType.description.name }, |
| | extra: { methodName }, |
| | }); |
| | } |
| | return method; |
| | } |
| |
|
| | private getNodeType({ name, version }: INodeTypeNameVersion) { |
| | return this.nodeTypes.getByNameAndVersion(name, version); |
| | } |
| |
|
| | private getWorkflow( |
| | nodeTypeAndVersion: INodeTypeNameVersion, |
| | currentNodeParameters: INodeParameters, |
| | credentials?: INodeCredentials, |
| | ) { |
| | const node: INode = { |
| | parameters: currentNodeParameters, |
| | id: 'uuid-1234', |
| | name: 'Temp-Node', |
| | type: nodeTypeAndVersion.name, |
| | typeVersion: nodeTypeAndVersion.version, |
| | position: [0, 0], |
| | }; |
| |
|
| | if (credentials) { |
| | node.credentials = credentials; |
| | } |
| |
|
| | return new Workflow({ |
| | nodes: [node], |
| | connections: {}, |
| | active: false, |
| | nodeTypes: this.nodeTypes, |
| | }); |
| | } |
| |
|
| | private getThisArg( |
| | path: string, |
| | additionalData: IWorkflowExecuteAdditionalData, |
| | workflow: Workflow, |
| | ) { |
| | const node = workflow.nodes['Temp-Node']; |
| | return new LoadOptionsContext(workflow, node, additionalData, path); |
| | } |
| |
|
| | private getLocalLoadOptionsContext( |
| | path: string, |
| | additionalData: IWorkflowExecuteAdditionalData, |
| | ): ILocalLoadOptionsFunctions { |
| | return new LocalLoadOptionsContext( |
| | this.nodeTypes, |
| | additionalData, |
| | path, |
| | this.workflowLoaderService, |
| | ); |
| | } |
| | } |
| |
|