callman-core 1.15.0 → 1.16.0
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/dist/host/connections.d.ts +3 -0
- package/dist/host/connections.d.ts.map +1 -1
- package/dist/host/connections.js +3 -0
- package/dist/host/connections.js.map +1 -1
- package/dist/host/db/connectionMapping.d.ts +3 -1
- package/dist/host/db/connectionMapping.d.ts.map +1 -1
- package/dist/host/db/connectionMapping.js +3 -0
- package/dist/host/db/connectionMapping.js.map +1 -1
- package/dist/host/db/db-query-reference.d.ts.map +1 -1
- package/dist/host/db/db-query-reference.js +12 -1
- package/dist/host/db/db-query-reference.js.map +1 -1
- package/dist/host/db/mssqlAdapter.d.ts +8 -0
- package/dist/host/db/mssqlAdapter.d.ts.map +1 -0
- package/dist/host/db/mssqlAdapter.js +118 -0
- package/dist/host/db/mssqlAdapter.js.map +1 -0
- package/dist/host/db/queryExecutor.d.ts.map +1 -1
- package/dist/host/db/queryExecutor.js +8 -0
- package/dist/host/db/queryExecutor.js.map +1 -1
- package/dist/host/db/sqlNormalize.d.ts +1 -1
- package/dist/host/db/sqlNormalize.d.ts.map +1 -1
- package/dist/host/db/types.d.ts +5 -1
- package/dist/host/db/types.d.ts.map +1 -1
- package/dist/host/index.d.ts +2 -1
- package/dist/host/index.d.ts.map +1 -1
- package/dist/host/index.js +2 -1
- package/dist/host/index.js.map +1 -1
- package/dist/host/redis/redisService.d.ts +17 -0
- package/dist/host/redis/redisService.d.ts.map +1 -1
- package/dist/host/redis/redisService.js +233 -40
- package/dist/host/redis/redisService.js.map +1 -1
- package/dist/host/redis/types.d.ts +4 -0
- package/dist/host/redis/types.d.ts.map +1 -1
- package/dist/scenario-runner/adapters/helpers/request-helpers.d.ts.map +1 -1
- package/dist/scenario-runner/adapters/helpers/request-helpers.js +28 -2
- package/dist/scenario-runner/adapters/helpers/request-helpers.js.map +1 -1
- package/dist/scenario-runner/condition.d.ts +4 -0
- package/dist/scenario-runner/condition.d.ts.map +1 -1
- package/dist/scenario-runner/condition.js +66 -7
- package/dist/scenario-runner/condition.js.map +1 -1
- package/dist/scenario-runner/conditionTypes.d.ts +14 -0
- package/dist/scenario-runner/conditionTypes.d.ts.map +1 -1
- package/dist/scenario-runner/extraction.d.ts +10 -0
- package/dist/scenario-runner/extraction.d.ts.map +1 -0
- package/dist/scenario-runner/extraction.js +147 -0
- package/dist/scenario-runner/extraction.js.map +1 -0
- package/dist/scenario-runner/index.d.ts +1 -0
- package/dist/scenario-runner/index.d.ts.map +1 -1
- package/dist/scenario-runner/index.js +2 -0
- package/dist/scenario-runner/index.js.map +1 -1
- package/dist/scenario-runner/runScenario.d.ts.map +1 -1
- package/dist/scenario-runner/runScenario.js +113 -6
- package/dist/scenario-runner/runScenario.js.map +1 -1
- package/dist/scenario-runner/types.d.ts +23 -1
- package/dist/scenario-runner/types.d.ts.map +1 -1
- package/dist-cjs/host/connections.js +3 -0
- package/dist-cjs/host/connections.js.map +1 -1
- package/dist-cjs/host/db/connectionMapping.js +3 -0
- package/dist-cjs/host/db/connectionMapping.js.map +1 -1
- package/dist-cjs/host/db/db-query-reference.js +12 -1
- package/dist-cjs/host/db/db-query-reference.js.map +1 -1
- package/dist-cjs/host/db/mssqlAdapter.js +124 -0
- package/dist-cjs/host/db/mssqlAdapter.js.map +1 -0
- package/dist-cjs/host/db/queryExecutor.js +8 -0
- package/dist-cjs/host/db/queryExecutor.js.map +1 -1
- package/dist-cjs/host/index.js +8 -1
- package/dist-cjs/host/index.js.map +1 -1
- package/dist-cjs/host/redis/redisService.js +237 -42
- package/dist-cjs/host/redis/redisService.js.map +1 -1
- package/dist-cjs/scenario-runner/adapters/helpers/request-helpers.js +28 -2
- package/dist-cjs/scenario-runner/adapters/helpers/request-helpers.js.map +1 -1
- package/dist-cjs/scenario-runner/condition.js +68 -8
- package/dist-cjs/scenario-runner/condition.js.map +1 -1
- package/dist-cjs/scenario-runner/extraction.js +151 -0
- package/dist-cjs/scenario-runner/extraction.js.map +1 -0
- package/dist-cjs/scenario-runner/index.js +2 -0
- package/dist-cjs/scenario-runner/index.js.map +1 -1
- package/dist-cjs/scenario-runner/runScenario.js +112 -5
- package/dist-cjs/scenario-runner/runScenario.js.map +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Value extraction for condition operands and set-vars assignments:
|
|
3
|
+
// JSONPath (jsonpath-plus, ALWAYS eval:false — path expressions must never
|
|
4
|
+
// execute code) and regex capture (plain RegExp with hard caps — re2 is a
|
|
5
|
+
// native module and would poison the browser/Electron bundle; the scenario
|
|
6
|
+
// author already runs arbitrary JS in script nodes, so a pathological
|
|
7
|
+
// pattern is not a privilege escalation, only an accident the caps bound).
|
|
8
|
+
//
|
|
9
|
+
// `applyExtract` NEVER throws: failures come back as `{ value: undefined,
|
|
10
|
+
// error }` so callers surface them in reports/debug views instead of
|
|
11
|
+
// failing the whole node on a bad expression.
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.applyExtract = exports.EXTRACT_MATCHES_MAX = exports.EXTRACT_REGEX_INPUT_MAX = exports.EXTRACT_REGEX_PATTERN_MAX = void 0;
|
|
14
|
+
const jsonpath_plus_1 = require("jsonpath-plus");
|
|
15
|
+
exports.EXTRACT_REGEX_PATTERN_MAX = 500;
|
|
16
|
+
exports.EXTRACT_REGEX_INPUT_MAX = 1_000_000;
|
|
17
|
+
exports.EXTRACT_MATCHES_MAX = 1000;
|
|
18
|
+
const failure = (error) => ({ value: undefined, error });
|
|
19
|
+
const toStructuredSource = (source) => {
|
|
20
|
+
if (typeof source === "string") {
|
|
21
|
+
const trimmed = source.trim();
|
|
22
|
+
if (!trimmed) {
|
|
23
|
+
return { error: "JSONPath source is empty" };
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
return { json: JSON.parse(trimmed) };
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return { error: "JSONPath source is a string that is not valid JSON" };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (source === null || typeof source === "undefined") {
|
|
33
|
+
return { error: "JSONPath source is empty" };
|
|
34
|
+
}
|
|
35
|
+
if (typeof source !== "object") {
|
|
36
|
+
return { error: `JSONPath source must be an object or array, got ${typeof source}` };
|
|
37
|
+
}
|
|
38
|
+
return { json: source };
|
|
39
|
+
};
|
|
40
|
+
const applyJsonPath = (source, extract) => {
|
|
41
|
+
const structured = toStructuredSource(source);
|
|
42
|
+
if ("error" in structured) {
|
|
43
|
+
return failure(structured.error);
|
|
44
|
+
}
|
|
45
|
+
let results;
|
|
46
|
+
try {
|
|
47
|
+
results = (0, jsonpath_plus_1.JSONPath)({
|
|
48
|
+
path: extract.expression,
|
|
49
|
+
json: structured.json,
|
|
50
|
+
eval: false,
|
|
51
|
+
wrap: true,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
return failure(`Invalid JSONPath expression: ${error instanceof Error ? error.message : String(error)}`);
|
|
56
|
+
}
|
|
57
|
+
if ((extract.select ?? "first") === "all") {
|
|
58
|
+
return { value: results.slice(0, exports.EXTRACT_MATCHES_MAX) };
|
|
59
|
+
}
|
|
60
|
+
return { value: results.length > 0 ? results[0] : undefined };
|
|
61
|
+
};
|
|
62
|
+
const toRegexInput = (source) => {
|
|
63
|
+
if (typeof source === "string") {
|
|
64
|
+
return source;
|
|
65
|
+
}
|
|
66
|
+
if (source === null || typeof source === "undefined") {
|
|
67
|
+
return "";
|
|
68
|
+
}
|
|
69
|
+
if (typeof source === "number" || typeof source === "boolean") {
|
|
70
|
+
return String(source);
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
return JSON.stringify(source);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return String(source);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const ALLOWED_REGEX_FLAGS = /^[gimsuy]*$/;
|
|
80
|
+
// Group selection: an explicit `group` is strict; without one, the first
|
|
81
|
+
// capture group wins when the pattern has any, else the whole match.
|
|
82
|
+
const pickGroup = (match, extract) => {
|
|
83
|
+
if (typeof extract.group === "number") {
|
|
84
|
+
if (extract.group < 0 || extract.group >= match.length) {
|
|
85
|
+
return failure(`Capture group ${extract.group} is not present in the match`);
|
|
86
|
+
}
|
|
87
|
+
return { value: match[extract.group] };
|
|
88
|
+
}
|
|
89
|
+
return { value: match.length > 1 ? match[1] : match[0] };
|
|
90
|
+
};
|
|
91
|
+
const applyRegex = (source, extract) => {
|
|
92
|
+
if (extract.expression.length > exports.EXTRACT_REGEX_PATTERN_MAX) {
|
|
93
|
+
return failure(`Regex pattern exceeds ${exports.EXTRACT_REGEX_PATTERN_MAX} characters`);
|
|
94
|
+
}
|
|
95
|
+
const input = toRegexInput(source);
|
|
96
|
+
if (input.length > exports.EXTRACT_REGEX_INPUT_MAX) {
|
|
97
|
+
return failure(`Regex input exceeds ${exports.EXTRACT_REGEX_INPUT_MAX} characters`);
|
|
98
|
+
}
|
|
99
|
+
const rawFlags = extract.flags ?? "";
|
|
100
|
+
if (!ALLOWED_REGEX_FLAGS.test(rawFlags)) {
|
|
101
|
+
return failure(`Invalid regex flags "${rawFlags}" — allowed: g i m s u y`);
|
|
102
|
+
}
|
|
103
|
+
if ((extract.select ?? "first") === "all") {
|
|
104
|
+
let regex;
|
|
105
|
+
try {
|
|
106
|
+
regex = new RegExp(extract.expression, rawFlags.includes("g") ? rawFlags : `${rawFlags}g`);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
return failure(`Invalid regex: ${error instanceof Error ? error.message : String(error)}`);
|
|
110
|
+
}
|
|
111
|
+
const values = [];
|
|
112
|
+
for (const match of input.matchAll(regex)) {
|
|
113
|
+
const picked = pickGroup(match, extract);
|
|
114
|
+
if (picked.error) {
|
|
115
|
+
return picked;
|
|
116
|
+
}
|
|
117
|
+
values.push(picked.value);
|
|
118
|
+
if (values.length >= exports.EXTRACT_MATCHES_MAX) {
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return { value: values };
|
|
123
|
+
}
|
|
124
|
+
let regex;
|
|
125
|
+
try {
|
|
126
|
+
regex = new RegExp(extract.expression, rawFlags.replace("g", ""));
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
return failure(`Invalid regex: ${error instanceof Error ? error.message : String(error)}`);
|
|
130
|
+
}
|
|
131
|
+
const match = regex.exec(input);
|
|
132
|
+
if (!match) {
|
|
133
|
+
return { value: undefined };
|
|
134
|
+
}
|
|
135
|
+
return pickGroup(match, extract);
|
|
136
|
+
};
|
|
137
|
+
const applyExtract = (source, extract) => {
|
|
138
|
+
if (!extract.expression || !extract.expression.trim()) {
|
|
139
|
+
return failure("Extraction expression is empty");
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
return extract.type === "jsonpath"
|
|
143
|
+
? applyJsonPath(source, extract)
|
|
144
|
+
: applyRegex(source, extract);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
exports.applyExtract = applyExtract;
|
|
151
|
+
//# sourceMappingURL=extraction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extraction.js","sourceRoot":"","sources":["../../src/scenario-runner/extraction.ts"],"names":[],"mappings":"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"}
|
|
@@ -33,6 +33,8 @@ Object.defineProperty(exports, "resolveScenarioTemplateString", { enumerable: tr
|
|
|
33
33
|
// frontend Scenario Builder can drive their UI from the same source of
|
|
34
34
|
// truth as the runtime executor.
|
|
35
35
|
__exportStar(require("./condition.js"), exports);
|
|
36
|
+
// JSONPath/regex extraction shared by condition operands and set-vars rows
|
|
37
|
+
__exportStar(require("./extraction.js"), exports);
|
|
36
38
|
// Assertion node — a no-code checkpoint built on the condition engine
|
|
37
39
|
// (createDefaultAssertionConfig / normalize / validate / evaluate).
|
|
38
40
|
__exportStar(require("./assertion.js"), exports);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scenario-runner/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,mDAA+C;AAAtC,6GAAA,WAAW,OAAA;AACpB,yEAAqE;AAA5D,mIAAA,sBAAsB,OAAA;AAC/B,6DAQ+B;AAP7B,kIAAA,2BAA2B,OAAA;AAC3B,kIAAA,2BAA2B,OAAA;AAC3B,oIAAA,6BAA6B,OAAA;AAC7B,iIAAA,0BAA0B,OAAA;AAC1B,qJAAA,8CAA8C,OAAA;AAC9C,wIAAA,iCAAiC,OAAA;AACjC,oIAAA,6BAA6B,OAAA;AAE/B,iEAAiE;AACjE,qEAAqE;AACrE,uEAAuE;AACvE,uEAAuE;AACvE,iCAAiC;AACjC,iDAA+B;AAC/B,sEAAsE;AACtE,oEAAoE;AACpE,iDAA+B;AAC/B,oDAGiC;AAF/B,4HAAA,gCAAgC,OAAA;AAChC,sHAAA,0BAA0B,OAAA;AAE5B,wDAAsC;AACtC,sDAAoC;AACpC,sDAAoC;AACpC,6CAA2B;AAC3B,sDAAoC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scenario-runner/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,mDAA+C;AAAtC,6GAAA,WAAW,OAAA;AACpB,yEAAqE;AAA5D,mIAAA,sBAAsB,OAAA;AAC/B,6DAQ+B;AAP7B,kIAAA,2BAA2B,OAAA;AAC3B,kIAAA,2BAA2B,OAAA;AAC3B,oIAAA,6BAA6B,OAAA;AAC7B,iIAAA,0BAA0B,OAAA;AAC1B,qJAAA,8CAA8C,OAAA;AAC9C,wIAAA,iCAAiC,OAAA;AACjC,oIAAA,6BAA6B,OAAA;AAE/B,iEAAiE;AACjE,qEAAqE;AACrE,uEAAuE;AACvE,uEAAuE;AACvE,iCAAiC;AACjC,iDAA+B;AAC/B,2EAA2E;AAC3E,kDAAgC;AAChC,sEAAsE;AACtE,oEAAoE;AACpE,iDAA+B;AAC/B,oDAGiC;AAF/B,4HAAA,gCAAgC,OAAA;AAChC,sHAAA,0BAA0B,OAAA;AAE5B,wDAAsC;AACtC,sDAAoC;AACpC,sDAAoC;AACpC,6CAA2B;AAC3B,sDAAoC"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runScenario = void 0;
|
|
4
4
|
const condition_js_1 = require("./condition.js");
|
|
5
|
+
const extraction_js_1 = require("./extraction.js");
|
|
5
6
|
const assertion_js_1 = require("./assertion.js");
|
|
6
7
|
const graphTraversal_js_1 = require("./graphTraversal.js");
|
|
7
8
|
const executionPolicyRunner_js_1 = require("./executionPolicyRunner.js");
|
|
@@ -446,6 +447,9 @@ const updateRecordFromEvent = (records, event) => {
|
|
|
446
447
|
retryDelayMs: event.retryDelayMs ?? current.retryDelayMs,
|
|
447
448
|
failurePolicy: event.failurePolicy ?? current.failurePolicy,
|
|
448
449
|
continuedAfterFailure: event.continuedAfterFailure ?? current.continuedAfterFailure,
|
|
450
|
+
errorHandled: event.type === "step:fail"
|
|
451
|
+
? (event.errorHandled ?? current.errorHandled)
|
|
452
|
+
: current.errorHandled,
|
|
449
453
|
errors: event.errors ?? current.errors,
|
|
450
454
|
durationMs: event.durationMs ?? current.durationMs,
|
|
451
455
|
statusCode: event.statusCode ?? current.statusCode,
|
|
@@ -715,6 +719,38 @@ const runScenario = async ({ scenario, environment = {}, globals = {}, runtimeAd
|
|
|
715
719
|
const markRemainingNodesSkipped = async (reason) => {
|
|
716
720
|
await markNodeIdsSkipped(rootScope, rootScope.graph.nodeMap.keys(), reason);
|
|
717
721
|
};
|
|
722
|
+
// After a failure is routed into error edges, the node's NORMAL outgoing
|
|
723
|
+
// subtree must not linger idle (an idle source blocks downstream joins in
|
|
724
|
+
// isNodeReady). Skip everything reachable from the normal edges that is
|
|
725
|
+
// NOT also reachable from the taken error edges — shared downstream joins
|
|
726
|
+
// stay runnable.
|
|
727
|
+
const skipUntakenNormalBranches = async (scope, nodeId, errorEdges) => {
|
|
728
|
+
const outgoing = scope.graph.outgoingMap.get(nodeId) ?? [];
|
|
729
|
+
const normalTargets = outgoing
|
|
730
|
+
.filter((edge) => (edge.type ?? "default") !== "error")
|
|
731
|
+
.map((edge) => edge.target);
|
|
732
|
+
if (normalTargets.length === 0) {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
const collectReachable = (startIds) => {
|
|
736
|
+
const stack = [...startIds];
|
|
737
|
+
const seen = new Set();
|
|
738
|
+
while (stack.length > 0) {
|
|
739
|
+
const current = stack.pop();
|
|
740
|
+
if (!current || seen.has(current)) {
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
seen.add(current);
|
|
744
|
+
for (const edge of scope.graph.outgoingMap.get(current) ?? []) {
|
|
745
|
+
stack.push(edge.target);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return seen;
|
|
749
|
+
};
|
|
750
|
+
const reachableFromError = collectReachable(errorEdges.map((edge) => edge.target));
|
|
751
|
+
const untaken = Array.from(collectReachable(normalTargets)).filter((candidate) => !reachableFromError.has(candidate));
|
|
752
|
+
await markNodeIdsSkipped(scope, untaken, "Error branch taken");
|
|
753
|
+
};
|
|
718
754
|
const markScopeRemainingNodesSkipped = async (scope, reason) => {
|
|
719
755
|
await markNodeIdsSkipped(scope, scope.graph.nodeMap.keys(), reason);
|
|
720
756
|
};
|
|
@@ -763,6 +799,10 @@ const runScenario = async ({ scenario, environment = {}, globals = {}, runtimeAd
|
|
|
763
799
|
if (!node) {
|
|
764
800
|
return;
|
|
765
801
|
}
|
|
802
|
+
// Synchronous claim: nothing may await between the idle/ready checks
|
|
803
|
+
// above and this line, or two branches converging on a join could both
|
|
804
|
+
// execute it (each inbound edge calls executeNode once).
|
|
805
|
+
localStatuses.set(stepId, "running");
|
|
766
806
|
const title = buildTitle(node);
|
|
767
807
|
const nodeLabel = node.data.label.trim();
|
|
768
808
|
const executionPolicy = (0, nodePolicy_js_1.supportsScenarioNodeExecutionPolicy)(node)
|
|
@@ -1270,6 +1310,7 @@ const runScenario = async ({ scenario, environment = {}, globals = {}, runtimeAd
|
|
|
1270
1310
|
// may reference a variable set by an earlier one.
|
|
1271
1311
|
const writtenEnvironment = {};
|
|
1272
1312
|
const writtenGlobals = {};
|
|
1313
|
+
const extractionOutcomes = [];
|
|
1273
1314
|
for (const assignment of node.data.config.assignments ?? []) {
|
|
1274
1315
|
if (!assignment || assignment.enabled === false) {
|
|
1275
1316
|
continue;
|
|
@@ -1288,7 +1329,49 @@ const runScenario = async ({ scenario, environment = {}, globals = {}, runtimeAd
|
|
|
1288
1329
|
if (!resolvedKey) {
|
|
1289
1330
|
continue;
|
|
1290
1331
|
}
|
|
1291
|
-
|
|
1332
|
+
let resolvedValue;
|
|
1333
|
+
const extractorType = assignment.extractorType ?? "template";
|
|
1334
|
+
if (extractorType === "jsonpath" || extractorType === "regex") {
|
|
1335
|
+
// Extractor rows resolve `source` with condition-operand path
|
|
1336
|
+
// semantics, then apply the expression. A failed extraction
|
|
1337
|
+
// SKIPS the write (never persists a wrong/empty value
|
|
1338
|
+
// silently) and surfaces the error in outputData.
|
|
1339
|
+
const sourcePath = (assignment.source ?? "").trim();
|
|
1340
|
+
const resolvedSource = (0, condition_js_1.resolveConditionRuntimeValue)({
|
|
1341
|
+
environment: runtimeContext.environment,
|
|
1342
|
+
globals: runtimeContext.globals,
|
|
1343
|
+
workflowContext: runtimeContext.workflowContext,
|
|
1344
|
+
nodeMetaByLabel: runtimeContext.nodeMetaByLabel,
|
|
1345
|
+
responseRoot: runtimeContext.responseRoot,
|
|
1346
|
+
dbResult: runtimeContext.dbResult,
|
|
1347
|
+
kafkaEvent: runtimeContext.kafkaEvent,
|
|
1348
|
+
redisResult: runtimeContext.redisResult,
|
|
1349
|
+
}, sourcePath);
|
|
1350
|
+
const outcome = (0, extraction_js_1.applyExtract)(resolvedSource.value, {
|
|
1351
|
+
type: extractorType,
|
|
1352
|
+
expression: assignment.expression ?? "",
|
|
1353
|
+
flags: assignment.flags,
|
|
1354
|
+
group: assignment.group,
|
|
1355
|
+
select: assignment.select,
|
|
1356
|
+
});
|
|
1357
|
+
if (outcome.error || typeof outcome.value === "undefined") {
|
|
1358
|
+
extractionOutcomes.push({
|
|
1359
|
+
key: resolvedKey,
|
|
1360
|
+
extractorType,
|
|
1361
|
+
source: sourcePath,
|
|
1362
|
+
error: outcome.error ?? "Extraction produced no value",
|
|
1363
|
+
});
|
|
1364
|
+
continue;
|
|
1365
|
+
}
|
|
1366
|
+
extractionOutcomes.push({ key: resolvedKey, extractorType, source: sourcePath });
|
|
1367
|
+
resolvedValue =
|
|
1368
|
+
typeof outcome.value === "string"
|
|
1369
|
+
? outcome.value
|
|
1370
|
+
: JSON.stringify(outcome.value);
|
|
1371
|
+
}
|
|
1372
|
+
else {
|
|
1373
|
+
resolvedValue = (0, templateResolver_js_1.resolveScenarioTemplateString)(assignment.value ?? "", rowTemplateValues);
|
|
1374
|
+
}
|
|
1292
1375
|
applyVariableMutationsToContext(runtimeContext, [
|
|
1293
1376
|
{
|
|
1294
1377
|
scope: assignment.scope === "global" ? "global" : "environment",
|
|
@@ -1307,6 +1390,8 @@ const runScenario = async ({ scenario, environment = {}, globals = {}, runtimeAd
|
|
|
1307
1390
|
outputData = {
|
|
1308
1391
|
environment: writtenEnvironment,
|
|
1309
1392
|
globals: writtenGlobals,
|
|
1393
|
+
// Additive: present only when extractor rows ran.
|
|
1394
|
+
...(extractionOutcomes.length > 0 ? { extraction: extractionOutcomes } : {}),
|
|
1310
1395
|
};
|
|
1311
1396
|
}
|
|
1312
1397
|
if (node.type === "aggregator") {
|
|
@@ -1379,7 +1464,14 @@ const runScenario = async ({ scenario, environment = {}, globals = {}, runtimeAd
|
|
|
1379
1464
|
},
|
|
1380
1465
|
});
|
|
1381
1466
|
const durationMs = Math.round(Date.now() - executionStartedAtMs);
|
|
1382
|
-
|
|
1467
|
+
// "error" edges (no-code try/catch): a failure with a drawn handler is
|
|
1468
|
+
// routed into the error branch instead of stopping or continuing on
|
|
1469
|
+
// the normal path — the user's explicit handler overrides onFailure.
|
|
1470
|
+
const errorEdges = policyResult.outcome === "failed" || policyResult.outcome === "continued"
|
|
1471
|
+
? (scope.graph.outgoingMap.get(nodeId) ?? []).filter((edge) => (edge.type ?? "default") === "error")
|
|
1472
|
+
: [];
|
|
1473
|
+
const failureRouted = errorEdges.length > 0;
|
|
1474
|
+
if (policyResult.outcome === "failed" && !failureRouted) {
|
|
1383
1475
|
localStatuses.set(stepId, "failed");
|
|
1384
1476
|
const failedAt = new Date().toISOString();
|
|
1385
1477
|
const nodeMeta = buildNodeExecutionMeta({
|
|
@@ -1452,7 +1544,9 @@ const runScenario = async ({ scenario, environment = {}, globals = {}, runtimeAd
|
|
|
1452
1544
|
});
|
|
1453
1545
|
throw new ScenarioNodeFailureError(policyResult.finalErrorMessage ?? "Scenario node failed");
|
|
1454
1546
|
}
|
|
1455
|
-
if (policyResult.outcome === "continued") {
|
|
1547
|
+
if (policyResult.outcome === "continued" || policyResult.outcome === "failed") {
|
|
1548
|
+
// Either policy-continue, or a failure routed into error edges —
|
|
1549
|
+
// both leave the node terminal as failed_continued so joins unblock.
|
|
1456
1550
|
degradedStepIds.add(stepId);
|
|
1457
1551
|
localStatuses.set(stepId, "failed_continued");
|
|
1458
1552
|
const continuedAt = new Date().toISOString();
|
|
@@ -1498,7 +1592,9 @@ const runScenario = async ({ scenario, environment = {}, globals = {}, runtimeAd
|
|
|
1498
1592
|
retryEnabled: executionPolicy.retry.enabled,
|
|
1499
1593
|
retryDelayMs: executionPolicy.retry.delayMs,
|
|
1500
1594
|
failurePolicy: executionPolicy.onFailure,
|
|
1501
|
-
continuedAfterFailure:
|
|
1595
|
+
continuedAfterFailure: policyResult.outcome === "continued",
|
|
1596
|
+
errorHandled: failureRouted ? true : undefined,
|
|
1597
|
+
errorEdgeIds: failureRouted ? errorEdges.map((edge) => edge.id) : undefined,
|
|
1502
1598
|
errors: policyResult.errors,
|
|
1503
1599
|
nodeMeta,
|
|
1504
1600
|
errorMessage: policyResult.finalErrorMessage ??
|
|
@@ -1670,10 +1766,21 @@ const runScenario = async ({ scenario, environment = {}, globals = {}, runtimeAd
|
|
|
1670
1766
|
syncLoopRoots();
|
|
1671
1767
|
}
|
|
1672
1768
|
}
|
|
1769
|
+
if (failureRouted) {
|
|
1770
|
+
await skipUntakenNormalBranches(scope, nodeId, errorEdges);
|
|
1771
|
+
for (const edge of errorEdges) {
|
|
1772
|
+
throwIfStopped(signal);
|
|
1773
|
+
await executeNode(edge.target, scope.stepIdForEdge(edge.id), {}, scope);
|
|
1774
|
+
}
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1673
1777
|
const outgoingEdges = scope.graph.outgoingMap.get(nodeId) ?? [];
|
|
1674
1778
|
const nextEdges = node.type === "condition"
|
|
1675
1779
|
? outgoingEdges.filter((edge) => (conditionResult ? "yes" : "no") === (edge.type ?? "default"))
|
|
1676
|
-
:
|
|
1780
|
+
: // Error edges are handler paths — never taken on success (or on a
|
|
1781
|
+
// policy-continue without routing). No-op for scenarios that have
|
|
1782
|
+
// no error edges.
|
|
1783
|
+
outgoingEdges.filter((edge) => (edge.type ?? "default") !== "error");
|
|
1677
1784
|
for (const edge of nextEdges) {
|
|
1678
1785
|
throwIfStopped(signal);
|
|
1679
1786
|
await executeNode(edge.target, scope.stepIdForEdge(edge.id), {}, scope);
|