@usearazzo/runner 1.0.1-alpha.20 → 1.0.1-alpha.22
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/CHANGELOG.md +25 -0
- package/README.md +2 -2
- package/dist/arazzo-runner.browser.js +2750 -412
- package/dist/arazzo-runner.browser.min.js +1 -1
- package/package.json +5 -5
- package/src/action/ActionResolver.cjs +8 -4
- package/src/action/ActionResolver.mjs +8 -4
- package/src/criterion/CriterionEvaluator.cjs +28 -10
- package/src/criterion/CriterionEvaluator.mjs +27 -10
- package/src/criterion/JSONPathCriterionEvaluator.cjs +4 -1
- package/src/criterion/JSONPathCriterionEvaluator.mjs +5 -2
- package/src/criterion/RegexCriterionEvaluator.cjs +3 -1
- package/src/criterion/RegexCriterionEvaluator.mjs +3 -1
- package/src/criterion/XPathCriterionEvaluator.cjs +3 -0
- package/src/criterion/XPathCriterionEvaluator.mjs +3 -0
- package/src/executor/ArazzoWorkflowLocatorNormalizer.cjs +18 -7
- package/src/executor/ArazzoWorkflowLocatorNormalizer.mjs +18 -7
- package/src/executor/StepExecutor.cjs +90 -24
- package/src/executor/StepExecutor.mjs +90 -24
- package/src/executor/StepRetryRunner.cjs +0 -10
- package/src/executor/StepRetryRunner.mjs +0 -9
- package/src/executor/WorkflowExecutor.cjs +20 -23
- package/src/executor/WorkflowExecutor.mjs +20 -23
- package/src/vendor/swagger-client.cjs +1 -1
- package/src/vendor/swagger-client.mjs +1 -1
- package/types/runner.d.ts +107 -21
|
@@ -37,11 +37,27 @@ export const STEP_TARGET_FIELDS = ['operationId', 'operationPath', 'workflowId']
|
|
|
37
37
|
* @public
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* The outcome of one criterion a step evaluated: one of its `successCriteria`,
|
|
42
|
+
* or one of the `criteria` of an action it considered.
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
|
|
40
46
|
/**
|
|
41
47
|
* The outcome of executing a single step.
|
|
42
48
|
* @public
|
|
43
49
|
*/
|
|
44
50
|
|
|
51
|
+
/**
|
|
52
|
+
* What one attempt at a step produced.
|
|
53
|
+
*
|
|
54
|
+
* Both kinds of step reduce to this — an operation step (whose richer
|
|
55
|
+
* {@link StepExecutionResult} satisfies this shape) and a sub-workflow step
|
|
56
|
+
* (synthesized from the sub-run's outcome) — so the retry loop drives them
|
|
57
|
+
* identically and knows nothing about either.
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
|
|
45
61
|
/**
|
|
46
62
|
* Executes a single Arazzo step that invokes an OpenAPI operation.
|
|
47
63
|
*
|
|
@@ -183,16 +199,22 @@ class StepExecutor {
|
|
|
183
199
|
// evaluate criteria, outputs, and the next action against the post-request
|
|
184
200
|
// context (with $request / $url / $method and $response / $statusCode).
|
|
185
201
|
const postContext = state.toContext(this.#requestContext(response), this.#responseContext(response));
|
|
186
|
-
const
|
|
202
|
+
const successCriteria = this.evaluateCriteria(step, postContext);
|
|
203
|
+
const successful = successCriteria.every(criterion => criterion.met);
|
|
187
204
|
const outputs = this.#outputResolver.resolve(step.outputs, expression => this.#evaluate(postContext, expression), scope);
|
|
188
|
-
const
|
|
205
|
+
const {
|
|
206
|
+
matchedActions,
|
|
207
|
+
actionCriteria
|
|
208
|
+
} = this.selectActions(step, successful, postContext);
|
|
189
209
|
return {
|
|
190
210
|
stepId,
|
|
191
211
|
response,
|
|
192
212
|
successful,
|
|
193
213
|
outputs,
|
|
194
214
|
action: matchedActions[0],
|
|
195
|
-
matchedActions
|
|
215
|
+
matchedActions,
|
|
216
|
+
successCriteria,
|
|
217
|
+
actionCriteria
|
|
196
218
|
};
|
|
197
219
|
}
|
|
198
220
|
|
|
@@ -265,9 +287,12 @@ class StepExecutor {
|
|
|
265
287
|
}
|
|
266
288
|
|
|
267
289
|
/**
|
|
268
|
-
* Evaluates the step's `successCriteria
|
|
269
|
-
*
|
|
270
|
-
*
|
|
290
|
+
* Evaluates the step's `successCriteria`, returning one record per criterion
|
|
291
|
+
* in list order; the step succeeds when every record is met. Every criterion
|
|
292
|
+
* is evaluated, so the records explain each unmet one, not only the first. A
|
|
293
|
+
* step with no criteria returns no records and succeeds on a received
|
|
294
|
+
* response. An unreadable `successCriteria` throws, see
|
|
295
|
+
* {@link StepExecutor.assertSuccessCriteria}.
|
|
271
296
|
*
|
|
272
297
|
* Public for the same reason as {@link StepExecutor.selectActions}: a step
|
|
273
298
|
* targeting a `workflowId` is run by the workflow executor and never reaches
|
|
@@ -277,15 +302,16 @@ class StepExecutor {
|
|
|
277
302
|
evaluateCriteria(step, context) {
|
|
278
303
|
this.assertSuccessCriteria(step);
|
|
279
304
|
const criteria = step.successCriteria;
|
|
280
|
-
if (criteria === undefined) return
|
|
305
|
+
if (criteria === undefined) return [];
|
|
281
306
|
|
|
282
307
|
// assertSuccessCriteria above guarantees every entry is a Criterion Object
|
|
283
|
-
return [...criteria].
|
|
308
|
+
return [...criteria].map(criterion => this.#evaluateCriterion(criterion, context));
|
|
284
309
|
}
|
|
285
310
|
|
|
286
311
|
/**
|
|
287
312
|
* Selects every matching `onSuccess` / `onFailure` action for the outcome, in
|
|
288
|
-
* list order, gating each action's criteria against the context
|
|
313
|
+
* list order, gating each action's criteria against the context, and records
|
|
314
|
+
* the outcome of every action criterion evaluated.
|
|
289
315
|
*
|
|
290
316
|
* The step's own list is the whole story here: a step that declares none of
|
|
291
317
|
* its own already carries the workflow-level defaults, inherited by
|
|
@@ -304,25 +330,65 @@ class StepExecutor {
|
|
|
304
330
|
* the expression context through this one method rather than wiring their own.
|
|
305
331
|
*/
|
|
306
332
|
selectActions(step, successful, context) {
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
// abort over a default written for the operation steps.
|
|
316
|
-
if (error instanceof RuntimeExpressionError && error.reason === 'unavailable-expression') {
|
|
317
|
-
return false;
|
|
318
|
-
}
|
|
319
|
-
throw error;
|
|
320
|
-
}
|
|
333
|
+
const actionCriteria = [];
|
|
334
|
+
const isCriterionMet = (criterion, action) => {
|
|
335
|
+
const record = {
|
|
336
|
+
...this.#evaluateCriterion(criterion, context),
|
|
337
|
+
actionName: toValue(action.name)
|
|
338
|
+
};
|
|
339
|
+
actionCriteria.push(record);
|
|
340
|
+
return record.met;
|
|
321
341
|
};
|
|
322
342
|
const scope = {
|
|
323
343
|
stepId: toValue(step.stepId)
|
|
324
344
|
};
|
|
325
|
-
|
|
345
|
+
const matchedActions = successful ? this.#actionResolver.resolveOnSuccess(step.onSuccess, isCriterionMet, scope) : this.#actionResolver.resolveOnFailure(step.onFailure, isCriterionMet, scope);
|
|
346
|
+
return {
|
|
347
|
+
matchedActions,
|
|
348
|
+
actionCriteria
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Evaluates one criterion into a {@link CriterionEvaluationRecord}. A
|
|
354
|
+
* condition that cannot be evaluated is unmet and records why; any other
|
|
355
|
+
* error propagates.
|
|
356
|
+
*/
|
|
357
|
+
#evaluateCriterion(criterion, context) {
|
|
358
|
+
const condition = toValue(criterion.condition);
|
|
359
|
+
try {
|
|
360
|
+
const {
|
|
361
|
+
met,
|
|
362
|
+
error
|
|
363
|
+
} = this.#criterionEvaluator.evaluate(criterion, expression => this.#evaluate(context, expression));
|
|
364
|
+
if (error === undefined) return {
|
|
365
|
+
condition,
|
|
366
|
+
met
|
|
367
|
+
};
|
|
368
|
+
return {
|
|
369
|
+
condition,
|
|
370
|
+
met,
|
|
371
|
+
diagnostic: {
|
|
372
|
+
code: error.reason,
|
|
373
|
+
message: error.message
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
} catch (error) {
|
|
377
|
+
// a step that calls a workflow made no HTTP call, so a condition on one
|
|
378
|
+
// fails like any unevaluable condition; the diagnostic keeps the
|
|
379
|
+
// authoring mistake visible.
|
|
380
|
+
if (error instanceof RuntimeExpressionError && error.reason === 'unavailable-expression') {
|
|
381
|
+
return {
|
|
382
|
+
condition,
|
|
383
|
+
met: false,
|
|
384
|
+
diagnostic: {
|
|
385
|
+
code: error.reason,
|
|
386
|
+
message: error.message
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
throw error;
|
|
391
|
+
}
|
|
326
392
|
}
|
|
327
393
|
|
|
328
394
|
/**
|
|
@@ -9,16 +9,6 @@ var _apidomCore = require("@speclynx/apidom-core");
|
|
|
9
9
|
var _apidomDatamodel = require("@speclynx/apidom-datamodel");
|
|
10
10
|
var _apidomNsArazzo = require("@speclynx/apidom-ns-arazzo-1");
|
|
11
11
|
var _ExecutionError = _interopRequireDefault(require("../errors/ExecutionError.cjs"));
|
|
12
|
-
/**
|
|
13
|
-
* What one attempt at a step produced.
|
|
14
|
-
*
|
|
15
|
-
* Both kinds of step reduce to this — an operation step (whose richer
|
|
16
|
-
* `StepExecutionResult` satisfies this shape) and a sub-workflow step
|
|
17
|
-
* (synthesized from the sub-run's outcome) — so the retry loop drives them
|
|
18
|
-
* identically and knows nothing about either.
|
|
19
|
-
* @internal
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
12
|
/**
|
|
23
13
|
* The terminal outcome of a step once its retries are settled.
|
|
24
14
|
* @internal
|
|
@@ -2,15 +2,6 @@ import { toValue } from '@speclynx/apidom-core';
|
|
|
2
2
|
import { isNumberElement, isStringElement } from '@speclynx/apidom-datamodel';
|
|
3
3
|
import { isFailureActionElement } from '@speclynx/apidom-ns-arazzo-1';
|
|
4
4
|
import ExecutionError from "../errors/ExecutionError.mjs";
|
|
5
|
-
/**
|
|
6
|
-
* What one attempt at a step produced.
|
|
7
|
-
*
|
|
8
|
-
* Both kinds of step reduce to this — an operation step (whose richer
|
|
9
|
-
* `StepExecutionResult` satisfies this shape) and a sub-workflow step
|
|
10
|
-
* (synthesized from the sub-run's outcome) — so the retry loop drives them
|
|
11
|
-
* identically and knows nothing about either.
|
|
12
|
-
* @internal
|
|
13
|
-
*/
|
|
14
5
|
/**
|
|
15
6
|
* The terminal outcome of a step once its retries are settled.
|
|
16
7
|
* @internal
|
|
@@ -359,6 +359,8 @@ class WorkflowExecutor {
|
|
|
359
359
|
action,
|
|
360
360
|
attempts,
|
|
361
361
|
durationMs: this.#now() - stepStartedAt,
|
|
362
|
+
successCriteria: outcome.successCriteria,
|
|
363
|
+
actionCriteria: outcome.actionCriteria,
|
|
362
364
|
...(subWorkflows.length > 0 ? {
|
|
363
365
|
subWorkflows
|
|
364
366
|
} : {}),
|
|
@@ -776,12 +778,6 @@ class WorkflowExecutor {
|
|
|
776
778
|
const locator = await this.#workflowLocatorNormalizer.normalize(reference, document, {
|
|
777
779
|
workflowId
|
|
778
780
|
});
|
|
779
|
-
if (!locator.document.workflowIndex.has(locator.workflowId)) {
|
|
780
|
-
throw new _ExecutionError.default(`workflow "${workflowId}" depends on "${reference}", which the Arazzo document at "${locator.document.uri}" does not define`, {
|
|
781
|
-
workflowId,
|
|
782
|
-
reason: 'workflow-not-found'
|
|
783
|
-
});
|
|
784
|
-
}
|
|
785
781
|
refs.push({
|
|
786
782
|
locator,
|
|
787
783
|
inputsKey: reference
|
|
@@ -889,19 +885,26 @@ class WorkflowExecutor {
|
|
|
889
885
|
// apply on top — they are the author's assertion about this step, and
|
|
890
886
|
// dropping them because the step happens to target a workflow would
|
|
891
887
|
// silently discard it. They see the sub-run's own outputs (`{}` when it
|
|
892
|
-
// transferred) through `$outputs` and `$workflows
|
|
888
|
+
// transferred) through `$outputs` and `$workflows`, and are evaluated
|
|
889
|
+
// even when the sub-run failed, so the record explains them either way.
|
|
893
890
|
// the criteria and actions are the *calling* step's, so they are
|
|
894
891
|
// evaluated by the parent document's step executor — only the sub-run
|
|
895
892
|
// itself switched documents.
|
|
896
893
|
const stepExecutor = this.#stepExecutor.forDocument(document);
|
|
897
|
-
const
|
|
898
|
-
const
|
|
894
|
+
const successCriteria = stepExecutor.evaluateCriteria(step, context);
|
|
895
|
+
const successful = !this.#settledFailed(result) && successCriteria.every(criterion => criterion.met);
|
|
896
|
+
const {
|
|
897
|
+
matchedActions,
|
|
898
|
+
actionCriteria
|
|
899
|
+
} = stepExecutor.selectActions(step, successful, context);
|
|
899
900
|
return {
|
|
900
901
|
stepId,
|
|
901
902
|
successful,
|
|
902
903
|
outputs,
|
|
903
904
|
action: matchedActions[0],
|
|
904
|
-
matchedActions
|
|
905
|
+
matchedActions,
|
|
906
|
+
successCriteria,
|
|
907
|
+
actionCriteria
|
|
905
908
|
};
|
|
906
909
|
};
|
|
907
910
|
}
|
|
@@ -963,24 +966,14 @@ class WorkflowExecutor {
|
|
|
963
966
|
*
|
|
964
967
|
* Shared rather than duplicated by its two callers, which otherwise repeat
|
|
965
968
|
* the identical "resolve the reference, then run with no inputs" sequence.
|
|
966
|
-
*
|
|
967
|
-
*
|
|
968
|
-
* `workflow-not-found` (reached only once `#run` itself resolves the
|
|
969
|
-
* target) — which carries no caller context at all, since it is reached
|
|
970
|
-
* generically from every kind of nested call.
|
|
969
|
+
* A target its document does not define is rejected by the normalizer,
|
|
970
|
+
* attributed to the calling workflow and `stepId`.
|
|
971
971
|
*/
|
|
972
972
|
async #runReferencedWorkflow(reference, stepId, invocation, scope, via) {
|
|
973
973
|
const locator = await this.#workflowLocatorNormalizer.normalize(reference, invocation.document, {
|
|
974
974
|
workflowId: invocation.workflowId,
|
|
975
975
|
stepId
|
|
976
976
|
});
|
|
977
|
-
if (!locator.document.workflowIndex.has(locator.workflowId)) {
|
|
978
|
-
throw new _ExecutionError.default(`workflow "${locator.workflowId}" not found in Arazzo document at "${locator.document.uri}"`, {
|
|
979
|
-
workflowId: locator.workflowId,
|
|
980
|
-
stepId,
|
|
981
|
-
reason: 'workflow-not-found'
|
|
982
|
-
});
|
|
983
|
-
}
|
|
984
977
|
return this.#run(locator, {}, scope, invocation.callStack, via);
|
|
985
978
|
}
|
|
986
979
|
|
|
@@ -988,7 +981,11 @@ class WorkflowExecutor {
|
|
|
988
981
|
* Extracts and normalizes the workflow a locator names. A workflow its
|
|
989
982
|
* document does not define is the executor-level `workflow-not-found`
|
|
990
983
|
* authoring error, raised here rather than leaking the extractor's
|
|
991
|
-
* `ExtractionError`.
|
|
984
|
+
* `ExtractionError`. Only the root {@link WorkflowExecutor.execute} id
|
|
985
|
+
* reaches this check unvalidated — every reference position goes through
|
|
986
|
+
* the locator normalizer, which already rejects a missing workflow with
|
|
987
|
+
* the referencing context attached — so the error's `workflowId` is the
|
|
988
|
+
* missing id itself.
|
|
992
989
|
*/
|
|
993
990
|
async #resolveWorkflow(locator, scope) {
|
|
994
991
|
const {
|
|
@@ -345,6 +345,8 @@ class WorkflowExecutor {
|
|
|
345
345
|
action,
|
|
346
346
|
attempts,
|
|
347
347
|
durationMs: this.#now() - stepStartedAt,
|
|
348
|
+
successCriteria: outcome.successCriteria,
|
|
349
|
+
actionCriteria: outcome.actionCriteria,
|
|
348
350
|
...(subWorkflows.length > 0 ? {
|
|
349
351
|
subWorkflows
|
|
350
352
|
} : {}),
|
|
@@ -762,12 +764,6 @@ class WorkflowExecutor {
|
|
|
762
764
|
const locator = await this.#workflowLocatorNormalizer.normalize(reference, document, {
|
|
763
765
|
workflowId
|
|
764
766
|
});
|
|
765
|
-
if (!locator.document.workflowIndex.has(locator.workflowId)) {
|
|
766
|
-
throw new ExecutionError(`workflow "${workflowId}" depends on "${reference}", which the Arazzo document at "${locator.document.uri}" does not define`, {
|
|
767
|
-
workflowId,
|
|
768
|
-
reason: 'workflow-not-found'
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
767
|
refs.push({
|
|
772
768
|
locator,
|
|
773
769
|
inputsKey: reference
|
|
@@ -875,19 +871,26 @@ class WorkflowExecutor {
|
|
|
875
871
|
// apply on top — they are the author's assertion about this step, and
|
|
876
872
|
// dropping them because the step happens to target a workflow would
|
|
877
873
|
// silently discard it. They see the sub-run's own outputs (`{}` when it
|
|
878
|
-
// transferred) through `$outputs` and `$workflows
|
|
874
|
+
// transferred) through `$outputs` and `$workflows`, and are evaluated
|
|
875
|
+
// even when the sub-run failed, so the record explains them either way.
|
|
879
876
|
// the criteria and actions are the *calling* step's, so they are
|
|
880
877
|
// evaluated by the parent document's step executor — only the sub-run
|
|
881
878
|
// itself switched documents.
|
|
882
879
|
const stepExecutor = this.#stepExecutor.forDocument(document);
|
|
883
|
-
const
|
|
884
|
-
const
|
|
880
|
+
const successCriteria = stepExecutor.evaluateCriteria(step, context);
|
|
881
|
+
const successful = !this.#settledFailed(result) && successCriteria.every(criterion => criterion.met);
|
|
882
|
+
const {
|
|
883
|
+
matchedActions,
|
|
884
|
+
actionCriteria
|
|
885
|
+
} = stepExecutor.selectActions(step, successful, context);
|
|
885
886
|
return {
|
|
886
887
|
stepId,
|
|
887
888
|
successful,
|
|
888
889
|
outputs,
|
|
889
890
|
action: matchedActions[0],
|
|
890
|
-
matchedActions
|
|
891
|
+
matchedActions,
|
|
892
|
+
successCriteria,
|
|
893
|
+
actionCriteria
|
|
891
894
|
};
|
|
892
895
|
};
|
|
893
896
|
}
|
|
@@ -949,24 +952,14 @@ class WorkflowExecutor {
|
|
|
949
952
|
*
|
|
950
953
|
* Shared rather than duplicated by its two callers, which otherwise repeat
|
|
951
954
|
* the identical "resolve the reference, then run with no inputs" sequence.
|
|
952
|
-
*
|
|
953
|
-
*
|
|
954
|
-
* `workflow-not-found` (reached only once `#run` itself resolves the
|
|
955
|
-
* target) — which carries no caller context at all, since it is reached
|
|
956
|
-
* generically from every kind of nested call.
|
|
955
|
+
* A target its document does not define is rejected by the normalizer,
|
|
956
|
+
* attributed to the calling workflow and `stepId`.
|
|
957
957
|
*/
|
|
958
958
|
async #runReferencedWorkflow(reference, stepId, invocation, scope, via) {
|
|
959
959
|
const locator = await this.#workflowLocatorNormalizer.normalize(reference, invocation.document, {
|
|
960
960
|
workflowId: invocation.workflowId,
|
|
961
961
|
stepId
|
|
962
962
|
});
|
|
963
|
-
if (!locator.document.workflowIndex.has(locator.workflowId)) {
|
|
964
|
-
throw new ExecutionError(`workflow "${locator.workflowId}" not found in Arazzo document at "${locator.document.uri}"`, {
|
|
965
|
-
workflowId: locator.workflowId,
|
|
966
|
-
stepId,
|
|
967
|
-
reason: 'workflow-not-found'
|
|
968
|
-
});
|
|
969
|
-
}
|
|
970
963
|
return this.#run(locator, {}, scope, invocation.callStack, via);
|
|
971
964
|
}
|
|
972
965
|
|
|
@@ -974,7 +967,11 @@ class WorkflowExecutor {
|
|
|
974
967
|
* Extracts and normalizes the workflow a locator names. A workflow its
|
|
975
968
|
* document does not define is the executor-level `workflow-not-found`
|
|
976
969
|
* authoring error, raised here rather than leaking the extractor's
|
|
977
|
-
* `ExtractionError`.
|
|
970
|
+
* `ExtractionError`. Only the root {@link WorkflowExecutor.execute} id
|
|
971
|
+
* reaches this check unvalidated — every reference position goes through
|
|
972
|
+
* the locator normalizer, which already rejects a missing workflow with
|
|
973
|
+
* the referencing context attached — so the error's `workflowId` is the
|
|
974
|
+
* missing id itself.
|
|
978
975
|
*/
|
|
979
976
|
async #resolveWorkflow(locator, scope) {
|
|
980
977
|
const {
|