@usearazzo/runner 1.0.1-alpha.25 → 1.0.1-alpha.27

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usearazzo/runner",
3
- "version": "1.0.1-alpha.25",
3
+ "version": "1.0.1-alpha.27",
4
4
  "description": "Runner for Arazzo Workflows.",
5
5
  "keywords": [
6
6
  "arazzo",
@@ -56,25 +56,25 @@
56
56
  "author": "Vladimir Gorej <vladimir.gorej@gmail.com>",
57
57
  "license": "Apache-2.0",
58
58
  "dependencies": {
59
- "@babel/runtime-corejs3": "^8.0.8",
60
- "@speclynx/apidom-core": "^6.1.2",
61
- "@speclynx/apidom-datamodel": "^6.1.2",
62
- "@speclynx/apidom-error": "^6.1.2",
63
- "@speclynx/apidom-json-pointer": "^6.1.2",
64
- "@speclynx/apidom-ns-arazzo-1": "^6.1.2",
65
- "@speclynx/apidom-ns-openapi-2": "^6.1.2",
66
- "@speclynx/apidom-ns-openapi-3-0": "^6.1.2",
67
- "@speclynx/apidom-ns-openapi-3-1": "^6.1.2",
68
- "@speclynx/apidom-reference": "^6.1.2",
69
- "@speclynx/apidom-traverse": "^6.1.2",
59
+ "@babel/runtime-corejs3": "^8.1.0",
60
+ "@speclynx/apidom-core": "^7.0.1",
61
+ "@speclynx/apidom-datamodel": "^7.0.1",
62
+ "@speclynx/apidom-error": "^7.0.1",
63
+ "@speclynx/apidom-json-pointer": "^7.0.1",
64
+ "@speclynx/apidom-ns-arazzo-1": "^7.0.1",
65
+ "@speclynx/apidom-ns-openapi-2": "^7.0.1",
66
+ "@speclynx/apidom-ns-openapi-3-0": "^7.0.1",
67
+ "@speclynx/apidom-ns-openapi-3-1": "^7.0.1",
68
+ "@speclynx/apidom-reference": "^7.0.1",
69
+ "@speclynx/apidom-traverse": "^7.0.1",
70
70
  "@swaggerexpert/arazzo-criterion": "^1.0.1",
71
71
  "@swaggerexpert/arazzo-runtime-expression": "^3.3.0",
72
72
  "@swaggerexpert/cookie": "^2.1.1",
73
73
  "@swaggerexpert/json-pointer": "^3.0.2",
74
74
  "@swaggerexpert/jsonpath": "^4.0.5",
75
75
  "@swaggerexpert/media-types": "^1.0.2",
76
- "@usearazzo/parser": "1.0.1-alpha.25",
77
- "@usearazzo/resolver": "1.0.1-alpha.25",
76
+ "@usearazzo/parser": "1.0.1-alpha.27",
77
+ "@usearazzo/resolver": "1.0.1-alpha.27",
78
78
  "@xmldom/xmldom": "^0.9.12",
79
79
  "fontoxpath": "^3.34.0",
80
80
  "ramda": "^0.32.0",
@@ -94,5 +94,5 @@
94
94
  "devDependencies": {
95
95
  "swagger-client": "^3.38.4"
96
96
  },
97
- "gitHead": "37de6e45a52dabf8a924f09fed3ae4d41396be7d"
97
+ "gitHead": "c2017ebba31c3a95f6f6dd0ba75058f0a19a8d0d"
98
98
  }
@@ -167,7 +167,7 @@ class CriterionEvaluator {
167
167
  #resolveType(criterion) {
168
168
  const type = criterion.type;
169
169
  if (type === undefined) return 'simple';
170
- if ((0, _apidomNsArazzo.isCriterionExpressionTypeElement)(type) && (0, _apidomDatamodel.isStringElement)(type.type)) {
170
+ if ((0, _apidomNsArazzo.isExpressionTypeElement)(type) && (0, _apidomDatamodel.isStringElement)(type.type)) {
171
171
  return (0, _apidomCore.toValue)(type.type);
172
172
  }
173
173
  if ((0, _apidomDatamodel.isStringElement)(type)) return (0, _apidomCore.toValue)(type);
@@ -185,7 +185,7 @@ class CriterionEvaluator {
185
185
  */
186
186
  #resolveVersion(criterion) {
187
187
  const type = criterion.type;
188
- if (!(0, _apidomNsArazzo.isCriterionExpressionTypeElement)(type) || type.version === undefined) {
188
+ if (!(0, _apidomNsArazzo.isExpressionTypeElement)(type) || type.version === undefined) {
189
189
  return undefined;
190
190
  }
191
191
  if ((0, _apidomDatamodel.isStringElement)(type.version)) {
@@ -1,6 +1,6 @@
1
1
  import { toValue } from '@speclynx/apidom-core';
2
2
  import { isStringElement } from '@speclynx/apidom-datamodel';
3
- import { isCriterionElement, isCriterionExpressionTypeElement } from '@speclynx/apidom-ns-arazzo-1';
3
+ import { isCriterionElement, isExpressionTypeElement } from '@speclynx/apidom-ns-arazzo-1';
4
4
  import { test as isRuntimeExpression } from '@swaggerexpert/arazzo-runtime-expression';
5
5
  import CriterionError from "../errors/CriterionError.mjs";
6
6
  import CriterionEvaluationError from "../errors/CriterionEvaluationError.mjs";
@@ -157,7 +157,7 @@ class CriterionEvaluator {
157
157
  #resolveType(criterion) {
158
158
  const type = criterion.type;
159
159
  if (type === undefined) return 'simple';
160
- if (isCriterionExpressionTypeElement(type) && isStringElement(type.type)) {
160
+ if (isExpressionTypeElement(type) && isStringElement(type.type)) {
161
161
  return toValue(type.type);
162
162
  }
163
163
  if (isStringElement(type)) return toValue(type);
@@ -175,7 +175,7 @@ class CriterionEvaluator {
175
175
  */
176
176
  #resolveVersion(criterion) {
177
177
  const type = criterion.type;
178
- if (!isCriterionExpressionTypeElement(type) || type.version === undefined) {
178
+ if (!isExpressionTypeElement(type) || type.version === undefined) {
179
179
  return undefined;
180
180
  }
181
181
  if (isStringElement(type.version)) {
@@ -56,8 +56,9 @@ class ArazzoDocument extends _APIDocument.default {
56
56
 
57
57
  /**
58
58
  * The workflows of this document that can be addressed by `workflowId`, in
59
- * document order. Reads this document only: source descriptions are neither
60
- * loaded nor listed.
59
+ * document order. A duplicated `workflowId` is listed once, for its first
60
+ * workflow. Reads this document only: source descriptions are neither loaded
61
+ * nor listed.
61
62
  */
62
63
  listWorkflows() {
63
64
  return [...this.workflowIndex].map(([workflowId, jsonPointer]) => {
@@ -48,8 +48,9 @@ class ArazzoDocument extends APIDocument {
48
48
 
49
49
  /**
50
50
  * The workflows of this document that can be addressed by `workflowId`, in
51
- * document order. Reads this document only: source descriptions are neither
52
- * loaded nor listed.
51
+ * document order. A duplicated `workflowId` is listed once, for its first
52
+ * workflow. Reads this document only: source descriptions are neither loaded
53
+ * nor listed.
53
54
  */
54
55
  listWorkflows() {
55
56
  return [...this.workflowIndex].map(([workflowId, jsonPointer]) => {
@@ -15,6 +15,13 @@ var _ArazzoRunnerError = _interopRequireDefault(require("./ArazzoRunnerError.cjs
15
15
  * @public
16
16
  */
17
17
  class ResolverError extends _ArazzoRunnerError.default {
18
+ /**
19
+ * A parameter of a Success / Failure Action declares an `in` inline, which
20
+ * the Arazzo Specification forbids; one inherited from a
21
+ * `$components.parameters` Reusable Object maps by name instead. Thrown
22
+ * before the workflow's first request.
23
+ */
24
+ static ActionParameterLocation = 'action-parameter-location';
18
25
  /** A parameter cannot be delivered unambiguously at its location. */
19
26
  static AmbiguousDelivery = 'ambiguous-delivery';
20
27
  /** An output or a request body payload holds a value that is not a valid runtime expression. */
@@ -8,6 +8,13 @@ import ArazzoRunnerError from "./ArazzoRunnerError.mjs";
8
8
  * @public
9
9
  */
10
10
  class ResolverError extends ArazzoRunnerError {
11
+ /**
12
+ * A parameter of a Success / Failure Action declares an `in` inline, which
13
+ * the Arazzo Specification forbids; one inherited from a
14
+ * `$components.parameters` Reusable Object maps by name instead. Thrown
15
+ * before the workflow's first request.
16
+ */
17
+ static ActionParameterLocation = 'action-parameter-location';
11
18
  /** A parameter cannot be delivered unambiguously at its location. */
12
19
  static AmbiguousDelivery = 'ambiguous-delivery';
13
20
  /** An output or a request body payload holds a value that is not a valid runtime expression. */
@@ -224,7 +224,8 @@ class StepExecutor {
224
224
  action: matchedActions[0],
225
225
  matchedActions,
226
226
  successCriteria,
227
- actionCriteria
227
+ actionCriteria,
228
+ actionContext: this.actionContext(postContext, stepId, outputs)
228
229
  };
229
230
  }
230
231
 
@@ -366,6 +367,28 @@ class StepExecutor {
366
367
  };
367
368
  }
368
369
 
370
+ /**
371
+ * The context a step's actions are taken in — see
372
+ * {@link StepExecutionResult.actionContext}: `context` with
373
+ * `$steps.{stepId}.outputs` replaced by the outputs the attempt just
374
+ * resolved, as a fresh object, so run state is never written.
375
+ *
376
+ * Public for the same reason as {@link StepExecutor.selectActions}: the
377
+ * workflow executor builds this context for a step targeting a
378
+ * `workflowId`, which never reaches {@link StepExecutor.execute}.
379
+ */
380
+ actionContext(context, stepId, outputs) {
381
+ return {
382
+ ...context,
383
+ steps: {
384
+ ...context.steps,
385
+ [stepId]: {
386
+ outputs
387
+ }
388
+ }
389
+ };
390
+ }
391
+
369
392
  /**
370
393
  * Evaluates one criterion into a {@link CriterionEvaluationRecord}. A
371
394
  * condition that cannot be evaluated is unmet and records why; any other
@@ -217,7 +217,8 @@ class StepExecutor {
217
217
  action: matchedActions[0],
218
218
  matchedActions,
219
219
  successCriteria,
220
- actionCriteria
220
+ actionCriteria,
221
+ actionContext: this.actionContext(postContext, stepId, outputs)
221
222
  };
222
223
  }
223
224
 
@@ -359,6 +360,28 @@ class StepExecutor {
359
360
  };
360
361
  }
361
362
 
363
+ /**
364
+ * The context a step's actions are taken in — see
365
+ * {@link StepExecutionResult.actionContext}: `context` with
366
+ * `$steps.{stepId}.outputs` replaced by the outputs the attempt just
367
+ * resolved, as a fresh object, so run state is never written.
368
+ *
369
+ * Public for the same reason as {@link StepExecutor.selectActions}: the
370
+ * workflow executor builds this context for a step targeting a
371
+ * `workflowId`, which never reaches {@link StepExecutor.execute}.
372
+ */
373
+ actionContext(context, stepId, outputs) {
374
+ return {
375
+ ...context,
376
+ steps: {
377
+ ...context.steps,
378
+ [stepId]: {
379
+ outputs
380
+ }
381
+ }
382
+ };
383
+ }
384
+
362
385
  /**
363
386
  * Evaluates one criterion into a {@link CriterionEvaluationRecord}. A
364
387
  * condition that cannot be evaluated is unmet and records why; any other
@@ -149,7 +149,7 @@ class StepRetryRunner {
149
149
  // zero/negative/NaN value.
150
150
  const delayMs = this.#retryAfterMs(action);
151
151
  if (delayMs > 0) await this.#sleep(delayMs, context.signal);
152
- if (runReference !== undefined) await runReference(action);
152
+ if (runReference !== undefined) await runReference(action, outcome);
153
153
  fired = true;
154
154
  break;
155
155
  }
@@ -139,7 +139,7 @@ class StepRetryRunner {
139
139
  // zero/negative/NaN value.
140
140
  const delayMs = this.#retryAfterMs(action);
141
141
  if (delayMs > 0) await this.#sleep(delayMs, context.signal);
142
- if (runReference !== undefined) await runReference(action);
142
+ if (runReference !== undefined) await runReference(action, outcome);
143
143
  fired = true;
144
144
  break;
145
145
  }
@@ -301,6 +301,16 @@ class WorkflowExecutor {
301
301
  // included: an unreadable assertion found at step three must not cost the
302
302
  // run the requests of steps one and two.
303
303
  for (const step of steps) this.#stepExecutor.assertSuccessCriteria(step, workflowId);
304
+ // and to every action's `parameters`, for the same reason: an `in` the
305
+ // specification forbids must not cost the run the requests before the
306
+ // step whose action carries it.
307
+ // the workflow's own default actions too: one every step overrides by
308
+ // name never reaches a step's list, yet the validator still reports it.
309
+ this.#assertActionParameters([workflow.successActions, workflow.failureActions], workflowId);
310
+ for (const step of steps) {
311
+ const stepId = (0, _apidomCore.toValue)(step.stepId);
312
+ this.#assertActionParameters([step.onSuccess, step.onFailure], workflowId, stepId);
313
+ }
304
314
  const dependencies = await this.#runDependencies(workflow, invocation, scope);
305
315
  if (dependencies.some(dependency => this.#settledFailed(dependency))) {
306
316
  // a declared prerequisite did not complete, so this workflow cannot be
@@ -396,7 +406,7 @@ class WorkflowExecutor {
396
406
  stepId,
397
407
  callStack: nested
398
408
  });
399
- const transferredTo = await this.#runReferencedWorkflow(transition.workflowId, stepId, invocation, scope, 'goto');
409
+ const transferredTo = await this.#runReferencedWorkflow(transition.workflowId, stepId, invocation, scope, 'goto', this.#actionInputs(action?.parameters, outcome, invocation, stepId));
400
410
  runOutcome = {
401
411
  kind: 'transferred',
402
412
  transferredTo
@@ -572,16 +582,16 @@ class WorkflowExecutor {
572
582
  * for entering the reference itself, matching the "1 for the step itself
573
583
  * plus whatever its sub-run spends" convention a sub-workflow step
574
584
  * already follows (without it, a reference to a workflow with few or no
575
- * steps of its own would cost nothing to fire). It runs with no inputs:
576
- * the specification gives a retry reference no input-mapping mechanism
577
- * of its own (the same gap `dependsOn` has), and `{}` is the only
578
- * spec-honest fallback — see issue `#62`. Its outputs are recorded via
579
- * `state.setWorkflow`, which *is* "context transfers back" — but its
580
- * `inputs` are preserved rather than overwritten with `{}` when this
581
- * workflowId was already recorded (by an earlier `dependsOn` prerequisite
582
- * or sub-workflow step sharing the id): this call's own lack of a real
583
- * inputs value must not erase one a different, better-informed caller
584
- * already gave.
585
+ * steps of its own would cost nothing to fire). Its inputs are the
586
+ * action's `parameters` (`#115`), resolved again on every firing
587
+ * against the attempt that fired it — `{}` without them. Its outputs are
588
+ * recorded via `state.setWorkflow`, which *is* "context transfers back",
589
+ * together with the inputs it ran with when the action carries
590
+ * `parameters`. Without them, the recorded `inputs` are preserved rather
591
+ * than overwritten with `{}` when this workflowId was already recorded
592
+ * (by an earlier `dependsOn` prerequisite or sub-workflow step sharing
593
+ * the id): this call's own lack of a real inputs value must not erase one
594
+ * a different, better-informed caller already gave.
585
595
  * - a `stepId` reference must name a step of the current workflow (checked
586
596
  * lazily, at fire time, via the same lookup a `goto` target uses) and
587
597
  * runs as one more attempt at that step, through
@@ -604,7 +614,7 @@ class WorkflowExecutor {
604
614
  state,
605
615
  callStack
606
616
  } = invocation;
607
- return async action => {
617
+ return async (action, outcome) => {
608
618
  (0, _abort.throwIfAborted)(scope.signal, {
609
619
  workflowId,
610
620
  stepId,
@@ -623,13 +633,16 @@ class WorkflowExecutor {
623
633
  }
624
634
  if (hasWorkflowId) {
625
635
  const reference = (0, _apidomCore.toValue)(action.workflowId);
636
+ // resolved before the budget is charged, so an authoring error in them
637
+ // costs no budget unit.
638
+ const inputs = this.#actionInputs(action.parameters, outcome, invocation, stepId);
626
639
  this.#chargeBudget(scope, invocation, stepId);
627
- const result = await this.#runReferencedWorkflow(reference, stepId, invocation, scope, 'retry');
640
+ const result = await this.#runReferencedWorkflow(reference, stepId, invocation, scope, 'retry', inputs);
628
641
  // the result's workflowId is the bare id the reference resolved to —
629
642
  // the key run state is recorded under, whichever document owns it.
630
643
  const refId = result.workflowId;
631
644
  state.setWorkflow(refId, {
632
- inputs: state.getWorkflow(refId)?.inputs ?? {},
645
+ inputs: action.hasKey('parameters') ? inputs : state.getWorkflow(refId)?.inputs ?? {},
633
646
  outputs: result.outputs
634
647
  });
635
648
  retryReferences.push({
@@ -647,12 +660,12 @@ class WorkflowExecutor {
647
660
  });
648
661
  const refStep = steps[refIndex];
649
662
  const refSubWorkflows = [];
650
- const outcome = await this.#chargedAttempt(this.#stepAttempt(refStep, refStepId, invocation, scope, refSubWorkflows), invocation, scope, refStepId)();
651
- state.setStepOutputs(outcome.stepId, outcome.outputs);
663
+ const refOutcome = await this.#chargedAttempt(this.#stepAttempt(refStep, refStepId, invocation, scope, refSubWorkflows), invocation, scope, refStepId)();
664
+ state.setStepOutputs(refOutcome.stepId, refOutcome.outputs);
652
665
  retryReferences.push({
653
666
  kind: 'step',
654
667
  id: refStepId,
655
- successful: outcome.successful,
668
+ successful: refOutcome.successful,
656
669
  ...(refSubWorkflows.length > 0 ? {
657
670
  subWorkflow: refSubWorkflows[0]
658
671
  } : {})
@@ -905,7 +918,8 @@ class WorkflowExecutor {
905
918
  action: matchedActions[0],
906
919
  matchedActions,
907
920
  successCriteria,
908
- actionCriteria
921
+ actionCriteria,
922
+ actionContext: stepExecutor.actionContext(context, stepId, outputs)
909
923
  };
910
924
  };
911
925
  }
@@ -962,20 +976,95 @@ class WorkflowExecutor {
962
976
  /**
963
977
  * Runs a workflow an action references directly — a retry's `workflowId`
964
978
  * reference or a goto's transfer, plain id or cross-document expression —
965
- * with `{}` inputs, since neither has an input-mapping mechanism of its own
966
- * (issue `#62`).
979
+ * with the inputs its action's `parameters` resolved to (`#115`), `{}`
980
+ * without them.
967
981
  *
968
982
  * Shared rather than duplicated by its two callers, which otherwise repeat
969
- * the identical "resolve the reference, then run with no inputs" sequence.
983
+ * the identical "resolve the reference, then run it" sequence.
970
984
  * A target its document does not define is rejected by the normalizer,
971
985
  * attributed to the calling workflow and `stepId`.
972
986
  */
973
- async #runReferencedWorkflow(reference, stepId, invocation, scope, via) {
987
+ async #runReferencedWorkflow(reference, stepId, invocation, scope, via, inputs) {
974
988
  const locator = await this.#workflowLocatorNormalizer.normalize(reference, invocation.document, {
975
989
  workflowId: invocation.workflowId,
976
990
  stepId
977
991
  });
978
- return this.#run(locator, {}, scope, invocation.callStack, via);
992
+ return this.#run(locator, inputs, scope, invocation.callStack, via);
993
+ }
994
+
995
+ /**
996
+ * The inputs a `goto` / `retry` action naming a `workflowId` passes its
997
+ * target: the action's own `parameters`, mapped by name and resolved against
998
+ * the context of the attempt that fired it — `$statusCode`, `$response.*`
999
+ * and the rest of that step outcome, plus the outputs it just produced.
1000
+ * Resolved against the calling invocation's document, so its `$components`
1001
+ * apply, never the target's. No `parameters` gives `{}`. Their shape was
1002
+ * checked before the workflow's first request, by
1003
+ * {@link WorkflowExecutor.#assertActionParameters}.
1004
+ */
1005
+ #actionInputs(parameters, outcome, invocation, stepId) {
1006
+ const {
1007
+ workflowId,
1008
+ document
1009
+ } = invocation;
1010
+ return this.#parameterResolver.resolve(parameters, expression => this.#evaluate(outcome.actionContext, expression, document), {
1011
+ workflowId,
1012
+ stepId
1013
+ });
1014
+ }
1015
+
1016
+ /**
1017
+ * Throws {@link ResolverError} unless every action in `lists` — a step's
1018
+ * `onSuccess` / `onFailure`, or the workflow's own `successActions` /
1019
+ * `failureActions` — has an absent `parameters` or a list free of inline `in`:
1020
+ * `malformed-parameters` for one that is present but not a list,
1021
+ * `action-parameter-location` for a Parameter Object that declares `in`. The
1022
+ * specification says outright that "The `in` field MUST NOT be used" on
1023
+ * them. An `in` arriving through a `$components.parameters` Reusable Object
1024
+ * is not inline — a shared component carries `in` for the operation steps
1025
+ * that use it — and the dereferencer marks the transcluded copy with
1026
+ * `ref-fields`.
1027
+ *
1028
+ * Checked for every action, `workflowId` or not, before any step of the
1029
+ * workflow runs: both are authoring errors the validator reports, so a run
1030
+ * must not make requests on the way to discovering them. Valid `parameters`
1031
+ * on an action naming no `workflowId` are still ignored, as the validator
1032
+ * only warns about them. A list that is not an array is skipped here and left
1033
+ * for the code that reads it to report. `stepId` is absent for the
1034
+ * workflow's own lists, and the error then names the workflow alone.
1035
+ */
1036
+ #assertActionParameters(lists, workflowId, stepId) {
1037
+ const owner = stepId === undefined ? `workflow "${workflowId}"` : `step "${stepId}" in workflow "${workflowId}"`;
1038
+ for (const actions of lists) {
1039
+ if (!(0, _apidomDatamodel.isArrayElement)(actions)) continue;
1040
+ for (const action of actions) {
1041
+ if (!(0, _apidomNsArazzo.isSuccessActionElement)(action) && !(0, _apidomNsArazzo.isFailureActionElement)(action)) continue;
1042
+ if (!action.hasKey('parameters')) continue;
1043
+ const scope = {
1044
+ workflowId,
1045
+ ...(stepId === undefined ? {} : {
1046
+ stepId
1047
+ }),
1048
+ target: 'parameters'
1049
+ };
1050
+ const {
1051
+ parameters
1052
+ } = action;
1053
+ if (!(0, _apidomDatamodel.isArrayElement)(parameters)) {
1054
+ throw new _ResolverError.default('`parameters` is present but is not a list', {
1055
+ ...scope,
1056
+ reason: _ResolverError.default.MalformedParameters
1057
+ });
1058
+ }
1059
+ const inline = [...parameters].some(parameter => (0, _apidomNsArazzo.isParameterElement)(parameter) && parameter.hasKey('in') && !parameter.meta.hasKey('ref-fields'));
1060
+ if (inline) {
1061
+ throw new _ResolverError.default(`An action parameter of ${owner} declares an \`in\`; action parameters map to the target workflow's inputs by name`, {
1062
+ ...scope,
1063
+ reason: _ResolverError.default.ActionParameterLocation
1064
+ });
1065
+ }
1066
+ }
1067
+ }
979
1068
  }
980
1069
 
981
1070
  /**