@usearazzo/runner 1.0.1-alpha.22 → 1.0.1-alpha.23
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 +6 -0
- package/dist/arazzo-runner.browser.js +122 -78
- package/dist/arazzo-runner.browser.min.js +1 -1
- package/package.json +4 -4
- package/src/executor/OpenAPIOperationLocatorNormalizer.cjs +79 -47
- package/src/executor/OpenAPIOperationLocatorNormalizer.mjs +80 -48
- package/src/executor/StepExecutor.cjs +35 -25
- package/src/executor/StepExecutor.mjs +35 -25
- package/src/executor/WorkflowExecutor.cjs +4 -3
- package/src/executor/WorkflowExecutor.mjs +4 -3
- package/types/runner.d.ts +40 -15
|
@@ -127,38 +127,45 @@ class StepExecutor {
|
|
|
127
127
|
* request nobody is waiting for, whose wire-level cancellation would surface
|
|
128
128
|
* as a transport {@link ClientError} — a request that failed on its own terms
|
|
129
129
|
* — rather than as the withdrawal it is.
|
|
130
|
+
*
|
|
131
|
+
* `workflowId` names the workflow the step belongs to; every error raised
|
|
132
|
+
* here then carries it alongside the `stepId`, since step ids are only
|
|
133
|
+
* unique within a workflow. The workflow executor always passes it.
|
|
130
134
|
*/
|
|
131
|
-
async execute(step, state, executeOptions = {}) {
|
|
135
|
+
async execute(step, state, executeOptions = {}, workflowId) {
|
|
132
136
|
const stepId = toValue(step.stepId);
|
|
137
|
+
const scope = {
|
|
138
|
+
...(workflowId === undefined ? {} : {
|
|
139
|
+
workflowId
|
|
140
|
+
}),
|
|
141
|
+
stepId
|
|
142
|
+
};
|
|
133
143
|
|
|
134
144
|
// a step's target fields are mutually exclusive; declaring more than one is
|
|
135
145
|
// malformed and has no defined resolution.
|
|
136
146
|
const declaredTargets = STEP_TARGET_FIELDS.filter(field => isStringElement(step[field]));
|
|
137
147
|
if (declaredTargets.length > 1) {
|
|
138
148
|
throw new ExecutionError(`Step "${stepId}" declares more than one of ${STEP_TARGET_FIELDS.join(', ')} (mutually exclusive)`, {
|
|
139
|
-
|
|
149
|
+
...scope,
|
|
140
150
|
reason: 'ambiguous-target'
|
|
141
151
|
});
|
|
142
152
|
}
|
|
143
153
|
if (isStringElement(step.workflowId)) {
|
|
144
154
|
throw new ExecutionError(`Step "${stepId}" targets a workflowId; use the workflow executor`, {
|
|
145
|
-
|
|
155
|
+
...scope,
|
|
146
156
|
reason: 'workflow-step'
|
|
147
157
|
});
|
|
148
158
|
}
|
|
149
|
-
this.assertSuccessCriteria(step);
|
|
159
|
+
this.assertSuccessCriteria(step, workflowId);
|
|
150
160
|
|
|
151
161
|
// locate which operation the step invokes — reduced to a (document, JSON
|
|
152
162
|
// Pointer) locator that the operation executor extracts, normalizes,
|
|
153
163
|
// assembles into a standalone doc, and runs.
|
|
154
|
-
const locator = await this.#locateOperation(step,
|
|
164
|
+
const locator = await this.#locateOperation(step, scope);
|
|
155
165
|
|
|
156
166
|
// resolve parameters and request body against the pre-request context.
|
|
157
167
|
// keyed by '{in}.{name}' rather than bare name, so two parameters that
|
|
158
168
|
// legally differ only in their location each reach their own place.
|
|
159
|
-
const scope = {
|
|
160
|
-
stepId
|
|
161
|
-
};
|
|
162
169
|
const preContext = state.toContext();
|
|
163
170
|
const parameters = this.#parameterResolver.resolve(step.parameters, expression => this.#evaluate(preContext, expression), scope);
|
|
164
171
|
const requestBody = this.#requestBodyResolver.resolve(step.requestBody, expression => this.#evaluate(preContext, expression), scope);
|
|
@@ -169,9 +176,7 @@ class StepExecutor {
|
|
|
169
176
|
// live side effect. The signal stays in the bag, so a request already in
|
|
170
177
|
// flight is aborted by the transport as before.
|
|
171
178
|
const signal = readAbortSignal(executeOptions);
|
|
172
|
-
throwIfAborted(signal,
|
|
173
|
-
stepId
|
|
174
|
-
});
|
|
179
|
+
throwIfAborted(signal, scope);
|
|
175
180
|
|
|
176
181
|
// the Arazzo-derived parameters and request body are spread after the opaque
|
|
177
182
|
// executeOptions bag so they take precedence over it. the operation executor
|
|
@@ -190,22 +195,20 @@ class StepExecutor {
|
|
|
190
195
|
// cancelled, it did not: reporting it as the abort keeps one error shape
|
|
191
196
|
// whether the run was withdrawn between two steps or mid-flight, and keeps
|
|
192
197
|
// the whole feature from reading differently per transport.
|
|
193
|
-
throwIfAborted(signal,
|
|
194
|
-
stepId
|
|
195
|
-
});
|
|
198
|
+
throwIfAborted(signal, scope);
|
|
196
199
|
throw error;
|
|
197
200
|
}
|
|
198
201
|
|
|
199
202
|
// evaluate criteria, outputs, and the next action against the post-request
|
|
200
203
|
// context (with $request / $url / $method and $response / $statusCode).
|
|
201
204
|
const postContext = state.toContext(this.#requestContext(response), this.#responseContext(response));
|
|
202
|
-
const successCriteria = this.evaluateCriteria(step, postContext);
|
|
205
|
+
const successCriteria = this.evaluateCriteria(step, postContext, workflowId);
|
|
203
206
|
const successful = successCriteria.every(criterion => criterion.met);
|
|
204
207
|
const outputs = this.#outputResolver.resolve(step.outputs, expression => this.#evaluate(postContext, expression), scope);
|
|
205
208
|
const {
|
|
206
209
|
matchedActions,
|
|
207
210
|
actionCriteria
|
|
208
|
-
} = this.selectActions(step, successful, postContext);
|
|
211
|
+
} = this.selectActions(step, successful, postContext, workflowId);
|
|
209
212
|
return {
|
|
210
213
|
stepId,
|
|
211
214
|
response,
|
|
@@ -235,15 +238,15 @@ class StepExecutor {
|
|
|
235
238
|
requestContentType: requestBody.contentType
|
|
236
239
|
};
|
|
237
240
|
}
|
|
238
|
-
async #locateOperation(step,
|
|
241
|
+
async #locateOperation(step, scope) {
|
|
239
242
|
if (isStringElement(step.operationId)) {
|
|
240
|
-
return this.#locatorNormalizer.normalizeOperationId(toValue(step.operationId), this.#document);
|
|
243
|
+
return this.#locatorNormalizer.normalizeOperationId(toValue(step.operationId), this.#document, scope);
|
|
241
244
|
}
|
|
242
245
|
if (isStringElement(step.operationPath)) {
|
|
243
|
-
return this.#locatorNormalizer.normalizeOperationPath(toValue(step.operationPath), this.#document);
|
|
246
|
+
return this.#locatorNormalizer.normalizeOperationPath(toValue(step.operationPath), this.#document, scope);
|
|
244
247
|
}
|
|
245
|
-
throw new ExecutionError(`Step "${stepId}" has no operationId or operationPath`, {
|
|
246
|
-
|
|
248
|
+
throw new ExecutionError(`Step "${scope.stepId}" has no operationId or operationPath`, {
|
|
249
|
+
...scope,
|
|
247
250
|
reason: 'missing-operation'
|
|
248
251
|
});
|
|
249
252
|
}
|
|
@@ -297,10 +300,12 @@ class StepExecutor {
|
|
|
297
300
|
* Public for the same reason as {@link StepExecutor.selectActions}: a step
|
|
298
301
|
* targeting a `workflowId` is run by the workflow executor and never reaches
|
|
299
302
|
* {@link StepExecutor.execute}, but its criteria are still the author's
|
|
300
|
-
* assertion about the step and must be evaluated somewhere.
|
|
303
|
+
* assertion about the step and must be evaluated somewhere. `workflowId`
|
|
304
|
+
* names the step's workflow on a thrown error, as for
|
|
305
|
+
* {@link StepExecutor.execute}.
|
|
301
306
|
*/
|
|
302
|
-
evaluateCriteria(step, context) {
|
|
303
|
-
this.assertSuccessCriteria(step);
|
|
307
|
+
evaluateCriteria(step, context, workflowId) {
|
|
308
|
+
this.assertSuccessCriteria(step, workflowId);
|
|
304
309
|
const criteria = step.successCriteria;
|
|
305
310
|
if (criteria === undefined) return [];
|
|
306
311
|
|
|
@@ -328,8 +333,10 @@ class StepExecutor {
|
|
|
328
333
|
* it runs itself and which therefore never reaches
|
|
329
334
|
* {@link StepExecutor.execute}. Both callers reach the criteria evaluator and
|
|
330
335
|
* the expression context through this one method rather than wiring their own.
|
|
336
|
+
* `workflowId` names the step's workflow on a thrown error, as for
|
|
337
|
+
* {@link StepExecutor.execute}.
|
|
331
338
|
*/
|
|
332
|
-
selectActions(step, successful, context) {
|
|
339
|
+
selectActions(step, successful, context, workflowId) {
|
|
333
340
|
const actionCriteria = [];
|
|
334
341
|
const isCriterionMet = (criterion, action) => {
|
|
335
342
|
const record = {
|
|
@@ -340,6 +347,9 @@ class StepExecutor {
|
|
|
340
347
|
return record.met;
|
|
341
348
|
};
|
|
342
349
|
const scope = {
|
|
350
|
+
...(workflowId === undefined ? {} : {
|
|
351
|
+
workflowId
|
|
352
|
+
}),
|
|
343
353
|
stepId: toValue(step.stepId)
|
|
344
354
|
};
|
|
345
355
|
const matchedActions = successful ? this.#actionResolver.resolveOnSuccess(step.onSuccess, isCriterionMet, scope) : this.#actionResolver.resolveOnFailure(step.onFailure, isCriterionMet, scope);
|
|
@@ -810,10 +810,11 @@ class WorkflowExecutor {
|
|
|
810
810
|
callStack
|
|
811
811
|
} = invocation;
|
|
812
812
|
if (!(0, _apidomDatamodel.isStringElement)(step.workflowId)) {
|
|
813
|
-
return () => this.#stepExecutor.forDocument(document).execute(step, state, scope.executeOptions);
|
|
813
|
+
return () => this.#stepExecutor.forDocument(document).execute(step, state, scope.executeOptions, workflowId);
|
|
814
814
|
}
|
|
815
815
|
const reference = this.#subWorkflowReference(step, stepId, workflowId);
|
|
816
816
|
const stepScope = {
|
|
817
|
+
workflowId,
|
|
817
818
|
stepId
|
|
818
819
|
};
|
|
819
820
|
// the reference resolves to the same locator on every attempt, so it is
|
|
@@ -891,12 +892,12 @@ class WorkflowExecutor {
|
|
|
891
892
|
// evaluated by the parent document's step executor — only the sub-run
|
|
892
893
|
// itself switched documents.
|
|
893
894
|
const stepExecutor = this.#stepExecutor.forDocument(document);
|
|
894
|
-
const successCriteria = stepExecutor.evaluateCriteria(step, context);
|
|
895
|
+
const successCriteria = stepExecutor.evaluateCriteria(step, context, workflowId);
|
|
895
896
|
const successful = !this.#settledFailed(result) && successCriteria.every(criterion => criterion.met);
|
|
896
897
|
const {
|
|
897
898
|
matchedActions,
|
|
898
899
|
actionCriteria
|
|
899
|
-
} = stepExecutor.selectActions(step, successful, context);
|
|
900
|
+
} = stepExecutor.selectActions(step, successful, context, workflowId);
|
|
900
901
|
return {
|
|
901
902
|
stepId,
|
|
902
903
|
successful,
|
|
@@ -796,10 +796,11 @@ class WorkflowExecutor {
|
|
|
796
796
|
callStack
|
|
797
797
|
} = invocation;
|
|
798
798
|
if (!isStringElement(step.workflowId)) {
|
|
799
|
-
return () => this.#stepExecutor.forDocument(document).execute(step, state, scope.executeOptions);
|
|
799
|
+
return () => this.#stepExecutor.forDocument(document).execute(step, state, scope.executeOptions, workflowId);
|
|
800
800
|
}
|
|
801
801
|
const reference = this.#subWorkflowReference(step, stepId, workflowId);
|
|
802
802
|
const stepScope = {
|
|
803
|
+
workflowId,
|
|
803
804
|
stepId
|
|
804
805
|
};
|
|
805
806
|
// the reference resolves to the same locator on every attempt, so it is
|
|
@@ -877,12 +878,12 @@ class WorkflowExecutor {
|
|
|
877
878
|
// evaluated by the parent document's step executor — only the sub-run
|
|
878
879
|
// itself switched documents.
|
|
879
880
|
const stepExecutor = this.#stepExecutor.forDocument(document);
|
|
880
|
-
const successCriteria = stepExecutor.evaluateCriteria(step, context);
|
|
881
|
+
const successCriteria = stepExecutor.evaluateCriteria(step, context, workflowId);
|
|
881
882
|
const successful = !this.#settledFailed(result) && successCriteria.every(criterion => criterion.met);
|
|
882
883
|
const {
|
|
883
884
|
matchedActions,
|
|
884
885
|
actionCriteria
|
|
885
|
-
} = stepExecutor.selectActions(step, successful, context);
|
|
886
|
+
} = stepExecutor.selectActions(step, successful, context, workflowId);
|
|
886
887
|
return {
|
|
887
888
|
stepId,
|
|
888
889
|
successful,
|
package/types/runner.d.ts
CHANGED
|
@@ -1254,7 +1254,7 @@ export declare interface OpenAPIOperationLocator {
|
|
|
1254
1254
|
* `$sourceDescriptions.{name}.{operationId}`. Per the Arazzo Specification, a
|
|
1255
1255
|
* plain name is only valid when a single (non-arazzo) source description is
|
|
1256
1256
|
* defined; multiple sources require the expression form. A plain name is
|
|
1257
|
-
* therefore
|
|
1257
|
+
* therefore looked up in the first OpenAPI source description only.
|
|
1258
1258
|
* - `operationPath` embeds a `{$sourceDescriptions.{name}.url}` runtime
|
|
1259
1259
|
* expression followed by a JSON Pointer to the operation.
|
|
1260
1260
|
*
|
|
@@ -1262,6 +1262,14 @@ export declare interface OpenAPIOperationLocator {
|
|
|
1262
1262
|
* or wrong); a source is classified by what it actually parses to. The
|
|
1263
1263
|
* normalizer acquires the referenced source documents from the registry on
|
|
1264
1264
|
* demand.
|
|
1265
|
+
*
|
|
1266
|
+
* A reference that cannot be resolved throws {@link ExecutionError}, attributed
|
|
1267
|
+
* to the referencing step when a {@link StepScope} is given:
|
|
1268
|
+
* `operation-not-found` when no operation answers it,
|
|
1269
|
+
* `source-description-not-found` for a source name the document does not
|
|
1270
|
+
* define, `source-description-not-openapi` for a source that is not an OpenAPI
|
|
1271
|
+
* document, and `invalid-operation-reference` for a reference malformed as
|
|
1272
|
+
* written. Every returned locator names an existing operation.
|
|
1265
1273
|
* @public
|
|
1266
1274
|
*/
|
|
1267
1275
|
export declare class OpenAPIOperationLocatorNormalizer extends SourceDescriptionsLocatorNormalizer {
|
|
@@ -1270,10 +1278,10 @@ export declare class OpenAPIOperationLocatorNormalizer extends SourceDescription
|
|
|
1270
1278
|
* Normalizes a plain or expression `operationId` to a canonical locator.
|
|
1271
1279
|
*
|
|
1272
1280
|
* A whole runtime expression (`$sourceDescriptions.{name}.{operationId}`) is
|
|
1273
|
-
* resolved against its named source; a plain name is
|
|
1274
|
-
*
|
|
1281
|
+
* resolved against its named source; a plain name is looked up in the first
|
|
1282
|
+
* OpenAPI source description only.
|
|
1275
1283
|
*/
|
|
1276
|
-
normalizeOperationId(operationId: string, document: ArazzoDocument): Promise<OpenAPIOperationLocator>;
|
|
1284
|
+
normalizeOperationId(operationId: string, document: ArazzoDocument, scope?: StepScope): Promise<OpenAPIOperationLocator>;
|
|
1277
1285
|
/**
|
|
1278
1286
|
* Normalizes an `operationPath` —
|
|
1279
1287
|
* `{$sourceDescriptions.{name}.url}#/json/pointer` — to a canonical locator.
|
|
@@ -1287,10 +1295,12 @@ export declare class OpenAPIOperationLocatorNormalizer extends SourceDescription
|
|
|
1287
1295
|
* The runner only needs a URL and a JSON Pointer, so it does not enforce that
|
|
1288
1296
|
* the source description is identified by a runtime expression rather than a
|
|
1289
1297
|
* literal URL (a literal URL interpolates to itself and still resolves) — that
|
|
1290
|
-
* is a conformance concern for the validator.
|
|
1291
|
-
* not
|
|
1298
|
+
* is a conformance concern for the validator. A source name the document does
|
|
1299
|
+
* not define is detected before interpolation, so it is reported as such
|
|
1300
|
+
* rather than as the empty URL it would interpolate to; the JSON Pointer must
|
|
1301
|
+
* locate an operation in the acquired OpenAPI document.
|
|
1292
1302
|
*/
|
|
1293
|
-
normalizeOperationPath(operationPath: string, document: ArazzoDocument): Promise<OpenAPIOperationLocator>;
|
|
1303
|
+
normalizeOperationPath(operationPath: string, document: ArazzoDocument, scope?: StepScope): Promise<OpenAPIOperationLocator>;
|
|
1294
1304
|
}
|
|
1295
1305
|
|
|
1296
1306
|
/**
|
|
@@ -2049,8 +2059,12 @@ export declare class StepExecutor {
|
|
|
2049
2059
|
* request nobody is waiting for, whose wire-level cancellation would surface
|
|
2050
2060
|
* as a transport {@link ClientError} — a request that failed on its own terms
|
|
2051
2061
|
* — rather than as the withdrawal it is.
|
|
2062
|
+
*
|
|
2063
|
+
* `workflowId` names the workflow the step belongs to; every error raised
|
|
2064
|
+
* here then carries it alongside the `stepId`, since step ids are only
|
|
2065
|
+
* unique within a workflow. The workflow executor always passes it.
|
|
2052
2066
|
*/
|
|
2053
|
-
execute(step: StepElement, state: ContextSource, executeOptions?: Record<string, unknown
|
|
2067
|
+
execute(step: StepElement, state: ContextSource, executeOptions?: Record<string, unknown>, workflowId?: string): Promise<StepExecutionResult>;
|
|
2054
2068
|
/**
|
|
2055
2069
|
* Throws {@link ResolverError} unless the step's `successCriteria` is absent
|
|
2056
2070
|
* or a list of Criterion Objects: `malformed-criteria` for one that is
|
|
@@ -2077,9 +2091,11 @@ export declare class StepExecutor {
|
|
|
2077
2091
|
* Public for the same reason as {@link StepExecutor.selectActions}: a step
|
|
2078
2092
|
* targeting a `workflowId` is run by the workflow executor and never reaches
|
|
2079
2093
|
* {@link StepExecutor.execute}, but its criteria are still the author's
|
|
2080
|
-
* assertion about the step and must be evaluated somewhere.
|
|
2094
|
+
* assertion about the step and must be evaluated somewhere. `workflowId`
|
|
2095
|
+
* names the step's workflow on a thrown error, as for
|
|
2096
|
+
* {@link StepExecutor.execute}.
|
|
2081
2097
|
*/
|
|
2082
|
-
evaluateCriteria(step: StepElement, context: RuntimeExpressionContext): CriterionEvaluationRecord[];
|
|
2098
|
+
evaluateCriteria(step: StepElement, context: RuntimeExpressionContext, workflowId?: string): CriterionEvaluationRecord[];
|
|
2083
2099
|
/**
|
|
2084
2100
|
* Selects every matching `onSuccess` / `onFailure` action for the outcome, in
|
|
2085
2101
|
* list order, gating each action's criteria against the context, and records
|
|
@@ -2100,8 +2116,10 @@ export declare class StepExecutor {
|
|
|
2100
2116
|
* it runs itself and which therefore never reaches
|
|
2101
2117
|
* {@link StepExecutor.execute}. Both callers reach the criteria evaluator and
|
|
2102
2118
|
* the expression context through this one method rather than wiring their own.
|
|
2119
|
+
* `workflowId` names the step's workflow on a thrown error, as for
|
|
2120
|
+
* {@link StepExecutor.execute}.
|
|
2103
2121
|
*/
|
|
2104
|
-
selectActions(step: StepElement, successful: boolean, context: RuntimeExpressionContext): Pick<StepExecutionResult, 'matchedActions' | 'actionCriteria'>;
|
|
2122
|
+
selectActions(step: StepElement, successful: boolean, context: RuntimeExpressionContext, workflowId?: string): Pick<StepExecutionResult, 'matchedActions' | 'actionCriteria'>;
|
|
2105
2123
|
}
|
|
2106
2124
|
|
|
2107
2125
|
/**
|
|
@@ -2210,14 +2228,21 @@ export declare interface StepRunRecord {
|
|
|
2210
2228
|
}
|
|
2211
2229
|
|
|
2212
2230
|
/**
|
|
2213
|
-
* Identifies the step
|
|
2214
|
-
*
|
|
2215
|
-
*
|
|
2231
|
+
* Identifies the step something is being resolved or located for, so a thrown
|
|
2232
|
+
* error can say which one it came from. Every resolver that only ever runs
|
|
2233
|
+
* within a step's scope — {@link OpenAPIOperationParameterResolver},
|
|
2216
2234
|
* {@link WorkflowParameterResolver}, {@link RequestBodyResolver},
|
|
2217
|
-
* {@link ActionResolver} — takes this
|
|
2235
|
+
* {@link ActionResolver} — takes this, as does
|
|
2236
|
+
* {@link OpenAPIOperationLocatorNormalizer} for the step whose operation it
|
|
2237
|
+
* locates.
|
|
2218
2238
|
* @public
|
|
2219
2239
|
*/
|
|
2220
2240
|
export declare interface StepScope {
|
|
2241
|
+
/**
|
|
2242
|
+
* The workflow the step belongs to, when the caller knows it: step ids are
|
|
2243
|
+
* only unique within a workflow.
|
|
2244
|
+
*/
|
|
2245
|
+
readonly workflowId?: string;
|
|
2221
2246
|
readonly stepId: string;
|
|
2222
2247
|
}
|
|
2223
2248
|
|