Skip to content

Commit 84ba389

Browse files
author
drowl87
committed
reverting since executionMode doesn't appear to allow processing each item separately; bump version to 0.3.7
1 parent 307f87f commit 84ba389

2 files changed

Lines changed: 54 additions & 147 deletions

File tree

nodes/DynamicNode/DynamicNode.node.ts

Lines changed: 53 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -28,35 +28,31 @@ export class DynamicNode implements INodeType {
2828
default: {},
2929
description: 'Paste in your exported node JSON here',
3030
},
31-
{
32-
displayName: 'Execute Individually Per Item?',
33-
name: 'executeIndividually',
34-
type: 'boolean',
35-
default: true,
36-
description: 'Whether to execute the sub-workflow once per input item. If false, all items are passed in together.',
37-
},
38-
{
39-
displayName: 'Disable Waiting for Child Workflow(s) to Finish?',
40-
name: 'doNotWaitToFinish',
41-
type: 'boolean',
42-
default: false,
43-
description: 'Whether to return immediately after starting the sub-workflow. Advanced: disables result collection.',
44-
},
4531
],
4632
};
4733

4834
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
49-
const inputItems = this.getInputData();
50-
const executeIndividually = this.getNodeParameter('executeIndividually', 0) as boolean;
51-
const doNotWaitToFinish = this.getNodeParameter('doNotWaitToFinish', 0) as boolean;
35+
const items = this.getInputData();
36+
37+
// ✅ Enforce: exactly 1 item per call
38+
if (items.length !== 1) {
39+
throw new NodeOperationError(
40+
this.getNode(),
41+
`Dynamic Node must be called with exactly 1 item. Received ${items.length}.`,
42+
);
43+
}
5244

5345
const rawParam = this.getNodeParameter('nodeJson', 0) as any;
5446
let raw: any;
47+
5548
if (typeof rawParam === 'string') {
5649
try {
5750
raw = JSON.parse(rawParam);
5851
} catch {
59-
throw new NodeOperationError(this.getNode(), 'Node JSON must be valid JSON');
52+
throw new NodeOperationError(
53+
this.getNode(),
54+
'Node JSON must be a valid JSON object or a parseable JSON string',
55+
);
6056
}
6157
} else {
6258
raw = rawParam;
@@ -66,144 +62,55 @@ export class DynamicNode implements INodeType {
6662
throw new NodeOperationError(this.getNode(), 'Node JSON must be an object');
6763
}
6864

69-
const baseNode = Array.isArray(raw.nodes) && raw.nodes.length > 0 ? raw.nodes[0] : raw;
70-
delete baseNode.connections;
71-
delete baseNode.pinData;
72-
delete baseNode.meta;
65+
const nodeJson = Array.isArray(raw.nodes) && raw.nodes.length > 0 ? raw.nodes[0] : raw;
7366

74-
if (!baseNode.name) {
67+
delete nodeJson.connections;
68+
delete nodeJson.pinData;
69+
delete nodeJson.meta;
70+
71+
if (!nodeJson.name) {
7572
throw new NodeOperationError(this.getNode(), 'Your JSON must include a `name` field');
7673
}
7774

78-
const allResults: INodeExecutionData[] = [];
79-
80-
const processItem = async (item: INodeExecutionData, index: number): Promise<void> => {
81-
const template = JSON.parse(JSON.stringify(subWorkflowTemplate));
82-
const nodeClone = JSON.parse(JSON.stringify(baseNode));
83-
84-
nodeClone.name = `${baseNode.name} - Dynamic Node [${index + 1}]`;
85-
nodeClone.id = `dynamic-${uuidv4()}`;
86-
nodeClone.position = Array.isArray(baseNode.position) && baseNode.position.length === 2
87-
? baseNode.position
88-
: [240, 0];
89-
90-
// Evaluate top-level parameters
91-
for (const key of Object.keys(nodeClone.parameters)) {
92-
const value = nodeClone.parameters[key];
93-
if (typeof value === 'string' && value.includes('{{')) {
94-
try {
95-
nodeClone.parameters[key] = await this.evaluateExpression(value, index);
96-
} catch (err) {
97-
this.logger.warn(`DynamicNode: Failed to evaluate parameter '${key}': ${err instanceof Error ? err.message : String(err)}`);
98-
}
99-
}
100-
}
75+
nodeJson.name = `${nodeJson.name} - Dynamic Node`;
76+
nodeJson.id = `dynamic-${uuidv4()}`;
77+
nodeJson.position = [240, 0];
10178

102-
// Evaluate nested body parameters
103-
const bodyParams = nodeClone.parameters?.bodyParameters?.parameters;
104-
if (Array.isArray(bodyParams)) {
105-
for (const param of bodyParams) {
106-
if (typeof param.value === 'string' && param.value.includes('{{')) {
107-
try {
108-
param.value = await this.evaluateExpression(param.value, index);
109-
} catch (err) {
110-
this.logger.warn(`DynamicNode: Failed to evaluate body param '${param.name}': ${err instanceof Error ? err.message : String(err)}`);
111-
}
112-
}
113-
}
114-
}
79+
const template = JSON.parse(JSON.stringify(subWorkflowTemplate));
80+
template.nodes.push(nodeJson);
81+
template.connections.Start.main[0][0].node = nodeJson.name;
11582

116-
template.nodes.push(nodeClone);
117-
template.connections.Start.main[0][0].node = nodeClone.name;
118-
119-
const workflowProxy = this.getWorkflowDataProxy(index);
120-
121-
const execResult = await this.executeWorkflow(
122-
{ code: template },
123-
[item],
124-
{},
125-
{
126-
parentExecution: {
127-
executionId: workflowProxy.$execution.id,
128-
workflowId: workflowProxy.$workflow.id,
129-
},
130-
doNotWaitToFinish,
131-
additionalData: {
132-
itemIndex: index,
133-
},
134-
} as any
135-
);
83+
const workflowProxy = this.getWorkflowDataProxy(0);
13684

137-
if (!doNotWaitToFinish && execResult) {
138-
const resultArray = Array.isArray(execResult)
139-
? execResult
140-
: Array.isArray((execResult as any).data)
141-
? (execResult as any).data
142-
: [];
143-
144-
allResults.push(
145-
...resultArray.flat().filter((entry: unknown): entry is INodeExecutionData =>
146-
entry !== null && typeof entry === 'object',
147-
),
148-
);
149-
}
150-
};
151-
152-
if (executeIndividually) {
153-
for (let i = 0; i < inputItems.length; i++) {
154-
try {
155-
await processItem(inputItems[i], i);
156-
} catch (err) {
157-
this.logger.warn(`DynamicNode: Error processing item #${i + 1}: ${err instanceof Error ? err.message : String(err)}`);
158-
}
159-
}
160-
} else {
161-
// One execution for all items (rare)
162-
const template = JSON.parse(JSON.stringify(subWorkflowTemplate));
163-
const nodeClone = JSON.parse(JSON.stringify(baseNode));
164-
165-
nodeClone.name = `${baseNode.name} - Dynamic Node [all]`;
166-
nodeClone.id = `dynamic-${uuidv4()}`;
167-
nodeClone.position = Array.isArray(baseNode.position) && baseNode.position.length === 2
168-
? baseNode.position
169-
: [240, 0];
170-
171-
template.nodes.push(nodeClone);
172-
template.connections.Start.main[0][0].node = nodeClone.name;
173-
174-
const workflowProxy = this.getWorkflowDataProxy(0);
175-
176-
const execResult = await this.executeWorkflow(
177-
{ code: template },
178-
inputItems,
179-
{},
180-
{
181-
parentExecution: {
182-
executionId: workflowProxy.$execution.id,
183-
workflowId: workflowProxy.$workflow.id,
184-
},
185-
doNotWaitToFinish,
186-
additionalData: {
187-
itemIndex: 0,
188-
},
189-
} as any,
190-
);
85+
const executionResult: any = await this.executeWorkflow(
86+
{ code: template },
87+
items,
88+
{},
89+
{
90+
parentExecution: {
91+
executionId: workflowProxy.$execution.id,
92+
workflowId: workflowProxy.$workflow.id,
93+
},
94+
doNotWaitToFinish: false,
95+
},
96+
);
19197

192-
if (!doNotWaitToFinish && execResult) {
193-
const resultArray = Array.isArray(execResult)
194-
? execResult
195-
: Array.isArray((execResult as any).data)
196-
? (execResult as any).data
197-
: [];
198-
199-
allResults.push(
200-
...resultArray.flat().filter((entry: unknown): entry is INodeExecutionData =>
201-
entry !== null && typeof entry === 'object',
202-
),
203-
);
98+
let returnedData: INodeExecutionData[][] = [];
99+
100+
if (Array.isArray(executionResult)) {
101+
returnedData = executionResult as INodeExecutionData[][];
102+
} else if (executionResult && typeof executionResult === 'object' && 'data' in executionResult) {
103+
if (Array.isArray((executionResult as any).data)) {
104+
returnedData = (executionResult as any).data as INodeExecutionData[][];
105+
} else {
106+
this.logger.warn('DynamicNode: Sub-workflow executionResult.data was not an array. Returning empty data.');
204107
}
108+
} else if (executionResult === null || executionResult === undefined) {
109+
this.logger.warn('DynamicNode: Sub-workflow executionResult was null or undefined. Returning empty data.');
110+
} else {
111+
this.logger.warn(`DynamicNode: Unexpected structure from sub-workflow execution. Type: ${typeof executionResult}. Returning empty data.`);
205112
}
206113

207-
return [allResults];
114+
return returnedData;
208115
}
209116
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "n8n-nodes-dynamic-node",
3-
"version": "0.3.6",
3+
"version": "0.3.7",
44
"description": "A dynamic n8n node wrapper that can execute any node JSON by feeding it at runtime.",
55
"keywords": [
66
"n8n-community-node-package",

0 commit comments

Comments
 (0)