@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.
@@ -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
- stepId,
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
- stepId,
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, stepId);
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, stepId) {
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
- stepId,
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 resolved against the first source description that has it.
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 resolved against the
1274
- * first source description that has it.
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. It fails only when the URL does
1291
- * not resolve to an OpenAPI source or the JSON Pointer is invalid.
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>): Promise<StepExecutionResult>;
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 a resolver is resolving for, so a thrown
2214
- * {@link ResolverError} can say which one it came from. Every resolver that
2215
- * only ever runs within a step's scope — {@link OpenAPIOperationParameterResolver},
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