callman-core 1.14.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/request/oauth2.d.ts +1 -1
- package/dist/request/oauth2.d.ts.map +1 -1
- package/dist/runner/applyRequestAuth.d.ts +8 -0
- package/dist/runner/applyRequestAuth.d.ts.map +1 -0
- package/dist/runner/applyRequestAuth.js +71 -0
- package/dist/runner/applyRequestAuth.js.map +1 -0
- package/dist/runner/requestRunner.d.ts.map +1 -1
- package/dist/runner/requestRunner.js +23 -0
- package/dist/runner/requestRunner.js.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 +25 -17
- package/dist/scenario-runner/types.d.ts.map +1 -1
- package/dist/types/index.d.ts +26 -0
- package/dist/types/index.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/runner/applyRequestAuth.js +75 -0
- package/dist-cjs/runner/applyRequestAuth.js.map +1 -0
- package/dist-cjs/runner/requestRunner.js +23 -0
- package/dist-cjs/runner/requestRunner.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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conditionTypes.d.ts","sourceRoot":"","sources":["../../src/scenario-runner/conditionTypes.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEjD,MAAM,MAAM,wBAAwB,GAAG,KAAK,GAAG,IAAI,CAAC;AAEpD,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,SAAS,CAAC;AAEzD,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpF,MAAM,MAAM,iBAAiB,GACzB,QAAQ,GACR,YAAY,GACZ,eAAe,GACf,mBAAmB,GACnB,cAAc,GACd,WAAW,GACX,kBAAkB,GAClB,eAAe,GACf,SAAS,GACT,UAAU,GACV,cAAc,GACd,aAAa,GACb,WAAW,GACX,OAAO,GACP,QAAQ,GACR,YAAY,GACZ,UAAU,GACV,WAAW,GACX,SAAS,GACT,UAAU,GACV,IAAI,GACJ,QAAQ,CAAC;AAEb,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,oBAAoB,CAAC;IAC3B,SAAS,EAAE,kBAAkB,CAAC;IAC9B,KAAK,EAAE,OAAO,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"conditionTypes.d.ts","sourceRoot":"","sources":["../../src/scenario-runner/conditionTypes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { OperandExtract } from "./conditionTypes.js";
|
|
2
|
+
export declare const EXTRACT_REGEX_PATTERN_MAX = 500;
|
|
3
|
+
export declare const EXTRACT_REGEX_INPUT_MAX = 1000000;
|
|
4
|
+
export declare const EXTRACT_MATCHES_MAX = 1000;
|
|
5
|
+
export interface ExtractOutcome {
|
|
6
|
+
value: unknown;
|
|
7
|
+
error?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const applyExtract: (source: unknown, extract: OperandExtract) => ExtractOutcome;
|
|
10
|
+
//# sourceMappingURL=extraction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extraction.d.ts","sourceRoot":"","sources":["../../src/scenario-runner/extraction.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAC7C,eAAO,MAAM,uBAAuB,UAAY,CAAC;AACjD,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAExC,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAoID,eAAO,MAAM,YAAY,GAAI,QAAQ,OAAO,EAAE,SAAS,cAAc,KAAG,cAWvE,CAAC"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Value extraction for condition operands and set-vars assignments:
|
|
2
|
+
// JSONPath (jsonpath-plus, ALWAYS eval:false — path expressions must never
|
|
3
|
+
// execute code) and regex capture (plain RegExp with hard caps — re2 is a
|
|
4
|
+
// native module and would poison the browser/Electron bundle; the scenario
|
|
5
|
+
// author already runs arbitrary JS in script nodes, so a pathological
|
|
6
|
+
// pattern is not a privilege escalation, only an accident the caps bound).
|
|
7
|
+
//
|
|
8
|
+
// `applyExtract` NEVER throws: failures come back as `{ value: undefined,
|
|
9
|
+
// error }` so callers surface them in reports/debug views instead of
|
|
10
|
+
// failing the whole node on a bad expression.
|
|
11
|
+
import { JSONPath } from "jsonpath-plus";
|
|
12
|
+
export const EXTRACT_REGEX_PATTERN_MAX = 500;
|
|
13
|
+
export const EXTRACT_REGEX_INPUT_MAX = 1_000_000;
|
|
14
|
+
export const EXTRACT_MATCHES_MAX = 1000;
|
|
15
|
+
const failure = (error) => ({ value: undefined, error });
|
|
16
|
+
const toStructuredSource = (source) => {
|
|
17
|
+
if (typeof source === "string") {
|
|
18
|
+
const trimmed = source.trim();
|
|
19
|
+
if (!trimmed) {
|
|
20
|
+
return { error: "JSONPath source is empty" };
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
return { json: JSON.parse(trimmed) };
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return { error: "JSONPath source is a string that is not valid JSON" };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (source === null || typeof source === "undefined") {
|
|
30
|
+
return { error: "JSONPath source is empty" };
|
|
31
|
+
}
|
|
32
|
+
if (typeof source !== "object") {
|
|
33
|
+
return { error: `JSONPath source must be an object or array, got ${typeof source}` };
|
|
34
|
+
}
|
|
35
|
+
return { json: source };
|
|
36
|
+
};
|
|
37
|
+
const applyJsonPath = (source, extract) => {
|
|
38
|
+
const structured = toStructuredSource(source);
|
|
39
|
+
if ("error" in structured) {
|
|
40
|
+
return failure(structured.error);
|
|
41
|
+
}
|
|
42
|
+
let results;
|
|
43
|
+
try {
|
|
44
|
+
results = JSONPath({
|
|
45
|
+
path: extract.expression,
|
|
46
|
+
json: structured.json,
|
|
47
|
+
eval: false,
|
|
48
|
+
wrap: true,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
return failure(`Invalid JSONPath expression: ${error instanceof Error ? error.message : String(error)}`);
|
|
53
|
+
}
|
|
54
|
+
if ((extract.select ?? "first") === "all") {
|
|
55
|
+
return { value: results.slice(0, EXTRACT_MATCHES_MAX) };
|
|
56
|
+
}
|
|
57
|
+
return { value: results.length > 0 ? results[0] : undefined };
|
|
58
|
+
};
|
|
59
|
+
const toRegexInput = (source) => {
|
|
60
|
+
if (typeof source === "string") {
|
|
61
|
+
return source;
|
|
62
|
+
}
|
|
63
|
+
if (source === null || typeof source === "undefined") {
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
66
|
+
if (typeof source === "number" || typeof source === "boolean") {
|
|
67
|
+
return String(source);
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
return JSON.stringify(source);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return String(source);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const ALLOWED_REGEX_FLAGS = /^[gimsuy]*$/;
|
|
77
|
+
// Group selection: an explicit `group` is strict; without one, the first
|
|
78
|
+
// capture group wins when the pattern has any, else the whole match.
|
|
79
|
+
const pickGroup = (match, extract) => {
|
|
80
|
+
if (typeof extract.group === "number") {
|
|
81
|
+
if (extract.group < 0 || extract.group >= match.length) {
|
|
82
|
+
return failure(`Capture group ${extract.group} is not present in the match`);
|
|
83
|
+
}
|
|
84
|
+
return { value: match[extract.group] };
|
|
85
|
+
}
|
|
86
|
+
return { value: match.length > 1 ? match[1] : match[0] };
|
|
87
|
+
};
|
|
88
|
+
const applyRegex = (source, extract) => {
|
|
89
|
+
if (extract.expression.length > EXTRACT_REGEX_PATTERN_MAX) {
|
|
90
|
+
return failure(`Regex pattern exceeds ${EXTRACT_REGEX_PATTERN_MAX} characters`);
|
|
91
|
+
}
|
|
92
|
+
const input = toRegexInput(source);
|
|
93
|
+
if (input.length > EXTRACT_REGEX_INPUT_MAX) {
|
|
94
|
+
return failure(`Regex input exceeds ${EXTRACT_REGEX_INPUT_MAX} characters`);
|
|
95
|
+
}
|
|
96
|
+
const rawFlags = extract.flags ?? "";
|
|
97
|
+
if (!ALLOWED_REGEX_FLAGS.test(rawFlags)) {
|
|
98
|
+
return failure(`Invalid regex flags "${rawFlags}" — allowed: g i m s u y`);
|
|
99
|
+
}
|
|
100
|
+
if ((extract.select ?? "first") === "all") {
|
|
101
|
+
let regex;
|
|
102
|
+
try {
|
|
103
|
+
regex = new RegExp(extract.expression, rawFlags.includes("g") ? rawFlags : `${rawFlags}g`);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
return failure(`Invalid regex: ${error instanceof Error ? error.message : String(error)}`);
|
|
107
|
+
}
|
|
108
|
+
const values = [];
|
|
109
|
+
for (const match of input.matchAll(regex)) {
|
|
110
|
+
const picked = pickGroup(match, extract);
|
|
111
|
+
if (picked.error) {
|
|
112
|
+
return picked;
|
|
113
|
+
}
|
|
114
|
+
values.push(picked.value);
|
|
115
|
+
if (values.length >= EXTRACT_MATCHES_MAX) {
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { value: values };
|
|
120
|
+
}
|
|
121
|
+
let regex;
|
|
122
|
+
try {
|
|
123
|
+
regex = new RegExp(extract.expression, rawFlags.replace("g", ""));
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
return failure(`Invalid regex: ${error instanceof Error ? error.message : String(error)}`);
|
|
127
|
+
}
|
|
128
|
+
const match = regex.exec(input);
|
|
129
|
+
if (!match) {
|
|
130
|
+
return { value: undefined };
|
|
131
|
+
}
|
|
132
|
+
return pickGroup(match, extract);
|
|
133
|
+
};
|
|
134
|
+
export const applyExtract = (source, extract) => {
|
|
135
|
+
if (!extract.expression || !extract.expression.trim()) {
|
|
136
|
+
return failure("Extraction expression is empty");
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
return extract.type === "jsonpath"
|
|
140
|
+
? applyJsonPath(source, extract)
|
|
141
|
+
: applyRegex(source, extract);
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
//# 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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"}
|
|
@@ -2,6 +2,7 @@ export { runScenario } from "./runScenario.js";
|
|
|
2
2
|
export { runScenarioWithDataset } from "./runScenarioWithDataset.js";
|
|
3
3
|
export { buildScenarioTemplateValues, buildScenarioExecutionGraph, collectExclusiveBranchNodeIds, evaluateScenarioExpression, replaceScenarioTemplateTokensForJsonValidation, resolveScenarioJsonTemplateString, resolveScenarioTemplateString, } from "./templateResolver.js";
|
|
4
4
|
export * from "./condition.js";
|
|
5
|
+
export * from "./extraction.js";
|
|
5
6
|
export * from "./assertion.js";
|
|
6
7
|
export { buildNotificationDeliveryRequest, renderNotificationTemplate, } from "./notification/index.js";
|
|
7
8
|
export * from "./waitNodeExecutor.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/scenario-runner/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EACL,2BAA2B,EAC3B,2BAA2B,EAC3B,6BAA6B,EAC7B,0BAA0B,EAC1B,8CAA8C,EAC9C,iCAAiC,EACjC,6BAA6B,GAC9B,MAAM,uBAAuB,CAAC;AAM/B,cAAc,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/scenario-runner/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EACL,2BAA2B,EAC3B,2BAA2B,EAC3B,6BAA6B,EAC7B,0BAA0B,EAC1B,8CAA8C,EAC9C,iCAAiC,EACjC,6BAA6B,GAC9B,MAAM,uBAAuB,CAAC;AAM/B,cAAc,gBAAgB,CAAC;AAE/B,cAAc,iBAAiB,CAAC;AAGhC,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EACL,gCAAgC,EAChC,0BAA0B,GAC3B,MAAM,yBAAyB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC"}
|
|
@@ -7,6 +7,8 @@ export { buildScenarioTemplateValues, buildScenarioExecutionGraph, collectExclus
|
|
|
7
7
|
// frontend Scenario Builder can drive their UI from the same source of
|
|
8
8
|
// truth as the runtime executor.
|
|
9
9
|
export * from "./condition.js";
|
|
10
|
+
// JSONPath/regex extraction shared by condition operands and set-vars rows
|
|
11
|
+
export * from "./extraction.js";
|
|
10
12
|
// Assertion node — a no-code checkpoint built on the condition engine
|
|
11
13
|
// (createDefaultAssertionConfig / normalize / validate / evaluate).
|
|
12
14
|
export * from "./assertion.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scenario-runner/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EACL,2BAA2B,EAC3B,2BAA2B,EAC3B,6BAA6B,EAC7B,0BAA0B,EAC1B,8CAA8C,EAC9C,iCAAiC,EACjC,6BAA6B,GAC9B,MAAM,uBAAuB,CAAC;AAC/B,iEAAiE;AACjE,qEAAqE;AACrE,uEAAuE;AACvE,uEAAuE;AACvE,iCAAiC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,sEAAsE;AACtE,oEAAoE;AACpE,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EACL,gCAAgC,EAChC,0BAA0B,GAC3B,MAAM,yBAAyB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scenario-runner/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EACL,2BAA2B,EAC3B,2BAA2B,EAC3B,6BAA6B,EAC7B,0BAA0B,EAC1B,8CAA8C,EAC9C,iCAAiC,EACjC,6BAA6B,GAC9B,MAAM,uBAAuB,CAAC;AAC/B,iEAAiE;AACjE,qEAAqE;AACrE,uEAAuE;AACvE,uEAAuE;AACvE,iCAAiC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,2EAA2E;AAC3E,cAAc,iBAAiB,CAAC;AAChC,sEAAsE;AACtE,oEAAoE;AACpE,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EACL,gCAAgC,EAChC,0BAA0B,GAC3B,MAAM,yBAAyB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runScenario.d.ts","sourceRoot":"","sources":["../../src/scenario-runner/runScenario.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"runScenario.d.ts","sourceRoot":"","sources":["../../src/scenario-runner/runScenario.ts"],"names":[],"mappings":"AA4CA,OAAO,KAAK,EAKV,kBAAkB,EAUlB,iBAAiB,EAKlB,MAAM,YAAY,CAAC;AA8rBpB,eAAO,MAAM,WAAW,GAAU,mGAQ/B,kBAAkB,KAAG,OAAO,CAAC,iBAAiB,CAytDhD,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { buildConditionOutputPayload, evaluateConditionConfig, evaluateConditionRule, validateConditionConfig, } from "./condition.js";
|
|
1
|
+
import { buildConditionOutputPayload, evaluateConditionConfig, evaluateConditionRule, resolveConditionRuntimeValue, validateConditionConfig, } from "./condition.js";
|
|
2
|
+
import { applyExtract } from "./extraction.js";
|
|
2
3
|
import { evaluateAssertionConfig, validateAssertionConfig, } from "./assertion.js";
|
|
3
4
|
import { createSubScenarioStepId, buildRuntimeStepDescriptors, DEFAULT_END_LOOP_DELAY_MS, DEFAULT_END_LOOP_ITERATIONS, flattenScenarioNodesForContext, MAX_END_LOOP_ITERATIONS, } from "./graphTraversal.js";
|
|
4
5
|
import { executeWithScenarioNodePolicy } from "./executionPolicyRunner.js";
|
|
@@ -443,6 +444,9 @@ const updateRecordFromEvent = (records, event) => {
|
|
|
443
444
|
retryDelayMs: event.retryDelayMs ?? current.retryDelayMs,
|
|
444
445
|
failurePolicy: event.failurePolicy ?? current.failurePolicy,
|
|
445
446
|
continuedAfterFailure: event.continuedAfterFailure ?? current.continuedAfterFailure,
|
|
447
|
+
errorHandled: event.type === "step:fail"
|
|
448
|
+
? (event.errorHandled ?? current.errorHandled)
|
|
449
|
+
: current.errorHandled,
|
|
446
450
|
errors: event.errors ?? current.errors,
|
|
447
451
|
durationMs: event.durationMs ?? current.durationMs,
|
|
448
452
|
statusCode: event.statusCode ?? current.statusCode,
|
|
@@ -712,6 +716,38 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
712
716
|
const markRemainingNodesSkipped = async (reason) => {
|
|
713
717
|
await markNodeIdsSkipped(rootScope, rootScope.graph.nodeMap.keys(), reason);
|
|
714
718
|
};
|
|
719
|
+
// After a failure is routed into error edges, the node's NORMAL outgoing
|
|
720
|
+
// subtree must not linger idle (an idle source blocks downstream joins in
|
|
721
|
+
// isNodeReady). Skip everything reachable from the normal edges that is
|
|
722
|
+
// NOT also reachable from the taken error edges — shared downstream joins
|
|
723
|
+
// stay runnable.
|
|
724
|
+
const skipUntakenNormalBranches = async (scope, nodeId, errorEdges) => {
|
|
725
|
+
const outgoing = scope.graph.outgoingMap.get(nodeId) ?? [];
|
|
726
|
+
const normalTargets = outgoing
|
|
727
|
+
.filter((edge) => (edge.type ?? "default") !== "error")
|
|
728
|
+
.map((edge) => edge.target);
|
|
729
|
+
if (normalTargets.length === 0) {
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
const collectReachable = (startIds) => {
|
|
733
|
+
const stack = [...startIds];
|
|
734
|
+
const seen = new Set();
|
|
735
|
+
while (stack.length > 0) {
|
|
736
|
+
const current = stack.pop();
|
|
737
|
+
if (!current || seen.has(current)) {
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
seen.add(current);
|
|
741
|
+
for (const edge of scope.graph.outgoingMap.get(current) ?? []) {
|
|
742
|
+
stack.push(edge.target);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return seen;
|
|
746
|
+
};
|
|
747
|
+
const reachableFromError = collectReachable(errorEdges.map((edge) => edge.target));
|
|
748
|
+
const untaken = Array.from(collectReachable(normalTargets)).filter((candidate) => !reachableFromError.has(candidate));
|
|
749
|
+
await markNodeIdsSkipped(scope, untaken, "Error branch taken");
|
|
750
|
+
};
|
|
715
751
|
const markScopeRemainingNodesSkipped = async (scope, reason) => {
|
|
716
752
|
await markNodeIdsSkipped(scope, scope.graph.nodeMap.keys(), reason);
|
|
717
753
|
};
|
|
@@ -760,6 +796,10 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
760
796
|
if (!node) {
|
|
761
797
|
return;
|
|
762
798
|
}
|
|
799
|
+
// Synchronous claim: nothing may await between the idle/ready checks
|
|
800
|
+
// above and this line, or two branches converging on a join could both
|
|
801
|
+
// execute it (each inbound edge calls executeNode once).
|
|
802
|
+
localStatuses.set(stepId, "running");
|
|
763
803
|
const title = buildTitle(node);
|
|
764
804
|
const nodeLabel = node.data.label.trim();
|
|
765
805
|
const executionPolicy = supportsScenarioNodeExecutionPolicy(node)
|
|
@@ -1267,6 +1307,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
1267
1307
|
// may reference a variable set by an earlier one.
|
|
1268
1308
|
const writtenEnvironment = {};
|
|
1269
1309
|
const writtenGlobals = {};
|
|
1310
|
+
const extractionOutcomes = [];
|
|
1270
1311
|
for (const assignment of node.data.config.assignments ?? []) {
|
|
1271
1312
|
if (!assignment || assignment.enabled === false) {
|
|
1272
1313
|
continue;
|
|
@@ -1285,7 +1326,49 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
1285
1326
|
if (!resolvedKey) {
|
|
1286
1327
|
continue;
|
|
1287
1328
|
}
|
|
1288
|
-
|
|
1329
|
+
let resolvedValue;
|
|
1330
|
+
const extractorType = assignment.extractorType ?? "template";
|
|
1331
|
+
if (extractorType === "jsonpath" || extractorType === "regex") {
|
|
1332
|
+
// Extractor rows resolve `source` with condition-operand path
|
|
1333
|
+
// semantics, then apply the expression. A failed extraction
|
|
1334
|
+
// SKIPS the write (never persists a wrong/empty value
|
|
1335
|
+
// silently) and surfaces the error in outputData.
|
|
1336
|
+
const sourcePath = (assignment.source ?? "").trim();
|
|
1337
|
+
const resolvedSource = resolveConditionRuntimeValue({
|
|
1338
|
+
environment: runtimeContext.environment,
|
|
1339
|
+
globals: runtimeContext.globals,
|
|
1340
|
+
workflowContext: runtimeContext.workflowContext,
|
|
1341
|
+
nodeMetaByLabel: runtimeContext.nodeMetaByLabel,
|
|
1342
|
+
responseRoot: runtimeContext.responseRoot,
|
|
1343
|
+
dbResult: runtimeContext.dbResult,
|
|
1344
|
+
kafkaEvent: runtimeContext.kafkaEvent,
|
|
1345
|
+
redisResult: runtimeContext.redisResult,
|
|
1346
|
+
}, sourcePath);
|
|
1347
|
+
const outcome = applyExtract(resolvedSource.value, {
|
|
1348
|
+
type: extractorType,
|
|
1349
|
+
expression: assignment.expression ?? "",
|
|
1350
|
+
flags: assignment.flags,
|
|
1351
|
+
group: assignment.group,
|
|
1352
|
+
select: assignment.select,
|
|
1353
|
+
});
|
|
1354
|
+
if (outcome.error || typeof outcome.value === "undefined") {
|
|
1355
|
+
extractionOutcomes.push({
|
|
1356
|
+
key: resolvedKey,
|
|
1357
|
+
extractorType,
|
|
1358
|
+
source: sourcePath,
|
|
1359
|
+
error: outcome.error ?? "Extraction produced no value",
|
|
1360
|
+
});
|
|
1361
|
+
continue;
|
|
1362
|
+
}
|
|
1363
|
+
extractionOutcomes.push({ key: resolvedKey, extractorType, source: sourcePath });
|
|
1364
|
+
resolvedValue =
|
|
1365
|
+
typeof outcome.value === "string"
|
|
1366
|
+
? outcome.value
|
|
1367
|
+
: JSON.stringify(outcome.value);
|
|
1368
|
+
}
|
|
1369
|
+
else {
|
|
1370
|
+
resolvedValue = resolveScenarioTemplateString(assignment.value ?? "", rowTemplateValues);
|
|
1371
|
+
}
|
|
1289
1372
|
applyVariableMutationsToContext(runtimeContext, [
|
|
1290
1373
|
{
|
|
1291
1374
|
scope: assignment.scope === "global" ? "global" : "environment",
|
|
@@ -1304,6 +1387,8 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
1304
1387
|
outputData = {
|
|
1305
1388
|
environment: writtenEnvironment,
|
|
1306
1389
|
globals: writtenGlobals,
|
|
1390
|
+
// Additive: present only when extractor rows ran.
|
|
1391
|
+
...(extractionOutcomes.length > 0 ? { extraction: extractionOutcomes } : {}),
|
|
1307
1392
|
};
|
|
1308
1393
|
}
|
|
1309
1394
|
if (node.type === "aggregator") {
|
|
@@ -1376,7 +1461,14 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
1376
1461
|
},
|
|
1377
1462
|
});
|
|
1378
1463
|
const durationMs = Math.round(Date.now() - executionStartedAtMs);
|
|
1379
|
-
|
|
1464
|
+
// "error" edges (no-code try/catch): a failure with a drawn handler is
|
|
1465
|
+
// routed into the error branch instead of stopping or continuing on
|
|
1466
|
+
// the normal path — the user's explicit handler overrides onFailure.
|
|
1467
|
+
const errorEdges = policyResult.outcome === "failed" || policyResult.outcome === "continued"
|
|
1468
|
+
? (scope.graph.outgoingMap.get(nodeId) ?? []).filter((edge) => (edge.type ?? "default") === "error")
|
|
1469
|
+
: [];
|
|
1470
|
+
const failureRouted = errorEdges.length > 0;
|
|
1471
|
+
if (policyResult.outcome === "failed" && !failureRouted) {
|
|
1380
1472
|
localStatuses.set(stepId, "failed");
|
|
1381
1473
|
const failedAt = new Date().toISOString();
|
|
1382
1474
|
const nodeMeta = buildNodeExecutionMeta({
|
|
@@ -1449,7 +1541,9 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
1449
1541
|
});
|
|
1450
1542
|
throw new ScenarioNodeFailureError(policyResult.finalErrorMessage ?? "Scenario node failed");
|
|
1451
1543
|
}
|
|
1452
|
-
if (policyResult.outcome === "continued") {
|
|
1544
|
+
if (policyResult.outcome === "continued" || policyResult.outcome === "failed") {
|
|
1545
|
+
// Either policy-continue, or a failure routed into error edges —
|
|
1546
|
+
// both leave the node terminal as failed_continued so joins unblock.
|
|
1453
1547
|
degradedStepIds.add(stepId);
|
|
1454
1548
|
localStatuses.set(stepId, "failed_continued");
|
|
1455
1549
|
const continuedAt = new Date().toISOString();
|
|
@@ -1495,7 +1589,9 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
1495
1589
|
retryEnabled: executionPolicy.retry.enabled,
|
|
1496
1590
|
retryDelayMs: executionPolicy.retry.delayMs,
|
|
1497
1591
|
failurePolicy: executionPolicy.onFailure,
|
|
1498
|
-
continuedAfterFailure:
|
|
1592
|
+
continuedAfterFailure: policyResult.outcome === "continued",
|
|
1593
|
+
errorHandled: failureRouted ? true : undefined,
|
|
1594
|
+
errorEdgeIds: failureRouted ? errorEdges.map((edge) => edge.id) : undefined,
|
|
1499
1595
|
errors: policyResult.errors,
|
|
1500
1596
|
nodeMeta,
|
|
1501
1597
|
errorMessage: policyResult.finalErrorMessage ??
|
|
@@ -1667,10 +1763,21 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
1667
1763
|
syncLoopRoots();
|
|
1668
1764
|
}
|
|
1669
1765
|
}
|
|
1766
|
+
if (failureRouted) {
|
|
1767
|
+
await skipUntakenNormalBranches(scope, nodeId, errorEdges);
|
|
1768
|
+
for (const edge of errorEdges) {
|
|
1769
|
+
throwIfStopped(signal);
|
|
1770
|
+
await executeNode(edge.target, scope.stepIdForEdge(edge.id), {}, scope);
|
|
1771
|
+
}
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1670
1774
|
const outgoingEdges = scope.graph.outgoingMap.get(nodeId) ?? [];
|
|
1671
1775
|
const nextEdges = node.type === "condition"
|
|
1672
1776
|
? outgoingEdges.filter((edge) => (conditionResult ? "yes" : "no") === (edge.type ?? "default"))
|
|
1673
|
-
:
|
|
1777
|
+
: // Error edges are handler paths — never taken on success (or on a
|
|
1778
|
+
// policy-continue without routing). No-op for scenarios that have
|
|
1779
|
+
// no error edges.
|
|
1780
|
+
outgoingEdges.filter((edge) => (edge.type ?? "default") !== "error");
|
|
1674
1781
|
for (const edge of nextEdges) {
|
|
1675
1782
|
throwIfStopped(signal);
|
|
1676
1783
|
await executeNode(edge.target, scope.stepIdForEdge(edge.id), {}, scope);
|