import { Expression , IConnections , IDeferredPromise , IExecuteResponsePromiseData , IGetExecuteTriggerFunctions , INode , INodeExecuteFunctions , INodeExecutionData , INodeIssues , INodeParameters , INodes , INodeType , INodeTypes , IPollFunctions , IRunExecutionData , ITaskDataConnections , ITriggerResponse , IWebhookData , IWebhookResponseData , IWorfklowIssues , IWorkflowExecuteAdditionalData , IWorkflowSettings , NodeHelpers , NodeParameterValue , ObservableObject , WebhookSetupMethodNames , WorkflowActivateMode , WorkflowExecuteMode , } from '.' ; import { IConnection , IDataObject , IObservableObject } from './Interfaces' ; export class Workflow { id : string | undefined ; name : string | undefined ; nodes : INodes = { } ; connectionsBySourceNode : IConnections ; connectionsByDestinationNode : IConnections ; nodeTypes : INodeTypes ; expression : Expression ; active : boolean ; settings : IWorkflowSettings ; staticData : IDataObject ; constructor ( parameters ) { this . id = parameters . id ; this . name = parameters . name ; this . nodeTypes = parameters . nodeTypes ; let nodeType ; for ( const node of parameters . nodes ) { this . nodes [ node . name ] = node ; nodeType = this . nodeTypes . getByNameAndVersion ( node . type , node . typeVersion ) ; if ( nodeType === undefined ) { continue ; } const nodeParameters = NodeHelpers . getNodeParameters ( nodeType . description . properties , node . parameters , true , false ) ; node . parameters = nodeParameters !== null ? nodeParameters : { } ; } this . connectionsBySourceNode = parameters . connections ; this . connectionsByDestinationNode = this . __getConnectionsByDestination ( parameters . connections ) ; this . active = parameters . active || false ; this . staticData = ObservableObject . create ( parameters . staticData || { } , undefined , { ignoreEmptyOnFirstChild : true , } ) ; this . settings = parameters . settings || { } ; this . expression = new Expression ( this ) ; } param { IConnections } connections returns { IConnections } memberof __getConnectionsByDestination ( connections ) { const returnConnection = { } ; let connectionInfo ; let maxIndex ; for ( const sourceNode in connections ) { if ( ! connections . hasOwnProperty ( sourceNode ) ) { continue ; } for ( const type in connections [ sourceNode ] ) { if ( ! connections [ sourceNode ] . hasOwnProperty ( type ) ) { continue ; } for ( const inputIndex in connections [ sourceNode ] [ type ] ) { if ( ! connections [ sourceNode ] [ type ] . hasOwnProperty ( inputIndex ) ) { continue ; } for ( connectionInfo of connections [ sourceNode ] [ type ] [ inputIndex ] ) { if ( ! returnConnection . hasOwnProperty ( connectionInfo . node ) ) { returnConnection [ connectionInfo . node ] = { } ; } if ( ! returnConnection [ connectionInfo . node ] . hasOwnProperty ( connectionInfo . type ) ) { returnConnection [ connectionInfo . node ] [ connectionInfo . type ] = [ ] ; } maxIndex = returnConnection [ connectionInfo . node ] [ connectionInfo . type ] . length - 1 ; for ( let j = maxIndex ; j < connectionInfo . index ; j ++ ) { returnConnection [ connectionInfo . node ] [ connectionInfo . type ] . push ( [ ] ) ; } returnConnection [ connectionInfo . node ] [ connectionInfo . type ] [ connectionInfo . index ] . push ( { node : sourceNode , type , index : parseInt ( inputIndex , 10 ) , } ) ; } } } } return returnConnection ; } param { string [ ] } ignoreNodeTypes returns { boolean } memberof checkIfWorkflowCanBeActivated ( ignoreNodeTypes ? ) { let node ; let nodeType ; for ( const nodeName of Object . keys ( this . nodes ) ) { node = this . nodes [ nodeName ] ; if ( node . disabled === true ) { continue ; } if ( ignoreNodeTypes !== undefined && ignoreNodeTypes . includes ( node . type ) ) { continue ; } nodeType = this . nodeTypes . getByNameAndVersion ( node . type , node . typeVersion ) ; if ( nodeType === undefined ) { continue ; } if ( nodeType . poll !== undefined || nodeType . trigger !== undefined || nodeType . webhook !== undefined ) { return true ; } } return false ; } returns { ( IWorfklowIssues | null ) } memberof checkReadyForExecution ( inputData ) { let node ; let nodeType ; let nodeIssues = null ; const workflowIssues = { } ; let checkNodes = [ ] ; if ( inputData . destinationNode ) { checkNodes = this . getParentNodes ( inputData . destinationNode ) ; checkNodes . push ( inputData . destinationNode ) ; } else if ( inputData . startNode ) { checkNodes = this . getChildNodes ( inputData . startNode ) ; checkNodes . push ( inputData . startNode ) ; } for ( const nodeName of checkNodes ) { nodeIssues = null ; node = this . nodes [ nodeName ] ; if ( node . disabled === true ) { continue ; } nodeType = this . nodeTypes . getByNameAndVersion ( node . type , node . typeVersion ) ; if ( nodeType === undefined ) { nodeIssues = { typeUnknown : true , } ; } else { nodeIssues = NodeHelpers . getNodeParametersIssues ( nodeType . description . properties , node ) ; } if ( nodeIssues !== null ) { workflowIssues [ node . name ] = nodeIssues ; } } if ( Object . keys ( workflowIssues ) . length === 0 ) { return null ; } return workflowIssues ; } param { string } type param { INode } node returns { IDataObject } memberof getStaticData ( type , node ? ) { let key ; if ( type === 'global' ) { key = 'global' ; } else if ( type === 'node' ) { if ( node === undefined ) { throw new Error ( `The request data of context type "node" the node parameter has to be set!` ) ; } key = `node:${ node . name }` ; } else { throw new Error ( `The context type "${ type }" is not know. Only "global" and node" are supported!` ) ; } if ( this . staticData [ key ] === undefined ) { this . staticData [ key ] = ObservableObject . create ( { } , this . staticData as IObservableObject ) ; } return this . staticData [ key ] as IDataObject ; } returns { INode [ ] } memberof getTriggerNodes ( ) { return this . queryNodes ( ( nodeType ) => ! ! nodeType . trigger ) ; } returns { INode [ ] } memberof getPollNodes ( ) { return this . queryNodes ( ( nodeType ) => ! ! nodeType . poll ) ; } param { ( nodeType : INodeType ) => boolean } checkFunction returns { INode [ ] } memberof queryNodes ( checkFunction ) { const returnNodes = [ ] ; let node ; let nodeType ; for ( const nodeName of Object . keys ( this . nodes ) ) { node = this . nodes [ nodeName ] ; if ( node . disabled === true ) { continue ; } nodeType = this . nodeTypes . getByNameAndVersion ( node . type , node . typeVersion ) ; if ( nodeType !== undefined && checkFunction ( nodeType ) ) { returnNodes . push ( node ) ; } } return returnNodes ; } param { string } nodeName returns { ( INode | null ) } memberof getNode ( nodeName ) { if ( this . nodes . hasOwnProperty ( nodeName ) ) { return this . nodes [ nodeName ] ; } return null ; } param { ( NodeParameterValue | INodeParameters | NodeParameterValue [ ] | INodeParameters [ ] ) } parameterValue param { string } currentName param { string } newName returns { ( NodeParameterValue | INodeParameters | NodeParameterValue [ ] | INodeParameters [ ] ) } memberof renameNodeInExpressions ( parameterValue , currentName , newName ) { if ( typeof parameterValue !== 'object' ) { if ( typeof parameterValue === 'string' && parameterValue . charAt ( 0 ) === '=' ) { if ( parameterValue . includes ( currentName ) ) { const currentNameEscaped = currentName . replace ( /[.*+?^${}()|[\]\\]/g , '\\$&' ) ; parameterValue = parameterValue . replace ( new RegExp ( `(\\$node(\\.|\\["|\\['))${ currentNameEscaped }((\\.|"\\]|'\\]))` , 'g' ) , `$1${ newName }$3` ) ; } } return parameterValue ; } if ( Array . isArray ( parameterValue ) ) { const returnArray = [ ] ; for ( const currentValue of parameterValue ) { returnArray . push ( this . renameNodeInExpressions ( currentValue , currentName , newName ) ) ; } return returnArray ; } const returnData = { } ; for ( const parameterName of Object . keys ( parameterValue || { } ) ) { returnData [ parameterName ] = this . renameNodeInExpressions ( parameterValue ! [ parameterName ] , currentName , newName ) ; } return returnData ; } param { string } currentName param { string } newName memberof renameNode ( currentName , newName ) { if ( this . nodes [ currentName ] !== undefined ) { this . nodes [ newName ] = this . nodes [ currentName ] ; this . nodes [ newName ] . name = newName ; delete this . nodes [ currentName ] ; } for ( const node of Object . values ( this . nodes ) ) { node . parameters = this . renameNodeInExpressions ( node . parameters , currentName , newName ) as INodeParameters ; } if ( this . connectionsBySourceNode . hasOwnProperty ( currentName ) ) { this . connectionsBySourceNode [ newName ] = this . connectionsBySourceNode [ currentName ] ; delete this . connectionsBySourceNode [ currentName ] ; } let sourceNode ; let type ; let sourceIndex ; let connectionIndex ; let connectionData ; for ( sourceNode of Object . keys ( this . connectionsBySourceNode ) ) { for ( type of Object . keys ( this . connectionsBySourceNode [ sourceNode ] ) ) { for ( sourceIndex of Object . keys ( this . connectionsBySourceNode [ sourceNode ] [ type ] ) ) { for ( connectionIndex of Object . keys ( this . connectionsBySourceNode [ sourceNode ] [ type ] [ parseInt ( sourceIndex , 10 ) ] ) ) { connectionData = this . connectionsBySourceNode [ sourceNode ] [ type ] [ parseInt ( sourceIndex , 10 ) ] [ parseInt ( connectionIndex , 10 ) ] ; if ( connectionData . node === currentName ) { connectionData . node = newName ; } } } } } this . connectionsByDestinationNode = this . __getConnectionsByDestination ( this . connectionsBySourceNode ) ; } param { string } nodeName param { string } type param { number } nodeConnectionIndex returns { string [ ] } memberof getHighestNode ( nodeName , type = 'main' , nodeConnectionIndex ? , checkedNodes ? ) { const currentHighest = [ ] ; if ( this . nodes [ nodeName ] . disabled === false ) { currentHighest . push ( nodeName ) ; } if ( ! this . connectionsByDestinationNode . hasOwnProperty ( nodeName ) ) { return currentHighest ; } if ( ! this . connectionsByDestinationNode [ nodeName ] . hasOwnProperty ( type ) ) { return currentHighest ; } checkedNodes = checkedNodes || [ ] ; if ( checkedNodes . includes ( nodeName ) ) { return currentHighest ; } checkedNodes . push ( nodeName ) ; const returnNodes = [ ] ; let addNodes ; let connectionsByIndex ; for ( let connectionIndex = 0 ; connectionIndex < this . connectionsByDestinationNode [ nodeName ] [ type ] . length ; connectionIndex ++ ) { if ( nodeConnectionIndex !== undefined && nodeConnectionIndex !== connectionIndex ) { continue ; } connectionsByIndex = this . connectionsByDestinationNode [ nodeName ] [ type ] [ connectionIndex ] ; connectionsByIndex . forEach ( ( connection ) => { if ( checkedNodes ! . includes ( connection . node ) ) { return ; } addNodes = this . getHighestNode ( connection . node , type , undefined , checkedNodes ) ; if ( addNodes . length === 0 ) { if ( this . nodes [ connection . node ] . disabled !== true ) { addNodes = [ connection . node ] ; } } addNodes . forEach ( ( name ) => { if ( returnNodes . indexOf ( name ) === - 1 ) { returnNodes . push ( name ) ; } } ) ; } ) ; } return returnNodes ; } param { string } nodeName param { string } type param { * } depth returns { string [ ] } memberof getChildNodes ( nodeName , type = 'main' , depth = - 1 ) { return this . getConnectedNodes ( this . connectionsBySourceNode , nodeName , type , depth ) ; } param { string } nodeName param { string } type param { * } depth returns { string [ ] } memberof getParentNodes ( nodeName , type = 'main' , depth = - 1 ) { return this . getConnectedNodes ( this . connectionsByDestinationNode , nodeName , type , depth ) ; } param { IConnections } connections param { string } nodeName param { string } type param { * } depth param { string [ ] } checkedNodes returns { string [ ] } memberof getConnectedNodes ( connections , nodeName , type = 'main' , depth = - 1 , checkedNodes ? ) { depth = depth === - 1 ? - 1 : depth ; const newDepth = depth === - 1 ? depth : depth - 1 ; if ( depth === 0 ) { return [ ] ; } if ( ! connections . hasOwnProperty ( nodeName ) ) { return [ ] ; } if ( ! connections [ nodeName ] . hasOwnProperty ( type ) ) { return [ ] ; } checkedNodes = checkedNodes || [ ] ; if ( checkedNodes . includes ( nodeName ) ) { return [ ] ; } checkedNodes . push ( nodeName ) ; const returnNodes = [ ] ; let addNodes ; let nodeIndex ; let i ; let parentNodeName ; connections [ nodeName ] [ type ] . forEach ( ( connectionsByIndex ) => { connectionsByIndex . forEach ( ( connection ) => { if ( checkedNodes ! . includes ( connection . node ) ) { return ; } returnNodes . unshift ( connection . node ) ; addNodes = this . getConnectedNodes ( connections , connection . node , type , newDepth , checkedNodes ) ; for ( i = addNodes . length ; i -- ; i > 0 ) { parentNodeName = addNodes [ i ] ; nodeIndex = returnNodes . indexOf ( parentNodeName ) ; if ( nodeIndex !== - 1 ) { returnNodes . splice ( nodeIndex , 1 ) ; } returnNodes . unshift ( parentNodeName ) ; } } ) ; } ) ; return returnNodes ; } param { string } nodeName param { string } parentNodeName param { string } type param { * } depth param { string [ ] } checkedNodes returns { ( number | undefined ) } memberof getNodeConnectionOutputIndex ( nodeName , parentNodeName , type = 'main' , depth = - 1 , checkedNodes ? ) { const node = this . getNode ( parentNodeName ) ; if ( node === null ) { return undefined ; } const nodeType = this . nodeTypes . getByNameAndVersion ( node . type , node . typeVersion ) as INodeType ; if ( nodeType . description . outputs . length === 1 ) { return 0 ; } depth = depth === - 1 ? - 1 : depth ; const newDepth = depth === - 1 ? depth : depth - 1 ; if ( depth === 0 ) { return undefined ; } if ( ! this . connectionsByDestinationNode . hasOwnProperty ( nodeName ) ) { return undefined ; } if ( ! this . connectionsByDestinationNode [ nodeName ] . hasOwnProperty ( type ) ) { return undefined ; } checkedNodes = checkedNodes || [ ] ; if ( checkedNodes . includes ( nodeName ) ) { return undefined ; } checkedNodes . push ( nodeName ) ; let outputIndex ; for ( const connectionsByIndex of this . connectionsByDestinationNode [ nodeName ] [ type ] ) { for ( const connection of connectionsByIndex ) { if ( parentNodeName === connection . node ) { return connection . index ; } if ( checkedNodes . includes ( connection . node ) ) { continue ; } outputIndex = this . getNodeConnectionOutputIndex ( connection . node , parentNodeName , type , newDepth , checkedNodes ) ; if ( outputIndex !== undefined ) { return outputIndex ; } } } return undefined ; } param { string [ ] } nodeNames returns { ( INode | undefined ) } memberof __getStartNode ( nodeNames ) { let node ; let nodeType ; for ( const nodeName of nodeNames ) { node = this . nodes [ nodeName ] ; nodeType = this . nodeTypes . getByNameAndVersion ( node . type , node . typeVersion ) as INodeType ; if ( nodeType . trigger !== undefined || nodeType . poll !== undefined ) { if ( node . disabled === true ) { continue ; } return node ; } } const startNodeType = 'n8n-nodes-base.start' ; for ( const nodeName of nodeNames ) { node = this . nodes [ nodeName ] ; if ( node . type === startNodeType ) { return node ; } } return undefined ; } param { string } destinationNode returns { ( INode | undefined ) } memberof getStartNode ( destinationNode ? ) { if ( destinationNode ) { const nodeNames = this . getHighestNode ( destinationNode ) ; if ( nodeNames . length === 0 ) { nodeNames . push ( destinationNode ) ; } const node = this . __getStartNode ( nodeNames ) ; if ( node !== undefined ) { return node ; } return this . nodes [ nodeNames [ 0 ] ] ; } return this . __getStartNode ( Object . keys ( this . nodes ) ) ; } param { WebhookSetupMethodNames } method param { IWebhookData } webhookData param { INodeExecuteFunctions } nodeExecuteFunctions param { WorkflowExecuteMode } mode returns { ( Promise < boolean | undefined > ) } memberof async runWebhookMethod ( method , webhookData , nodeExecuteFunctions , mode , activation , isTest ? ) { const node = this . getNode ( webhookData . node ) as INode ; const nodeType = this . nodeTypes . getByNameAndVersion ( node . type , node . typeVersion ) as INodeType ; if ( nodeType . webhookMethods === undefined ) { return ; } if ( nodeType . webhookMethods [ webhookData . webhookDescription . name ] === undefined ) { return ; } if ( nodeType . webhookMethods [ webhookData . webhookDescription . name ] [ method ] === undefined ) { return ; } const thisArgs = nodeExecuteFunctions . getExecuteHookFunctions ( this , node , webhookData . workflowExecuteAdditionalData , mode , activation , isTest , webhookData ) ; return nodeType . webhookMethods [ webhookData . webhookDescription . name ] [ method ] ! . call ( thisArgs ) ; } param { INode } node param { IGetExecuteTriggerFunctions } getTriggerFunctions param { IWorkflowExecuteAdditionalData } additionalData param { WorkflowExecuteMode } mode returns { ( Promise < ITriggerResponse | undefined > ) } memberof async runTrigger ( node , getTriggerFunctions , additionalData , mode , activation ) { const triggerFunctions = getTriggerFunctions ( this , node , additionalData , mode , activation ) ; const nodeType = this . nodeTypes . getByNameAndVersion ( node . type , node . typeVersion ) ; if ( nodeType === undefined ) { throw new Error ( `The node type "${ node . type }" of node "${ node . name }" is not known.` ) ; } if ( ! nodeType . trigger ) { throw new Error ( `The node type "${ node . type }" of node "${ node . name }" does not have a trigger function defined.` ) ; } if ( mode === 'manual' ) { const triggerResponse = await nodeType . trigger . call ( triggerFunctions ) ; triggerResponse ! . manualTriggerResponse = new Promise ( ( resolve ) => { triggerFunctions . emit = ( ( resolveEmit ) => ( data , responsePromise ? ) => { additionalData . hooks ! . hookFunctions . sendResponse = [ async ( response ) : Promise < void > => { if ( responsePromise ) { responsePromise . resolve ( response ) ; } } , ] ; resolveEmit ( data ) ; } ) ( resolve ) ; } ) ; return triggerResponse ; } return nodeType . trigger . call ( triggerFunctions ) ; } param { INode } node param { IPollFunctions } pollFunctions returns memberof async runPoll ( node , pollFunctions ) { const nodeType = this . nodeTypes . getByNameAndVersion ( node . type , node . typeVersion ) ; if ( nodeType === undefined ) { throw new Error ( `The node type "${ node . type }" of node "${ node . name }" is not known.` ) ; } if ( ! nodeType . poll ) { throw new Error ( `The node type "${ node . type }" of node "${ node . name }" does not have a poll function defined.` ) ; } return nodeType . poll . call ( pollFunctions ) ; } param { INode } node param { IWorkflowExecuteAdditionalData } additionalData param { INodeExecuteFunctions } nodeExecuteFunctions param { WorkflowExecuteMode } mode returns { Promise < IWebhookResponseData > } memberof async runWebhook ( webhookData , node , additionalData , nodeExecuteFunctions , mode ) { const nodeType = this . nodeTypes . getByNameAndVersion ( node . type , node . typeVersion ) ; if ( nodeType === undefined ) { throw new Error ( `The type of the webhook node "${ node . name }" is not known.` ) ; } else if ( nodeType . webhook === undefined ) { throw new Error ( `The node "${ node . name }" does not have any webhooks defined.` ) ; } const thisArgs = nodeExecuteFunctions . getExecuteWebhookFunctions ( this , node , additionalData , mode , webhookData ) ; return nodeType . webhook . call ( thisArgs ) ; } param { INode } node param { ITaskDataConnections } inputData param { IRunExecutionData } runExecutionData param { number } runIndex param { IWorkflowExecuteAdditionalData } additionalData param { INodeExecuteFunctions } nodeExecuteFunctions param { WorkflowExecuteMode } mode returns { ( Promise < INodeExecutionData [ ] [ ] | null > ) } memberof async runNode ( node , inputData , runExecutionData , runIndex , additionalData , nodeExecuteFunctions , mode ) { if ( node . disabled === true ) { if ( inputData . hasOwnProperty ( 'main' ) && inputData . main . length > 0 ) { if ( inputData . main [ 0 ] === null ) { return undefined ; } return [ inputData . main [ 0 ] ] ; } return undefined ; } const nodeType = this . nodeTypes . getByNameAndVersion ( node . type , node . typeVersion ) ; if ( nodeType === undefined ) { throw new Error ( `Node type "${ node . type }" is not known so can not run it!` ) ; } let connectionInputData = [ ] ; if ( nodeType . execute || nodeType . executeSingle ) { if ( inputData . hasOwnProperty ( 'main' ) && inputData . main . length > 0 ) { connectionInputData = inputData . main [ 0 ] as INodeExecutionData [ ] ; } if ( connectionInputData . length === 0 ) { return undefined ; } } if ( runExecutionData . resultData . lastNodeExecuted === node . name && runExecutionData . resultData . error !== undefined ) { const error = new Error ( runExecutionData . resultData . error . message ) ; error . stack = runExecutionData . resultData . error . stack ; throw error ; } if ( node . executeOnce === true ) { connectionInputData = connectionInputData . slice ( 0 , 1 ) ; const newInputData = { } ; for ( const inputName of Object . keys ( inputData ) ) { newInputData [ inputName ] = inputData [ inputName ] . map ( ( input ) => { return input && input . slice ( 0 , 1 ) ; } ) ; } inputData = newInputData ; } if ( nodeType . executeSingle ) { const returnPromises = [ ] ; for ( let itemIndex = 0 ; itemIndex < connectionInputData . length ; itemIndex ++ ) { const thisArgs = nodeExecuteFunctions . getExecuteSingleFunctions ( this , runExecutionData , runIndex , connectionInputData , inputData , node , itemIndex , additionalData , mode ) ; returnPromises . push ( nodeType . executeSingle . call ( thisArgs ) ) ; } if ( returnPromises . length === 0 ) { return null ; } let promiseResults ; try { promiseResults = await Promise . all ( returnPromises ) ; } catch ( error ) { return Promise . reject ( error ) ; } if ( promiseResults ) { return [ promiseResults ] ; } } else if ( nodeType . execute ) { const thisArgs = nodeExecuteFunctions . getExecuteFunctions ( this , runExecutionData , runIndex , connectionInputData , inputData , node , additionalData , mode ) ; return nodeType . execute . call ( thisArgs ) ; } else if ( nodeType . poll ) { if ( mode === 'manual' ) { const thisArgs = nodeExecuteFunctions . getExecutePollFunctions ( this , node , additionalData , mode , 'manual' ) ; return nodeType . poll . call ( thisArgs ) ; } return inputData . main as INodeExecutionData [ ] [ ] ; } else if ( nodeType . trigger ) { if ( mode === 'manual' ) { const triggerResponse = await this . runTrigger ( node , nodeExecuteFunctions . getExecuteTriggerFunctions , additionalData , mode , 'manual' ) ; if ( triggerResponse === undefined ) { return null ; } if ( triggerResponse . manualTriggerFunction !== undefined ) { await triggerResponse . manualTriggerFunction ( ) ; } const response = await triggerResponse . manualTriggerResponse ! ; if ( triggerResponse . closeFunction ) { await triggerResponse . closeFunction ( ) ; } if ( response . length === 0 ) { return null ; } return response ; } return inputData . main as INodeExecutionData [ ] [ ] ; } else if ( nodeType . webhook ) { return inputData . main as INodeExecutionData [ ] [ ] ; } return null ; } }