2021-08-21 05:11:32 -07:00
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
2020-04-12 09:42:29 -07:00
/* eslint-disable @typescript-eslint/no-unused-vars */
2023-07-31 02:00:48 -07:00
2020-04-12 09:42:29 -07:00
/* eslint-disable @typescript-eslint/no-this-alias */
2021-08-21 05:11:32 -07:00
/* eslint-disable @typescript-eslint/no-unsafe-return */
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' ;
2023-01-27 05:56:56 -08:00
import type {
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 ,
2022-09-22 10:04:26 -07:00
INodeParameterResourceLocator ,
2022-09-21 06:44:45 -07:00
NodeParameterValueType ,
2021-01-29 00:31:40 -08:00
WorkflowExecuteMode ,
2023-04-19 04:09:46 -07:00
ProxyInput ,
2022-09-23 07:14:28 -07:00
} from './Interfaces' ;
import * as NodeHelpers from './NodeHelpers' ;
import { ExpressionError } from './ExpressionError' ;
import type { Workflow } from './Workflow' ;
2023-03-16 07:14:34 -07:00
import { augmentArray , augmentObject } from './AugmentObject' ;
2023-08-07 02:52:33 -07:00
import { deepCopy } from './utils' ;
2019-06-23 03:35:23 -07:00
2022-09-22 10:04:26 -07:00
export function isResourceLocatorValue ( value : unknown ) : value is INodeParameterResourceLocator {
return Boolean (
typeof value === 'object' && value && 'mode' in value && 'value' in value && '__rl' in value ,
) ;
}
2022-10-13 05:28:02 -07:00
const SCRIPTING_NODE_TYPES = [
'n8n-nodes-base.function' ,
'n8n-nodes-base.functionItem' ,
'n8n-nodes-base.code' ,
] ;
const isScriptingNode = ( nodeName : string , workflow : Workflow ) = > {
const node = workflow . getNode ( nodeName ) ;
return node && SCRIPTING_NODE_TYPES . includes ( node . type ) ;
} ;
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
2023-10-02 08:33:43 -07:00
private contextNodeName : string ;
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-01-27 00:02:20 -08:00
private selfData : IDataObject ;
2019-06-23 03:35:23 -07:00
2021-08-21 05:11:32 -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 ;
2023-10-02 08:33:43 -07:00
// TODO: Clean that up at some point and move all the options into an options object
2021-08-21 05:11:32 -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-21 05:11:32 -07:00
additionalKeys : IWorkflowDataProxyAdditionalKeys ,
2022-06-03 08:25:07 -07:00
executeData? : IExecuteData ,
2021-08-21 05:11:32 -07:00
defaultReturnRunIndex = - 1 ,
selfData = { } ,
2023-10-02 08:33:43 -07:00
contextNodeName? : string ,
2021-08-21 05:11:32 -07:00
) {
2022-10-13 05:28:02 -07:00
this . activeNodeName = activeNodeName ;
2023-10-02 08:33:43 -07:00
this . contextNodeName = contextNodeName || activeNodeName ;
2019-06-23 03:35:23 -07:00
this . workflow = workflow ;
2022-10-13 05:28:02 -07:00
2023-10-02 08:33:43 -07:00
this . runExecutionData = isScriptingNode ( this . contextNodeName , workflow )
2023-03-16 07:14:34 -07:00
? runExecutionData !== null
? augmentObject ( runExecutionData )
: null
2022-10-13 05:28:02 -07:00
: runExecutionData ;
2023-10-02 08:33:43 -07:00
this . connectionInputData = isScriptingNode ( this . contextNodeName , workflow )
2023-03-16 07:14:34 -07:00
? augmentArray ( connectionInputData )
2022-10-13 05:28:02 -07:00
: connectionInputData ;
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 ;
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 ;
2023-03-24 05:11:48 -07:00
this . timezone = workflow . settings ? . timezone ? ? 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
* /
private nodeContextGetter ( nodeName : string ) {
const that = this ;
const node = this . workflow . nodes [ nodeName ] ;
2023-02-02 03:35:38 -08:00
if ( ! that . runExecutionData ? . executionData && that . connectionInputData . length > 0 ) {
2022-12-07 03:07:32 -08:00
return { } ; // incoming connection has pinned data, so stub context object
}
2023-01-06 01:07:36 -08:00
if ( ! that . runExecutionData ? . executionData && ! that . runExecutionData ? . resultData ) {
2022-09-29 14:02:25 -07:00
throw new ExpressionError (
2022-12-29 03:20:43 -08:00
"The workflow hasn't been executed yet, so you can't reference any context data" ,
2022-09-29 14:02:25 -07:00
{
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
} ,
) ;
}
2019-06-23 03:35:23 -07:00
return new Proxy (
{ } ,
{
2023-07-07 07:43:45 -07:00
has : ( ) = > true ,
2019-06-23 03:35:23 -07:00
ownKeys ( target ) {
if ( Reflect . ownKeys ( target ) . length === 0 ) {
// Target object did not get set yet
Object . assign ( target , NodeHelpers . getContext ( that . runExecutionData ! , 'node' , node ) ) ;
}
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 ) {
2023-02-02 03:35:38 -08:00
if ( name === 'isProxy' ) return true ;
2023-07-31 02:00:48 -07:00
2019-06-23 03:35:23 -07:00
name = name . toString ( ) ;
const contextData = NodeHelpers . getContext ( that . runExecutionData ! , 'node' , node ) ;
return contextData [ name ] ;
2021-08-29 11:58:11 -07:00
} ,
2020-10-22 06:46:03 -07:00
} ,
2019-06-23 03:35:23 -07:00
) ;
}
2021-01-27 00:02:20 -08:00
private selfGetter() {
const that = this ;
return new Proxy (
{ } ,
{
2023-07-07 07:43:45 -07:00
has : ( ) = > true ,
2021-01-27 00:02:20 -08:00
ownKeys ( target ) {
return Reflect . ownKeys ( target ) ;
} ,
2023-07-31 02:00:48 -07:00
2021-01-27 00:02:20 -08:00
get ( target , name , receiver ) {
2023-02-02 03:35:38 -08:00
if ( name === 'isProxy' ) return true ;
2021-01-27 00:02:20 -08:00
name = name . toString ( ) ;
return that . selfData [ name ] ;
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
* /
private nodeParameterGetter ( nodeName : string ) {
const that = this ;
const node = this . workflow . nodes [ nodeName ] ;
return new Proxy ( node . parameters , {
2023-07-07 07:43:45 -07:00
has : ( ) = > true ,
2019-06-23 03:35:23 -07:00
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 ) {
2023-02-02 03:35:38 -08:00
if ( name === 'isProxy' ) return true ;
2023-08-07 02:52:33 -07:00
if ( name === 'toJSON' ) return ( ) = > deepCopy ( target ) ;
2019-06-23 03:35:23 -07:00
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
2023-08-07 02:52:33 -07:00
// Avoid recursion
if ( returnValue === ` ={{ $ parameter. ${ name } }} ` ) return undefined ;
2022-09-22 10:04:26 -07:00
if ( isResourceLocatorValue ( returnValue ) ) {
if ( returnValue . __regex && typeof returnValue . value === 'string' ) {
const expr = new RegExp ( returnValue . __regex ) ;
const extracted = expr . exec ( returnValue . value ) ;
if ( extracted && extracted . length >= 2 ) {
returnValue = extracted [ 1 ] ;
} else {
return returnValue . value ;
}
} else {
returnValue = returnValue . value ;
}
}
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-21 05:11:32 -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-21 05:11:32 -07:00
that . additionalKeys ,
2022-06-03 08:25:07 -07:00
that . executeData ,
2023-10-02 08:33:43 -07:00
false ,
{ } ,
that . contextNodeName ,
2021-08-21 05:11:32 -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
* /
private getNodeExecutionData (
nodeName : string ,
shortSyntax = false ,
outputIndex? : number ,
runIndex? : number ,
) : INodeExecutionData [ ] {
const that = this ;
let executionData : INodeExecutionData [ ] ;
if ( ! shortSyntax ) {
// Long syntax got used to return data from node in path
if ( that . runExecutionData === null ) {
2022-09-29 14:02:25 -07:00
throw new ExpressionError (
2022-12-29 03:20:43 -08:00
"The workflow hasn't been executed yet, so you can't reference any output data" ,
2022-09-29 14:02:25 -07:00
{
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-12-01 04:26:22 -08:00
throw new ExpressionError ( ` no data, execute " ${ nodeName } " node first ` , {
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
} ) ;
2022-03-13 01:34:44 -08:00
}
2022-12-01 04:26:22 -08:00
throw new ExpressionError ( ` " ${ nodeName } " node doesn't exist ` , {
2022-06-03 08:25:07 -07:00
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 ;
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 ! ;
2023-07-31 02:00:48 -07:00
if ( ! taskData . main ? . length || taskData . main [ 0 ] === null ) {
2020-04-12 09:42:29 -07:00
// throw new Error(`No data found for item-index: "${itemIndex}"`);
2022-12-29 03:20:43 -08:00
throw new ExpressionError ( 'No data found from "main" input.' , {
2022-06-03 08:25:07 -07:00
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 (
2023-10-02 08:33:43 -07:00
that . contextNodeName ,
2020-04-12 09:42:29 -07:00
nodeName ,
'main' ,
) ;
2022-06-03 08:25:07 -07:00
if ( nodeConnection === undefined ) {
2023-10-02 08:33:43 -07:00
throw new ExpressionError ( ` connect " ${ that . contextNodeName } " to " ${ nodeName } " ` , {
2022-12-01 04:26:22 -08:00
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
} ) ;
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
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
* /
private nodeDataGetter ( nodeName : string , shortSyntax = false ) {
const that = this ;
const node = this . workflow . nodes [ nodeName ] ;
return new Proxy (
2022-09-09 07:34:50 -07:00
{ binary : undefined , data : undefined , json : undefined } ,
2019-06-23 03:35:23 -07:00
{
2023-07-07 07:43:45 -07:00
has : ( ) = > true ,
2019-06-23 03:35:23 -07:00
get ( target , name , receiver ) {
2023-02-02 03:35:38 -08:00
if ( name === 'isProxy' ) return true ;
2019-06-23 03:35:23 -07:00
name = name . toString ( ) ;
2022-12-01 04:26:22 -08:00
if ( ! node ) {
throw new ExpressionError ( ` " ${ nodeName } " node doesn't exist ` ) ;
}
2020-02-15 16:01:00 -08:00
if ( [ 'binary' , 'data' , 'json' ] . includes ( name ) ) {
2019-06-23 03:35:23 -07:00
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
}
if ( [ 'data' , 'json' ] . includes ( name ) ) {
// JSON-Data
return executionData [ that . itemIndex ] . json ;
2021-08-29 11:58:11 -07:00
}
2019-06-23 03:35:23 -07:00
if ( name === 'binary' ) {
// Binary-Data
const returnData : IDataObject = { } ;
if ( ! executionData [ that . itemIndex ] . binary ) {
return returnData ;
2021-08-29 11:58:11 -07:00
}
2019-06-23 03:35:23 -07:00
2021-08-21 05:11:32 -07:00
const binaryKeyData = executionData [ that . itemIndex ] . binary ! ;
2019-06-23 03:35:23 -07:00
for ( const keyName of Object . keys ( binaryKeyData ) ) {
returnData [ keyName ] = { } ;
2021-08-29 11:58:11 -07:00
2019-06-23 03:35:23 -07:00
const binaryData = binaryKeyData [ keyName ] ;
for ( const propertyName in binaryData ) {
if ( propertyName === 'data' ) {
// Skip the data property
2023-07-31 02:00:48 -07:00
2019-06-23 03:35:23 -07:00
continue ;
2021-08-29 11:58:11 -07:00
}
2019-06-23 03:35:23 -07:00
( returnData [ keyName ] as IDataObject ) [ propertyName ] = binaryData [ propertyName ] ;
}
}
return returnData ;
2021-08-29 11:58:11 -07:00
}
2019-06-23 03:35:23 -07:00
} else if ( name === 'context' ) {
return that . nodeContextGetter ( nodeName ) ;
} else if ( name === 'parameter' ) {
// Get node parameter data
return that . nodeParameterGetter ( nodeName ) ;
2020-04-13 06:57:01 -07:00
} else if ( name === 'runIndex' ) {
2023-07-31 02:00:48 -07:00
if ( ! that . runExecutionData ? . resultData . runData [ nodeName ] ) {
2020-04-13 06:57:01 -07:00
return - 1 ;
2021-08-29 11:58:11 -07:00
}
2020-04-13 06:57:01 -07:00
return that . runExecutionData . resultData . runData [ nodeName ] . length - 1 ;
}
2019-06-23 03:35:23 -07:00
return Reflect . get ( target , name , receiver ) ;
2021-08-29 11:58:11 -07:00
} ,
2020-10-22 06:46:03 -07:00
} ,
2019-06-23 03:35:23 -07:00
) ;
}
/ * *
* Returns a proxy to query data from the environment
*
* @private
* /
private envGetter() {
2022-06-08 12:06:38 -07:00
const that = this ;
2019-06-23 03:35:23 -07:00
return new Proxy (
{ } ,
{
2023-07-07 07:43:45 -07:00
has : ( ) = > true ,
2019-06-23 03:35:23 -07:00
get ( target , name , receiver ) {
2023-02-02 03:35:38 -08:00
if ( name === 'isProxy' ) return true ;
2023-03-10 05:16:18 -08:00
if ( typeof process === 'undefined' ) {
throw new ExpressionError ( 'not accessible via UI, please run node' , {
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
} ) ;
}
if ( process . env . N8N_BLOCK_ENV_ACCESS_IN_NODE === 'true' ) {
2022-12-01 04:26:22 -08:00
throw new ExpressionError ( 'access to env vars denied' , {
2022-06-08 12:06:38 -07:00
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 ,
} ) ;
}
2019-06-23 03:35:23 -07:00
return process . env [ name . toString ( ) ] ;
2021-08-29 11:58:11 -07:00
} ,
2020-10-22 06:46:03 -07:00
} ,
2019-06-23 03:35:23 -07:00
) ;
}
2022-09-29 14:02:25 -07:00
private prevNodeGetter() {
const allowedValues = [ 'name' , 'outputIndex' , 'runIndex' ] ;
const that = this ;
return new Proxy (
{ } ,
{
2023-07-07 07:43:45 -07:00
has : ( ) = > true ,
2022-09-29 14:02:25 -07:00
ownKeys ( target ) {
return allowedValues ;
} ,
getOwnPropertyDescriptor ( k ) {
return {
enumerable : true ,
configurable : true ,
} ;
} ,
get ( target , name , receiver ) {
2023-02-02 03:35:38 -08:00
if ( name === 'isProxy' ) return true ;
2022-09-29 14:02:25 -07:00
if ( ! that . executeData ? . source ) {
// Means the previous node did not get executed yet
return undefined ;
}
2022-12-21 01:46:26 -08:00
const sourceData : ISourceData = that . executeData . source . main [ 0 ] as ISourceData ;
2022-09-29 14:02:25 -07:00
if ( name === 'name' ) {
return sourceData . previousNode ;
}
if ( name === 'outputIndex' ) {
return sourceData . previousNodeOutput || 0 ;
}
if ( name === 'runIndex' ) {
return sourceData . previousNodeRun || 0 ;
}
return Reflect . get ( target , name , receiver ) ;
} ,
} ,
) ;
}
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
* /
private workflowGetter() {
const allowedValues = [ 'active' , 'id' , 'name' ] ;
const that = this ;
return new Proxy (
{ } ,
{
2023-07-07 07:43:45 -07:00
has : ( ) = > true ,
2021-12-23 02:41:46 -08:00
ownKeys ( target ) {
return allowedValues ;
} ,
getOwnPropertyDescriptor ( k ) {
return {
enumerable : true ,
configurable : true ,
} ;
} ,
2020-02-15 17:07:01 -08:00
get ( target , name , receiver ) {
2023-02-02 03:35:38 -08:00
if ( name === 'isProxy' ) return true ;
2022-09-29 14:02:25 -07:00
if ( allowedValues . includes ( name . toString ( ) ) ) {
const value = that . workflow [ name as keyof typeof target ] ;
if ( value === undefined && name === 'id' ) {
2022-12-01 04:26:22 -08:00
throw new ExpressionError ( 'save workflow to view' , {
2022-12-29 03:20:43 -08:00
description : 'Please save the workflow first to use $workflow' ,
2022-09-29 14:02:25 -07:00
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
} ) ;
}
return value ;
2020-02-15 17:07:01 -08:00
}
2022-09-29 14:02:25 -07:00
return Reflect . get ( target , name , receiver ) ;
2021-08-29 11:58:11 -07:00
} ,
2020-10-22 06:46:03 -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
* /
private nodeGetter() {
const that = this ;
return new Proxy (
{ } ,
{
2023-07-07 07:43:45 -07:00
has : ( ) = > true ,
2019-06-23 03:35:23 -07:00
get ( target , name , receiver ) {
2023-02-02 03:35:38 -08:00
if ( name === 'isProxy' ) return true ;
2022-12-07 03:07:32 -08:00
const nodeName = name . toString ( ) ;
2023-01-13 07:22:37 -08:00
if ( that . workflow . getNode ( nodeName ) === null ) {
throw new ExpressionError ( ` " ${ nodeName } " node doesn't exist ` , {
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
} ) ;
}
2022-12-07 03:07:32 -08:00
return that . nodeDataGetter ( nodeName ) ;
2021-08-29 11:58:11 -07:00
} ,
2020-10-22 06:46:03 -07:00
} ,
2019-06-23 03:35:23 -07:00
) ;
}
/ * *
* Returns the data proxy object which allows to query data from current run
*
* /
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 ) = > {
2023-02-02 03:35:38 -08:00
if ( typeof data !== 'object' || typeof query !== 'string' ) {
throw new ExpressionError ( 'expected two arguments (Object, string) for this function' , {
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
} ) ;
}
2022-03-13 01:34:44 -08:00
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 ;
2022-10-14 09:56:04 -07:00
functionality ? : 'pairedItem' ;
2022-09-29 14:02:25 -07:00
functionOverrides ? : {
// Custom data to display for Function-Nodes
message? : string ;
description? : string ;
} ;
itemIndex? : number ;
2022-07-22 03:19:45 -07:00
messageTemplate? : string ;
2022-09-29 14:02:25 -07:00
moreInfoLink? : boolean ;
nodeCause? : string ;
runIndex? : number ;
type ? : string ;
2022-06-03 08:25:07 -07:00
} ,
) = > {
2022-10-13 05:28:02 -07:00
if ( isScriptingNode ( that . activeNodeName , that . workflow ) && context ? . functionOverrides ) {
2022-09-29 14:02:25 -07:00
// If the node in which the error is thrown is a function node,
// display a different error message in case there is one defined
message = context . functionOverrides . message || message ;
context . description = context . functionOverrides . description || context . description ;
// The error will be in the code and not on an expression on a parameter
// so remove the messageTemplate as it would overwrite the message
context . messageTemplate = undefined ;
}
if ( context ? . nodeCause ) {
const nodeName = context . nodeCause ;
2022-07-22 03:19:45 -07:00
const pinData = this . workflow . getPinDataOfNode ( nodeName ) ;
if ( pinData ) {
if ( ! context ) {
context = { } ;
}
2022-09-29 14:02:25 -07:00
message = ` ‘ Node ${ nodeName } ‘ must be unpinned to execute` ;
context . messageTemplate = undefined ;
context . description = ` To fetch the data for the expression, you must unpin the node <strong>' ${ nodeName } '</strong> and execute the workflow again. ` ;
context . descriptionTemplate = ` To fetch the data for the expression under '%%PARAMETER%%', you must unpin the node <strong>' ${ nodeName } '</strong> and execute the workflow again. ` ;
}
2022-10-13 05:28:02 -07:00
if ( context . moreInfoLink && ( pinData || isScriptingNode ( nodeName , that . workflow ) ) ) {
2022-09-29 14:02:25 -07:00
const moreInfoLink =
' <a target="_blank" href="https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-errors/">More info</a>' ;
context . description += moreInfoLink ;
2023-10-11 02:34:55 -07:00
if ( context . descriptionTemplate ) context . descriptionTemplate += moreInfoLink ;
2022-07-22 03:19:45 -07:00
}
}
2022-06-03 08:25:07 -07:00
return new ExpressionError ( message , {
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
. . . context ,
} ) ;
} ;
const getPairedItem = (
destinationNodeName : string ,
incomingSourceData : ISourceData | null ,
pairedItem : IPairedItemData ,
) : INodeExecutionData | null = > {
let taskData : ITaskData ;
let sourceData : ISourceData | null = incomingSourceData ;
2022-12-09 04:39:06 -08:00
if ( pairedItem . sourceOverwrite ) {
sourceData = pairedItem . sourceOverwrite ;
}
2022-06-03 08:25:07 -07:00
if ( typeof pairedItem === 'number' ) {
pairedItem = {
item : pairedItem ,
} ;
}
2022-09-29 14:02:25 -07:00
let currentPairedItem = pairedItem ;
2022-07-22 03:19:45 -07:00
let nodeBeforeLast : string | undefined ;
2022-10-04 05:05:46 -07:00
2023-04-14 04:33:27 -07:00
while ( sourceData !== null && destinationNodeName !== sourceData . previousNode ) {
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 ) {
2022-09-29 14:02:25 -07:00
throw createExpressionError ( 'Can’ t get data for expression' , {
messageTemplate : 'Can’ t get data for expression under ‘ %%PARAMETER%%’ field' ,
functionOverrides : {
message : 'Can’ t get data' ,
2022-07-22 03:19:45 -07:00
} ,
2022-09-29 14:02:25 -07:00
nodeCause : nodeBeforeLast ,
2022-12-29 03:20:43 -08:00
description : 'Apologies, this is an internal error. See details for more information' ,
2022-09-29 14:02:25 -07:00
causeDetailed : 'Referencing a non-existent output on a node, problem with source data' ,
type : 'internal' ,
} ) ;
2022-06-03 08:25:07 -07:00
}
if ( pairedItem . item >= taskData . data ! . main [ previousNodeOutput ] ! . length ) {
2022-09-29 14:02:25 -07:00
throw createExpressionError ( 'Can’ t get data for expression' , {
2022-12-29 03:20:43 -08:00
messageTemplate : 'Can’ t get data for expression under ‘ %%PARAMETER%%’ field' ,
2022-10-14 09:56:04 -07:00
functionality : 'pairedItem' ,
2022-09-29 14:02:25 -07:00
functionOverrides : {
message : 'Can’ t get data' ,
2022-07-22 03:19:45 -07:00
} ,
2022-09-29 14:02:25 -07:00
nodeCause : nodeBeforeLast ,
description : ` In node ‘ <strong> ${ nodeBeforeLast ! } </strong>’ , output item ${
currentPairedItem . item || 0
} $ {
sourceData . previousNodeRun
? ` of run ${ ( sourceData . previousNodeRun || 0 ) . toString ( ) } `
: ''
} points to an input item on node ‘ < strong > $ {
sourceData . previousNode
} < / strong > ‘ that doesn ’ t exist . ` ,
type : 'invalid pairing info' ,
moreInfoLink : true ,
} ) ;
2022-06-03 08:25:07 -07:00
}
const itemPreviousNode : INodeExecutionData =
taskData . data ! . main [ previousNodeOutput ] ! [ pairedItem . item ] ;
if ( itemPreviousNode . pairedItem === undefined ) {
2022-09-29 14:02:25 -07:00
throw createExpressionError ( 'Can’ t get data for expression' , {
2022-12-29 03:20:43 -08:00
messageTemplate : 'Can’ t get data for expression under ‘ %%PARAMETER%%’ field' ,
2022-10-14 09:56:04 -07:00
functionality : 'pairedItem' ,
2022-09-29 14:02:25 -07:00
functionOverrides : {
message : 'Can’ t get data' ,
2022-07-22 03:19:45 -07:00
} ,
2022-09-29 14:02:25 -07:00
nodeCause : sourceData.previousNode ,
description : ` To fetch the data from other nodes that this expression needs, more information is needed from the node ‘ <strong> ${ sourceData . previousNode } </strong>’ ` ,
causeDetailed : ` Missing pairedItem data (node ‘ ${ sourceData . previousNode } ’ probably didn’ t supply it)` ,
type : 'no pairing info' ,
moreInfoLink : true ,
} ) ;
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 ) {
2023-06-21 00:38:28 -07:00
// Check if the results are all the same
const firstResult = results [ 0 ] ;
if ( results . every ( ( result ) = > result === firstResult ) ) {
// All results are the same so return the first one
return firstResult ;
}
2022-06-03 08:25:07 -07:00
throw createExpressionError ( 'Invalid expression' , {
messageTemplate : 'Invalid expression under ‘ %%PARAMETER%%’ ' ,
2022-10-14 09:56:04 -07:00
functionality : 'pairedItem' ,
2022-09-29 14:02:25 -07:00
functionOverrides : {
description : ` The code uses data in the node ‘ <strong> ${ destinationNodeName } </strong>’ but there is more than one matching item in that node ` ,
message : 'Invalid code' ,
} ,
description : ` The expression uses data in the node ‘ <strong> ${ destinationNodeName } </strong>’ but there is more than one matching item in that node ` ,
type : 'multiple matches' ,
2022-06-03 08:25:07 -07:00
} ) ;
}
return results [ 0 ] ;
}
2022-09-29 14:02:25 -07:00
currentPairedItem = pairedItem ;
2022-06-03 08:25:07 -07:00
// 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%%’ ' ,
2022-10-14 09:56:04 -07:00
functionality : 'pairedItem' ,
2022-09-29 14:02:25 -07:00
functionOverrides : {
description : ` The code uses data in the node ‘ <strong> ${ destinationNodeName } </strong>’ but there is no path back to it. Please check this node is connected to it (there can be other nodes in between). ` ,
message : 'Invalid code' ,
} ,
description : ` The expression uses data in the node ‘ <strong> ${ destinationNodeName } </strong>’ but there is no path back to it. Please check this node is connected to it (there can be other nodes in between). ` ,
type : 'no connection' ,
moreInfoLink : true ,
2022-06-03 08:25:07 -07:00
} ) ;
}
2022-09-29 14:02:25 -07:00
throw createExpressionError ( 'Can’ t get data for expression' , {
2022-12-29 03:20:43 -08:00
messageTemplate : 'Can’ t get data for expression under ‘ %%PARAMETER%%’ field' ,
2022-10-14 09:56:04 -07:00
functionality : 'pairedItem' ,
2022-09-29 14:02:25 -07:00
functionOverrides : {
2022-12-29 03:20:43 -08:00
message : 'Can’ t get data' ,
2022-07-22 03:19:45 -07:00
} ,
2022-09-29 14:02:25 -07:00
nodeCause : nodeBeforeLast ,
description : ` In node ‘ <strong> ${ sourceData . previousNode } </strong>’ , output item ${
currentPairedItem . item || 0
} of $ {
sourceData . previousNodeRun
? ` of run ${ ( sourceData . previousNodeRun || 0 ) . toString ( ) } `
: ''
} points to a branch that doesn ’ t exist . ` ,
type : 'invalid pairing info' ,
} ) ;
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 ;
2022-12-09 04:39:06 -08:00
if ( pairedItem . sourceOverwrite ) {
sourceData = pairedItem . sourceOverwrite ;
}
2022-06-03 08:25:07 -07:00
}
if ( sourceData === null ) {
2022-09-29 14:02:25 -07:00
throw createExpressionError ( 'Can’ t get data for expression' , {
2022-12-29 03:20:43 -08:00
messageTemplate : 'Can’ t get data for expression under ‘ %%PARAMETER%%’ field' ,
2022-10-14 09:56:04 -07:00
functionality : 'pairedItem' ,
2022-09-29 14:02:25 -07:00
functionOverrides : {
2022-12-29 03:20:43 -08:00
message : 'Can’ t get data' ,
2022-07-22 03:19:45 -07:00
} ,
2022-09-29 14:02:25 -07:00
nodeCause : nodeBeforeLast ,
2023-10-02 08:33:43 -07:00
description : 'Could not resolve, probably no pairedItem exists' ,
2022-09-29 14:02:25 -07:00
type : 'no pairing info' ,
moreInfoLink : true ,
} ) ;
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 ) {
throw createExpressionError ( 'Can’ t get data for expression' , {
2022-12-29 03:20:43 -08:00
messageTemplate : 'Can’ t get data for expression under ‘ %%PARAMETER%%’ field' ,
2022-10-14 09:56:04 -07:00
functionality : 'pairedItem' ,
2022-09-29 14:02:25 -07:00
functionOverrides : {
2022-12-29 03:20:43 -08:00
message : 'Can’ t get data' ,
2022-09-29 14:02:25 -07:00
} ,
2022-12-29 03:20:43 -08:00
description : 'Item points to a node output which does not exist' ,
2022-06-03 08:25:07 -07:00
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)` ,
2022-09-29 14:02:25 -07:00
type : 'invalid pairing info' ,
2022-06-03 08:25:07 -07:00
} ) ;
}
if ( pairedItem . item >= taskData . data ! . main [ previousNodeOutput ] ! . length ) {
2022-09-29 14:02:25 -07:00
throw createExpressionError ( 'Can’ t get data for expression' , {
2022-12-29 03:20:43 -08:00
messageTemplate : 'Can’ t get data for expression under ‘ %%PARAMETER%%’ field' ,
2022-10-14 09:56:04 -07:00
functionality : 'pairedItem' ,
2022-09-29 14:02:25 -07:00
functionOverrides : {
2022-12-29 03:20:43 -08:00
message : 'Can’ t get data' ,
2022-07-22 03:19:45 -07:00
} ,
2022-09-29 14:02:25 -07:00
nodeCause : nodeBeforeLast ,
description : ` In node ‘ <strong> ${ nodeBeforeLast ! } </strong>’ , output item ${
currentPairedItem . item || 0
} $ {
sourceData . previousNodeRun
? ` of run ${ ( sourceData . previousNodeRun || 0 ) . toString ( ) } `
: ''
} points to an input item on node ‘ < strong > $ {
sourceData . previousNode
} < / strong > ‘ that doesn ’ t exist . ` ,
type : 'invalid pairing info' ,
moreInfoLink : true ,
} ) ;
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-09-29 14:02:25 -07:00
throw createExpressionError ( 'When calling $(), please specify a node' ) ;
}
const referencedNode = that . workflow . getNode ( nodeName ) ;
if ( referencedNode === null ) {
2022-12-01 04:26:22 -08:00
throw createExpressionError ( ` " ${ nodeName } " node doesn't exist ` ) ;
2022-03-13 01:34:44 -08:00
}
2023-06-22 16:45:54 -07:00
if ( ! that ? . runExecutionData ? . resultData ? . runData . hasOwnProperty ( nodeName ) ) {
throw createExpressionError ( ` no data, execute " ${ nodeName } " node first ` ) ;
}
2022-03-13 01:34:44 -08:00
return new Proxy (
{ } ,
{
2023-07-07 07:43:45 -07:00
has : ( ) = > true ,
2023-01-06 01:07:36 -08:00
ownKeys ( target ) {
return [
'pairedItem' ,
'itemMatching' ,
'item' ,
'first' ,
'last' ,
'all' ,
'context' ,
'params' ,
] ;
} ,
2022-03-13 01:34:44 -08:00
get ( target , property , receiver ) {
2023-02-02 03:35:38 -08:00
if ( property === 'isProxy' ) return true ;
2022-09-29 14:02:25 -07:00
if ( [ 'pairedItem' , 'itemMatching' , 'item' ] . includes ( property as string ) ) {
const pairedItemMethod = ( itemIndex? : number ) = > {
2022-06-03 08:25:07 -07:00
if ( itemIndex === undefined ) {
2022-09-29 14:02:25 -07:00
if ( property === 'itemMatching' ) {
throw createExpressionError ( 'Missing item index for .itemMatching()' , {
itemIndex ,
} ) ;
}
2022-06-03 08:25:07 -07:00
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 ) {
2022-09-29 14:02:25 -07:00
throw createExpressionError ( 'Can’ t get data for expression' , {
2022-12-29 03:20:43 -08:00
messageTemplate : 'Can’ t get data for expression under ‘ %%PARAMETER%%’ field' ,
2022-10-14 09:56:04 -07:00
functionality : 'pairedItem' ,
2022-09-29 14:02:25 -07:00
functionOverrides : {
description : ` To fetch the data from other nodes that this code needs, more information is needed from the node ‘ <strong> ${ that . activeNodeName } </strong>‘ ` ,
2022-12-29 03:20:43 -08:00
message : 'Can’ t get data' ,
2022-09-29 14:02:25 -07:00
} ,
description : ` To fetch the data from other nodes that this expression needs, more information is needed from the node ‘ <strong> ${ that . activeNodeName } </strong>‘ ` ,
causeDetailed : ` Missing pairedItem data (node ‘ ${ that . activeNodeName } ‘ probably didn’ t supply it)` ,
2022-06-03 08:25:07 -07:00
itemIndex ,
} ) ;
}
if ( ! that . executeData ? . source ) {
2022-09-29 14:02:25 -07:00
throw createExpressionError ( 'Can’ t get data for expression' , {
messageTemplate : 'Can’ t get data for expression under ‘ %%PARAMETER%%’ field' ,
2022-10-14 09:56:04 -07:00
functionality : 'pairedItem' ,
2022-09-29 14:02:25 -07:00
functionOverrides : {
2022-12-29 03:20:43 -08:00
message : 'Can’ t get data' ,
2022-09-29 14:02:25 -07:00
} ,
2022-12-29 03:20:43 -08:00
description :
'Apologies, this is an internal error. See details for more information' ,
causeDetailed : 'Missing sourceData (probably an internal error)' ,
2022-06-03 08:25:07 -07:00
itemIndex ,
} ) ;
}
// Before resolving the pairedItem make sure that the requested node comes in the
// graph before the current one
2023-10-02 08:33:43 -07:00
const parentNodes = that . workflow . getParentNodes ( that . contextNodeName ) ;
2022-06-03 08:25:07 -07:00
if ( ! parentNodes . includes ( nodeName ) ) {
2022-09-29 14:02:25 -07:00
throw createExpressionError ( 'Invalid expression' , {
2022-06-03 08:25:07 -07:00
messageTemplate : 'Invalid expression under ‘ %%PARAMETER%%’ ' ,
2022-10-14 09:56:04 -07:00
functionality : 'pairedItem' ,
2022-09-29 14:02:25 -07:00
functionOverrides : {
description : ` The code uses data in the node <strong>‘ ${ nodeName } ’ </strong> but there is no path back to it. Please check this node is connected to it (there can be other nodes in between).` ,
message : ` No path back to node ‘ ${ nodeName } ’ ` ,
} ,
description : ` The expression uses data in the node <strong>‘ ${ nodeName } ’ </strong> but there is no path back to it. Please check this node is connected to it (there can be other nodes in between).` ,
2022-06-03 08:25:07 -07:00
itemIndex ,
} ) ;
}
2023-10-12 08:32:14 -07:00
const sourceData : ISourceData | null =
that . executeData . source . main [ pairedItem . input || 0 ] ? ?
that . executeData . source . main [ 0 ] ;
2022-06-03 08:25:07 -07:00
return getPairedItem ( nodeName , sourceData , pairedItem ) ;
2022-03-13 01:34:44 -08:00
} ;
2022-09-29 14:02:25 -07:00
if ( property === 'item' ) {
return pairedItemMethod ( ) ;
}
return pairedItemMethod ;
2022-03-13 01:34:44 -08:00
}
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 ) ;
} ,
} ,
) ;
} ,
2023-04-19 04:09:46 -07:00
$input : new Proxy ( { } as ProxyInput , {
2023-07-07 07:43:45 -07:00
has : ( ) = > true ,
2023-04-19 04:09:46 -07:00
ownKeys ( target ) {
return [ 'all' , 'context' , 'first' , 'item' , 'last' , 'params' ] ;
} ,
getOwnPropertyDescriptor ( k ) {
return {
enumerable : true ,
configurable : true ,
} ;
} ,
get ( target , property , receiver ) {
if ( property === 'isProxy' ) return true ;
2022-09-29 14:02:25 -07:00
2023-04-19 04:09:46 -07:00
if ( property === 'item' ) {
return that . connectionInputData [ that . itemIndex ] ;
}
if ( property === 'first' ) {
return ( . . . args : unknown [ ] ) = > {
if ( args . length ) {
throw createExpressionError ( '$input.first() should have no arguments' ) ;
2022-09-29 14:02:25 -07:00
}
2023-04-19 04:09:46 -07:00
const result = that . connectionInputData ;
if ( result [ 0 ] ) {
return result [ 0 ] ;
}
return undefined ;
} ;
}
if ( property === 'last' ) {
return ( . . . args : unknown [ ] ) = > {
if ( args . length ) {
throw createExpressionError ( '$input.last() should have no arguments' ) ;
}
2022-09-29 14:02:25 -07:00
2023-04-19 04:09:46 -07:00
const result = that . connectionInputData ;
if ( result . length && result [ result . length - 1 ] ) {
return result [ result . length - 1 ] ;
2022-09-29 14:02:25 -07:00
}
2023-04-19 04:09:46 -07:00
return undefined ;
} ;
}
if ( property === 'all' ) {
return ( ) = > {
const result = that . connectionInputData ;
if ( result . length ) {
return result ;
2022-09-29 14:02:25 -07:00
}
2023-04-19 04:09:46 -07:00
return [ ] ;
} ;
}
if ( [ 'context' , 'params' ] . includes ( property as string ) ) {
// For the following properties we need the source data so fail in case it is missing
// for some reason (even though that should actually never happen)
if ( ! that . executeData ? . source ) {
throw createExpressionError ( 'Can’ t get data for expression' , {
messageTemplate : 'Can’ t get data for expression under ‘ %%PARAMETER%%’ field' ,
functionOverrides : {
message : 'Can’ t get data' ,
} ,
description :
'Apologies, this is an internal error. See details for more information' ,
causeDetailed : 'Missing sourceData (probably an internal error)' ,
runIndex : that.runIndex ,
} ) ;
2022-09-29 14:02:25 -07:00
}
2023-04-19 04:09:46 -07:00
const sourceData : ISourceData = that . executeData . source . main [ 0 ] as ISourceData ;
if ( property === 'context' ) {
return that . nodeContextGetter ( sourceData . previousNode ) ;
}
if ( property === 'params' ) {
return that . workflow . getNode ( sourceData . previousNode ) ? . parameters ;
}
}
return Reflect . get ( target , property , receiver ) ;
2022-03-13 01:34:44 -08:00
} ,
2023-04-19 04:09:46 -07:00
} ) ,
2022-03-13 01:34:44 -08:00
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-21 05:11:32 -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-21 05:11:32 -07:00
that . additionalKeys ,
2022-06-03 08:25:07 -07:00
that . executeData ,
2023-10-02 08:33:43 -07:00
false ,
{ } ,
that . contextNodeName ,
2021-08-21 05:11:32 -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-21 05:11:32 -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-21 05:11:32 -07:00
that . additionalKeys ,
2022-06-03 08:25:07 -07:00
that . executeData ,
2021-08-21 05:11:32 -07:00
defaultReturnRunIndex ,
2023-10-02 08:33:43 -07:00
that . contextNodeName ,
2021-08-21 05:11:32 -07:00
) ;
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 ) = > {
if ( nodeName === undefined ) {
2022-11-29 08:30:39 -08:00
nodeName = ( that . prevNodeGetter ( ) as { name : string } ) . name ;
2023-01-23 03:47:07 -08:00
const node = this . workflow . nodes [ nodeName ] ;
let result = that . connectionInputData ;
if ( node . executeOnce === true ) {
result = result . slice ( 0 , 1 ) ;
}
if ( result . length ) {
return result ;
}
return [ ] ;
2020-04-12 09:42:29 -07:00
}
2022-11-29 08:30:39 -08:00
outputIndex = outputIndex || 0 ;
runIndex = runIndex === undefined ? - 1 : runIndex ;
2022-11-22 07:24:16 -08:00
2022-11-29 08:30:39 -08:00
return that . getNodeExecutionData ( nodeName , false , outputIndex , runIndex ) ;
2020-04-12 09:42:29 -07:00
} ,
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 ) ,
2022-09-29 14:02:25 -07:00
$prevNode : this.prevNodeGetter ( ) ,
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-09-29 14:02:25 -07:00
$itemIndex : this.itemIndex ,
2022-03-13 01:34:44 -08:00
$now : DateTime.now ( ) ,
$today : DateTime.now ( ) . set ( { hour : 0 , minute : 0 , second : 0 , millisecond : 0 } ) ,
2022-09-29 14:02:25 -07:00
$jmesPath : jmespathWrapper ,
2022-03-13 01:34:44 -08:00
// 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 ,
2022-09-29 14:02:25 -07:00
// deprecated
$jmespath : jmespathWrapper ,
$position : this.itemIndex ,
$thisItem : that.connectionInputData [ that . itemIndex ] ,
$thisItemIndex : this.itemIndex ,
$thisRunIndex : this.runIndex ,
2019-06-23 03:35:23 -07:00
} ;
return new Proxy ( base , {
2023-07-07 07:43:45 -07:00
has : ( ) = > true ,
2019-06-23 03:35:23 -07:00
get ( target , name , receiver ) {
2023-02-02 03:35:38 -08:00
if ( name === 'isProxy' ) return true ;
2020-02-15 16:01:00 -08:00
if ( [ '$data' , '$json' ] . includes ( name as string ) ) {
2023-10-02 08:33:43 -07:00
return that . nodeDataGetter ( that . contextNodeName , true ) ? . json ;
2019-06-23 03:35:23 -07:00
}
if ( name === '$binary' ) {
2023-10-02 08:33:43 -07:00
return that . nodeDataGetter ( that . contextNodeName , 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
} ) ;
}
}