2021-08-29 11:58:11 -07:00
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-param-reassign */
/* eslint-disable @typescript-eslint/no-this-alias */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable no-prototype-builtins */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
2022-03-13 01:34:44 -08:00
2022-04-10 02:33:42 -07:00
import { DateTime , Duration , Interval , Settings } from 'luxon' ;
2022-03-13 01:34:44 -08:00
import * as jmespath from 'jmespath' ;
2021-08-29 11:58:11 -07:00
// eslint-disable-next-line import/no-cycle
2019-06-23 03:35:23 -07:00
import {
2022-06-03 08:25:07 -07:00
ExpressionError ,
2019-06-23 03:35:23 -07:00
IDataObject ,
2022-06-03 08:25:07 -07:00
IExecuteData ,
2019-06-23 03:35:23 -07:00
INodeExecutionData ,
2021-05-14 16:16:48 -07:00
INodeParameters ,
2022-06-03 08:25:07 -07:00
IPairedItemData ,
2019-06-23 03:35:23 -07:00
IRunExecutionData ,
2022-06-03 08:25:07 -07:00
ISourceData ,
ITaskData ,
2021-08-21 05:11:32 -07:00
IWorkflowDataProxyAdditionalKeys ,
2019-09-04 05:53:39 -07:00
IWorkflowDataProxyData ,
NodeHelpers ,
2022-09-21 06:44:45 -07:00
NodeParameterValueType ,
2019-06-23 03:35:23 -07:00
Workflow ,
2021-01-29 00:31:40 -08:00
WorkflowExecuteMode ,
2021-08-29 11:58:11 -07:00
} from '.' ;
2019-06-23 03:35:23 -07:00
export class WorkflowDataProxy {
private workflow : Workflow ;
2021-08-29 11:58:11 -07:00
2019-06-23 03:35:23 -07:00
private runExecutionData : IRunExecutionData | null ;
2021-08-29 11:58:11 -07:00
2020-04-13 06:57:01 -07:00
private defaultReturnRunIndex : number ;
2021-08-29 11:58:11 -07:00
2019-06-23 03:35:23 -07:00
private runIndex : number ;
2021-08-29 11:58:11 -07:00
2019-06-23 03:35:23 -07:00
private itemIndex : number ;
2021-08-29 11:58:11 -07:00
2019-06-23 03:35:23 -07:00
private activeNodeName : string ;
2021-08-29 11:58:11 -07:00
2019-06-23 03:35:23 -07:00
private connectionInputData : INodeExecutionData [ ] ;
2021-08-29 11:58:11 -07:00
2021-05-14 16:16:48 -07:00
private siblingParameters : INodeParameters ;
2021-08-29 11:58:11 -07:00
2021-01-29 00:31:40 -08:00
private mode : WorkflowExecuteMode ;
2019-06-23 03:35:23 -07:00
2021-08-29 11:58:11 -07:00
private selfData : IDataObject ;
2019-06-23 03:35:23 -07:00
2021-08-29 11:58:11 -07:00
private additionalKeys : IWorkflowDataProxyAdditionalKeys ;
2019-06-23 03:35:23 -07:00
2022-06-03 08:25:07 -07:00
private executeData : IExecuteData | undefined ;
2022-04-10 02:33:42 -07:00
private defaultTimezone : string ;
private timezone : string ;
2021-08-29 11:58:11 -07:00
constructor (
workflow : Workflow ,
runExecutionData : IRunExecutionData | null ,
runIndex : number ,
itemIndex : number ,
activeNodeName : string ,
connectionInputData : INodeExecutionData [ ] ,
siblingParameters : INodeParameters ,
mode : WorkflowExecuteMode ,
2022-04-10 02:33:42 -07:00
defaultTimezone : string ,
2021-08-29 11:58:11 -07:00
additionalKeys : IWorkflowDataProxyAdditionalKeys ,
2022-06-03 08:25:07 -07:00
executeData? : IExecuteData ,
2021-08-29 11:58:11 -07:00
defaultReturnRunIndex = - 1 ,
selfData = { } ,
) {
2019-06-23 03:35:23 -07:00
this . workflow = workflow ;
this . runExecutionData = runExecutionData ;
2020-04-13 06:57:01 -07:00
this . defaultReturnRunIndex = defaultReturnRunIndex ;
2019-06-23 03:35:23 -07:00
this . runIndex = runIndex ;
this . itemIndex = itemIndex ;
this . activeNodeName = activeNodeName ;
this . connectionInputData = connectionInputData ;
2021-05-14 16:16:48 -07:00
this . siblingParameters = siblingParameters ;
2021-01-29 00:31:40 -08:00
this . mode = mode ;
2022-04-10 02:33:42 -07:00
this . defaultTimezone = defaultTimezone ;
this . timezone = ( this . workflow . settings . timezone as string ) || this . defaultTimezone ;
2021-01-27 00:02:20 -08:00
this . selfData = selfData ;
2021-08-21 05:11:32 -07:00
this . additionalKeys = additionalKeys ;
2022-06-03 08:25:07 -07:00
this . executeData = executeData ;
2022-04-10 02:33:42 -07:00
Settings . defaultZone = this . timezone ;
2019-06-23 03:35:23 -07:00
}
/ * *
* Returns a proxy which allows to query context data of a given node
*
* @private
* @param { string } nodeName The name of the node to get the context from
* @returns
* @memberof WorkflowDataProxy
* /
private nodeContextGetter ( nodeName : string ) {
const that = this ;
const node = this . workflow . nodes [ nodeName ] ;
2021-08-29 11:58:11 -07:00
return new Proxy (
{ } ,
{
ownKeys ( target ) {
if ( Reflect . ownKeys ( target ) . length === 0 ) {
// Target object did not get set yet
Object . assign ( target , NodeHelpers . getContext ( that . runExecutionData ! , 'node' , node ) ) ;
}
2019-06-23 03:35:23 -07:00
2021-08-29 11:58:11 -07:00
return Reflect . ownKeys ( target ) ;
} ,
2021-12-23 02:41:46 -08:00
getOwnPropertyDescriptor ( k ) {
return {
enumerable : true ,
configurable : true ,
} ;
} ,
2021-08-29 11:58:11 -07:00
get ( target , name , receiver ) {
// eslint-disable-next-line no-param-reassign
name = name . toString ( ) ;
const contextData = NodeHelpers . getContext ( that . runExecutionData ! , 'node' , node ) ;
2019-06-23 03:35:23 -07:00
2021-08-29 11:58:11 -07:00
if ( ! contextData . hasOwnProperty ( name ) ) {
// Parameter does not exist on node
throw new Error ( ` Could not find parameter " ${ name } " on context of node " ${ nodeName } " ` ) ;
}
2019-06-23 03:35:23 -07:00
2021-08-29 11:58:11 -07:00
return contextData [ name ] ;
} ,
2020-10-22 06:46:03 -07:00
} ,
2021-08-29 11:58:11 -07:00
) ;
2019-06-23 03:35:23 -07:00
}
2021-01-27 00:02:20 -08:00
private selfGetter() {
const that = this ;
2021-08-29 11:58:11 -07:00
return new Proxy (
{ } ,
{
ownKeys ( target ) {
return Reflect . ownKeys ( target ) ;
} ,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
get ( target , name , receiver ) {
name = name . toString ( ) ;
return that . selfData [ name ] ;
} ,
2021-01-27 00:02:20 -08:00
} ,
2021-08-29 11:58:11 -07:00
) ;
2021-01-27 00:02:20 -08:00
}
2019-06-23 03:35:23 -07:00
/ * *
* Returns a proxy which allows to query parameter data of a given node
*
* @private
* @param { string } nodeName The name of the node to query data from
* @returns
* @memberof WorkflowDataGetter
* /
private nodeParameterGetter ( nodeName : string ) {
const that = this ;
const node = this . workflow . nodes [ nodeName ] ;
return new Proxy ( node . parameters , {
ownKeys ( target ) {
return Reflect . ownKeys ( target ) ;
} ,
2021-12-23 02:41:46 -08:00
getOwnPropertyDescriptor ( k ) {
return {
enumerable : true ,
configurable : true ,
} ;
} ,
2019-06-23 03:35:23 -07:00
get ( target , name , receiver ) {
name = name . toString ( ) ;
2022-09-21 06:44:45 -07:00
let returnValue : NodeParameterValueType ;
2021-05-14 16:20:21 -07:00
if ( name [ 0 ] === '&' ) {
2021-05-14 16:16:48 -07:00
const key = name . slice ( 1 ) ;
if ( ! that . siblingParameters . hasOwnProperty ( key ) ) {
throw new Error ( ` Could not find sibling parameter " ${ key } " on node " ${ nodeName } " ` ) ;
}
returnValue = that . siblingParameters [ key ] ;
} else {
if ( ! node . parameters . hasOwnProperty ( name ) ) {
// Parameter does not exist on node
2022-02-19 03:38:46 -08:00
return undefined ;
2021-05-14 16:16:48 -07:00
}
2019-06-23 03:35:23 -07:00
2021-05-14 16:16:48 -07:00
returnValue = node . parameters [ name ] ;
}
2019-06-23 03:35:23 -07:00
if ( typeof returnValue === 'string' && returnValue . charAt ( 0 ) === '=' ) {
// The found value is an expression so resolve it
2021-08-29 11:58:11 -07:00
return that . workflow . expression . getParameterValue (
returnValue ,
that . runExecutionData ,
that . runIndex ,
that . itemIndex ,
that . activeNodeName ,
that . connectionInputData ,
that . mode ,
2022-04-10 02:33:42 -07:00
that . timezone ,
2021-08-29 11:58:11 -07:00
that . additionalKeys ,
2022-06-03 08:25:07 -07:00
that . executeData ,
2021-08-29 11:58:11 -07:00
) ;
2019-06-23 03:35:23 -07:00
}
return returnValue ;
2020-10-22 06:46:03 -07:00
} ,
2019-06-23 03:35:23 -07:00
} ) ;
}
2020-04-12 09:42:29 -07:00
/ * *
* Returns the node ExecutionData
*
* @private
* @param { string } nodeName The name of the node query data from
* @param { boolean } [ shortSyntax = false ] If short syntax got used
* @param { number } [ outputIndex ] The index of the output , if not given the first one gets used
* @param { number } [ runIndex ] The index of the run , if not given the current one does get used
* @returns { INodeExecutionData [ ] }
* @memberof WorkflowDataProxy
* /
2021-08-29 11:58:11 -07:00
private getNodeExecutionData (
nodeName : string ,
shortSyntax = false ,
outputIndex? : number ,
runIndex? : number ,
) : INodeExecutionData [ ] {
2020-04-12 09:42:29 -07:00
const that = this ;
let executionData : INodeExecutionData [ ] ;
2021-08-29 11:58:11 -07:00
if ( ! shortSyntax ) {
2020-04-12 09:42:29 -07:00
// Long syntax got used to return data from node in path
if ( that . runExecutionData === null ) {
2022-06-03 08:25:07 -07:00
throw new ExpressionError ( ` Workflow did not run so do not have any execution-data. ` , {
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
} ) ;
2020-04-12 09:42:29 -07:00
}
if ( ! that . runExecutionData . resultData . runData . hasOwnProperty ( nodeName ) ) {
2022-03-13 01:34:44 -08:00
if ( that . workflow . getNode ( nodeName ) ) {
2022-06-03 08:25:07 -07:00
throw new ExpressionError (
2022-03-13 01:34:44 -08:00
` The node " ${ nodeName } " hasn't been executed yet, so you can't reference its output data ` ,
2022-06-03 08:25:07 -07:00
{
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
} ,
2022-03-13 01:34:44 -08:00
) ;
}
2022-06-03 08:25:07 -07:00
throw new ExpressionError ( ` No node called " ${ nodeName } " in this workflow ` , {
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
} ) ;
2020-04-12 09:42:29 -07:00
}
2020-04-13 06:57:01 -07:00
runIndex = runIndex === undefined ? that.defaultReturnRunIndex : runIndex ;
2021-08-29 11:58:11 -07:00
runIndex =
runIndex === - 1 ? that . runExecutionData . resultData . runData [ nodeName ] . length - 1 : runIndex ;
2020-04-12 09:42:29 -07:00
2022-03-13 01:34:44 -08:00
if ( that . runExecutionData . resultData . runData [ nodeName ] . length <= runIndex ) {
2022-06-03 08:25:07 -07:00
throw new ExpressionError ( ` Run ${ runIndex } of node " ${ nodeName } " not found ` , {
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
} ) ;
2020-04-12 09:42:29 -07:00
}
const taskData = that . runExecutionData . resultData . runData [ nodeName ] [ runIndex ] . data ! ;
if ( taskData . main === null || ! taskData . main . length || taskData . main [ 0 ] === null ) {
// throw new Error(`No data found for item-index: "${itemIndex}"`);
2022-06-03 08:25:07 -07:00
throw new ExpressionError ( ` No data found from "main" input. ` , {
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
} ) ;
2020-04-12 09:42:29 -07:00
}
// Check from which output to read the data.
// Depends on how the nodes are connected.
// (example "IF" node. If node is connected to "true" or to "false" output)
if ( outputIndex === undefined ) {
2022-06-03 08:25:07 -07:00
const nodeConnection = that . workflow . getNodeConnectionIndexes (
2021-08-29 11:58:11 -07:00
that . activeNodeName ,
nodeName ,
'main' ,
) ;
2020-04-12 09:42:29 -07:00
2022-06-03 08:25:07 -07:00
if ( nodeConnection === undefined ) {
throw new ExpressionError (
2021-08-29 11:58:11 -07:00
` The node " ${ that . activeNodeName } " is not connected with node " ${ nodeName } " so no data can get returned from it. ` ,
2022-06-03 08:25:07 -07:00
{
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
} ,
2021-08-29 11:58:11 -07:00
) ;
2020-04-12 09:42:29 -07:00
}
2022-06-03 08:25:07 -07:00
outputIndex = nodeConnection . sourceIndex ;
2020-04-12 09:42:29 -07:00
}
if ( outputIndex === undefined ) {
outputIndex = 0 ;
}
2022-03-13 01:34:44 -08:00
if ( taskData . main . length <= outputIndex ) {
2022-06-03 08:25:07 -07:00
throw new ExpressionError ( ` Node " ${ nodeName } " has no branch with index ${ outputIndex } . ` , {
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
} ) ;
2020-04-12 09:42:29 -07:00
}
executionData = taskData . main [ outputIndex ] as INodeExecutionData [ ] ;
} else {
// Short syntax got used to return data from active node
// TODO: Here have to generate connection Input data for the current node by itself
// Data needed:
// #- the run-index
// - node which did send data (has to be the one from last recent execution)
// - later also the name of the input and its index (currently not needed as it is always "main" and index "0")
executionData = that . connectionInputData ;
}
return executionData ;
}
2019-06-23 03:35:23 -07:00
/ * *
* Returns a proxy which allows to query data of a given node
*
* @private
* @param { string } nodeName The name of the node query data from
* @param { boolean } [ shortSyntax = false ] If short syntax got used
* @returns
* @memberof WorkflowDataGetter
* /
private nodeDataGetter ( nodeName : string , shortSyntax = false ) {
const that = this ;
const node = this . workflow . nodes [ nodeName ] ;
if ( ! node ) {
2022-02-19 03:38:46 -08:00
return undefined ;
2019-06-23 03:35:23 -07:00
}
2021-08-29 11:58:11 -07:00
return new Proxy (
2022-09-09 07:34:50 -07:00
{ binary : undefined , data : undefined , json : undefined } ,
2021-08-29 11:58:11 -07:00
{
get ( target , name , receiver ) {
name = name . toString ( ) ;
2019-06-23 03:35:23 -07:00
2021-08-29 11:58:11 -07:00
if ( [ 'binary' , 'data' , 'json' ] . includes ( name ) ) {
const executionData = that . getNodeExecutionData ( nodeName , shortSyntax , undefined ) ;
if ( executionData . length <= that . itemIndex ) {
2022-06-03 08:25:07 -07:00
throw new ExpressionError ( ` No data found for item-index: " ${ that . itemIndex } " ` , {
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
} ) ;
2019-06-23 03:35:23 -07:00
}
2021-08-29 11:58:11 -07:00
if ( [ 'data' , 'json' ] . includes ( name ) ) {
// JSON-Data
return executionData [ that . itemIndex ] . json ;
}
if ( name === 'binary' ) {
// Binary-Data
const returnData : IDataObject = { } ;
2019-06-23 03:35:23 -07:00
2021-08-29 11:58:11 -07:00
if ( ! executionData [ that . itemIndex ] . binary ) {
return returnData ;
}
2019-06-23 03:35:23 -07:00
2021-08-29 11:58:11 -07:00
const binaryKeyData = executionData [ that . itemIndex ] . binary ! ;
for ( const keyName of Object . keys ( binaryKeyData ) ) {
returnData [ keyName ] = { } ;
const binaryData = binaryKeyData [ keyName ] ;
for ( const propertyName in binaryData ) {
if ( propertyName === 'data' ) {
// Skip the data property
// eslint-disable-next-line no-continue
continue ;
}
( returnData [ keyName ] as IDataObject ) [ propertyName ] = binaryData [ propertyName ] ;
2019-06-23 03:35:23 -07:00
}
}
2021-08-29 11:58:11 -07:00
return returnData ;
}
} else if ( name === 'context' ) {
return that . nodeContextGetter ( nodeName ) ;
} else if ( name === 'parameter' ) {
// Get node parameter data
return that . nodeParameterGetter ( nodeName ) ;
} else if ( name === 'runIndex' ) {
if (
that . runExecutionData === null ||
! that . runExecutionData . resultData . runData [ nodeName ]
) {
return - 1 ;
}
return that . runExecutionData . resultData . runData [ nodeName ] . length - 1 ;
2020-04-13 06:57:01 -07:00
}
2019-06-23 03:35:23 -07:00
2021-08-29 11:58:11 -07:00
return Reflect . get ( target , name , receiver ) ;
} ,
2020-10-22 06:46:03 -07:00
} ,
2021-08-29 11:58:11 -07:00
) ;
2019-06-23 03:35:23 -07:00
}
/ * *
* Returns a proxy to query data from the environment
*
* @private
* @returns
* @memberof WorkflowDataGetter
* /
private envGetter() {
2022-06-08 12:06:38 -07:00
const that = this ;
2021-08-29 11:58:11 -07:00
return new Proxy (
{ } ,
{
get ( target , name , receiver ) {
2022-06-08 12:06:38 -07:00
if ( process . env . N8N_BLOCK_ENV_ACCESS_IN_NODE === 'true' ) {
throw new ExpressionError ( 'Environment variable access got disabled' , {
causeDetailed :
'If you need access please contact the administrator to remove the environment variable ‘ N8N_BLOCK_ENV_ACCESS_IN_NODE‘ ' ,
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
failExecution : true ,
} ) ;
}
2021-08-29 11:58:11 -07:00
return process . env [ name . toString ( ) ] ;
} ,
2020-10-22 06:46:03 -07:00
} ,
2021-08-29 11:58:11 -07:00
) ;
2019-06-23 03:35:23 -07:00
}
2020-02-15 17:07:01 -08:00
/ * *
2022-09-02 07:13:17 -07:00
* Returns a proxy to query data from the workflow
2020-02-15 17:07:01 -08:00
*
* @private
* @returns
* @memberof WorkflowDataProxy
* /
private workflowGetter() {
2021-08-29 11:58:11 -07:00
const allowedValues = [ 'active' , 'id' , 'name' ] ;
2020-02-15 17:07:01 -08:00
const that = this ;
2021-08-29 11:58:11 -07:00
return new Proxy (
{ } ,
{
2021-12-23 02:41:46 -08:00
ownKeys ( target ) {
return allowedValues ;
} ,
getOwnPropertyDescriptor ( k ) {
return {
enumerable : true ,
configurable : true ,
} ;
} ,
2021-08-29 11:58:11 -07:00
get ( target , name , receiver ) {
if ( ! allowedValues . includes ( name . toString ( ) ) ) {
throw new Error ( ` The key " ${ name . toString ( ) } " is not supported! ` ) ;
}
2020-02-15 17:07:01 -08:00
2022-09-09 07:34:50 -07:00
return that . workflow [ name as keyof typeof target ] ;
2021-08-29 11:58:11 -07:00
} ,
2020-10-22 06:46:03 -07:00
} ,
2021-08-29 11:58:11 -07:00
) ;
2020-02-15 17:07:01 -08:00
}
2019-06-23 03:35:23 -07:00
/ * *
* Returns a proxy to query data of all nodes
*
* @private
* @returns
* @memberof WorkflowDataGetter
* /
private nodeGetter() {
const that = this ;
2021-08-29 11:58:11 -07:00
return new Proxy (
{ } ,
{
get ( target , name , receiver ) {
return that . nodeDataGetter ( name . toString ( ) ) ;
} ,
2020-10-22 06:46:03 -07:00
} ,
2021-08-29 11:58:11 -07:00
) ;
2019-06-23 03:35:23 -07:00
}
/ * *
* Returns the data proxy object which allows to query data from current run
*
* @returns
* @memberof WorkflowDataGetter
* /
2019-09-04 05:53:39 -07:00
getDataProxy ( ) : IWorkflowDataProxyData {
2019-06-23 03:35:23 -07:00
const that = this ;
2022-03-13 01:34:44 -08:00
const getNodeOutput = ( nodeName? : string , branchIndex? : number , runIndex? : number ) = > {
let executionData : INodeExecutionData [ ] ;
if ( nodeName === undefined ) {
executionData = that . connectionInputData ;
} else {
branchIndex = branchIndex || 0 ;
runIndex = runIndex === undefined ? - 1 : runIndex ;
executionData = that . getNodeExecutionData ( nodeName , false , branchIndex , runIndex ) ;
}
return executionData ;
} ;
// replacing proxies with the actual data.
const jmespathWrapper = ( data : IDataObject | IDataObject [ ] , query : string ) = > {
if ( ! Array . isArray ( data ) && typeof data === 'object' ) {
return jmespath . search ( { . . . data } , query ) ;
}
return jmespath . search ( data , query ) ;
} ;
2022-06-03 08:25:07 -07:00
const createExpressionError = (
message : string ,
context ? : {
causeDetailed? : string ;
2022-07-22 03:19:45 -07:00
description? : string ;
descriptionTemplate? : string ;
messageTemplate? : string ;
2022-06-03 08:25:07 -07:00
} ,
2022-07-22 03:19:45 -07:00
nodeName? : string ,
2022-06-03 08:25:07 -07:00
) = > {
2022-07-22 03:19:45 -07:00
if ( nodeName ) {
const pinData = this . workflow . getPinDataOfNode ( nodeName ) ;
if ( pinData ) {
if ( ! context ) {
context = { } ;
}
message = ` ‘ ${ nodeName } ‘ must be unpinned to execute` ;
context . description = ` To fetch the data the expression needs, The node ‘ ${ nodeName } ’ needs to execute without being pinned. <a>Unpin it</a>` ;
context . description = ` To fetch the data for the expression, you must unpin the node ' ${ nodeName } ' and execute the workflow again. ` ;
context . descriptionTemplate = ` To fetch the data for the expression under '%%PARAMETER%%', you must unpin the node ' ${ nodeName } ' and execute the workflow again. ` ;
}
}
2022-06-03 08:25:07 -07:00
return new ExpressionError ( message , {
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
failExecution : true ,
. . . context ,
} ) ;
} ;
const getPairedItem = (
destinationNodeName : string ,
incomingSourceData : ISourceData | null ,
pairedItem : IPairedItemData ,
) : INodeExecutionData | null = > {
let taskData : ITaskData ;
let sourceData : ISourceData | null = incomingSourceData ;
if ( typeof pairedItem === 'number' ) {
pairedItem = {
item : pairedItem ,
} ;
}
2022-07-22 03:19:45 -07:00
let nodeBeforeLast : string | undefined ;
2022-06-03 08:25:07 -07:00
while ( sourceData !== null && destinationNodeName !== sourceData . previousNode ) {
taskData =
that . runExecutionData ! . resultData . runData [ sourceData . previousNode ] [
sourceData ? . previousNodeRun || 0
] ;
const previousNodeOutput = sourceData . previousNodeOutput || 0 ;
if ( previousNodeOutput >= taskData . data ! . main . length ) {
// `Could not resolve as the defined node-output is not valid on node '${sourceData.previousNode}'.`
2022-07-22 03:19:45 -07:00
throw createExpressionError (
'Can’ t get data for expression' ,
{
messageTemplate : 'Can’ t get data for expression under ‘ %%PARAMETER%%’ ' ,
description : ` Apologies, this is an internal error. See details for more information ` ,
causeDetailed :
'Referencing a non-existent output on a node, problem with source data' ,
} ,
nodeBeforeLast ,
) ;
2022-06-03 08:25:07 -07:00
}
if ( pairedItem . item >= taskData . data ! . main [ previousNodeOutput ] ! . length ) {
// `Could not resolve as the defined item index is not valid on node '${sourceData.previousNode}'.
2022-07-22 03:19:45 -07:00
throw createExpressionError (
'Can’ t get data for expression' ,
{
messageTemplate : ` Can’ t get data for expression under ‘ %%PARAMETER%%’ ` ,
description : ` Item points to an item which does not exist ` ,
causeDetailed : ` The pairedItem data points to an item ‘ ${ pairedItem . item } ‘ which does not exist on node ‘ ${ sourceData . previousNode } ‘ (output node did probably supply a wrong one)` ,
} ,
nodeBeforeLast ,
) ;
2022-06-03 08:25:07 -07:00
}
const itemPreviousNode : INodeExecutionData =
taskData . data ! . main [ previousNodeOutput ] ! [ pairedItem . item ] ;
if ( itemPreviousNode . pairedItem === undefined ) {
// `Could not resolve, as pairedItem data is missing on node '${sourceData.previousNode}'.`,
2022-07-22 03:19:45 -07:00
throw createExpressionError (
'Can’ t get data for expression' ,
{
messageTemplate : ` Can’ t get data for expression under ‘ %%PARAMETER%%’ ` ,
description : ` To fetch the data from other nodes that this expression needs, more information is needed from the node ‘ ${ sourceData . previousNode } ’ ` ,
causeDetailed : ` Missing pairedItem data (node ‘ ${ sourceData . previousNode } ’ did probably not supply it)` ,
} ,
sourceData . previousNode ,
) ;
2022-06-03 08:25:07 -07:00
}
if ( Array . isArray ( itemPreviousNode . pairedItem ) ) {
// Item is based on multiple items so check all of them
const results = itemPreviousNode . pairedItem
// eslint-disable-next-line @typescript-eslint/no-loop-func
. map ( ( item ) = > {
try {
const itemInput = item . input || 0 ;
if ( itemInput >= taskData . source . length ) {
// `Could not resolve pairedItem as the defined node input '${itemInput}' does not exist on node '${sourceData!.previousNode}'.`
// Actual error does not matter as it gets caught below and `null` will be returned
throw new Error ( 'Not found' ) ;
}
return getPairedItem ( destinationNodeName , taskData . source [ itemInput ] , item ) ;
} catch ( error ) {
// Means pairedItem could not be found
return null ;
}
} )
. filter ( ( result ) = > result !== null ) ;
if ( results . length !== 1 ) {
throw createExpressionError ( 'Invalid expression' , {
messageTemplate : 'Invalid expression under ‘ %%PARAMETER%%’ ' ,
description : ` The expression uses data in node ‘ ${ destinationNodeName } ’ but there is more than one matching item in that node` ,
} ) ;
}
return results [ 0 ] ;
}
// pairedItem is not an array
if ( typeof itemPreviousNode . pairedItem === 'number' ) {
pairedItem = {
item : itemPreviousNode.pairedItem ,
} ;
} else {
pairedItem = itemPreviousNode . pairedItem ;
}
const itemInput = pairedItem . input || 0 ;
if ( itemInput >= taskData . source . length ) {
if ( taskData . source . length === 0 ) {
// A trigger node got reached, so looks like that that item can not be resolved
throw createExpressionError ( 'Invalid expression' , {
messageTemplate : 'Invalid expression under ‘ %%PARAMETER%%’ ' ,
description : ` The expression uses data in node ‘ ${ destinationNodeName } ’ but there is no path back to it. Please check this node is connected to node ‘ ${ that . activeNodeName } ’ (there can be other nodes in between).` ,
} ) ;
}
// `Could not resolve pairedItem as the defined node input '${itemInput}' does not exist on node '${sourceData.previousNode}'.`
2022-07-22 03:19:45 -07:00
throw createExpressionError (
'Can’ t get data for expression' ,
{
messageTemplate : ` Can’ t get data for expression under ‘ %%PARAMETER%%’ ` ,
description : ` Item points to a node input which does not exist ` ,
causeDetailed : ` The pairedItem data points to a node input ‘ ${ itemInput } ‘ which does not exist on node ‘ ${ sourceData . previousNode } ‘ (node did probably supply a wrong one)` ,
} ,
nodeBeforeLast ,
) ;
2022-06-03 08:25:07 -07:00
}
2022-07-22 03:19:45 -07:00
nodeBeforeLast = sourceData . previousNode ;
2022-06-03 08:25:07 -07:00
sourceData = taskData . source [ pairedItem . input || 0 ] || null ;
}
if ( sourceData === null ) {
2022-09-02 07:13:17 -07:00
// 'Could not resolve, probably no pairedItem exists.'
2022-07-22 03:19:45 -07:00
throw createExpressionError (
'Can’ t get data for expression' ,
{
messageTemplate : ` Can’ t get data for expression under ‘ %%PARAMETER%%’ ` ,
2022-09-02 07:13:17 -07:00
description : ` Could not resolve, probably no pairedItem exists ` ,
2022-07-22 03:19:45 -07:00
} ,
nodeBeforeLast ,
) ;
2022-06-03 08:25:07 -07:00
}
taskData =
that . runExecutionData ! . resultData . runData [ sourceData . previousNode ] [
sourceData ? . previousNodeRun || 0
] ;
const previousNodeOutput = sourceData . previousNodeOutput || 0 ;
if ( previousNodeOutput >= taskData . data ! . main . length ) {
// `Could not resolve pairedItem as the node output '${previousNodeOutput}' does not exist on node '${sourceData.previousNode}'`
throw createExpressionError ( 'Can’ t get data for expression' , {
messageTemplate : ` Can’ t get data for expression under ‘ %%PARAMETER%%’ ` ,
description : ` Item points to a node output which does not exist ` ,
causeDetailed : ` The sourceData points to a node output ‘ ${ previousNodeOutput } ‘ which does not exist on node ‘ ${ sourceData . previousNode } ‘ (output node did probably supply a wrong one)` ,
} ) ;
}
if ( pairedItem . item >= taskData . data ! . main [ previousNodeOutput ] ! . length ) {
// `Could not resolve pairedItem as the item with the index '${pairedItem.item}' does not exist on node '${sourceData.previousNode}'.`
2022-07-22 03:19:45 -07:00
throw createExpressionError (
'Can’ t get data for expression' ,
{
messageTemplate : ` Can’ t get data for expression under ‘ %%PARAMETER%%’ ` ,
description : ` Item points to an item which does not exist ` ,
causeDetailed : ` The pairedItem data points to an item ‘ ${ pairedItem . item } ‘ which does not exist on node ‘ ${ sourceData . previousNode } ‘ (output node did probably supply a wrong one)` ,
} ,
nodeBeforeLast ,
) ;
2022-06-03 08:25:07 -07:00
}
return taskData . data ! . main [ previousNodeOutput ] ! [ pairedItem . item ] ;
} ;
2019-06-23 03:35:23 -07:00
const base = {
2022-03-13 01:34:44 -08:00
$ : ( nodeName : string ) = > {
if ( ! nodeName ) {
2022-06-03 08:25:07 -07:00
throw new ExpressionError ( 'When calling $(), please specify a node' , {
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
failExecution : true ,
} ) ;
2022-03-13 01:34:44 -08:00
}
return new Proxy (
{ } ,
{
get ( target , property , receiver ) {
if ( property === 'pairedItem' ) {
2022-06-03 08:25:07 -07:00
return ( itemIndex? : number ) = > {
if ( itemIndex === undefined ) {
itemIndex = that . itemIndex ;
2022-03-13 01:34:44 -08:00
}
2022-06-03 08:25:07 -07:00
const executionData = that . connectionInputData ;
// As we operate on the incoming item we can be sure that pairedItem is not an
// array. After all can it only come from exactly one previous node via a certain
// input. For that reason do we not have to consider the array case.
const pairedItem = executionData [ itemIndex ] . pairedItem as IPairedItemData ;
if ( pairedItem === undefined ) {
throw new ExpressionError ( 'Can’ t get data for expression' , {
messageTemplate : ` Can’ t get data for expression under ‘ %%PARAMETER%%’ ` ,
description : ` To fetch the data from other nodes that this expression needs, more information is needed from the node ‘ ${ that . activeNodeName } ‘ ` ,
causeDetailed : ` Missing pairedItem data (node ‘ ${ that . activeNodeName } ‘ did probably not supply it)` ,
runIndex : that.runIndex ,
itemIndex ,
failExecution : true ,
} ) ;
}
if ( ! that . executeData ? . source ) {
throw new ExpressionError ( 'Can’ t get data for expression' , {
messageTemplate : 'Can’ t get data for expression under ‘ %%PARAMETER%%’ ' ,
description : ` Apologies, this is an internal error. See details for more information ` ,
causeDetailed : ` Missing sourceData (probably an internal error) ` ,
runIndex : that.runIndex ,
itemIndex ,
failExecution : true ,
} ) ;
}
// Before resolving the pairedItem make sure that the requested node comes in the
// graph before the current one
const parentNodes = that . workflow . getParentNodes ( that . activeNodeName ) ;
if ( ! parentNodes . includes ( nodeName ) ) {
throw new ExpressionError ( 'Invalid expression' , {
messageTemplate : 'Invalid expression under ‘ %%PARAMETER%%’ ' ,
description : ` The expression uses data in node ‘ ${ nodeName } ’ but there is no path back to it. Please check this node is connected to node ‘ ${ that . activeNodeName } ’ (there can be other nodes in between).` ,
runIndex : that.runIndex ,
itemIndex ,
failExecution : true ,
} ) ;
}
const sourceData : ISourceData = that . executeData ? . source . main ! [
pairedItem . input || 0
] as ISourceData ;
return getPairedItem ( nodeName , sourceData , pairedItem ) ;
2022-03-13 01:34:44 -08:00
} ;
}
if ( property === 'item' ) {
return ( itemIndex? : number , branchIndex? : number , runIndex? : number ) = > {
if ( itemIndex === undefined ) {
itemIndex = that . itemIndex ;
branchIndex = 0 ;
runIndex = that . runIndex ;
}
const executionData = getNodeOutput ( nodeName , branchIndex , runIndex ) ;
2022-06-03 08:25:07 -07:00
2022-03-13 01:34:44 -08:00
if ( executionData [ itemIndex ] ) {
return executionData [ itemIndex ] ;
}
let errorMessage = '' ;
if ( branchIndex === undefined && runIndex === undefined ) {
errorMessage = `
No item found at index $ { itemIndex }
( for node "${nodeName}" ) ` ;
throw new Error ( errorMessage ) ;
}
if ( branchIndex === undefined ) {
errorMessage = `
No item found at index $ { itemIndex }
in run $ { runIndex || that . runIndex }
( for node "${nodeName}" ) ` ;
throw new Error ( errorMessage ) ;
}
if ( runIndex === undefined ) {
errorMessage = `
No item found at index $ { itemIndex }
of branch $ { branchIndex || 0 }
( for node "${nodeName}" ) ` ;
throw new Error ( errorMessage ) ;
}
errorMessage = `
No item found at index $ { itemIndex }
of branch $ { branchIndex || 0 }
in run $ { runIndex || that . runIndex }
( for node "${nodeName}" ) ` ;
throw new Error ( errorMessage ) ;
} ;
}
if ( property === 'first' ) {
return ( branchIndex? : number , runIndex? : number ) = > {
const executionData = getNodeOutput ( nodeName , branchIndex , runIndex ) ;
if ( executionData [ 0 ] ) return executionData [ 0 ] ;
return undefined ;
} ;
}
if ( property === 'last' ) {
return ( branchIndex? : number , runIndex? : number ) = > {
const executionData = getNodeOutput ( nodeName , branchIndex , runIndex ) ;
if ( ! executionData . length ) return undefined ;
if ( executionData [ executionData . length - 1 ] ) {
return executionData [ executionData . length - 1 ] ;
}
return undefined ;
} ;
}
if ( property === 'all' ) {
return ( branchIndex? : number , runIndex? : number ) = >
getNodeOutput ( nodeName , branchIndex , runIndex ) ;
}
if ( property === 'context' ) {
return that . nodeContextGetter ( nodeName ) ;
}
if ( property === 'params' ) {
return that . workflow . getNode ( nodeName ) ? . parameters ;
}
return Reflect . get ( target , property , receiver ) ;
} ,
} ,
) ;
} ,
$input : new Proxy (
{ } ,
{
get ( target , property , receiver ) {
if ( property === 'thisItem' ) {
return that . connectionInputData [ that . itemIndex ] ;
}
if ( property === 'item' ) {
return ( itemIndex? : number ) = > {
if ( itemIndex === undefined ) itemIndex = that . itemIndex ;
const result = that . connectionInputData ;
if ( result [ itemIndex ] ) {
return result [ itemIndex ] ;
}
return undefined ;
} ;
}
if ( property === 'first' ) {
return ( ) = > {
const result = that . connectionInputData ;
if ( result [ 0 ] ) {
return result [ 0 ] ;
}
return undefined ;
} ;
}
if ( property === 'last' ) {
return ( ) = > {
const result = that . connectionInputData ;
if ( result . length && result [ result . length - 1 ] ) {
return result [ result . length - 1 ] ;
}
return undefined ;
} ;
}
if ( property === 'all' ) {
return ( ) = > {
const result = that . connectionInputData ;
if ( result . length ) {
return result ;
}
return [ ] ;
} ;
}
return Reflect . get ( target , property , receiver ) ;
} ,
} ,
) ,
$thisItem : that.connectionInputData [ that . itemIndex ] ,
2019-06-23 03:35:23 -07:00
$binary : { } , // Placeholder
$data : { } , // Placeholder
$env : this.envGetter ( ) ,
2020-04-13 07:51:04 -07:00
$evaluateExpression : ( expression : string , itemIndex? : number ) = > {
itemIndex = itemIndex || that . itemIndex ;
2021-08-29 11:58:11 -07:00
return that . workflow . expression . getParameterValue (
` = ${ expression } ` ,
that . runExecutionData ,
that . runIndex ,
itemIndex ,
that . activeNodeName ,
that . connectionInputData ,
that . mode ,
2022-04-10 02:33:42 -07:00
that . timezone ,
2021-08-29 11:58:11 -07:00
that . additionalKeys ,
2022-06-03 08:25:07 -07:00
that . executeData ,
2021-08-29 11:58:11 -07:00
) ;
2020-04-13 07:51:04 -07:00
} ,
2020-04-13 06:57:01 -07:00
$item : ( itemIndex : number , runIndex? : number ) = > {
const defaultReturnRunIndex = runIndex === undefined ? - 1 : runIndex ;
2021-08-29 11:58:11 -07:00
const dataProxy = new WorkflowDataProxy (
this . workflow ,
this . runExecutionData ,
this . runIndex ,
itemIndex ,
this . activeNodeName ,
this . connectionInputData ,
that . siblingParameters ,
that . mode ,
2022-04-10 02:33:42 -07:00
that . defaultTimezone ,
2021-08-29 11:58:11 -07:00
that . additionalKeys ,
2022-06-03 08:25:07 -07:00
that . executeData ,
2021-08-29 11:58:11 -07:00
defaultReturnRunIndex ,
) ;
2020-01-03 14:37:13 -08:00
return dataProxy . getDataProxy ( ) ;
} ,
2020-04-12 09:42:29 -07:00
$items : ( nodeName? : string , outputIndex? : number , runIndex? : number ) = > {
let executionData : INodeExecutionData [ ] ;
if ( nodeName === undefined ) {
executionData = that . connectionInputData ;
} else {
outputIndex = outputIndex || 0 ;
2020-04-13 06:57:01 -07:00
runIndex = runIndex === undefined ? - 1 : runIndex ;
2020-04-12 09:42:29 -07:00
executionData = that . getNodeExecutionData ( nodeName , false , outputIndex , runIndex ) ;
}
return executionData ;
} ,
2020-02-15 16:01:00 -08:00
$json : { } , // Placeholder
2019-06-23 03:35:23 -07:00
$node : this.nodeGetter ( ) ,
2021-01-27 00:02:20 -08:00
$self : this.selfGetter ( ) ,
2019-06-23 03:35:23 -07:00
$parameter : this.nodeParameterGetter ( this . activeNodeName ) ,
2021-07-23 09:03:42 -07:00
$position : this.itemIndex ,
2020-04-13 06:57:01 -07:00
$runIndex : this.runIndex ,
2021-01-29 00:31:40 -08:00
$mode : this.mode ,
2020-02-15 16:01:00 -08:00
$workflow : this.workflowGetter ( ) ,
2022-03-13 01:34:44 -08:00
$thisRunIndex : this.runIndex ,
$thisItemIndex : this.itemIndex ,
$now : DateTime.now ( ) ,
$today : DateTime.now ( ) . set ( { hour : 0 , minute : 0 , second : 0 , millisecond : 0 } ) ,
$jmespath : jmespathWrapper ,
// eslint-disable-next-line @typescript-eslint/naming-convention
DateTime ,
// eslint-disable-next-line @typescript-eslint/naming-convention
Interval ,
// eslint-disable-next-line @typescript-eslint/naming-convention
Duration ,
2021-08-21 05:11:32 -07:00
. . . that . additionalKeys ,
2019-06-23 03:35:23 -07:00
} ;
return new Proxy ( base , {
get ( target , name , receiver ) {
2020-02-15 16:01:00 -08:00
if ( [ '$data' , '$json' ] . includes ( name as string ) ) {
2022-09-09 07:34:50 -07:00
return that . nodeDataGetter ( that . activeNodeName , true ) ? . json ;
2021-08-29 11:58:11 -07:00
}
if ( name === '$binary' ) {
2022-09-09 07:34:50 -07:00
return that . nodeDataGetter ( that . activeNodeName , true ) ? . binary ;
2019-06-23 03:35:23 -07:00
}
return Reflect . get ( target , name , receiver ) ;
2020-10-22 06:46:03 -07:00
} ,
2019-06-23 03:35:23 -07:00
} ) ;
}
}