@usearazzo/runner 0.0.0-stage → 1.0.1-alpha.10

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 (152) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +20 -0
  4. package/README.md +777 -2
  5. package/dist/arazzo-runner.browser.js +113254 -0
  6. package/dist/arazzo-runner.browser.min.js +1 -0
  7. package/package.json +91 -4
  8. package/src/action/ActionResolver.cjs +160 -0
  9. package/src/action/ActionResolver.mjs +150 -0
  10. package/src/assembler/OpenAPI2DocumentAssembler.cjs +111 -0
  11. package/src/assembler/OpenAPI2DocumentAssembler.mjs +104 -0
  12. package/src/assembler/OpenAPI30DocumentAssembler.cjs +110 -0
  13. package/src/assembler/OpenAPI30DocumentAssembler.mjs +103 -0
  14. package/src/assembler/OpenAPI31DocumentAssembler.cjs +111 -0
  15. package/src/assembler/OpenAPI31DocumentAssembler.mjs +104 -0
  16. package/src/assembler/OpenAPIDocumentAssembler.cjs +58 -0
  17. package/src/assembler/OpenAPIDocumentAssembler.mjs +50 -0
  18. package/src/client/HTTPClient.cjs +5 -0
  19. package/src/client/HTTPClient.mjs +1 -0
  20. package/src/client/HTTPClientFetch.cjs +16 -0
  21. package/src/client/HTTPClientFetch.mjs +10 -0
  22. package/src/client/OpenAPIOperationRequest.cjs +5 -0
  23. package/src/client/OpenAPIOperationRequest.mjs +1 -0
  24. package/src/client/OpenAPIOperationResponse.cjs +42 -0
  25. package/src/client/OpenAPIOperationResponse.mjs +36 -0
  26. package/src/client/OpenAPIOperationResponseNormalizer.cjs +48 -0
  27. package/src/client/OpenAPIOperationResponseNormalizer.mjs +40 -0
  28. package/src/client/ParameterDelivery.cjs +119 -0
  29. package/src/client/ParameterDelivery.mjs +113 -0
  30. package/src/constants.cjs +9 -0
  31. package/src/constants.mjs +3 -0
  32. package/src/criterion/CriterionEvaluator.cjs +205 -0
  33. package/src/criterion/CriterionEvaluator.mjs +196 -0
  34. package/src/criterion/JSONPathCriterionEvaluator.cjs +47 -0
  35. package/src/criterion/JSONPathCriterionEvaluator.mjs +40 -0
  36. package/src/criterion/RegexCriterionEvaluator.cjs +67 -0
  37. package/src/criterion/RegexCriterionEvaluator.mjs +60 -0
  38. package/src/criterion/SimpleCriterionEvaluator.cjs +50 -0
  39. package/src/criterion/SimpleCriterionEvaluator.mjs +43 -0
  40. package/src/criterion/XPathCriterionEvaluator.cjs +117 -0
  41. package/src/criterion/XPathCriterionEvaluator.mjs +109 -0
  42. package/src/document/APIDocument.cjs +35 -0
  43. package/src/document/APIDocument.mjs +30 -0
  44. package/src/document/ArazzoDocument.cjs +81 -0
  45. package/src/document/ArazzoDocument.mjs +74 -0
  46. package/src/document/ArazzoSourceDescriptionIndex.cjs +26 -0
  47. package/src/document/ArazzoSourceDescriptionIndex.mjs +20 -0
  48. package/src/document/ArazzoStepIndex.cjs +25 -0
  49. package/src/document/ArazzoStepIndex.mjs +19 -0
  50. package/src/document/ArazzoWorkflowIndex.cjs +17 -0
  51. package/src/document/ArazzoWorkflowIndex.mjs +11 -0
  52. package/src/document/OpenAPIDocument.cjs +28 -0
  53. package/src/document/OpenAPIDocument.mjs +21 -0
  54. package/src/document/OpenAPIOperationIndex.cjs +24 -0
  55. package/src/document/OpenAPIOperationIndex.mjs +18 -0
  56. package/src/document/ParameterIdentity.cjs +93 -0
  57. package/src/document/ParameterIdentity.mjs +88 -0
  58. package/src/document/openapi-types.cjs +5 -0
  59. package/src/document/openapi-types.mjs +1 -0
  60. package/src/errors/ArazzoRunnerError.cjs +10 -0
  61. package/src/errors/ArazzoRunnerError.mjs +5 -0
  62. package/src/errors/AssemblerError.cjs +11 -0
  63. package/src/errors/AssemblerError.mjs +4 -0
  64. package/src/errors/ClientError.cjs +11 -0
  65. package/src/errors/ClientError.mjs +4 -0
  66. package/src/errors/CriterionError.cjs +11 -0
  67. package/src/errors/CriterionError.mjs +4 -0
  68. package/src/errors/ExecutionError.cjs +11 -0
  69. package/src/errors/ExecutionError.mjs +4 -0
  70. package/src/errors/ExtractionError.cjs +11 -0
  71. package/src/errors/ExtractionError.mjs +4 -0
  72. package/src/errors/InvalidEntryDocumentError.cjs +11 -0
  73. package/src/errors/InvalidEntryDocumentError.mjs +4 -0
  74. package/src/errors/NormalizationError.cjs +11 -0
  75. package/src/errors/NormalizationError.mjs +4 -0
  76. package/src/errors/ResolverError.cjs +11 -0
  77. package/src/errors/ResolverError.mjs +4 -0
  78. package/src/errors/RuntimeExpressionError.cjs +11 -0
  79. package/src/errors/RuntimeExpressionError.mjs +4 -0
  80. package/src/errors/UnmatchedProviderError.cjs +11 -0
  81. package/src/errors/UnmatchedProviderError.mjs +4 -0
  82. package/src/executor/ArazzoWorkflowLocatorNormalizer.cjs +147 -0
  83. package/src/executor/ArazzoWorkflowLocatorNormalizer.mjs +137 -0
  84. package/src/executor/OpenAPIOperationExecutor.cjs +458 -0
  85. package/src/executor/OpenAPIOperationExecutor.mjs +447 -0
  86. package/src/executor/OpenAPIOperationLocatorNormalizer.cjs +197 -0
  87. package/src/executor/OpenAPIOperationLocatorNormalizer.mjs +189 -0
  88. package/src/executor/SourceDescriptionsLocatorNormalizer.cjs +44 -0
  89. package/src/executor/SourceDescriptionsLocatorNormalizer.mjs +38 -0
  90. package/src/executor/StepExecutor.cjs +333 -0
  91. package/src/executor/StepExecutor.mjs +326 -0
  92. package/src/executor/StepRetryRunner.cjs +226 -0
  93. package/src/executor/StepRetryRunner.mjs +215 -0
  94. package/src/executor/StepTransitionInterpreter.cjs +159 -0
  95. package/src/executor/StepTransitionInterpreter.mjs +151 -0
  96. package/src/executor/WorkflowCallStack.cjs +116 -0
  97. package/src/executor/WorkflowCallStack.mjs +106 -0
  98. package/src/executor/WorkflowExecutor.cjs +1091 -0
  99. package/src/executor/WorkflowExecutor.mjs +1077 -0
  100. package/src/executor/abort.cjs +70 -0
  101. package/src/executor/abort.mjs +60 -0
  102. package/src/expression/RuntimeExpressionContext.cjs +5 -0
  103. package/src/expression/RuntimeExpressionContext.mjs +1 -0
  104. package/src/expression/RuntimeExpressionEvaluator.cjs +337 -0
  105. package/src/expression/RuntimeExpressionEvaluator.mjs +328 -0
  106. package/src/extractor/ArazzoStepExtractor.cjs +48 -0
  107. package/src/extractor/ArazzoStepExtractor.mjs +41 -0
  108. package/src/extractor/ArazzoWorkflowExtractor.cjs +50 -0
  109. package/src/extractor/ArazzoWorkflowExtractor.mjs +43 -0
  110. package/src/extractor/OpenAPIOperationExtractor.cjs +84 -0
  111. package/src/extractor/OpenAPIOperationExtractor.mjs +77 -0
  112. package/src/index.cjs +419 -0
  113. package/src/index.mjs +73 -0
  114. package/src/normalizer/ArazzoStepNormalizer.cjs +23 -0
  115. package/src/normalizer/ArazzoStepNormalizer.mjs +17 -0
  116. package/src/normalizer/ArazzoWorkflowNormalizer.cjs +214 -0
  117. package/src/normalizer/ArazzoWorkflowNormalizer.mjs +206 -0
  118. package/src/normalizer/OpenAPI2OperationNormalizer.cjs +128 -0
  119. package/src/normalizer/OpenAPI2OperationNormalizer.mjs +120 -0
  120. package/src/normalizer/OpenAPI30OperationNormalizer.cjs +129 -0
  121. package/src/normalizer/OpenAPI30OperationNormalizer.mjs +121 -0
  122. package/src/normalizer/OpenAPI31OperationNormalizer.cjs +131 -0
  123. package/src/normalizer/OpenAPI31OperationNormalizer.mjs +123 -0
  124. package/src/normalizer/OpenAPIOperationNormalizer.cjs +58 -0
  125. package/src/normalizer/OpenAPIOperationNormalizer.mjs +50 -0
  126. package/src/registry/DocumentRegistry.cjs +160 -0
  127. package/src/registry/DocumentRegistry.mjs +151 -0
  128. package/src/registry/providers/ArazzoDocumentRegistryProvider.cjs +154 -0
  129. package/src/registry/providers/ArazzoDocumentRegistryProvider.mjs +146 -0
  130. package/src/registry/providers/DocumentRegistryProvider.cjs +31 -0
  131. package/src/registry/providers/DocumentRegistryProvider.mjs +25 -0
  132. package/src/registry/providers/OpenAPIDocumentRegistryProvider.cjs +122 -0
  133. package/src/registry/providers/OpenAPIDocumentRegistryProvider.mjs +114 -0
  134. package/src/resolver/ArazzoValueResolver.cjs +42 -0
  135. package/src/resolver/ArazzoValueResolver.mjs +37 -0
  136. package/src/resolver/OpenAPIOperationParameterResolver.cjs +117 -0
  137. package/src/resolver/OpenAPIOperationParameterResolver.mjs +110 -0
  138. package/src/resolver/OutputResolver.cjs +71 -0
  139. package/src/resolver/OutputResolver.mjs +64 -0
  140. package/src/resolver/RequestBodyResolver.cjs +227 -0
  141. package/src/resolver/RequestBodyResolver.mjs +220 -0
  142. package/src/resolver/ResolverScope.cjs +5 -0
  143. package/src/resolver/ResolverScope.mjs +1 -0
  144. package/src/resolver/StepParameterResolver.cjs +22 -0
  145. package/src/resolver/StepParameterResolver.mjs +15 -0
  146. package/src/resolver/WorkflowParameterResolver.cjs +73 -0
  147. package/src/resolver/WorkflowParameterResolver.mjs +66 -0
  148. package/src/state/WorkflowExecutionState.cjs +99 -0
  149. package/src/state/WorkflowExecutionState.mjs +93 -0
  150. package/src/vendor/swagger-client.cjs +1 -0
  151. package/src/vendor/swagger-client.mjs +1 -0
  152. package/types/runner.d.ts +2341 -0
@@ -0,0 +1,215 @@
1
+ import { toValue } from '@speclynx/apidom-core';
2
+ import { isNumberElement, isStringElement } from '@speclynx/apidom-datamodel';
3
+ import { isFailureActionElement } from '@speclynx/apidom-ns-arazzo-1';
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
+ /**
15
+ * The terminal outcome of a step once its retries are settled.
16
+ * @internal
17
+ */
18
+ /**
19
+ * Options for the StepRetryRunner.
20
+ * @internal
21
+ */
22
+ /**
23
+ * Which step is being run — carried for diagnostics, plus the run's cancellation
24
+ * so the wait between attempts can be cut short.
25
+ * @internal
26
+ */
27
+ /**
28
+ * Runs a step until its `retry` failure actions are settled.
29
+ *
30
+ * On success, or an immediate non-retry failure, the step runs once. On a failure
31
+ * whose first matching action is a `retry`, the step is re-run up to that
32
+ * action's `retryLimit` (default 1), waiting `retryAfter` seconds between
33
+ * attempts. Per Arazzo 1.0.1 — "retryLimit MUST be exhausted prior to executing
34
+ * subsequent failure actions" — an exhausted retry falls through to the *next*
35
+ * matching failure action of the latest attempt, which may itself be another
36
+ * `retry` with its own independent budget, or a terminal `end` / `goto`. Each
37
+ * attempt re-runs the step and re-selects against the fresh outcome, so an action
38
+ * whose criteria stop matching stops being chosen.
39
+ *
40
+ * It is agnostic about what a step *is*: the caller supplies a thunk that runs one
41
+ * attempt and returns its {@link StepAttemptOutcome}, which is what lets an
42
+ * operation step and a sub-workflow step retry through identical machinery. That
43
+ * thunk is also where a caller puts anything it wants done per attempt — charging
44
+ * a run budget, say — so this class needs no notion of one; a thunk that throws
45
+ * ends the step there, without further attempts. One consequence is worth
46
+ * knowing: a `retryAfter` delay is awaited before the next attempt begins, so a
47
+ * caller whose per-attempt check is about to fail (a spent budget, say) still
48
+ * pays that delay before its error surfaces. Keeping the seam this narrow is
49
+ * worth one bounded wait on a run that was already doomed.
50
+ *
51
+ * A `retry` action's `stepId` / `workflowId` reference works the same way: this
52
+ * class only decides *when* one fires (once per firing, after the `retryAfter`
53
+ * wait, before the step is re-run) — it hands the action to
54
+ * {@link StepRetryRunContext.runReference} and knows nothing about *what* the
55
+ * reference is or how it runs. A caller that omits that callback gets the
56
+ * standalone rejection instead of a silently ignored reference.
57
+ *
58
+ * Cancellation is the one thing it will not wait out: the run's `AbortSignal`
59
+ * reaches the timer, which returns early, and the next attempt then trips the
60
+ * caller's own check. Turning an abort into an error stays the caller's job, as
61
+ * with the budget; the signal is here only because a wait already in progress
62
+ * cannot be cut short from inside the thunk.
63
+ * @internal
64
+ */
65
+ class StepRetryRunner {
66
+ static #DEFAULT_RETRY_LIMIT = 1;
67
+ /**
68
+ * A real timer that stops waiting as soon as the run is cancelled, and leaves
69
+ * no pending timeout behind either way — a `retryAfter` of minutes would
70
+ * otherwise hold the event loop open long after the caller walked away.
71
+ *
72
+ * It resolves on abort rather than rejecting: turning a cancellation into an
73
+ * error is the caller's per-attempt check, which reports it uniformly for a
74
+ * run cancelled mid-wait and one cancelled mid-request.
75
+ */
76
+ static #DEFAULT_SLEEP = (ms, signal) => new Promise(resolve => {
77
+ if (signal?.aborted === true) {
78
+ resolve();
79
+ return;
80
+ }
81
+ const onAbort = () => {
82
+ clearTimeout(timer);
83
+ resolve();
84
+ };
85
+ // unsubscribed when the wait ends normally, so a signal outliving many
86
+ // retries does not accumulate a listener per wait.
87
+ const timer = setTimeout(() => {
88
+ signal?.removeEventListener('abort', onAbort);
89
+ resolve();
90
+ }, ms);
91
+ signal?.addEventListener('abort', onAbort, {
92
+ once: true
93
+ });
94
+ });
95
+ #sleep;
96
+ constructor(options = {}) {
97
+ this.#sleep = options.sleep ?? StepRetryRunner.#DEFAULT_SLEEP;
98
+ }
99
+
100
+ /**
101
+ * Runs `attempt` until the step succeeds, a terminal failure action is reached,
102
+ * or its retries are exhausted.
103
+ */
104
+ async run(attempt, context) {
105
+ // attempts already spent on each retry action, keyed by the action element.
106
+ // The element is the correct identity here: re-running the step returns the
107
+ // same element instances (the workflow is normalized once and `onFailure`'s
108
+ // getter returns its stored children — nothing re-refracts per call), and
109
+ // keying by the element survives the criteria re-filtering that varies
110
+ // `matchedActions` between attempts (position within that filtered list is
111
+ // NOT stable when a response change drops an earlier action). So each retry
112
+ // in a chain keeps its own independent budget across re-runs.
113
+ const retriesSpent = new Map();
114
+ let attempts = 0;
115
+ for (;;) {
116
+ const outcome = await attempt();
117
+ attempts += 1;
118
+ if (outcome.successful) {
119
+ return {
120
+ outcome,
121
+ action: outcome.action,
122
+ attempts
123
+ };
124
+ }
125
+
126
+ // walk the matching failure actions: fire the first retry that still has
127
+ // budget, else fall through to the first terminal (non-retry) action.
128
+ let fired = false;
129
+ let terminal;
130
+ for (const action of outcome.matchedActions) {
131
+ if (!this.#isRetry(action)) {
132
+ terminal = action;
133
+ break;
134
+ }
135
+ const spent = retriesSpent.get(action) ?? 0;
136
+ if (spent >= this.#retryLimit(action)) continue; // exhausted — try the next action
137
+
138
+ const carriesReference = this.#carriesReference(action);
139
+ // narrowed once here rather than asserted at the call site below: by
140
+ // the time `runReference` is invoked, its absence would already have
141
+ // thrown on the line above, so the type reflects that instead of
142
+ // relying on a non-null assertion to say so.
143
+ const runReference = carriesReference ? context.runReference : undefined;
144
+ if (carriesReference && runReference === undefined) this.#rejectReference(context);
145
+ retriesSpent.set(action, spent + 1);
146
+ // only sleep for a real, positive delay — skip the event-loop yield of
147
+ // sleep(0) for immediate retries, and never hand a custom sleep a
148
+ // zero/negative/NaN value.
149
+ const delayMs = this.#retryAfterMs(action);
150
+ if (delayMs > 0) await this.#sleep(delayMs, context.signal);
151
+ if (runReference !== undefined) await runReference(action);
152
+ fired = true;
153
+ break;
154
+ }
155
+ if (fired) continue; // re-run the step
156
+ return {
157
+ outcome,
158
+ action: terminal,
159
+ attempts
160
+ };
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Whether a matched action is a `retry` failure action (narrowing it to
166
+ * {@link FailureActionElement} so `retryLimit` / `retryAfter` are accessible).
167
+ */
168
+ #isRetry(action) {
169
+ return isFailureActionElement(action) && toValue(action.type) === 'retry';
170
+ }
171
+
172
+ /**
173
+ * A `retry` action's attempt budget — its `retryLimit`, or the default of 1
174
+ * when unset or non-numeric.
175
+ */
176
+ #retryLimit(action) {
177
+ return isNumberElement(action.retryLimit) ? toValue(action.retryLimit) : StepRetryRunner.#DEFAULT_RETRY_LIMIT;
178
+ }
179
+
180
+ /**
181
+ * The delay before a retry, in milliseconds — the action's `retryAfter`
182
+ * (seconds) converted. `0` when unset, non-numeric, or not a finite positive
183
+ * value (a negative or `NaN` `retryAfter` means "no wait", never a bad value
184
+ * handed to `sleep`).
185
+ */
186
+ #retryAfterMs(action) {
187
+ if (!isNumberElement(action.retryAfter)) return 0;
188
+ const ms = toValue(action.retryAfter) * 1000;
189
+ return Number.isFinite(ms) && ms > 0 ? ms : 0;
190
+ }
191
+
192
+ /**
193
+ * Whether a `retry` action carries a `stepId` / `workflowId` reference to
194
+ * execute before retrying ("the reference is executed and the context is
195
+ * returned, after which the current step is retried").
196
+ */
197
+ #carriesReference(action) {
198
+ return isStringElement(action.stepId) || isStringElement(action.workflowId);
199
+ }
200
+
201
+ /**
202
+ * Rejects a referenced `retry` action when the caller supplied no
203
+ * {@link StepRetryRunContext.runReference}, so an unsupported caller
204
+ * (or a step retried directly against this runner in isolation) fails
205
+ * loudly rather than silently ignoring the reference.
206
+ */
207
+ #rejectReference(context) {
208
+ throw new ExecutionError(`retry action on step "${context.stepId}" in workflow "${context.workflowId}" carries a stepId/workflowId reference, but no runReference was given to run it`, {
209
+ stepId: context.stepId,
210
+ workflowId: context.workflowId,
211
+ reason: 'retry-reference-unsupported'
212
+ });
213
+ }
214
+ }
215
+ export default StepRetryRunner;
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.actionTargets = actionTargets;
8
+ exports.default = void 0;
9
+ var _apidomCore = require("@speclynx/apidom-core");
10
+ var _apidomDatamodel = require("@speclynx/apidom-datamodel");
11
+ var _ExecutionError = _interopRequireDefault(require("../errors/ExecutionError.cjs"));
12
+ /**
13
+ * Which of an action's mutually exclusive `stepId` / `workflowId` fields are
14
+ * present, ahead of a caller deciding what that means.
15
+ *
16
+ * Shared rather than duplicated by `goto`'s own check here and a `retry`
17
+ * action's reference check in `WorkflowExecutor` — both Success/Failure
18
+ * Action Object fields carry the identical "mutually exclusive" constraint,
19
+ * so both need the identical two `isStringElement` reads before diverging on
20
+ * their own wording and branch handling.
21
+ * @internal
22
+ */
23
+ function actionTargets(action) {
24
+ return {
25
+ hasStepId: (0, _apidomDatamodel.isStringElement)(action.stepId),
26
+ hasWorkflowId: (0, _apidomDatamodel.isStringElement)(action.workflowId)
27
+ };
28
+ }
29
+
30
+ /**
31
+ * What a workflow does after a step, once the step's action has been
32
+ * interpreted.
33
+ *
34
+ * `next` runs the following step (the success default, or a matched `goto` that
35
+ * targets the next step); `goto` jumps to a step by id within the current
36
+ * workflow; `transfer` hands control one-way to another workflow, which the
37
+ * caller's run then ends on; `end` stops the run with `status: ended`; `break`
38
+ * stops with `status: failed` (the failure default).
39
+ * @internal
40
+ */
41
+
42
+ /**
43
+ * Which step's outcome is being interpreted — carried for diagnostics only.
44
+ * @internal
45
+ */
46
+
47
+ /**
48
+ * Turns the action a step resolved to into what the workflow does next.
49
+ *
50
+ * This is the Arazzo 1.0.1 Success/Failure Action Object semantics in one place,
51
+ * and nothing else: it holds no state, performs no I/O, and does not act on the
52
+ * transition it returns — moving through the step list stays the workflow
53
+ * executor's business.
54
+ *
55
+ * - **no matching action** — the path default: "the next sequential step shall be
56
+ * executed" on success, "break and return" on failure.
57
+ * - **`end`** — "the workflow ends, and context returns to the caller with
58
+ * applicable outputs".
59
+ * - **`goto` a `stepId`** — jumps within the current workflow, which is where the
60
+ * specification requires the target to be.
61
+ * - **`goto` a `workflowId`** — a one-way transfer of control to another
62
+ * workflow: the `stepId`/`workflowId` field descriptions condition "context
63
+ * transfers back upon completion" on `retry` specifically, so for `goto` it
64
+ * does not — the current run ends here, and running the target is the
65
+ * executor's job, since running workflows is orchestration rather than
66
+ * policy. A `goto` naming both `stepId` and `workflowId` is malformed — the
67
+ * spec marks them mutually exclusive — and is rejected before either is
68
+ * read.
69
+ * - **anything else** — malformed input rather than a defined control flow.
70
+ * `retry` is the one action that never arrives here: it is settled by
71
+ * {@link StepRetryRunner}, which yields only the terminal action a retry chain
72
+ * resolves to.
73
+ * @internal
74
+ */
75
+ class StepTransitionInterpreter {
76
+ /**
77
+ * Interprets a step's terminal action into the workflow's next transition.
78
+ */
79
+ interpret(action, successful, context) {
80
+ if (action === undefined) {
81
+ // path default: next step on success, break-and-return on failure.
82
+ return successful ? {
83
+ kind: 'next'
84
+ } : {
85
+ kind: 'break'
86
+ };
87
+ }
88
+ const {
89
+ workflowId,
90
+ stepId
91
+ } = context;
92
+ const type = (0, _apidomCore.toValue)(action.type);
93
+ if (type === 'end') {
94
+ return {
95
+ kind: 'end'
96
+ };
97
+ }
98
+ if (type === 'goto') {
99
+ const {
100
+ hasStepId,
101
+ hasWorkflowId
102
+ } = actionTargets(action);
103
+ if (hasStepId && hasWorkflowId) {
104
+ throw new _ExecutionError.default(`goto action on step "${stepId}" in workflow "${workflowId}" declares both a stepId and a workflowId (mutually exclusive)`, {
105
+ stepId,
106
+ workflowId,
107
+ reason: 'ambiguous-target'
108
+ });
109
+ }
110
+ if (hasStepId) {
111
+ return {
112
+ kind: 'goto',
113
+ stepId: (0, _apidomCore.toValue)(action.stepId)
114
+ };
115
+ }
116
+ if (hasWorkflowId) {
117
+ return {
118
+ kind: 'transfer',
119
+ workflowId: (0, _apidomCore.toValue)(action.workflowId)
120
+ };
121
+ }
122
+ throw new _ExecutionError.default(`goto action on step "${stepId}" in workflow "${workflowId}" has neither stepId nor workflowId`, {
123
+ stepId,
124
+ workflowId,
125
+ reason: 'goto-target-missing'
126
+ });
127
+ }
128
+ throw new _ExecutionError.default(`action on step "${stepId}" in workflow "${workflowId}" has unsupported type "${type}"`, {
129
+ stepId,
130
+ workflowId,
131
+ reason: 'unknown-action-type'
132
+ });
133
+ }
134
+
135
+ /**
136
+ * The index of a step within the workflow's steps, by `stepId`; a target that
137
+ * names no step in this workflow is an authoring error.
138
+ *
139
+ * A general lookup against the workflow's step list, shared rather than
140
+ * duplicated by its two callers — a `goto` target and a `retry` action's
141
+ * `stepId` reference — which report a miss the same way except for the
142
+ * `reason` and wording that name which one it was. `descriptor` is
143
+ * mandatory, not defaulted to the `goto` wording: an optional default would
144
+ * let a future third caller silently inherit goto's diagnostics by
145
+ * forgetting to pass one.
146
+ */
147
+ indexOfStep(steps, stepId, workflowId, descriptor) {
148
+ const index = steps.findIndex(step => (0, _apidomCore.toValue)(step.stepId) === stepId);
149
+ if (index === -1) {
150
+ throw new _ExecutionError.default(`${descriptor.label} step "${stepId}" not found in workflow "${workflowId}"`, {
151
+ stepId,
152
+ workflowId,
153
+ reason: descriptor.reason
154
+ });
155
+ }
156
+ return index;
157
+ }
158
+ }
159
+ var _default = exports.default = StepTransitionInterpreter;
@@ -0,0 +1,151 @@
1
+ import { toValue } from '@speclynx/apidom-core';
2
+ import { isStringElement } from '@speclynx/apidom-datamodel';
3
+ import ExecutionError from "../errors/ExecutionError.mjs";
4
+ /**
5
+ * Which of an action's mutually exclusive `stepId` / `workflowId` fields are
6
+ * present, ahead of a caller deciding what that means.
7
+ *
8
+ * Shared rather than duplicated by `goto`'s own check here and a `retry`
9
+ * action's reference check in `WorkflowExecutor` — both Success/Failure
10
+ * Action Object fields carry the identical "mutually exclusive" constraint,
11
+ * so both need the identical two `isStringElement` reads before diverging on
12
+ * their own wording and branch handling.
13
+ * @internal
14
+ */
15
+ export function actionTargets(action) {
16
+ return {
17
+ hasStepId: isStringElement(action.stepId),
18
+ hasWorkflowId: isStringElement(action.workflowId)
19
+ };
20
+ }
21
+
22
+ /**
23
+ * What a workflow does after a step, once the step's action has been
24
+ * interpreted.
25
+ *
26
+ * `next` runs the following step (the success default, or a matched `goto` that
27
+ * targets the next step); `goto` jumps to a step by id within the current
28
+ * workflow; `transfer` hands control one-way to another workflow, which the
29
+ * caller's run then ends on; `end` stops the run with `status: ended`; `break`
30
+ * stops with `status: failed` (the failure default).
31
+ * @internal
32
+ */
33
+
34
+ /**
35
+ * Which step's outcome is being interpreted — carried for diagnostics only.
36
+ * @internal
37
+ */
38
+
39
+ /**
40
+ * Turns the action a step resolved to into what the workflow does next.
41
+ *
42
+ * This is the Arazzo 1.0.1 Success/Failure Action Object semantics in one place,
43
+ * and nothing else: it holds no state, performs no I/O, and does not act on the
44
+ * transition it returns — moving through the step list stays the workflow
45
+ * executor's business.
46
+ *
47
+ * - **no matching action** — the path default: "the next sequential step shall be
48
+ * executed" on success, "break and return" on failure.
49
+ * - **`end`** — "the workflow ends, and context returns to the caller with
50
+ * applicable outputs".
51
+ * - **`goto` a `stepId`** — jumps within the current workflow, which is where the
52
+ * specification requires the target to be.
53
+ * - **`goto` a `workflowId`** — a one-way transfer of control to another
54
+ * workflow: the `stepId`/`workflowId` field descriptions condition "context
55
+ * transfers back upon completion" on `retry` specifically, so for `goto` it
56
+ * does not — the current run ends here, and running the target is the
57
+ * executor's job, since running workflows is orchestration rather than
58
+ * policy. A `goto` naming both `stepId` and `workflowId` is malformed — the
59
+ * spec marks them mutually exclusive — and is rejected before either is
60
+ * read.
61
+ * - **anything else** — malformed input rather than a defined control flow.
62
+ * `retry` is the one action that never arrives here: it is settled by
63
+ * {@link StepRetryRunner}, which yields only the terminal action a retry chain
64
+ * resolves to.
65
+ * @internal
66
+ */
67
+ class StepTransitionInterpreter {
68
+ /**
69
+ * Interprets a step's terminal action into the workflow's next transition.
70
+ */
71
+ interpret(action, successful, context) {
72
+ if (action === undefined) {
73
+ // path default: next step on success, break-and-return on failure.
74
+ return successful ? {
75
+ kind: 'next'
76
+ } : {
77
+ kind: 'break'
78
+ };
79
+ }
80
+ const {
81
+ workflowId,
82
+ stepId
83
+ } = context;
84
+ const type = toValue(action.type);
85
+ if (type === 'end') {
86
+ return {
87
+ kind: 'end'
88
+ };
89
+ }
90
+ if (type === 'goto') {
91
+ const {
92
+ hasStepId,
93
+ hasWorkflowId
94
+ } = actionTargets(action);
95
+ if (hasStepId && hasWorkflowId) {
96
+ throw new ExecutionError(`goto action on step "${stepId}" in workflow "${workflowId}" declares both a stepId and a workflowId (mutually exclusive)`, {
97
+ stepId,
98
+ workflowId,
99
+ reason: 'ambiguous-target'
100
+ });
101
+ }
102
+ if (hasStepId) {
103
+ return {
104
+ kind: 'goto',
105
+ stepId: toValue(action.stepId)
106
+ };
107
+ }
108
+ if (hasWorkflowId) {
109
+ return {
110
+ kind: 'transfer',
111
+ workflowId: toValue(action.workflowId)
112
+ };
113
+ }
114
+ throw new ExecutionError(`goto action on step "${stepId}" in workflow "${workflowId}" has neither stepId nor workflowId`, {
115
+ stepId,
116
+ workflowId,
117
+ reason: 'goto-target-missing'
118
+ });
119
+ }
120
+ throw new ExecutionError(`action on step "${stepId}" in workflow "${workflowId}" has unsupported type "${type}"`, {
121
+ stepId,
122
+ workflowId,
123
+ reason: 'unknown-action-type'
124
+ });
125
+ }
126
+
127
+ /**
128
+ * The index of a step within the workflow's steps, by `stepId`; a target that
129
+ * names no step in this workflow is an authoring error.
130
+ *
131
+ * A general lookup against the workflow's step list, shared rather than
132
+ * duplicated by its two callers — a `goto` target and a `retry` action's
133
+ * `stepId` reference — which report a miss the same way except for the
134
+ * `reason` and wording that name which one it was. `descriptor` is
135
+ * mandatory, not defaulted to the `goto` wording: an optional default would
136
+ * let a future third caller silently inherit goto's diagnostics by
137
+ * forgetting to pass one.
138
+ */
139
+ indexOfStep(steps, stepId, workflowId, descriptor) {
140
+ const index = steps.findIndex(step => toValue(step.stepId) === stepId);
141
+ if (index === -1) {
142
+ throw new ExecutionError(`${descriptor.label} step "${stepId}" not found in workflow "${workflowId}"`, {
143
+ stepId,
144
+ workflowId,
145
+ reason: descriptor.reason
146
+ });
147
+ }
148
+ return index;
149
+ }
150
+ }
151
+ export default StepTransitionInterpreter;
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ var _ExecutionError = _interopRequireDefault(require("../errors/ExecutionError.cjs"));
9
+ /**
10
+ * How a workflow invocation was entered.
11
+ *
12
+ * Recorded per call so a detected loop can be named for the mechanism that formed
13
+ * it: a repeat reached purely through `dependsOn` edges is a dependency cycle,
14
+ * one involving a sub-workflow step call — a `retry` action's `workflowId`
15
+ * reference — or a `goto`'s one-way transfer to a `workflowId` — is a call
16
+ * cycle. `retry` and `goto` are grouped with `step` for that classification, not
17
+ * `dependsOn`: each is a call the current step makes, not a stated precondition.
18
+ * @internal
19
+ */
20
+
21
+ /**
22
+ * One workflow invocation in progress. Its identity is the (documentURI,
23
+ * workflowId) pair: two documents may each define a workflow of one id, and
24
+ * those are different workflows — comparing bare ids would call their meeting
25
+ * a cycle.
26
+ */
27
+
28
+ /**
29
+ * Options for the WorkflowCallStack.
30
+ * @internal
31
+ */
32
+
33
+ /**
34
+ * The chain of workflow invocations in progress, as an immutable value.
35
+ *
36
+ * {@link WorkflowCallStack.enter} returns a *new* stack with the given workflow
37
+ * pushed, or throws if entering it cannot be legitimate. Because entering yields
38
+ * a new value rather than mutating this one, leaving is implicit: an unwinding
39
+ * throw cannot leave a stale entry behind, and sibling calls each see only their
40
+ * own chain — so a diamond (a workflow reached twice on separate paths, having
41
+ * completed and unwound in between) is not a cycle, while a genuine re-entry is.
42
+ *
43
+ * Two distinct failures are guarded, in this order:
44
+ *
45
+ * - a **cycle** — the workflow is already in progress on this chain, so it can
46
+ * never terminate. Checked *first* deliberately: a cycle would eventually trip
47
+ * the depth ceiling too, but only after exhausting the whole budget of frames
48
+ * and then reporting the wrong cause.
49
+ * - **excessive depth** — distinct workflows nested past `maxDepth`, which is
50
+ * legitimate nesting that simply has to be bounded.
51
+ *
52
+ * Both carry the offending `path`, since the workflow the error names is rarely
53
+ * the one whose authoring needs looking at.
54
+ * @internal
55
+ */
56
+ class WorkflowCallStack {
57
+ #calls;
58
+ #maxDepth;
59
+ #entryDocumentURI;
60
+ constructor(options, calls = []) {
61
+ this.#maxDepth = options.maxDepth;
62
+ this.#entryDocumentURI = options.entryDocumentURI;
63
+ this.#calls = calls;
64
+ }
65
+
66
+ /**
67
+ * The workflows in progress, outermost first — bare ids for entry-document
68
+ * frames, `{documentURI}#{workflowId}` for foreign ones.
69
+ */
70
+ get path() {
71
+ return this.#calls.map(call => this.#display(call.workflowId, call.documentURI));
72
+ }
73
+ #display(workflowId, documentURI) {
74
+ return documentURI === this.#entryDocumentURI ? workflowId : `${documentURI}#${workflowId}`;
75
+ }
76
+
77
+ /**
78
+ * Returns the stack with the workflow pushed — identified by its bare id
79
+ * together with the canonical URI of the document that owns it — or throws
80
+ * when entering it would form a cycle or exceed the nesting ceiling.
81
+ */
82
+ enter(workflowId, via, documentURI) {
83
+ const repeated = this.#calls.findIndex(call => call.workflowId === workflowId && call.documentURI === documentURI);
84
+ if (repeated !== -1) {
85
+ const path = [...this.path, this.#display(workflowId, documentURI)];
86
+ // name the loop for the mechanism that formed it: every edge closing the
87
+ // cycle being a `dependsOn` edge makes it a dependency cycle, while any
88
+ // sub-workflow step call in the loop makes it a call cycle. One stack
89
+ // detects both, so a loop crossing the two mechanisms cannot slip between
90
+ // them.
91
+ const closingEdges = [...this.#calls.slice(repeated + 1).map(call => call.via), via];
92
+ const reason = closingEdges.every(edge => edge === 'dependsOn') ? 'dependsOn-cycle' : 'workflow-cycle';
93
+ throw new _ExecutionError.default(`workflow "${workflowId}" is already in progress; its call chain forms a cycle (${path.join(' -> ')})`, {
94
+ workflowId,
95
+ reason,
96
+ path
97
+ });
98
+ }
99
+ if (this.#calls.length >= this.#maxDepth) {
100
+ throw new _ExecutionError.default(`workflow "${workflowId}" nests deeper than the limit of ${this.#maxDepth} workflows`, {
101
+ workflowId,
102
+ reason: 'workflow-depth',
103
+ path: [...this.path, this.#display(workflowId, documentURI)]
104
+ });
105
+ }
106
+ return new WorkflowCallStack({
107
+ maxDepth: this.#maxDepth,
108
+ entryDocumentURI: this.#entryDocumentURI
109
+ }, [...this.#calls, {
110
+ workflowId,
111
+ documentURI,
112
+ via
113
+ }]);
114
+ }
115
+ }
116
+ var _default = exports.default = WorkflowCallStack;