mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
feat: No expression error when node hasn’t executed (#8448)
Co-authored-by: Giulio Andreini <andreini@netseven.it> Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
@@ -22,7 +22,7 @@ import type {
|
||||
ProxyInput,
|
||||
} from './Interfaces';
|
||||
import * as NodeHelpers from './NodeHelpers';
|
||||
import { ExpressionError } from './errors/expression.error';
|
||||
import { ExpressionError, type ExpressionErrorOptions } from './errors/expression.error';
|
||||
import type { Workflow } from './Workflow';
|
||||
import { augmentArray, augmentObject } from './AugmentObject';
|
||||
import { deepCopy } from './utils';
|
||||
@@ -104,6 +104,7 @@ export class WorkflowDataProxy {
|
||||
{
|
||||
runIndex: that.runIndex,
|
||||
itemIndex: that.itemIndex,
|
||||
type: 'no_execution_data',
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -274,19 +275,25 @@ export class WorkflowDataProxy {
|
||||
);
|
||||
}
|
||||
|
||||
if (!that.runExecutionData.resultData.runData.hasOwnProperty(nodeName)) {
|
||||
if (that.workflow.getNode(nodeName)) {
|
||||
throw new ExpressionError(`no data, execute "${nodeName}" node first`, {
|
||||
runIndex: that.runIndex,
|
||||
itemIndex: that.itemIndex,
|
||||
});
|
||||
}
|
||||
if (!that.workflow.getNode(nodeName)) {
|
||||
throw new ExpressionError(`"${nodeName}" node doesn't exist`, {
|
||||
runIndex: that.runIndex,
|
||||
itemIndex: that.itemIndex,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
!that.runExecutionData.resultData.runData.hasOwnProperty(nodeName) &&
|
||||
!that.workflow.getPinDataOfNode(nodeName)
|
||||
) {
|
||||
throw new ExpressionError(`no data, execute "${nodeName}" node first`, {
|
||||
runIndex: that.runIndex,
|
||||
itemIndex: that.itemIndex,
|
||||
type: 'no_node_execution_data',
|
||||
nodeCause: nodeName,
|
||||
});
|
||||
}
|
||||
|
||||
runIndex = runIndex === undefined ? that.defaultReturnRunIndex : runIndex;
|
||||
runIndex =
|
||||
runIndex === -1 ? that.runExecutionData.resultData.runData[nodeName].length - 1 : runIndex;
|
||||
@@ -372,6 +379,28 @@ export class WorkflowDataProxy {
|
||||
|
||||
if (['binary', 'data', 'json'].includes(name)) {
|
||||
const executionData = that.getNodeExecutionData(nodeName, shortSyntax, undefined);
|
||||
|
||||
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%%’',
|
||||
description:
|
||||
'This node has no input data. Please make sure this node is connected to another node.',
|
||||
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',
|
||||
});
|
||||
}
|
||||
|
||||
if (executionData.length <= that.itemIndex) {
|
||||
throw new ExpressionError(`No data found for item-index: "${that.itemIndex}"`, {
|
||||
runIndex: that.runIndex,
|
||||
@@ -615,22 +644,13 @@ export class WorkflowDataProxy {
|
||||
|
||||
const createExpressionError = (
|
||||
message: string,
|
||||
context?: {
|
||||
causeDetailed?: string;
|
||||
description?: string;
|
||||
descriptionTemplate?: string;
|
||||
functionality?: 'pairedItem';
|
||||
context?: ExpressionErrorOptions & {
|
||||
moreInfoLink?: boolean;
|
||||
functionOverrides?: {
|
||||
// Custom data to display for Function-Nodes
|
||||
message?: string;
|
||||
description?: string;
|
||||
};
|
||||
itemIndex?: number;
|
||||
messageTemplate?: string;
|
||||
moreInfoLink?: boolean;
|
||||
nodeCause?: string;
|
||||
runIndex?: number;
|
||||
type?: string;
|
||||
},
|
||||
) => {
|
||||
if (isScriptingNode(that.activeNodeName, that.workflow) && context?.functionOverrides) {
|
||||
@@ -678,7 +698,7 @@ export class WorkflowDataProxy {
|
||||
incomingSourceData: ISourceData | null,
|
||||
pairedItem: IPairedItemData,
|
||||
): INodeExecutionData | null => {
|
||||
let taskData: ITaskData;
|
||||
let taskData: ITaskData | undefined;
|
||||
|
||||
let sourceData: ISourceData | null = incomingSourceData;
|
||||
|
||||
@@ -697,13 +717,12 @@ export class WorkflowDataProxy {
|
||||
let nodeBeforeLast: string | undefined;
|
||||
|
||||
while (sourceData !== null && destinationNodeName !== sourceData.previousNode) {
|
||||
taskData =
|
||||
that.runExecutionData!.resultData.runData[sourceData.previousNode][
|
||||
sourceData?.previousNodeRun || 0
|
||||
];
|
||||
|
||||
const runIndex = sourceData?.previousNodeRun || 0;
|
||||
const previousNodeOutput = sourceData.previousNodeOutput || 0;
|
||||
if (previousNodeOutput >= taskData.data!.main.length) {
|
||||
taskData =
|
||||
that.runExecutionData?.resultData?.runData?.[sourceData.previousNode]?.[runIndex];
|
||||
|
||||
if (taskData?.data?.main && previousNodeOutput >= taskData.data.main.length) {
|
||||
throw createExpressionError('Can’t get data for expression', {
|
||||
messageTemplate: 'Can’t get data for expression under ‘%%PARAMETER%%’ field',
|
||||
functionOverrides: {
|
||||
@@ -716,7 +735,12 @@ export class WorkflowDataProxy {
|
||||
});
|
||||
}
|
||||
|
||||
if (pairedItem.item >= taskData.data!.main[previousNodeOutput]!.length) {
|
||||
const previousNodeOutputData =
|
||||
taskData?.data?.main?.[previousNodeOutput] ??
|
||||
(that.workflow.getPinDataOfNode(sourceData.previousNode) as INodeExecutionData[]);
|
||||
const source = taskData?.source ?? [];
|
||||
|
||||
if (pairedItem.item >= previousNodeOutputData.length) {
|
||||
throw createExpressionError('Can’t get data for expression', {
|
||||
messageTemplate: 'Can’t get data for expression under ‘%%PARAMETER%%’ field',
|
||||
functionality: 'pairedItem',
|
||||
@@ -733,13 +757,12 @@ export class WorkflowDataProxy {
|
||||
}points to an input item on node ‘<strong>${
|
||||
sourceData.previousNode
|
||||
}</strong>‘ that doesn’t exist.`,
|
||||
type: 'invalid pairing info',
|
||||
type: 'paired_item_invalid_info',
|
||||
moreInfoLink: true,
|
||||
});
|
||||
}
|
||||
|
||||
const itemPreviousNode: INodeExecutionData =
|
||||
taskData.data!.main[previousNodeOutput]![pairedItem.item];
|
||||
const itemPreviousNode: INodeExecutionData = previousNodeOutputData[pairedItem.item];
|
||||
|
||||
if (itemPreviousNode.pairedItem === undefined) {
|
||||
throw createExpressionError('Can’t get data for expression', {
|
||||
@@ -751,7 +774,7 @@ export class WorkflowDataProxy {
|
||||
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',
|
||||
type: 'paired_item_no_info',
|
||||
moreInfoLink: true,
|
||||
});
|
||||
}
|
||||
@@ -763,13 +786,13 @@ export class WorkflowDataProxy {
|
||||
.map((item) => {
|
||||
try {
|
||||
const itemInput = item.input || 0;
|
||||
if (itemInput >= taskData.source.length) {
|
||||
if (itemInput >= 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 ApplicationError('Not found');
|
||||
}
|
||||
|
||||
return getPairedItem(destinationNodeName, taskData.source[itemInput], item);
|
||||
return getPairedItem(destinationNodeName, source[itemInput], item);
|
||||
} catch (error) {
|
||||
// Means pairedItem could not be found
|
||||
return null;
|
||||
@@ -793,7 +816,7 @@ export class WorkflowDataProxy {
|
||||
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',
|
||||
type: 'paired_item_multiple_matches',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -812,8 +835,8 @@ export class WorkflowDataProxy {
|
||||
}
|
||||
|
||||
const itemInput = pairedItem.input || 0;
|
||||
if (itemInput >= taskData.source.length) {
|
||||
if (taskData.source.length === 0) {
|
||||
if (itemInput >= source.length) {
|
||||
if (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%%’',
|
||||
@@ -823,7 +846,7 @@ export class WorkflowDataProxy {
|
||||
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',
|
||||
type: 'paired_item_no_connection',
|
||||
moreInfoLink: true,
|
||||
});
|
||||
}
|
||||
@@ -841,12 +864,12 @@ export class WorkflowDataProxy {
|
||||
? `of run ${(sourceData.previousNodeRun || 0).toString()} `
|
||||
: ''
|
||||
}points to a branch that doesn’t exist.`,
|
||||
type: 'invalid pairing info',
|
||||
type: 'paired_item_invalid_info',
|
||||
});
|
||||
}
|
||||
|
||||
nodeBeforeLast = sourceData.previousNode;
|
||||
sourceData = taskData.source[pairedItem.input || 0] || null;
|
||||
sourceData = source[pairedItem.input || 0] || null;
|
||||
|
||||
if (pairedItem.sourceOverwrite) {
|
||||
sourceData = pairedItem.sourceOverwrite;
|
||||
@@ -862,7 +885,7 @@ export class WorkflowDataProxy {
|
||||
},
|
||||
nodeCause: nodeBeforeLast,
|
||||
description: 'Could not resolve, probably no pairedItem exists',
|
||||
type: 'no pairing info',
|
||||
type: 'paired_item_no_info',
|
||||
moreInfoLink: true,
|
||||
});
|
||||
}
|
||||
@@ -882,7 +905,7 @@ export class WorkflowDataProxy {
|
||||
},
|
||||
description: 'Item points to a node output which does not exist',
|
||||
causeDetailed: `The sourceData points to a node output ‘${previousNodeOutput}‘ which does not exist on node ‘${sourceData.previousNode}‘ (output node did probably supply a wrong one)`,
|
||||
type: 'invalid pairing info',
|
||||
type: 'paired_item_invalid_info',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -903,7 +926,7 @@ export class WorkflowDataProxy {
|
||||
}points to an input item on node ‘<strong>${
|
||||
sourceData.previousNode
|
||||
}</strong>‘ that doesn’t exist.`,
|
||||
type: 'invalid pairing info',
|
||||
type: 'paired_item_invalid_info',
|
||||
moreInfoLink: true,
|
||||
});
|
||||
}
|
||||
@@ -922,6 +945,18 @@ export class WorkflowDataProxy {
|
||||
throw createExpressionError(`"${nodeName}" node doesn't exist`);
|
||||
}
|
||||
|
||||
const ensureNodeExecutionData = () => {
|
||||
if (
|
||||
!that?.runExecutionData?.resultData?.runData.hasOwnProperty(nodeName) &&
|
||||
!that.workflow.getPinDataOfNode(nodeName)
|
||||
) {
|
||||
throw createExpressionError(`no data, execute "${nodeName}" node first`, {
|
||||
type: 'no_node_execution_data',
|
||||
nodeCause: nodeName,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return new Proxy(
|
||||
{},
|
||||
{
|
||||
@@ -942,13 +977,38 @@ export class WorkflowDataProxy {
|
||||
get(target, property, receiver) {
|
||||
if (property === 'isProxy') return true;
|
||||
|
||||
if (!that?.runExecutionData?.resultData?.runData.hasOwnProperty(nodeName)) {
|
||||
if (property === 'isExecuted') return false;
|
||||
throw createExpressionError(`no data, execute "${nodeName}" node first`);
|
||||
if (property === 'isExecuted') {
|
||||
return (
|
||||
that?.runExecutionData?.resultData?.runData.hasOwnProperty(nodeName) ?? false
|
||||
);
|
||||
}
|
||||
if (property === 'isExecuted') return true;
|
||||
|
||||
if (['pairedItem', 'itemMatching', 'item'].includes(property as string)) {
|
||||
// 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);
|
||||
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)) {
|
||||
throw createExpressionError('Invalid expression', {
|
||||
messageTemplate: 'Invalid expression under ‘%%PARAMETER%%’',
|
||||
functionality: 'pairedItem',
|
||||
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).`,
|
||||
nodeCause: nodeName,
|
||||
type: 'paired_item_no_connection',
|
||||
});
|
||||
}
|
||||
|
||||
ensureNodeExecutionData();
|
||||
|
||||
const pairedItemMethod = (itemIndex?: number) => {
|
||||
if (itemIndex === undefined) {
|
||||
if (property === 'itemMatching') {
|
||||
@@ -960,11 +1020,26 @@ export class WorkflowDataProxy {
|
||||
}
|
||||
|
||||
const executionData = that.connectionInputData;
|
||||
const input = executionData[itemIndex];
|
||||
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',
|
||||
});
|
||||
}
|
||||
|
||||
// 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;
|
||||
const pairedItem = input.pairedItem as IPairedItemData;
|
||||
|
||||
if (pairedItem === undefined) {
|
||||
throw createExpressionError('Can’t get data for expression', {
|
||||
@@ -994,28 +1069,6 @@ export class WorkflowDataProxy {
|
||||
});
|
||||
}
|
||||
|
||||
// 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);
|
||||
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)) {
|
||||
throw createExpressionError('Invalid expression', {
|
||||
messageTemplate: 'Invalid expression under ‘%%PARAMETER%%’',
|
||||
functionality: 'pairedItem',
|
||||
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).`,
|
||||
itemIndex,
|
||||
});
|
||||
}
|
||||
|
||||
const sourceData: ISourceData | null =
|
||||
that.executeData.source.main[pairedItem.input || 0] ??
|
||||
that.executeData.source.main[0];
|
||||
@@ -1029,6 +1082,7 @@ export class WorkflowDataProxy {
|
||||
return pairedItemMethod;
|
||||
}
|
||||
if (property === 'first') {
|
||||
ensureNodeExecutionData();
|
||||
return (branchIndex?: number, runIndex?: number) => {
|
||||
const executionData = getNodeOutput(nodeName, branchIndex, runIndex);
|
||||
if (executionData[0]) return executionData[0];
|
||||
@@ -1036,6 +1090,7 @@ export class WorkflowDataProxy {
|
||||
};
|
||||
}
|
||||
if (property === 'last') {
|
||||
ensureNodeExecutionData();
|
||||
return (branchIndex?: number, runIndex?: number) => {
|
||||
const executionData = getNodeOutput(nodeName, branchIndex, runIndex);
|
||||
if (!executionData.length) return undefined;
|
||||
@@ -1046,6 +1101,7 @@ export class WorkflowDataProxy {
|
||||
};
|
||||
}
|
||||
if (property === 'all') {
|
||||
ensureNodeExecutionData();
|
||||
return (branchIndex?: number, runIndex?: number) =>
|
||||
getNodeOutput(nodeName, branchIndex, runIndex);
|
||||
}
|
||||
@@ -1075,6 +1131,14 @@ export class WorkflowDataProxy {
|
||||
get(target, property, receiver) {
|
||||
if (property === 'isProxy') return true;
|
||||
|
||||
if (that.connectionInputData.length === 0) {
|
||||
throw createExpressionError('No execution data available', {
|
||||
runIndex: that.runIndex,
|
||||
itemIndex: that.itemIndex,
|
||||
type: 'no_execution_data',
|
||||
});
|
||||
}
|
||||
|
||||
if (property === 'item') {
|
||||
return that.connectionInputData[that.itemIndex];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user