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.
Files changed (94) hide show
  1. package/dist/host/connections.d.ts +3 -0
  2. package/dist/host/connections.d.ts.map +1 -1
  3. package/dist/host/connections.js +3 -0
  4. package/dist/host/connections.js.map +1 -1
  5. package/dist/host/db/connectionMapping.d.ts +3 -1
  6. package/dist/host/db/connectionMapping.d.ts.map +1 -1
  7. package/dist/host/db/connectionMapping.js +3 -0
  8. package/dist/host/db/connectionMapping.js.map +1 -1
  9. package/dist/host/db/db-query-reference.d.ts.map +1 -1
  10. package/dist/host/db/db-query-reference.js +12 -1
  11. package/dist/host/db/db-query-reference.js.map +1 -1
  12. package/dist/host/db/mssqlAdapter.d.ts +8 -0
  13. package/dist/host/db/mssqlAdapter.d.ts.map +1 -0
  14. package/dist/host/db/mssqlAdapter.js +118 -0
  15. package/dist/host/db/mssqlAdapter.js.map +1 -0
  16. package/dist/host/db/queryExecutor.d.ts.map +1 -1
  17. package/dist/host/db/queryExecutor.js +8 -0
  18. package/dist/host/db/queryExecutor.js.map +1 -1
  19. package/dist/host/db/sqlNormalize.d.ts +1 -1
  20. package/dist/host/db/sqlNormalize.d.ts.map +1 -1
  21. package/dist/host/db/types.d.ts +5 -1
  22. package/dist/host/db/types.d.ts.map +1 -1
  23. package/dist/host/index.d.ts +2 -1
  24. package/dist/host/index.d.ts.map +1 -1
  25. package/dist/host/index.js +2 -1
  26. package/dist/host/index.js.map +1 -1
  27. package/dist/host/redis/redisService.d.ts +17 -0
  28. package/dist/host/redis/redisService.d.ts.map +1 -1
  29. package/dist/host/redis/redisService.js +233 -40
  30. package/dist/host/redis/redisService.js.map +1 -1
  31. package/dist/host/redis/types.d.ts +4 -0
  32. package/dist/host/redis/types.d.ts.map +1 -1
  33. package/dist/request/oauth2.d.ts +1 -1
  34. package/dist/request/oauth2.d.ts.map +1 -1
  35. package/dist/runner/applyRequestAuth.d.ts +8 -0
  36. package/dist/runner/applyRequestAuth.d.ts.map +1 -0
  37. package/dist/runner/applyRequestAuth.js +71 -0
  38. package/dist/runner/applyRequestAuth.js.map +1 -0
  39. package/dist/runner/requestRunner.d.ts.map +1 -1
  40. package/dist/runner/requestRunner.js +23 -0
  41. package/dist/runner/requestRunner.js.map +1 -1
  42. package/dist/scenario-runner/adapters/helpers/request-helpers.d.ts.map +1 -1
  43. package/dist/scenario-runner/adapters/helpers/request-helpers.js +28 -2
  44. package/dist/scenario-runner/adapters/helpers/request-helpers.js.map +1 -1
  45. package/dist/scenario-runner/condition.d.ts +4 -0
  46. package/dist/scenario-runner/condition.d.ts.map +1 -1
  47. package/dist/scenario-runner/condition.js +66 -7
  48. package/dist/scenario-runner/condition.js.map +1 -1
  49. package/dist/scenario-runner/conditionTypes.d.ts +14 -0
  50. package/dist/scenario-runner/conditionTypes.d.ts.map +1 -1
  51. package/dist/scenario-runner/extraction.d.ts +10 -0
  52. package/dist/scenario-runner/extraction.d.ts.map +1 -0
  53. package/dist/scenario-runner/extraction.js +147 -0
  54. package/dist/scenario-runner/extraction.js.map +1 -0
  55. package/dist/scenario-runner/index.d.ts +1 -0
  56. package/dist/scenario-runner/index.d.ts.map +1 -1
  57. package/dist/scenario-runner/index.js +2 -0
  58. package/dist/scenario-runner/index.js.map +1 -1
  59. package/dist/scenario-runner/runScenario.d.ts.map +1 -1
  60. package/dist/scenario-runner/runScenario.js +113 -6
  61. package/dist/scenario-runner/runScenario.js.map +1 -1
  62. package/dist/scenario-runner/types.d.ts +25 -17
  63. package/dist/scenario-runner/types.d.ts.map +1 -1
  64. package/dist/types/index.d.ts +26 -0
  65. package/dist/types/index.d.ts.map +1 -1
  66. package/dist-cjs/host/connections.js +3 -0
  67. package/dist-cjs/host/connections.js.map +1 -1
  68. package/dist-cjs/host/db/connectionMapping.js +3 -0
  69. package/dist-cjs/host/db/connectionMapping.js.map +1 -1
  70. package/dist-cjs/host/db/db-query-reference.js +12 -1
  71. package/dist-cjs/host/db/db-query-reference.js.map +1 -1
  72. package/dist-cjs/host/db/mssqlAdapter.js +124 -0
  73. package/dist-cjs/host/db/mssqlAdapter.js.map +1 -0
  74. package/dist-cjs/host/db/queryExecutor.js +8 -0
  75. package/dist-cjs/host/db/queryExecutor.js.map +1 -1
  76. package/dist-cjs/host/index.js +8 -1
  77. package/dist-cjs/host/index.js.map +1 -1
  78. package/dist-cjs/host/redis/redisService.js +237 -42
  79. package/dist-cjs/host/redis/redisService.js.map +1 -1
  80. package/dist-cjs/runner/applyRequestAuth.js +75 -0
  81. package/dist-cjs/runner/applyRequestAuth.js.map +1 -0
  82. package/dist-cjs/runner/requestRunner.js +23 -0
  83. package/dist-cjs/runner/requestRunner.js.map +1 -1
  84. package/dist-cjs/scenario-runner/adapters/helpers/request-helpers.js +28 -2
  85. package/dist-cjs/scenario-runner/adapters/helpers/request-helpers.js.map +1 -1
  86. package/dist-cjs/scenario-runner/condition.js +68 -8
  87. package/dist-cjs/scenario-runner/condition.js.map +1 -1
  88. package/dist-cjs/scenario-runner/extraction.js +151 -0
  89. package/dist-cjs/scenario-runner/extraction.js.map +1 -0
  90. package/dist-cjs/scenario-runner/index.js +2 -0
  91. package/dist-cjs/scenario-runner/index.js.map +1 -1
  92. package/dist-cjs/scenario-runner/runScenario.js +112 -5
  93. package/dist-cjs/scenario-runner/runScenario.js.map +1 -1
  94. package/package.json +3 -1
@@ -1 +1 @@
1
- {"version":3,"file":"conditionTypes.d.ts","sourceRoot":"","sources":["../../src/scenario-runner/conditionTypes.ts"],"names":[],"mappings":"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"}
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":"AAAA,oEAAoE;AACpE,2EAA2E;AAC3E,0EAA0E;AAC1E,2EAA2E;AAC3E,sEAAsE;AACtE,2EAA2E;AAC3E,EAAE;AACF,0EAA0E;AAC1E,qEAAqE;AACrE,8CAA8C;AAE9C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAIzC,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAC;AAC7C,MAAM,CAAC,MAAM,uBAAuB,GAAG,SAAS,CAAC;AACjD,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAOxC,MAAM,OAAO,GAAG,CAAC,KAAa,EAAkB,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;AAEjF,MAAM,kBAAkB,GAAG,CAAC,MAAe,EAAyC,EAAE;IACpF,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC;QAC/C,CAAC;QACD,IAAI,CAAC;YACH,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,KAAK,EAAE,oDAAoD,EAAE,CAAC;QACzE,CAAC;IACH,CAAC;IACD,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QACrD,OAAO,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC;IAC/C,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,EAAE,KAAK,EAAE,mDAAmD,OAAO,MAAM,EAAE,EAAE,CAAC;IACvF,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC1B,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,CAAC,MAAe,EAAE,OAAuB,EAAkB,EAAE;IACjF,MAAM,UAAU,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC9C,IAAI,OAAO,IAAI,UAAU,EAAE,CAAC;QAC1B,OAAO,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAED,IAAI,OAAkB,CAAC;IACvB,IAAI,CAAC;QACH,OAAO,GAAG,QAAQ,CAAC;YACjB,IAAI,EAAE,OAAO,CAAC,UAAU;YACxB,IAAI,EAAE,UAAU,CAAC,IAAa;YAC9B,IAAI,EAAE,KAAK;YACX,IAAI,EAAE,IAAI;SACX,CAAc,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,OAAO,CACZ,gCAAgC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACzF,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,KAAK,KAAK,EAAE,CAAC;QAC1C,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,mBAAmB,CAAC,EAAE,CAAC;IAC1D,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;AAChE,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,MAAe,EAAU,EAAE;IAC/C,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QACrD,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,MAAM,KAAK,SAAS,EAAE,CAAC;QAC9D,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC;IACD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,aAAa,CAAC;AAE1C,yEAAyE;AACzE,qEAAqE;AACrE,MAAM,SAAS,GAAG,CAAC,KAAyC,EAAE,OAAuB,EAAkB,EAAE;IACvG,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QACtC,IAAI,OAAO,CAAC,KAAK,GAAG,CAAC,IAAI,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACvD,OAAO,OAAO,CAAC,iBAAiB,OAAO,CAAC,KAAK,8BAA8B,CAAC,CAAC;QAC/E,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;IACzC,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAC3D,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,MAAe,EAAE,OAAuB,EAAkB,EAAE;IAC9E,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,GAAG,yBAAyB,EAAE,CAAC;QAC1D,OAAO,OAAO,CAAC,yBAAyB,yBAAyB,aAAa,CAAC,CAAC;IAClF,CAAC;IACD,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACnC,IAAI,KAAK,CAAC,MAAM,GAAG,uBAAuB,EAAE,CAAC;QAC3C,OAAO,OAAO,CAAC,uBAAuB,uBAAuB,aAAa,CAAC,CAAC;IAC9E,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;IACrC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxC,OAAO,OAAO,CAAC,wBAAwB,QAAQ,0BAA0B,CAAC,CAAC;IAC7E,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,KAAK,KAAK,EAAE,CAAC;QAC1C,IAAI,KAAa,CAAC;QAClB,IAAI,CAAC;YACH,KAAK,GAAG,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,GAAG,CAAC,CAAC;QAC7F,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,OAAO,CACZ,kBAAkB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAC3E,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAc,EAAE,CAAC;QAC7B,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;YACzC,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACjB,OAAO,MAAM,CAAC;YAChB,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,MAAM,CAAC,MAAM,IAAI,mBAAmB,EAAE,CAAC;gBACzC,MAAM;YACR,CAAC;QACH,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAC3B,CAAC;IAED,IAAI,KAAa,CAAC;IAClB,IAAI,CAAC;QACH,KAAK,GAAG,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;IACpE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,OAAO,CAAC,kBAAkB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC7F,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAC9B,CAAC;IACD,OAAO,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AACnC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAe,EAAE,OAAuB,EAAkB,EAAE;IACvF,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC;QACtD,OAAO,OAAO,CAAC,gCAAgC,CAAC,CAAC;IACnD,CAAC;IACD,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,IAAI,KAAK,UAAU;YAChC,CAAC,CAAC,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC;YAChC,CAAC,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,OAAO,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACzE,CAAC;AACH,CAAC,CAAC"}
@@ -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;AAG/B,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.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":"AA0CA,OAAO,KAAK,EAKV,kBAAkB,EAUlB,iBAAiB,EAKlB,MAAM,YAAY,CAAC;AA0rBpB,eAAO,MAAM,WAAW,GAAU,mGAQ/B,kBAAkB,KAAG,OAAO,CAAC,iBAAiB,CAwlDhD,CAAC"}
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
- const resolvedValue = resolveScenarioTemplateString(assignment.value ?? "", rowTemplateValues);
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
- if (policyResult.outcome === "failed") {
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: true,
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
- : outgoingEdges;
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);