2021-08-29 11:58:11 -07:00
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable @typescript-eslint/no-this-alias */
/* eslint-disable @typescript-eslint/no-unsafe-return */
2022-03-13 01:34:44 -08:00
import * as jmespath from 'jmespath' ;
2024-09-18 00:19:33 -07:00
import { DateTime , Duration , Interval , Settings } from 'luxon' ;
2022-03-13 01:34:44 -08:00
2024-09-18 00:19:33 -07:00
import { augmentArray , augmentObject } from './AugmentObject' ;
import { SCRIPTING_NODE_TYPES } from './Constants' ;
import { ApplicationError } from './errors/application.error' ;
import { ExpressionError , type ExpressionErrorOptions } from './errors/expression.error' ;
import { getGlobalState } from './GlobalState' ;
2024-08-29 06:55:53 -07:00
import {
type IDataObject ,
type IExecuteData ,
type INodeExecutionData ,
type INodeParameters ,
type IPairedItemData ,
type IRunExecutionData ,
type ISourceData ,
type ITaskData ,
type IWorkflowDataProxyAdditionalKeys ,
type IWorkflowDataProxyData ,
type INodeParameterResourceLocator ,
type NodeParameterValueType ,
type WorkflowExecuteMode ,
type ProxyInput ,
NodeConnectionType ,
2022-09-23 07:14:28 -07:00
} from './Interfaces' ;
import * as NodeHelpers from './NodeHelpers' ;
2023-08-07 02:52:33 -07:00
import { deepCopy } from './utils' ;
2024-09-18 00:19:33 -07:00
import type { Workflow } from './Workflow' ;
2024-10-09 07:31:45 -07:00
import type { EnvProviderState } from './WorkflowDataProxyEnvProvider' ;
import { createEnvProvider , createEnvProviderState } from './WorkflowDataProxyEnvProvider' ;
2024-06-27 01:49:53 -07:00
import { getPinDataIfManualExecution } from './WorkflowDataProxyHelpers' ;
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 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 runExecutionData : IRunExecutionData | null ;
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
2022-04-10 02:33:42 -07:00
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-29 11:58:11 -07:00
constructor (
2023-10-27 05:17:52 -07:00
private workflow : Workflow ,
2021-08-29 11:58:11 -07:00
runExecutionData : IRunExecutionData | null ,
2023-10-27 05:17:52 -07:00
private runIndex : number ,
private itemIndex : number ,
private activeNodeName : string ,
2021-08-29 11:58:11 -07:00
connectionInputData : INodeExecutionData [ ] ,
2023-10-27 05:17:52 -07:00
private siblingParameters : INodeParameters ,
private mode : WorkflowExecuteMode ,
private additionalKeys : IWorkflowDataProxyAdditionalKeys ,
private executeData? : IExecuteData ,
private defaultReturnRunIndex = - 1 ,
private selfData : IDataObject = { } ,
private contextNodeName : string = activeNodeName ,
2024-10-09 07:31:45 -07:00
private envProviderState? : EnvProviderState ,
2021-08-29 11:58:11 -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 ;
2023-10-27 05:17:52 -07:00
this . timezone = workflow . settings ? . timezone ? ? getGlobalState ( ) . defaultTimezone ;
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 ,
2024-02-27 01:29:16 -08:00
type : 'no_execution_data' ,
2022-09-29 14:02:25 -07:00
} ,
) ;
}
2021-08-29 11:58:11 -07:00
return new Proxy (
{ } ,
{
2023-07-07 07:43:45 -07:00
has : ( ) = > true ,
2021-08-29 11:58:11 -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 ) ) ;
}
2019-06-23 03:35:23 -07:00
2021-08-29 11:58:11 -07:00
return Reflect . ownKeys ( target ) ;
} ,
2024-01-25 05:33:35 -08:00
getOwnPropertyDescriptor() {
2021-12-23 02:41:46 -08:00
return {
enumerable : true ,
configurable : true ,
} ;
} ,
2024-01-25 05:33:35 -08:00
get ( _ , name ) {
2023-02-02 03:35:38 -08:00
if ( name === 'isProxy' ) return true ;
2023-07-31 02:00:48 -07:00
2021-08-29 11:58:11 -07:00
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
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 (
{ } ,
{
2023-07-07 07:43:45 -07:00
has : ( ) = > true ,
2021-08-29 11:58:11 -07:00
ownKeys ( target ) {
return Reflect . ownKeys ( target ) ;
} ,
2023-07-31 02:00:48 -07:00
2024-01-25 05:33:35 -08:00
get ( _ , name ) {
2023-02-02 03:35:38 -08:00
if ( name === 'isProxy' ) return true ;
2021-08-29 11:58:11 -07:00
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
* /
private nodeParameterGetter ( nodeName : string ) {
const that = this ;
const node = this . workflow . nodes [ nodeName ] ;
2024-05-03 05:39:31 -07:00
// `node` is `undefined` only in expressions in credentials
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 ) ;
} ,
2024-01-25 05:33:35 -08:00
getOwnPropertyDescriptor() {
2021-12-23 02:41:46 -08:00
return {
enumerable : true ,
configurable : true ,
} ;
} ,
2024-01-25 05:33:35 -08:00
get ( target , name ) {
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 ) ) {
2023-11-30 03:46:45 -08:00
throw new ApplicationError ( 'Could not find sibling parameter on node' , {
extra : { nodeName , parameter : key } ,
} ) ;
2021-05-14 16:16:48 -07:00
}
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-29 11:58:11 -07:00
return that . workflow . expression . getParameterValue (
returnValue ,
that . runExecutionData ,
that . runIndex ,
that . itemIndex ,
that . activeNodeName ,
that . connectionInputData ,
that . mode ,
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-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
} ) ;
}
2024-06-27 01:49:53 -07:00
private getNodeExecutionOrPinnedData ( {
nodeName ,
branchIndex ,
runIndex ,
shortSyntax = false ,
} : {
nodeName : string ;
branchIndex? : number ;
runIndex? : number ;
shortSyntax? : boolean ;
} ) {
try {
return this . getNodeExecutionData ( nodeName , shortSyntax , branchIndex , runIndex ) ;
} catch ( e ) {
const pinData = getPinDataIfManualExecution ( this . workflow , nodeName , this . mode ) ;
if ( pinData ) {
return pinData ;
}
throw e ;
}
}
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
* /
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-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
}
2024-02-27 01:29:16 -08:00
if ( ! that . workflow . getNode ( nodeName ) ) {
2024-03-21 08:59:22 -07:00
throw new ExpressionError ( "Referenced node doesn't exist" , {
2022-06-03 08:25:07 -07:00
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
2024-03-21 08:59:22 -07:00
nodeCause : nodeName ,
descriptionKey : 'nodeNotFound' ,
2022-06-03 08:25:07 -07:00
} ) ;
2020-04-12 09:42:29 -07:00
}
2024-02-27 01:29:16 -08:00
if (
! that . runExecutionData . resultData . runData . hasOwnProperty ( nodeName ) &&
2024-06-27 01:49:53 -07:00
! getPinDataIfManualExecution ( that . workflow , nodeName , that . mode )
2024-02-27 01:29:16 -08:00
) {
2024-03-21 08:59:22 -07:00
throw new ExpressionError ( 'Referenced node is unexecuted' , {
2024-02-27 01:29:16 -08:00
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
type : 'no_node_execution_data' ,
2024-03-21 08:59:22 -07:00
descriptionKey : 'noNodeExecutionData' ,
2024-02-27 01:29:16 -08:00
nodeCause : nodeName ,
} ) ;
}
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 ! ;
2023-07-31 02:00:48 -07:00
if ( ! taskData . main ? . length || taskData . main [ 0 ] === null ) {
2023-11-30 03:46:45 -08:00
// throw new ApplicationError('No data found for item-index', { extra: { itemIndex } });
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 ,
2021-08-29 11:58:11 -07:00
nodeName ,
2024-08-29 06:55:53 -07:00
NodeConnectionType . Main ,
2021-08-29 11:58:11 -07:00
) ;
2020-04-12 09:42:29 -07:00
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
2024-11-04 01:13:09 -08:00
* @param { boolean } [ throwOnMissingExecutionData = true ] If an error should get thrown if no execution data is available
2019-06-23 03:35:23 -07:00
* /
2024-11-04 01:13:09 -08:00
private nodeDataGetter (
nodeName : string ,
shortSyntax = false ,
throwOnMissingExecutionData = true ,
) {
2019-06-23 03:35:23 -07:00
const that = this ;
const node = this . workflow . nodes [ nodeName ] ;
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
{
2023-07-07 07:43:45 -07:00
has : ( ) = > true ,
2021-08-29 11:58:11 -07:00
get ( target , name , receiver ) {
2023-02-02 03:35:38 -08:00
if ( name === 'isProxy' ) return true ;
2021-08-29 11:58:11 -07:00
name = name . toString ( ) ;
2019-06-23 03:35:23 -07:00
2022-12-01 04:26:22 -08:00
if ( ! node ) {
2024-03-21 08:59:22 -07:00
throw new ExpressionError ( "Referenced node doesn't exist" , {
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
nodeCause : nodeName ,
descriptionKey : 'nodeNotFound' ,
} ) ;
2022-12-01 04:26:22 -08:00
}
2021-08-29 11:58:11 -07:00
if ( [ 'binary' , 'data' , 'json' ] . includes ( name ) ) {
2024-06-27 01:49:53 -07:00
const executionData = that . getNodeExecutionOrPinnedData ( {
nodeName ,
shortSyntax ,
} ) ;
2024-02-27 01:29:16 -08:00
2024-11-04 01:13:09 -08:00
if ( executionData . length === 0 && ! throwOnMissingExecutionData ) {
return undefined ;
}
2024-02-27 01:29:16 -08:00
if ( executionData . length === 0 ) {
if ( that . workflow . getParentNodes ( nodeName ) . length === 0 ) {
throw new ExpressionError ( 'No execution data available' , {
messageTemplate :
'No execution data available to expression under ‘ %%PARAMETER%%’ ' ,
2024-03-21 08:59:22 -07:00
descriptionKey : 'noInputConnection' ,
2024-02-27 01:29:16 -08:00
nodeCause : nodeName ,
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
type : 'no_input_connection' ,
} ) ;
}
throw new ExpressionError ( 'No execution data available' , {
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
type : 'no_execution_data' ,
} ) ;
}
2021-08-29 11:58:11 -07:00
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
2023-07-31 02:00:48 -07:00
2021-08-29 11:58:11 -07:00
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' ) {
2023-07-31 02:00:48 -07:00
if ( ! that . runExecutionData ? . resultData . runData [ nodeName ] ) {
2021-08-29 11:58:11 -07:00
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
}
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 ,
2024-01-25 05:33:35 -08:00
ownKeys() {
2022-09-29 14:02:25 -07:00
return allowedValues ;
} ,
2024-01-25 05:33:35 -08:00
getOwnPropertyDescriptor() {
2022-09-29 14:02:25 -07:00
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() {
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 (
{ } ,
{
2023-07-07 07:43:45 -07:00
has : ( ) = > true ,
2024-01-25 05:33:35 -08:00
ownKeys() {
2021-12-23 02:41:46 -08:00
return allowedValues ;
} ,
2024-01-25 05:33:35 -08:00
getOwnPropertyDescriptor() {
2021-12-23 02:41:46 -08:00
return {
enumerable : true ,
configurable : true ,
} ;
} ,
2021-08-29 11:58:11 -07: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 ;
2021-08-29 11:58:11 -07:00
}
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
} ,
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
* /
private nodeGetter() {
const that = this ;
2021-08-29 11:58:11 -07:00
return new Proxy (
{ } ,
{
2023-07-07 07:43:45 -07:00
has : ( ) = > true ,
2024-01-25 05:33:35 -08:00
get ( _ , name ) {
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 ) {
2024-03-21 08:59:22 -07:00
throw new ExpressionError ( "Referenced node doesn't exist" , {
2023-01-13 07:22:37 -08:00
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
2024-03-21 08:59:22 -07:00
nodeCause : nodeName ,
descriptionKey : 'nodeNotFound' ,
2023-01-13 07:22:37 -08:00
} ) ;
}
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
} ,
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
*
* /
2024-11-04 01:13:09 -08:00
getDataProxy ( opts ? : { throwOnMissingExecutionData : boolean } ) : IWorkflowDataProxyData {
2019-06-23 03:35:23 -07:00
const that = this ;
2022-03-13 01:34:44 -08:00
// 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 ,
2024-02-27 01:29:16 -08:00
context? : ExpressionErrorOptions & {
moreInfoLink? : boolean ;
2022-09-29 14:02:25 -07:00
functionOverrides ? : {
// Custom data to display for Function-Nodes
message? : string ;
description? : 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 ;
2024-06-27 01:49:53 -07:00
const pinData = getPinDataIfManualExecution ( that . workflow , nodeName , that . mode ) ;
2022-07-22 03:19:45 -07:00
if ( pinData ) {
if ( ! context ) {
context = { } ;
}
2024-03-21 08:59:22 -07:00
message = ` Unpin ' ${ nodeName } ' to execute ` ;
2022-09-29 14:02:25 -07:00
context . messageTemplate = undefined ;
2024-03-21 08:59:22 -07:00
context . descriptionKey = 'pairedItemPinned' ;
2022-09-29 14:02:25 -07:00
}
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 ,
} ) ;
} ;
2024-03-21 08:59:22 -07:00
const createInvalidPairedItemError = ( { nodeName } : { nodeName : string } ) = > {
return createExpressionError ( "Can't get data for expression" , {
messageTemplate : 'Expression info invalid' ,
functionality : 'pairedItem' ,
functionOverrides : {
message : "Can't get data" ,
} ,
nodeCause : nodeName ,
descriptionKey : 'pairedItemInvalidInfo' ,
type : 'paired_item_invalid_info' ,
} ) ;
} ;
const createMissingPairedItemError = ( nodeCause : string ) = > {
return createExpressionError ( "Can't get data for expression" , {
messageTemplate : 'Info for expression missing from previous node' ,
functionality : 'pairedItem' ,
functionOverrides : {
message : "Can't get data" ,
} ,
nodeCause ,
descriptionKey : isScriptingNode ( nodeCause , that . workflow )
? 'pairedItemNoInfoCodeNode'
: 'pairedItemNoInfo' ,
causeDetailed : ` Missing pairedItem data (node ' ${ nodeCause } ' probably didn't supply it) ` ,
type : 'paired_item_no_info' ,
} ) ;
} ;
const createNoConnectionError = ( nodeCause : string ) = > {
return createExpressionError ( 'Invalid expression' , {
messageTemplate : 'No path back to referenced node' ,
functionality : 'pairedItem' ,
descriptionKey : isScriptingNode ( nodeCause , that . workflow )
? 'pairedItemNoConnectionCodeNode'
: 'pairedItemNoConnection' ,
type : 'paired_item_no_connection' ,
moreInfoLink : true ,
nodeCause ,
} ) ;
} ;
2024-04-10 05:02:02 -07:00
// eslint-disable-next-line complexity
2022-06-03 08:25:07 -07:00
const getPairedItem = (
destinationNodeName : string ,
incomingSourceData : ISourceData | null ,
pairedItem : IPairedItemData ,
) : INodeExecutionData | null = > {
2024-02-27 01:29:16 -08:00
let taskData : ITaskData | undefined ;
2022-06-03 08:25:07 -07:00
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 ) {
2024-02-27 01:29:16 -08:00
const runIndex = sourceData ? . previousNodeRun || 0 ;
const previousNodeOutput = sourceData . previousNodeOutput || 0 ;
2022-06-03 08:25:07 -07:00
taskData =
2024-02-27 01:29:16 -08:00
that . runExecutionData ? . resultData ? . runData ? . [ sourceData . previousNode ] ? . [ runIndex ] ;
2022-06-03 08:25:07 -07:00
2024-02-27 01:29:16 -08:00
if ( taskData ? . data ? . main && 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
}
2024-02-27 01:29:16 -08:00
const previousNodeOutputData =
taskData ? . data ? . main ? . [ previousNodeOutput ] ? ?
2024-06-27 01:49:53 -07:00
getPinDataIfManualExecution ( that . workflow , sourceData . previousNode , that . mode ) ? ?
[ ] ;
2024-02-27 01:29:16 -08:00
const source = taskData ? . source ? ? [ ] ;
if ( pairedItem . item >= previousNodeOutputData . length ) {
2024-03-21 08:59:22 -07:00
throw createInvalidPairedItemError ( {
nodeName : sourceData.previousNode ,
2022-09-29 14:02:25 -07:00
} ) ;
2022-06-03 08:25:07 -07:00
}
2024-02-27 01:29:16 -08:00
const itemPreviousNode : INodeExecutionData = previousNodeOutputData [ pairedItem . item ] ;
2022-06-03 08:25:07 -07:00
if ( itemPreviousNode . pairedItem === undefined ) {
2024-03-21 08:59:22 -07:00
throw createMissingPairedItemError ( 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
2024-06-24 03:13:18 -07:00
2022-06-03 08:25:07 -07:00
. map ( ( item ) = > {
try {
const itemInput = item . input || 0 ;
2024-02-27 01:29:16 -08:00
if ( itemInput >= source . length ) {
2022-06-03 08:25:07 -07:00
// `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
2023-11-30 03:46:45 -08:00
throw new ApplicationError ( 'Not found' ) ;
2022-06-03 08:25:07 -07:00
}
2024-02-27 01:29:16 -08:00
return getPairedItem ( destinationNodeName , source [ itemInput ] , item ) ;
2022-06-03 08:25:07 -07:00
} 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' , {
2024-03-21 08:59:22 -07:00
messageTemplate : ` Multiple matching items for expression [item ${
currentPairedItem . item || 0
} ] ` ,
2022-10-14 09:56:04 -07:00
functionality : 'pairedItem' ,
2022-09-29 14:02:25 -07:00
functionOverrides : {
2024-03-21 08:59:22 -07:00
message : ` Multiple matching items for code [item ${ currentPairedItem . item || 0 } ] ` ,
2022-09-29 14:02:25 -07:00
} ,
2024-03-21 08:59:22 -07:00
nodeCause : destinationNodeName ,
descriptionKey : isScriptingNode ( destinationNodeName , that . workflow )
? 'pairedItemMultipleMatchesCodeNode'
: 'pairedItemMultipleMatches' ,
2024-02-27 01:29:16 -08:00
type : 'paired_item_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 ;
2024-02-27 01:29:16 -08:00
if ( itemInput >= source . length ) {
if ( source . length === 0 ) {
2022-06-03 08:25:07 -07:00
// A trigger node got reached, so looks like that that item can not be resolved
2024-03-21 08:59:22 -07:00
throw createNoConnectionError ( destinationNodeName ) ;
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 . ` ,
2024-02-27 01:29:16 -08:00
type : 'paired_item_invalid_info' ,
2022-09-29 14:02:25 -07:00
} ) ;
2022-06-03 08:25:07 -07:00
}
2022-07-22 03:19:45 -07:00
nodeBeforeLast = sourceData . previousNode ;
2024-02-27 01:29:16 -08:00
sourceData = 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' ,
2024-02-27 01:29:16 -08:00
type : 'paired_item_no_info' ,
2022-09-29 14:02:25 -07:00
moreInfoLink : true ,
} ) ;
2022-06-03 08:25:07 -07:00
}
taskData =
2024-06-27 01:49:53 -07:00
that . runExecutionData ! . resultData . runData [ sourceData . previousNode ] ? . [
2022-06-03 08:25:07 -07:00
sourceData ? . previousNodeRun || 0
] ;
2024-06-27 01:49:53 -07:00
if ( ! taskData ) {
const pinData = getPinDataIfManualExecution (
that . workflow ,
sourceData . previousNode ,
that . mode ,
) ;
if ( pinData ) {
taskData = { data : { main : [ pinData ] } , startTime : 0 , executionTime : 0 , source : [ ] } ;
}
}
2022-06-03 08:25:07 -07:00
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
} ,
2024-03-26 10:17:00 -07:00
nodeCause : sourceData.previousNode ,
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)` ,
2024-02-27 01:29:16 -08:00
type : 'paired_item_invalid_info' ,
2022-06-03 08:25:07 -07:00
} ) ;
}
if ( pairedItem . item >= taskData . data ! . main [ previousNodeOutput ] ! . length ) {
2024-03-21 08:59:22 -07:00
throw createInvalidPairedItemError ( {
nodeName : sourceData.previousNode ,
2022-09-29 14:02:25 -07:00
} ) ;
2022-06-03 08:25:07 -07:00
}
return taskData . data ! . main [ previousNodeOutput ] ! [ pairedItem . item ] ;
} ;
2024-10-02 04:31:22 -07:00
const handleFromAi = (
name : string ,
_description? : string ,
_type : string = 'string' ,
defaultValue? : unknown ,
) = > {
if ( ! name || name === '' ) {
2024-11-08 07:15:33 -08:00
throw new ExpressionError ( "Add a key, e.g. $fromAI('placeholder_name')" , {
2024-10-02 04:31:22 -07:00
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
} ) ;
}
const nameValidationRegex = /^[a-zA-Z0-9_-]{0,64}$/ ;
if ( ! nameValidationRegex . test ( name ) ) {
throw new ExpressionError (
'Invalid parameter key, must be between 1 and 64 characters long and only contain lowercase letters, uppercase letters, numbers, underscores, and hyphens' ,
{
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
} ,
) ;
}
const placeholdersDataInputData =
that . runExecutionData ? . resultData . runData [ that . activeNodeName ] ? . [ 0 ] . inputOverride ? . [
NodeConnectionType . AiTool
] ? . [ 0 ] ? . [ 0 ] . json ;
if ( Boolean ( ! placeholdersDataInputData ) ) {
throw new ExpressionError ( 'No execution data available' , {
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
type : 'no_execution_data' ,
} ) ;
}
return placeholdersDataInputData ? . [ name ] ? ? defaultValue ;
} ;
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 ) {
2024-03-21 08:59:22 -07:00
throw createExpressionError ( "Referenced node doesn't exist" , {
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
nodeCause : nodeName ,
descriptionKey : 'nodeNotFound' ,
} ) ;
2022-03-13 01:34:44 -08:00
}
2024-02-27 01:29:16 -08:00
const ensureNodeExecutionData = ( ) = > {
if (
! that ? . runExecutionData ? . resultData ? . runData . hasOwnProperty ( nodeName ) &&
2024-06-27 01:49:53 -07:00
! getPinDataIfManualExecution ( that . workflow , nodeName , that . mode )
2024-02-27 01:29:16 -08:00
) {
2024-03-21 08:59:22 -07:00
throw createExpressionError ( 'Referenced node is unexecuted' , {
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
2024-02-27 01:29:16 -08:00
type : 'no_node_execution_data' ,
2024-03-21 08:59:22 -07:00
descriptionKey : 'noNodeExecutionData' ,
2024-02-27 01:29:16 -08:00
nodeCause : nodeName ,
} ) ;
}
} ;
2022-03-13 01:34:44 -08:00
return new Proxy (
{ } ,
{
2023-07-07 07:43:45 -07:00
has : ( ) = > true ,
2024-01-25 05:33:35 -08:00
ownKeys() {
2023-01-06 01:07:36 -08:00
return [
'pairedItem' ,
2024-02-20 09:25:04 -08:00
'isExecuted' ,
2023-01-06 01:07:36 -08:00
'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 ;
2024-02-27 01:29:16 -08:00
if ( property === 'isExecuted' ) {
return (
that ? . runExecutionData ? . resultData ? . runData . hasOwnProperty ( nodeName ) ? ? false
) ;
2024-02-20 09:25:04 -08:00
}
2022-09-29 14:02:25 -07:00
if ( [ 'pairedItem' , 'itemMatching' , 'item' ] . includes ( property as string ) ) {
2024-02-27 01:29:16 -08:00
// Before resolving the pairedItem make sure that the requested node comes in the
// graph before the current one
const activeNode = that . workflow . getNode ( that . activeNodeName ) ;
2024-05-09 23:51:26 -07:00
2024-02-27 01:29:16 -08:00
let contextNode = that . contextNodeName ;
if ( activeNode ) {
const parentMainInputNode = that . workflow . getParentMainInputNode ( activeNode ) ;
contextNode = parentMainInputNode . name ? ? contextNode ;
}
const parentNodes = that . workflow . getParentNodes ( contextNode ) ;
if ( ! parentNodes . includes ( nodeName ) ) {
2024-03-21 08:59:22 -07:00
throw createNoConnectionError ( nodeName ) ;
2024-02-27 01:29:16 -08:00
}
ensureNodeExecutionData ( ) ;
2022-09-29 14:02:25 -07:00
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
2024-06-27 01:49:53 -07:00
if ( ! that . connectionInputData . length ) {
const pinnedData = getPinDataIfManualExecution (
that . workflow ,
nodeName ,
that . mode ,
) ;
if ( pinnedData ) {
return pinnedData [ itemIndex ] ;
}
}
2022-06-03 08:25:07 -07:00
const executionData = that . connectionInputData ;
2024-06-27 01:49:53 -07:00
const input = executionData ? . [ itemIndex ] ;
2024-02-27 01:29:16 -08:00
if ( ! input ) {
throw createExpressionError ( 'Can’ t get data for expression' , {
messageTemplate : 'Can’ t get data for expression under ‘ %%PARAMETER%%’ field' ,
functionality : 'pairedItem' ,
functionOverrides : {
description : ` Some intermediate nodes between ‘ <strong> ${ nodeName } </strong>‘ and ‘ <strong> ${ that . activeNodeName } </strong>‘ have not executed yet. ` ,
message : 'Can’ t get data' ,
} ,
description : ` Some intermediate nodes between ‘ <strong> ${ nodeName } </strong>‘ and ‘ <strong> ${ that . activeNodeName } </strong>‘ have not executed yet. ` ,
causeDetailed : ` pairedItem can \ 't be found when intermediate nodes between ‘ <strong> ${ nodeName } </strong>‘ and ‘ <strong> ${ that . activeNodeName } </strong> have not executed yet. ` ,
itemIndex ,
type : 'paired_item_intermediate_nodes' ,
} ) ;
}
2022-06-03 08:25:07 -07:00
// 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.
2024-02-27 01:29:16 -08:00
const pairedItem = input . pairedItem as IPairedItemData ;
2022-06-03 08:25:07 -07:00
if ( pairedItem === undefined ) {
2024-03-21 08:59:22 -07:00
throw createMissingPairedItemError ( that . activeNodeName ) ;
2022-06-03 08:25:07 -07:00
}
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 ,
} ) ;
}
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
}
2024-06-27 01:49:53 -07:00
2022-03-13 01:34:44 -08:00
if ( property === 'first' ) {
2024-02-27 01:29:16 -08:00
ensureNodeExecutionData ( ) ;
2022-03-13 01:34:44 -08:00
return ( branchIndex? : number , runIndex? : number ) = > {
2024-06-19 06:10:30 -07:00
branchIndex =
branchIndex ? ?
// default to the output the active node is connected to
that . workflow . getNodeConnectionIndexes ( that . activeNodeName , nodeName )
? . sourceIndex ? ?
0 ;
2024-06-27 01:49:53 -07:00
const executionData = that . getNodeExecutionOrPinnedData ( {
nodeName ,
branchIndex ,
runIndex ,
} ) ;
2022-03-13 01:34:44 -08:00
if ( executionData [ 0 ] ) return executionData [ 0 ] ;
return undefined ;
} ;
}
if ( property === 'last' ) {
2024-02-27 01:29:16 -08:00
ensureNodeExecutionData ( ) ;
2022-03-13 01:34:44 -08:00
return ( branchIndex? : number , runIndex? : number ) = > {
2024-06-19 06:10:30 -07:00
branchIndex =
branchIndex ? ?
// default to the output the active node is connected to
that . workflow . getNodeConnectionIndexes ( that . activeNodeName , nodeName )
? . sourceIndex ? ?
0 ;
2024-06-27 01:49:53 -07:00
const executionData = that . getNodeExecutionOrPinnedData ( {
nodeName ,
branchIndex ,
runIndex ,
} ) ;
2022-03-13 01:34:44 -08:00
if ( ! executionData . length ) return undefined ;
if ( executionData [ executionData . length - 1 ] ) {
return executionData [ executionData . length - 1 ] ;
}
return undefined ;
} ;
}
if ( property === 'all' ) {
2024-02-27 01:29:16 -08:00
ensureNodeExecutionData ( ) ;
2024-06-19 06:10:30 -07:00
return ( branchIndex? : number , runIndex? : number ) = > {
branchIndex =
branchIndex ? ?
// default to the output the active node is connected to
that . workflow . getNodeConnectionIndexes ( that . activeNodeName , nodeName )
? . sourceIndex ? ?
0 ;
2024-06-27 01:49:53 -07:00
return that . getNodeExecutionOrPinnedData ( { nodeName , branchIndex , runIndex } ) ;
2024-06-19 06:10:30 -07:00
} ;
2022-03-13 01:34:44 -08:00
}
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 ,
2024-01-25 05:33:35 -08:00
ownKeys() {
2023-04-19 04:09:46 -07:00
return [ 'all' , 'context' , 'first' , 'item' , 'last' , 'params' ] ;
} ,
2024-01-25 05:33:35 -08:00
getOwnPropertyDescriptor() {
2023-04-19 04:09:46 -07:00
return {
enumerable : true ,
configurable : true ,
} ;
} ,
get ( target , property , receiver ) {
if ( property === 'isProxy' ) return true ;
2022-09-29 14:02:25 -07:00
2024-02-27 01:29:16 -08:00
if ( that . connectionInputData . length === 0 ) {
throw createExpressionError ( 'No execution data available' , {
runIndex : that.runIndex ,
itemIndex : that.itemIndex ,
type : 'no_execution_data' ,
} ) ;
}
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
2024-10-09 07:31:45 -07:00
$env : createEnvProvider (
that . runIndex ,
that . itemIndex ,
that . envProviderState ? ? createEnvProviderState ( ) ,
) ,
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 ,
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-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 ,
that . additionalKeys ,
2022-06-03 08:25:07 -07:00
that . executeData ,
2021-08-29 11:58:11 -07:00
defaultReturnRunIndex ,
2023-10-27 05:17:52 -07:00
{ } ,
2023-10-02 08:33:43 -07:00
that . contextNodeName ,
2021-08-29 11:58:11 -07:00
) ;
2020-01-03 14:37:13 -08:00
return dataProxy . getDataProxy ( ) ;
} ,
2024-10-02 04:31:22 -07:00
$fromAI : handleFromAi ,
// Make sure mis-capitalized $fromAI is handled correctly even though we don't auto-complete it
$fromai : handleFromAi ,
$fromAi : handleFromAi ,
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 ,
2024-06-24 03:13:18 -07:00
2022-03-13 01:34:44 -08:00
DateTime ,
2024-06-24 03:13:18 -07:00
2022-03-13 01:34:44 -08:00
Interval ,
2024-06-24 03:13:18 -07:00
2022-03-13 01:34:44 -08:00
Duration ,
2021-08-21 05:11:32 -07:00
. . . that . additionalKeys ,
2023-10-30 10:42:47 -07:00
$getPairedItem : getPairedItem ,
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 ,
2024-05-09 23:51:26 -07:00
$nodeVersion : that.workflow.getNode ( that . activeNodeName ) ? . typeVersion ,
2024-10-07 06:45:22 -07:00
$nodeId : that.workflow.getNode ( that . activeNodeName ) ? . id ,
2024-10-17 06:59:53 -07:00
$webhookId : that.workflow.getNode ( that . activeNodeName ) ? . webhookId ,
2019-06-23 03:35:23 -07:00
} ;
2024-11-04 01:13:09 -08:00
const throwOnMissingExecutionData = opts ? . throwOnMissingExecutionData ? ? true ;
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 ) ) {
2024-11-04 01:13:09 -08:00
return that . nodeDataGetter ( that . contextNodeName , true , throwOnMissingExecutionData ) ? . json ;
2021-08-29 11:58:11 -07:00
}
if ( name === '$binary' ) {
2024-11-04 01:13:09 -08:00
return that . nodeDataGetter ( that . contextNodeName , true , throwOnMissingExecutionData )
? . 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
} ) ;
}
}