Skip to content

Commit a412d60

Browse files
committed
fix: wire for-each loop compilation
Signed-off-by: zebbern <185730623+zebbern@users.noreply.github.com>
1 parent 96e8209 commit a412d60

2 files changed

Lines changed: 64 additions & 5 deletions

File tree

backend/src/dsl/compiler.ts

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ export function compileWorkflowGraph(graph: WorkflowGraphDto): WorkflowDefinitio
8383
}
8484
}
8585

86-
const orderedIds = topoSort(nodeIds, graph.edges);
8786
const incomingEdges = new Map<string, Set<string>>();
8887
type GraphEdge = (typeof graph.edges)[number];
8988
const edgesByTarget = new Map<string, GraphEdge[]>();
@@ -136,7 +135,7 @@ export function compileWorkflowGraph(graph: WorkflowGraphDto): WorkflowDefinitio
136135
};
137136
}
138137

139-
const actions: WorkflowAction[] = orderedIds.map((id) => {
138+
const preliminaryActions: WorkflowAction[] = nodeIds.map((id) => {
140139
const node = executableNodes.find((n: WorkflowNodeDto) => n.id === id)!;
141140
const config = (node.data?.config ?? {}) as Record<string, unknown>;
142141
const {
@@ -229,6 +228,61 @@ export function compileWorkflowGraph(graph: WorkflowGraphDto): WorkflowDefinitio
229228
};
230229
});
231230

231+
const actionsByRef = new Map(preliminaryActions.map((action) => [action.ref, action]));
232+
const loopExtraction = extractLoopBodies(
233+
executableNodes,
234+
graph.edges,
235+
nodesMetadata,
236+
actionsByRef,
237+
);
238+
const mainNodeIdSet = new Set(loopExtraction.mainNodeIds);
239+
const mainGraphEdges = loopExtraction.mainEdges;
240+
241+
const orderedIds = topoSortWithoutLoopBackCycles(
242+
loopExtraction.mainNodeIds,
243+
mainGraphEdges,
244+
loopExtraction.loopBodies,
245+
);
246+
247+
const mainIncomingEdges = new Map<string, Set<string>>();
248+
for (const nodeId of loopExtraction.mainNodeIds) {
249+
mainIncomingEdges.set(nodeId, new Set());
250+
}
251+
for (const edge of mainGraphEdges) {
252+
if (mainNodeIdSet.has(edge.source) && mainNodeIdSet.has(edge.target)) {
253+
mainIncomingEdges.get(edge.target)?.add(edge.source);
254+
}
255+
}
256+
257+
const actions: WorkflowAction[] = orderedIds.map((id) => {
258+
const action = actionsByRef.get(id);
259+
if (!action) {
260+
throw new Error(`Compiled action missing for node '${id}'.`);
261+
}
262+
263+
const inputMappings: WorkflowAction['inputMappings'] = {};
264+
for (const edge of edgesByTarget.get(id) ?? []) {
265+
if (!mainNodeIdSet.has(edge.source)) {
266+
continue;
267+
}
268+
const targetHandle = edge.targetHandle ?? edge.sourceHandle;
269+
const sourceHandle = edge.sourceHandle ?? '__self__';
270+
if (!targetHandle) {
271+
continue;
272+
}
273+
inputMappings[targetHandle] = {
274+
sourceRef: edge.source,
275+
sourceHandle,
276+
};
277+
}
278+
279+
return {
280+
...action,
281+
dependsOn: Array.from(mainIncomingEdges.get(id) ?? []),
282+
inputMappings,
283+
};
284+
});
285+
232286
const entrypointAction = actions.find(
233287
(action) => action.componentId === 'core.workflow.entrypoint',
234288
);
@@ -242,7 +296,7 @@ export function compileWorkflowGraph(graph: WorkflowGraphDto): WorkflowDefinitio
242296
dependencyCounts[action.ref] = action.dependsOn.length;
243297
}
244298

245-
const edges: WorkflowEdge[] = graph.edges.map((edge) => ({
299+
const edges: WorkflowEdge[] = mainGraphEdges.map((edge) => ({
246300
id: edge.id,
247301
sourceRef: edge.source,
248302
targetRef: edge.target,
@@ -269,10 +323,13 @@ export function compileWorkflowGraph(graph: WorkflowGraphDto): WorkflowDefinitio
269323
title: graph.name,
270324
description: graph.description,
271325
entrypoint: { ref: entryNode },
272-
nodes: nodesMetadata,
326+
nodes: Object.fromEntries(
327+
Object.entries(nodesMetadata).filter(([nodeId]) => mainNodeIdSet.has(nodeId)),
328+
),
273329
edges,
274330
dependencyCounts,
275331
actions,
332+
loopBodies: loopExtraction.loopBodies,
276333
config: { environment: 'default', timeoutSeconds: 0 },
277334
};
278335

worker/src/components/core/for-each.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ const inputSchema = inputs({
1515
{
1616
label: 'Items',
1717
description: 'List of values to process. Each item runs the connected loop body once.',
18-
connectionType: { kind: 'list', element: { kind: 'primitive', name: 'json' } },
18+
allowAny: true,
19+
reason: 'Loop items may be plain strings, package specs, or structured JSON objects.',
20+
connectionType: { kind: 'list', element: { kind: 'primitive', name: 'text' } },
1921
},
2022
),
2123
});

0 commit comments

Comments
 (0)