@teleporthq/teleport-plugin-next-workflows 0.45.15 → 0.45.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/cron-route-loop-templateparams.test.ts +5 -4
- package/__tests__/custom-js-loop-scope.test.ts +10 -5
- package/__tests__/loop-collection-unwrap.test.ts +6 -1
- package/__tests__/server-loop-body-branches.test.ts +188 -0
- package/__tests__/server-nested-loops.test.ts +392 -0
- package/__tests__/streaming-route-if-branch.test.ts +107 -0
- package/dist/cjs/api-route-generator.d.ts.map +1 -1
- package/dist/cjs/api-route-generator.js +57 -251
- package/dist/cjs/api-route-generator.js.map +1 -1
- package/dist/cjs/executor-generator.d.ts.map +1 -1
- package/dist/cjs/executor-generator.js +22 -0
- package/dist/cjs/executor-generator.js.map +1 -1
- package/dist/cjs/server-runtime-code.d.ts.map +1 -1
- package/dist/cjs/server-runtime-code.js +67 -0
- package/dist/cjs/server-runtime-code.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/api-route-generator.d.ts.map +1 -1
- package/dist/esm/api-route-generator.js +57 -251
- package/dist/esm/api-route-generator.js.map +1 -1
- package/dist/esm/executor-generator.d.ts.map +1 -1
- package/dist/esm/executor-generator.js +22 -0
- package/dist/esm/executor-generator.js.map +1 -1
- package/dist/esm/server-runtime-code.d.ts.map +1 -1
- package/dist/esm/server-runtime-code.js +67 -0
- package/dist/esm/server-runtime-code.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/api-route-generator.ts +57 -251
- package/src/executor-generator.ts +22 -0
- package/src/server-runtime-code.ts +67 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teleporthq/teleport-plugin-next-workflows",
|
|
3
|
-
"version": "0.45.
|
|
3
|
+
"version": "0.45.16",
|
|
4
4
|
"description": "A plugin for workflow code generation in Next.js projects.",
|
|
5
5
|
"author": "teleportHQ",
|
|
6
6
|
"license": "MIT",
|
|
@@ -69,5 +69,5 @@
|
|
|
69
69
|
"@teleporthq/teleport-shared": "^0.45.0",
|
|
70
70
|
"@teleporthq/teleport-types": "^0.45.0"
|
|
71
71
|
},
|
|
72
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "ff4b04c211701b9bc5fdce1f32e42226241b0792"
|
|
73
73
|
}
|
|
@@ -443,6 +443,8 @@ module.exports = async function handler(req, res) {
|
|
|
443
443
|
// the caller as if that branch had run (a custom node answers the output
|
|
444
444
|
// of the last node it ran).
|
|
445
445
|
if (context.__skippedNodes && context.__skippedNodes[node.id]) continue;
|
|
446
|
+
// A node of a loop body ran inside its loop, an if-statement included.
|
|
447
|
+
if (context.__loopBodyNodeIds && context.__loopBodyNodeIds[node.id]) continue;
|
|
446
448
|
const resolved = resolveNodeConfig(node, context);
|
|
447
449
|
// Every data node lives in a SERVER segment, so this guard — which turns an
|
|
448
450
|
// unresolved {{…}} into a validation error instead of letting the literal
|
|
@@ -458,119 +460,34 @@ module.exports = async function handler(req, res) {
|
|
|
458
460
|
if (node.type === 'general-if-statement') {
|
|
459
461
|
const condResult = utils.evaluateCondition(resolved, context, node.config);
|
|
460
462
|
context[node.id] = { result: condResult };
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
var skipEdge = segEdgesIf.find(function(e) { return e.source === node.id && e.sourceHandle === skipHandle; });
|
|
464
|
-
if (skipEdge) {
|
|
465
|
-
// BFS to collect all nodes in the non-taken branch
|
|
466
|
-
var skipIds = {};
|
|
467
|
-
var skipQueue = [skipEdge.target];
|
|
468
|
-
while (skipQueue.length > 0) {
|
|
469
|
-
var sid = skipQueue.shift();
|
|
470
|
-
if (skipIds[sid] || sid === node.id) continue;
|
|
471
|
-
skipIds[sid] = true;
|
|
472
|
-
segEdgesIf.forEach(function(e) {
|
|
473
|
-
if (e.source === sid && !skipIds[e.target] && e.target !== node.id) skipQueue.push(e.target);
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
if (!context.__skippedNodes) context.__skippedNodes = {};
|
|
477
|
-
Object.assign(context.__skippedNodes, skipIds);
|
|
478
|
-
}
|
|
463
|
+
if (!context.__skippedNodes) context.__skippedNodes = {};
|
|
464
|
+
Object.assign(context.__skippedNodes, utils.nonTakenBranchNodeIds(SEGMENT_CONFIG.edges, node.id, condResult));
|
|
479
465
|
continue;
|
|
480
466
|
}
|
|
481
467
|
|
|
482
|
-
//
|
|
483
|
-
//
|
|
468
|
+
// A loop runs its body once per pass of its type, a loop inside the body
|
|
469
|
+
// included (see runServerLoop in the server runtime).
|
|
484
470
|
if (node.type === 'general-loop') {
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
const queue = [loopBodyEdge.target];
|
|
493
|
-
while (queue.length > 0) {
|
|
494
|
-
const nid = queue.shift();
|
|
495
|
-
if (bodyNodeIds[nid] || nid === node.id) continue;
|
|
496
|
-
bodyNodeIds[nid] = true;
|
|
497
|
-
SEGMENT_CONFIG.edges.forEach(function(e) {
|
|
498
|
-
if (e.source === nid && (e.sourceHandle === 'loop-body-out' || !e.sourceHandle)) {
|
|
499
|
-
if (e.target !== node.id) queue.push(e.target);
|
|
500
|
-
}
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
const bodyNodes = sortedNodes.filter(function(n) { return bodyNodeIds[n.id]; });
|
|
505
|
-
// For loopType=map, the schema (general-loop in node-context-schemas.json)
|
|
506
|
-
// defines results as the array of last body node outputs (same as
|
|
507
|
-
// Array.prototype.map). Pick the last body node so downstream nodes
|
|
508
|
-
// (e.g. state-update-global-state) consume the transformed value
|
|
509
|
-
// instead of the per-iteration scaffolding object.
|
|
510
|
-
const __loopType = resolved.loopType || 'forEach';
|
|
511
|
-
const __lastBodyNode = bodyNodes[bodyNodes.length - 1];
|
|
512
|
-
|
|
513
|
-
// Resolve the collection to iterate over. unwrapWorkflowCollection
|
|
514
|
-
// handles the common envelope shape that helper nodes return —
|
|
515
|
-
// transform-array { result, ... }, integration-airtable { records, ... },
|
|
516
|
-
// utility-semantic-search { matches, ... } — by unwrapping a single
|
|
517
|
-
// array property when the upstream binding pointed at the parent node
|
|
518
|
-
// instead of the inner field. See runtime-utils for the full rules.
|
|
519
|
-
var collection = utils.unwrapWorkflowCollection(resolved.collection);
|
|
520
|
-
var loopResults = [];
|
|
521
|
-
|
|
522
|
-
// Track this loop's body scope so general-custom-js (and any handler
|
|
523
|
-
// that consults the scope stack) can build the params / innerParams
|
|
524
|
-
// arrays the workflow editor advertises: params = nodes BEFORE the
|
|
525
|
-
// outermost loop, innerParamsN = nodes inside the loop at depth N.
|
|
526
|
-
if (!context.__loopScopeStack) context.__loopScopeStack = [];
|
|
527
|
-
context.__loopScopeStack.push({ loopNodeId: node.id, bodyNodeIds: bodyNodeIds });
|
|
528
|
-
// Persistent registry of every loop scaffold node we've entered, so
|
|
529
|
-
// a sibling that runs AFTER an inner loop completes still recognises
|
|
530
|
-
// that inner loop id as scaffolding (not as a regular workflow node).
|
|
531
|
-
if (!context.__loopNodeIds) context.__loopNodeIds = {};
|
|
532
|
-
context.__loopNodeIds[node.id] = true;
|
|
533
|
-
|
|
534
|
-
var loopSteps = utils.createLoopSteps(node, resolved, context, collection);
|
|
535
|
-
for (var loopPass = loopSteps.next(context); loopPass; loopPass = loopSteps.next(context)) {
|
|
536
|
-
context[loopPass.key] = loopPass.value;
|
|
537
|
-
for (var bi = 0; bi < bodyNodes.length; bi++) {
|
|
538
|
-
var bNode = bodyNodes[bi];
|
|
539
|
-
var bResolved = resolveNodeConfig(bNode, context);
|
|
540
|
-
bResolved.__nodeId = bNode.id;
|
|
541
|
-
if (bNode.type === 'general-if-statement') {
|
|
542
|
-
context[bNode.id] = { result: utils.evaluateCondition(bResolved, context, bNode.config) };
|
|
543
|
-
continue;
|
|
544
|
-
}
|
|
545
|
-
var bHandler = nodeHandlers[bNode.type];
|
|
546
|
-
if (!bHandler) continue;
|
|
547
|
-
if (utils.isFireAndForgetNode(bNode)) {
|
|
548
|
-
utils.registerPendingNodePromise(context, utils.startFireAndForgetNode(bNode, bHandler, bResolved, context));
|
|
549
|
-
context[bNode.id] = null;
|
|
550
|
-
continue;
|
|
551
|
-
}
|
|
552
|
-
var bResult = await bHandler(bResolved, context);
|
|
553
|
-
if (bResult && (bResult.success === false || (typeof bResult.error === 'string' && bResult.error))) {
|
|
554
|
-
throw new Error(bResult.error || 'Loop body node execution failed');
|
|
555
|
-
}
|
|
556
|
-
context[bNode.id] = bResult;
|
|
471
|
+
const bodyNodeIds = await utils.runServerLoop(node, resolved, sortedNodes, SEGMENT_CONFIG.edges, context, async function(bNode, bResolved) {
|
|
472
|
+
var bHandler = nodeHandlers[bNode.type];
|
|
473
|
+
if (!bHandler) return;
|
|
474
|
+
if (utils.isFireAndForgetNode(bNode)) {
|
|
475
|
+
utils.registerPendingNodePromise(context, utils.startFireAndForgetNode(bNode, bHandler, bResolved, context));
|
|
476
|
+
context[bNode.id] = null;
|
|
477
|
+
return;
|
|
557
478
|
}
|
|
558
|
-
|
|
559
|
-
|
|
479
|
+
var bResult = await bHandler(bResolved, context);
|
|
480
|
+
if (bResult && (bResult.success === false || (typeof bResult.error === 'string' && bResult.error))) {
|
|
481
|
+
throw new Error(bResult.error || 'Loop body node execution failed');
|
|
560
482
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
context[node.id] = { completed: true, iterations: loopSteps.iterations(), results: loopResults };
|
|
566
|
-
// Store body node IDs so the outer loop can skip them
|
|
483
|
+
context[bNode.id] = bResult;
|
|
484
|
+
});
|
|
485
|
+
// The walk below must not run a body node again.
|
|
567
486
|
if (!context.__loopBodyNodeIds) context.__loopBodyNodeIds = {};
|
|
568
487
|
Object.assign(context.__loopBodyNodeIds, bodyNodeIds);
|
|
569
488
|
continue;
|
|
570
489
|
}
|
|
571
490
|
|
|
572
|
-
// Skip nodes that were part of a loop body (already executed inside the loop)
|
|
573
|
-
if (context.__loopBodyNodeIds && context.__loopBodyNodeIds[node.id]) continue;
|
|
574
491
|
|
|
575
492
|
// Switch nodes evaluate a value, match a case, execute that branch, skip all others
|
|
576
493
|
if (node.type === 'general-switch') {
|
|
@@ -866,6 +783,8 @@ module.exports = async function handler(req, res) {
|
|
|
866
783
|
for (let i = 0; i < sortedNodes.length; i++) {
|
|
867
784
|
const node = sortedNodes[i];
|
|
868
785
|
if (executed[node.id]) continue;
|
|
786
|
+
// The branch an if-statement or a switch did not take never runs.
|
|
787
|
+
if (context.__skippedNodes && context.__skippedNodes[node.id]) continue;
|
|
869
788
|
const resolved = resolveNodeConfig(node, context);
|
|
870
789
|
// See the non-streaming segment above — without this the unresolved-token
|
|
871
790
|
// guard never runs on the server, which is where every data node executes.
|
|
@@ -878,72 +797,27 @@ module.exports = async function handler(req, res) {
|
|
|
878
797
|
const condResult = utils.evaluateCondition(resolved, context, node.config);
|
|
879
798
|
context[node.id] = { result: condResult };
|
|
880
799
|
executed[node.id] = true;
|
|
800
|
+
if (!context.__skippedNodes) context.__skippedNodes = {};
|
|
801
|
+
Object.assign(context.__skippedNodes, utils.nonTakenBranchNodeIds(SEGMENT_CONFIG.edges, node.id, condResult));
|
|
881
802
|
if (streamStarted) {
|
|
882
803
|
__streamNodeResult(res, node.id, context[node.id]);
|
|
883
804
|
}
|
|
884
805
|
continue;
|
|
885
806
|
}
|
|
886
807
|
|
|
887
|
-
// Loop nodes in streaming segments
|
|
808
|
+
// Loop nodes in streaming segments: same runner as the regular-segment
|
|
809
|
+
// loop above, a loop inside the body included.
|
|
888
810
|
if (node.type === 'general-loop') {
|
|
889
|
-
var
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
if (ssBodyNodeIds[sslnid] || sslnid === node.id) continue;
|
|
897
|
-
ssBodyNodeIds[sslnid] = true;
|
|
898
|
-
SEGMENT_CONFIG.edges.forEach(function(e) {
|
|
899
|
-
if (e.source === sslnid && (e.sourceHandle === 'loop-body-out' || !e.sourceHandle)) {
|
|
900
|
-
if (e.target !== node.id) sslq.push(e.target);
|
|
901
|
-
}
|
|
902
|
-
});
|
|
811
|
+
var ssBodyNodeIds = await utils.runServerLoop(node, resolved, sortedNodes, SEGMENT_CONFIG.edges, context, async function(ssbNode, ssbRes) {
|
|
812
|
+
var ssbHandler = nodeHandlers[ssbNode.type];
|
|
813
|
+
if (!ssbHandler) return;
|
|
814
|
+
if (utils.isFireAndForgetNode(ssbNode)) {
|
|
815
|
+
utils.registerPendingNodePromise(context, utils.startFireAndForgetNode(ssbNode, ssbHandler, ssbRes, context));
|
|
816
|
+
context[ssbNode.id] = null;
|
|
817
|
+
return;
|
|
903
818
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
// See the regular-segment loop above: loopType=map must publish the
|
|
907
|
-
// last body node output as results[i] so the schema contract holds.
|
|
908
|
-
var ssLoopType = resolved.loopType || 'forEach';
|
|
909
|
-
var ssLastBodyNode = ssBodyNodes[ssBodyNodes.length - 1];
|
|
910
|
-
// Same envelope unwrap as the regular-segment loop above.
|
|
911
|
-
var ssLoopCollection = utils.unwrapWorkflowCollection(resolved.collection);
|
|
912
|
-
var ssLoopResults = [];
|
|
913
|
-
// Same loop-scope tracking as the regular-segment loop above. Custom-js
|
|
914
|
-
// bodies can read context.__loopScopeStack to recover the params /
|
|
915
|
-
// innerParams contract documented in workflow-utils.ts.
|
|
916
|
-
if (!context.__loopScopeStack) context.__loopScopeStack = [];
|
|
917
|
-
context.__loopScopeStack.push({ loopNodeId: node.id, bodyNodeIds: ssBodyNodeIds });
|
|
918
|
-
if (!context.__loopNodeIds) context.__loopNodeIds = {};
|
|
919
|
-
context.__loopNodeIds[node.id] = true;
|
|
920
|
-
var ssLoopSteps = utils.createLoopSteps(node, resolved, context, ssLoopCollection);
|
|
921
|
-
for (var ssLoopPass = ssLoopSteps.next(context); ssLoopPass; ssLoopPass = ssLoopSteps.next(context)) {
|
|
922
|
-
context[ssLoopPass.key] = ssLoopPass.value;
|
|
923
|
-
for (var ssbi = 0; ssbi < ssBodyNodes.length; ssbi++) {
|
|
924
|
-
var ssbNode = ssBodyNodes[ssbi];
|
|
925
|
-
var ssbRes = resolveNodeConfig(ssbNode, context);
|
|
926
|
-
ssbRes.__nodeId = ssbNode.id;
|
|
927
|
-
if (ssbNode.type === 'general-if-statement') {
|
|
928
|
-
context[ssbNode.id] = { result: utils.evaluateCondition(ssbRes, context, ssbNode.config) };
|
|
929
|
-
continue;
|
|
930
|
-
}
|
|
931
|
-
var ssbHandler = nodeHandlers[ssbNode.type];
|
|
932
|
-
if (!ssbHandler) continue;
|
|
933
|
-
if (utils.isFireAndForgetNode(ssbNode)) {
|
|
934
|
-
utils.registerPendingNodePromise(context, utils.startFireAndForgetNode(ssbNode, ssbHandler, ssbRes, context));
|
|
935
|
-
context[ssbNode.id] = null;
|
|
936
|
-
continue;
|
|
937
|
-
}
|
|
938
|
-
var ssbResult = await ssbHandler(ssbRes, context);
|
|
939
|
-
context[ssbNode.id] = ssbResult;
|
|
940
|
-
}
|
|
941
|
-
if (ssLoopType === 'map') {
|
|
942
|
-
ssLoopResults.push(ssLastBodyNode ? context[ssLastBodyNode.id] : context[node.id]);
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
context.__loopScopeStack.pop();
|
|
946
|
-
context[node.id] = { completed: true, iterations: ssLoopSteps.iterations(), results: ssLoopResults };
|
|
819
|
+
context[ssbNode.id] = await ssbHandler(ssbRes, context);
|
|
820
|
+
});
|
|
947
821
|
Object.keys(ssBodyNodeIds).forEach(function(k) { executed[k] = true; });
|
|
948
822
|
executed[node.id] = true;
|
|
949
823
|
if (streamStarted) {
|
|
@@ -1670,11 +1544,11 @@ const generateNodeExecutionLoop = (
|
|
|
1670
1544
|
// top-level one does: its handler only hands back the marker.
|
|
1671
1545
|
const loopBodyCustomNodeBlock = options?.hasCustomNodes
|
|
1672
1546
|
? `
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1547
|
+
if (bResult && bResult.__customNode) {
|
|
1548
|
+
var bcnFn = typeof __customNodes !== 'undefined' ? __customNodes[bResult.customNodeId] : null;
|
|
1549
|
+
context[bNode.id] = bcnFn ? await bcnFn(context, bResult.parameters, nodeHandlers) : null;
|
|
1550
|
+
return;
|
|
1551
|
+
}`
|
|
1678
1552
|
: ''
|
|
1679
1553
|
|
|
1680
1554
|
return ` var sortedNodes = ${configVar}.nodes.slice().sort(function(a, b) { return a.stepNumber - b.stepNumber; });
|
|
@@ -1684,6 +1558,8 @@ ${resumeBlock}
|
|
|
1684
1558
|
// Never resolved nor run on a non-taken branch, an if-statement included
|
|
1685
1559
|
// (see the segment executor above).
|
|
1686
1560
|
if (context.__skippedNodes && context.__skippedNodes[node.id]) continue;
|
|
1561
|
+
// A node of a loop body ran inside its loop, an if-statement included.
|
|
1562
|
+
if (context.__loopBodyNodeIds && context.__loopBodyNodeIds[node.id]) continue;
|
|
1687
1563
|
var resolved = resolveNodeConfig(node, context);
|
|
1688
1564
|
// See the other segment executors — the unresolved-token guard must run
|
|
1689
1565
|
// wherever data nodes execute, and they all execute on the server.
|
|
@@ -1694,103 +1570,33 @@ ${resumeBlock}
|
|
|
1694
1570
|
if (node.type === 'general-if-statement') {
|
|
1695
1571
|
var condResult = utils.evaluateCondition(resolved, context, node.config);
|
|
1696
1572
|
context[node.id] = { result: condResult };
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
var skipEdge = cfgEdges.find(function(e) { return e.source === node.id && e.sourceHandle === skipHandle; });
|
|
1700
|
-
if (skipEdge) {
|
|
1701
|
-
var skipIds = {};
|
|
1702
|
-
var skipQueue = [skipEdge.target];
|
|
1703
|
-
while (skipQueue.length > 0) {
|
|
1704
|
-
var sid = skipQueue.shift();
|
|
1705
|
-
if (skipIds[sid] || sid === node.id) continue;
|
|
1706
|
-
skipIds[sid] = true;
|
|
1707
|
-
cfgEdges.forEach(function(e) {
|
|
1708
|
-
if (e.source === sid && !skipIds[e.target] && e.target !== node.id) skipQueue.push(e.target);
|
|
1709
|
-
});
|
|
1710
|
-
}
|
|
1711
|
-
if (!context.__skippedNodes) context.__skippedNodes = {};
|
|
1712
|
-
Object.assign(context.__skippedNodes, skipIds);
|
|
1713
|
-
}
|
|
1573
|
+
if (!context.__skippedNodes) context.__skippedNodes = {};
|
|
1574
|
+
Object.assign(context.__skippedNodes, utils.nonTakenBranchNodeIds(${configVar}.edges, node.id, condResult));
|
|
1714
1575
|
continue;
|
|
1715
1576
|
}
|
|
1716
1577
|
|
|
1578
|
+
// Same runner as the segment routes, a loop inside the body included
|
|
1579
|
+
// (see runServerLoop in the server runtime).
|
|
1717
1580
|
if (node.type === 'general-loop') {
|
|
1718
|
-
var
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
var nid = lQueue.shift();
|
|
1726
|
-
if (bodyNodeIds[nid] || nid === node.id) continue;
|
|
1727
|
-
bodyNodeIds[nid] = true;
|
|
1728
|
-
${configVar}.edges.forEach(function(e) {
|
|
1729
|
-
if (e.source === nid && (e.sourceHandle === 'loop-body-out' || !e.sourceHandle)) {
|
|
1730
|
-
if (e.target !== node.id) lQueue.push(e.target);
|
|
1731
|
-
}
|
|
1732
|
-
});
|
|
1581
|
+
var bodyNodeIds = await utils.runServerLoop(node, resolved, sortedNodes, ${configVar}.edges, context, async function(bNode, bResolved) {
|
|
1582
|
+
var bHandler = nodeHandlers[bNode.type];
|
|
1583
|
+
if (!bHandler) return;
|
|
1584
|
+
if (utils.isFireAndForgetNode(bNode)) {
|
|
1585
|
+
utils.registerPendingNodePromise(context, utils.startFireAndForgetNode(bNode, bHandler, bResolved, context));
|
|
1586
|
+
context[bNode.id] = null;
|
|
1587
|
+
return;
|
|
1733
1588
|
}
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
// defines results as the array of last body node outputs (same as
|
|
1738
|
-
// Array.prototype.map). Pick the last body node so downstream nodes
|
|
1739
|
-
// (e.g. state-update-global-state) consume the transformed value.
|
|
1740
|
-
var __wfLoopType = resolved.loopType || 'forEach';
|
|
1741
|
-
var __wfLastBodyNode = bodyNodes[bodyNodes.length - 1];
|
|
1742
|
-
|
|
1743
|
-
// Same envelope unwrap as the regular-segment loop above.
|
|
1744
|
-
var collection = utils.unwrapWorkflowCollection(resolved.collection);
|
|
1745
|
-
var loopResults = [];
|
|
1746
|
-
|
|
1747
|
-
// Same loop-scope tracking as the regular-segment loop above so
|
|
1748
|
-
// general-custom-js inside the loop body sees the right params /
|
|
1749
|
-
// innerParams. Required for webhook + cron routes.
|
|
1750
|
-
if (!context.__loopScopeStack) context.__loopScopeStack = [];
|
|
1751
|
-
context.__loopScopeStack.push({ loopNodeId: node.id, bodyNodeIds: bodyNodeIds });
|
|
1752
|
-
if (!context.__loopNodeIds) context.__loopNodeIds = {};
|
|
1753
|
-
context.__loopNodeIds[node.id] = true;
|
|
1754
|
-
|
|
1755
|
-
var loopSteps = utils.createLoopSteps(node, resolved, context, collection);
|
|
1756
|
-
for (var loopPass = loopSteps.next(context); loopPass; loopPass = loopSteps.next(context)) {
|
|
1757
|
-
context[loopPass.key] = loopPass.value;
|
|
1758
|
-
for (var bi = 0; bi < bodyNodes.length; bi++) {
|
|
1759
|
-
var bNode = bodyNodes[bi];
|
|
1760
|
-
var bResolved = resolveNodeConfig(bNode, context);
|
|
1761
|
-
bResolved.__nodeId = bNode.id;
|
|
1762
|
-
if (bNode.type === 'general-if-statement') {
|
|
1763
|
-
context[bNode.id] = { result: utils.evaluateCondition(bResolved, context, bNode.config) };
|
|
1764
|
-
continue;
|
|
1765
|
-
}
|
|
1766
|
-
var bHandler = nodeHandlers[bNode.type];
|
|
1767
|
-
if (!bHandler) continue;
|
|
1768
|
-
if (utils.isFireAndForgetNode(bNode)) {
|
|
1769
|
-
utils.registerPendingNodePromise(context, utils.startFireAndForgetNode(bNode, bHandler, bResolved, context));
|
|
1770
|
-
context[bNode.id] = null;
|
|
1771
|
-
continue;
|
|
1772
|
-
}
|
|
1773
|
-
var bResult = await bHandler(bResolved, context);${loopBodyCustomNodeBlock}
|
|
1774
|
-
if (bResult && (bResult.success === false || (typeof bResult.error === 'string' && bResult.error))) {
|
|
1775
|
-
throw new Error(bResult.error || 'Loop body node execution failed');
|
|
1776
|
-
}
|
|
1777
|
-
context[bNode.id] = bResult;
|
|
1778
|
-
}
|
|
1779
|
-
if (__wfLoopType === 'map') {
|
|
1780
|
-
loopResults.push(__wfLastBodyNode ? context[__wfLastBodyNode.id] : context[node.id]);
|
|
1589
|
+
var bResult = await bHandler(bResolved, context);${loopBodyCustomNodeBlock}
|
|
1590
|
+
if (bResult && (bResult.success === false || (typeof bResult.error === 'string' && bResult.error))) {
|
|
1591
|
+
throw new Error(bResult.error || 'Loop body node execution failed');
|
|
1781
1592
|
}
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
context.__loopScopeStack.pop();
|
|
1785
|
-
|
|
1786
|
-
context[node.id] = { completed: true, iterations: loopSteps.iterations(), results: loopResults };
|
|
1593
|
+
context[bNode.id] = bResult;
|
|
1594
|
+
});
|
|
1787
1595
|
if (!context.__loopBodyNodeIds) context.__loopBodyNodeIds = {};
|
|
1788
1596
|
Object.assign(context.__loopBodyNodeIds, bodyNodeIds);
|
|
1789
1597
|
continue;
|
|
1790
1598
|
}
|
|
1791
1599
|
|
|
1792
|
-
if (context.__loopBodyNodeIds && context.__loopBodyNodeIds[node.id]) continue;
|
|
1793
|
-
|
|
1794
1600
|
if (node.type === 'general-switch') {
|
|
1795
1601
|
// Only an authored case expression is compiled (see matchSwitchCase).
|
|
1796
1602
|
var swMatchedCase = utils.matchSwitchCase(node.config, resolved, context);
|
|
@@ -215,6 +215,27 @@ function createLoopSteps(loopNode, config, context, collection) {
|
|
|
215
215
|
};
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
// The nodes of the branch an if-statement did NOT take: everything reachable
|
|
219
|
+
// from that branch's edge, never walking back through the if itself nor past
|
|
220
|
+
// stopAtId (the loop a body belongs to, whose loop-back edge leads out of it).
|
|
221
|
+
function nonTakenBranchNodeIds(edges, ifNodeId, conditionHeld, stopAtId) {
|
|
222
|
+
var skipHandle = conditionHeld ? 'false' : 'true';
|
|
223
|
+
var all = edges || [];
|
|
224
|
+
var skipEdge = all.find(function(e) { return e.source === ifNodeId && e.sourceHandle === skipHandle; });
|
|
225
|
+
var skipIds = {};
|
|
226
|
+
if (!skipEdge) return skipIds;
|
|
227
|
+
var queue = [skipEdge.target];
|
|
228
|
+
while (queue.length > 0) {
|
|
229
|
+
var id = queue.shift();
|
|
230
|
+
if (skipIds[id] || id === ifNodeId || id === stopAtId) continue;
|
|
231
|
+
skipIds[id] = true;
|
|
232
|
+
all.forEach(function(e) {
|
|
233
|
+
if (e.source === id && !skipIds[e.target]) queue.push(e.target);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return skipIds;
|
|
237
|
+
}
|
|
238
|
+
|
|
218
239
|
function resolveCtxRef(ref, context) {
|
|
219
240
|
var elem = context.triggerElement;
|
|
220
241
|
if (!elem || typeof elem.getAttribute !== 'function') return undefined;
|
|
@@ -1855,6 +1876,7 @@ module.exports = {
|
|
|
1855
1876
|
unwrapWorkflowCollection,
|
|
1856
1877
|
loopIterationContext,
|
|
1857
1878
|
createLoopSteps,
|
|
1879
|
+
nonTakenBranchNodeIds,
|
|
1858
1880
|
snapshotIncomingContext,
|
|
1859
1881
|
segmentReply,
|
|
1860
1882
|
withoutCredentialColumns,
|
|
@@ -860,7 +860,74 @@ function handOffPendingNodePromises(context) {
|
|
|
860
860
|
return Promise.resolve();
|
|
861
861
|
}
|
|
862
862
|
|
|
863
|
+
// One loop of a server route, a loop inside its body included: every route
|
|
864
|
+
// kind runs its loops through here and differs only in runBodyNode(node,
|
|
865
|
+
// resolvedConfig), which runs one ordinary body node and stores its result.
|
|
866
|
+
// The body is walked in step order, once per pass of the loop type (see
|
|
867
|
+
// createLoopSteps). An if-statement skips the branch it does not take for
|
|
868
|
+
// that pass only; a loop found in the body runs all of its own passes on
|
|
869
|
+
// every pass of this one, and its body nodes belong to it alone.
|
|
870
|
+
// Returns every node id between the loop and its loop-back edge (the bodies
|
|
871
|
+
// of inner loops included), which the route must not run again.
|
|
872
|
+
async function runServerLoop(loopNode, config, sortedNodes, edges, context, runBodyNode) {
|
|
873
|
+
var bodyNodeIds = {};
|
|
874
|
+
utils.getLoopBodyNodeIds(loopNode.id, edges).forEach(function(id) { bodyNodeIds[id] = true; });
|
|
875
|
+
var innerLoopBodyIds = {};
|
|
876
|
+
sortedNodes.forEach(function(n) {
|
|
877
|
+
if (!bodyNodeIds[n.id] || n.type !== 'general-loop') return;
|
|
878
|
+
utils.getLoopBodyNodeIds(n.id, edges).forEach(function(id) { innerLoopBodyIds[id] = true; });
|
|
879
|
+
});
|
|
880
|
+
var bodyNodes = sortedNodes.filter(function(n) { return bodyNodeIds[n.id] && !innerLoopBodyIds[n.id]; });
|
|
881
|
+
// A map loop publishes the output of its last body node per pass, as
|
|
882
|
+
// Array.prototype.map does (general-loop in node-context-schemas.json).
|
|
883
|
+
var isMap = (config.loopType || 'forEach') === 'map';
|
|
884
|
+
var lastBodyNode = bodyNodes[bodyNodes.length - 1];
|
|
885
|
+
// A helper node's envelope ({ result }, { records }, { matches }) is
|
|
886
|
+
// unwrapped to the one array it holds.
|
|
887
|
+
var collection = utils.unwrapWorkflowCollection(config.collection);
|
|
888
|
+
var results = [];
|
|
889
|
+
|
|
890
|
+
// The scope stack lets custom JavaScript tell the nodes before the outermost
|
|
891
|
+
// loop (params) from those inside a loop at each depth (innerParams); the
|
|
892
|
+
// registry keeps a finished inner loop recognisable as scaffolding.
|
|
893
|
+
if (!context.__loopScopeStack) context.__loopScopeStack = [];
|
|
894
|
+
context.__loopScopeStack.push({ loopNodeId: loopNode.id, bodyNodeIds: bodyNodeIds });
|
|
895
|
+
if (!context.__loopNodeIds) context.__loopNodeIds = {};
|
|
896
|
+
context.__loopNodeIds[loopNode.id] = true;
|
|
897
|
+
|
|
898
|
+
var steps = utils.createLoopSteps(loopNode, config, context, collection);
|
|
899
|
+
for (var pass = steps.next(context); pass; pass = steps.next(context)) {
|
|
900
|
+
context[pass.key] = pass.value;
|
|
901
|
+
var skippedThisPass = {};
|
|
902
|
+
for (var i = 0; i < bodyNodes.length; i++) {
|
|
903
|
+
var bodyNode = bodyNodes[i];
|
|
904
|
+
if (skippedThisPass[bodyNode.id]) continue;
|
|
905
|
+
var resolved = utils.resolveNodeConfig(bodyNode, context);
|
|
906
|
+
resolved.__nodeId = bodyNode.id;
|
|
907
|
+
if (bodyNode.type === 'general-if-statement') {
|
|
908
|
+
var held = utils.evaluateCondition(resolved, context, bodyNode.config);
|
|
909
|
+
context[bodyNode.id] = { result: held };
|
|
910
|
+
Object.assign(skippedThisPass, utils.nonTakenBranchNodeIds(edges, bodyNode.id, held, loopNode.id));
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
if (bodyNode.type === 'general-loop') {
|
|
914
|
+
await runServerLoop(bodyNode, resolved, sortedNodes, edges, context, runBodyNode);
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
await runBodyNode(bodyNode, resolved);
|
|
918
|
+
}
|
|
919
|
+
if (isMap) {
|
|
920
|
+
results.push(lastBodyNode ? context[lastBodyNode.id] : context[loopNode.id]);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
context.__loopScopeStack.pop();
|
|
925
|
+
context[loopNode.id] = { completed: true, iterations: steps.iterations(), results: results };
|
|
926
|
+
return bodyNodeIds;
|
|
927
|
+
}
|
|
928
|
+
|
|
863
929
|
module.exports = Object.assign({}, utils, {
|
|
930
|
+
runServerLoop: runServerLoop,
|
|
864
931
|
handOffPendingNodePromises: handOffPendingNodePromises,
|
|
865
932
|
claimSegmentContext: claimSegmentContext,
|
|
866
933
|
segmentReply: segmentReply,
|