@temporal-contract/worker 2.0.0 → 2.1.0

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/dist/workflow.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { _ as formatChildWorkflowValidationMessage, a as ActivityInputValidationError, c as ChildWorkflowNotFoundError, d as SignalInputValidationError, f as UpdateInputValidationError, g as WorkflowOutputValidationError, h as WorkflowInputValidationError, l as QueryInputValidationError, m as WorkflowCancelledError, n as createContinueAsNew, o as ActivityOutputValidationError, p as UpdateOutputValidationError, r as extractHandlerInput, s as ChildWorkflowError, t as buildRawActivitiesProxy, u as QueryOutputValidationError } from "./internal--45IXCxX.mjs";
1
+ import { _ as UpdateOutputValidationError, a as formatChildWorkflowValidationMessage, b as WorkflowOutputValidationError, c as ActivityInputValidationError, d as ChildWorkflowError, f as ChildWorkflowNotFoundError, g as UpdateInputValidationError, h as SignalInputValidationError, i as extractHandlerInput, l as ActivityOutputValidationError, m as QueryOutputValidationError, n as classifyChildWorkflowError, o as makeResultAsync, p as QueryInputValidationError, r as createContinueAsNew, t as buildRawActivitiesProxy, u as ChildWorkflowCancelledError, v as WorkflowCancelledError, x as WorkflowScopeError, y as WorkflowInputValidationError } from "./internal-BzG1KhEK.mjs";
2
2
  import { CancellationScope, defineQuery, defineSignal, defineUpdate, executeChild, isCancellation, setHandler, startChild, workflowInfo } from "@temporalio/workflow";
3
- import { ResultAsync, err, ok } from "neverthrow";
3
+ import { err, ok } from "neverthrow";
4
4
  //#region src/cancellation.ts
5
5
  /**
6
6
  * Typed wrappers around Temporal's `CancellationScope` so workflows can
@@ -10,9 +10,11 @@ import { ResultAsync, err, ok } from "neverthrow";
10
10
  * context — callers branch on `err(WorkflowCancelledError)` instead of
11
11
  * catching `CancelledFailure`.
12
12
  *
13
- * Non-cancellation errors thrown inside the scope are *not* swallowed:
14
- * the ResultAsync rejects with the original error so user-domain failures
15
- * keep their identity.
13
+ * Non-cancellation errors thrown inside the scope are wrapped in a
14
+ * {@link WorkflowScopeError} (with the original error preserved on
15
+ * `cause`) and surfaced on the same `err(...)` channel. Together with
16
+ * `WorkflowCancelledError` this makes the failure modes exhaustive on
17
+ * `result.match(...)` — nothing escapes as an unhandled rejection.
16
18
  */
17
19
  /**
18
20
  * Run `fn` inside a cancellable Temporal scope. If the workflow (or an
@@ -21,6 +23,11 @@ import { ResultAsync, err, ok } from "neverthrow";
21
23
  * letting callers handle cancellation explicitly — typically to perform
22
24
  * a graceful exit from the current step.
23
25
  *
26
+ * Non-cancellation errors thrown by `fn` resolve to
27
+ * `err(WorkflowScopeError)` (with the original error on `cause`) so
28
+ * domain failures surface on the same typed error channel rather than
29
+ * leaking as unhandled rejections.
30
+ *
24
31
  * @example
25
32
  * ```ts
26
33
  * const result = await context.cancellableScope(async () => {
@@ -29,8 +36,12 @@ import { ResultAsync, err, ok } from "neverthrow";
29
36
  *
30
37
  * result.match(
31
38
  * (output) => { ... },
32
- * (cancelled) => {
33
- * // cancelled instanceof WorkflowCancelledError — graceful exit
39
+ * (error) => {
40
+ * if (error instanceof WorkflowCancelledError) {
41
+ * // graceful exit
42
+ * } else {
43
+ * // error instanceof WorkflowScopeError — domain failure on `cause`
44
+ * }
34
45
  * },
35
46
  * );
36
47
  * ```
@@ -41,10 +52,10 @@ function cancellableScope(fn) {
41
52
  return ok(await CancellationScope.cancellable(async () => fn()));
42
53
  } catch (error) {
43
54
  if (isCancellation(error)) return err(new WorkflowCancelledError(error));
44
- throw error;
55
+ return err(new WorkflowScopeError(error));
45
56
  }
46
57
  };
47
- return new ResultAsync(work());
58
+ return makeResultAsync(work, (e) => new WorkflowScopeError(e));
48
59
  }
49
60
  /**
50
61
  * Run `fn` inside a non-cancellable Temporal scope. Cancellation requests
@@ -52,9 +63,10 @@ function cancellableScope(fn) {
52
63
  * to perform cleanup that must not be interrupted (e.g. releasing a
53
64
  * resource after a graceful shutdown).
54
65
  *
55
- * Mirrors `cancellableScope`'s `ResultAsync<...>` shape for symmetry;
56
- * the `err(...)` branch only triggers when cancellation is raised from
57
- * inside the scope (rare).
66
+ * Mirrors `cancellableScope`'s `ResultAsync<...>` shape for symmetry; the
67
+ * `err(WorkflowCancelledError)` branch only triggers when cancellation is
68
+ * raised from inside the scope (rare). Non-cancellation errors surface as
69
+ * `err(WorkflowScopeError)`.
58
70
  *
59
71
  * @example
60
72
  * ```ts
@@ -69,10 +81,10 @@ function nonCancellableScope(fn) {
69
81
  return ok(await CancellationScope.nonCancellable(async () => fn()));
70
82
  } catch (error) {
71
83
  if (isCancellation(error)) return err(new WorkflowCancelledError(error));
72
- throw error;
84
+ return err(new WorkflowScopeError(error));
73
85
  }
74
86
  };
75
- return new ResultAsync(work());
87
+ return makeResultAsync(work, (e) => new WorkflowScopeError(e));
76
88
  }
77
89
  //#endregion
78
90
  //#region src/handlers.ts
@@ -124,8 +136,30 @@ function bindQueryHandler(workflowDefinition, workflowName, queryName, handler)
124
136
  });
125
137
  }
126
138
  /**
127
- * Bind a typed update handler to the running workflow. Validates both
128
- * input and output (asynchronously, like signals).
139
+ * Bind a typed update handler to the running workflow.
140
+ *
141
+ * Input validation runs in Temporal's `validator` slot — a synchronous
142
+ * pre-admission hook. If it throws, Temporal rejects the update *before*
143
+ * appending a workflow history event: clients see
144
+ * `WorkflowUpdateValidationRejectedError` and the workflow's history is
145
+ * unaffected. This is the documented contract for `setHandler`'s
146
+ * `validator` option, and it is strictly better than running validation
147
+ * inside the handler body — which forces Temporal to admit the update,
148
+ * write a history event, and surface a `WorkflowUpdateFailedError` to
149
+ * the client only after the fact.
150
+ *
151
+ * Because the validator slot is synchronous, the input schema must also
152
+ * validate synchronously. Standard Schema is allowed to be async (Zod's
153
+ * `.refine(async)` is the typical case), but we trip a clear error when
154
+ * that happens rather than silently breaking admission semantics — same
155
+ * approach as `bindQueryHandler`. Users who need async input checks
156
+ * should run them inside the handler body and accept the post-admission
157
+ * failure mode, or restructure their schema.
158
+ *
159
+ * Output validation continues to run inside the handler body. Update
160
+ * outputs are *not* admission-gated — the handler must execute to
161
+ * produce a value to validate against — so the async-allowed shape is
162
+ * preserved.
129
163
  */
130
164
  function bindUpdateHandler(workflowDefinition, workflowName, updateName, handler) {
131
165
  if (!workflowDefinition.updates) throw new Error(`Update "${updateName}" cannot be defined: workflow "${workflowName}" has no updates in its contract`);
@@ -133,14 +167,120 @@ function bindUpdateHandler(workflowDefinition, workflowName, updateName, handler
133
167
  if (!updateDef) throw new Error(`Update "${updateName}" not found in workflow "${workflowName}" contract`);
134
168
  setHandler(defineUpdate(updateName), async (...args) => {
135
169
  const input = extractHandlerInput(args);
136
- const inputResult = await updateDef.input["~standard"].validate(input);
170
+ const inputResult = updateDef.input["~standard"].validate(input);
171
+ if (inputResult instanceof Promise) throw new Error(`Update "${updateName}" input validation must be synchronous. Use a schema library that supports synchronous validation for update inputs (Temporal's update validator slot is synchronous).`);
137
172
  if (inputResult.issues) throw new UpdateInputValidationError(updateName, inputResult.issues);
138
173
  const result = await handler(inputResult.value);
139
174
  const outputResult = await updateDef.output["~standard"].validate(result);
140
175
  if (outputResult.issues) throw new UpdateOutputValidationError(updateName, outputResult.issues);
141
176
  return outputResult.value;
177
+ }, { validator: (...args) => {
178
+ const input = extractHandlerInput(args);
179
+ const inputResult = updateDef.input["~standard"].validate(input);
180
+ if (inputResult instanceof Promise) throw new Error(`Update "${updateName}" input validation must be synchronous. Use a schema library that supports synchronous validation for update inputs (Temporal's update validator slot is synchronous).`);
181
+ if (inputResult.issues) throw new UpdateInputValidationError(updateName, inputResult.issues);
182
+ } });
183
+ }
184
+ //#endregion
185
+ //#region src/child-workflow.ts
186
+ async function validateChildWorkflowOutput(childDefinition, result, childWorkflowName) {
187
+ const outputResult = await childDefinition.output["~standard"].validate(result);
188
+ if (outputResult.issues) return err(new ChildWorkflowError(formatChildWorkflowValidationMessage(childWorkflowName, "output", outputResult.issues)));
189
+ return ok(outputResult.value);
190
+ }
191
+ async function getAndValidateChildWorkflow(childContract, childWorkflowName, args) {
192
+ const childDefinition = childContract.workflows[childWorkflowName];
193
+ if (!childDefinition) return err(new ChildWorkflowNotFoundError(childWorkflowName, Object.keys(childContract.workflows)));
194
+ const inputResult = await childDefinition.input["~standard"].validate(args);
195
+ if (inputResult.issues) return err(new ChildWorkflowError(formatChildWorkflowValidationMessage(childWorkflowName, "input", inputResult.issues)));
196
+ const validatedInput = inputResult.value;
197
+ return ok({
198
+ definition: childDefinition,
199
+ validatedInput,
200
+ taskQueue: childContract.taskQueue
142
201
  });
143
202
  }
203
+ function createTypedChildHandle(handle, childDefinition, childWorkflowName) {
204
+ return {
205
+ workflowId: handle.workflowId,
206
+ result: () => {
207
+ const work = async () => {
208
+ try {
209
+ return validateChildWorkflowOutput(childDefinition, await handle.result(), childWorkflowName);
210
+ } catch (error) {
211
+ return err(classifyChildWorkflowError("result", error, childWorkflowName));
212
+ }
213
+ };
214
+ return makeResultAsync(work, (error) => new ChildWorkflowError(`Child workflow execution failed: ${error instanceof Error ? error.message : String(error)}`, error));
215
+ }
216
+ };
217
+ }
218
+ function createStartChildWorkflow(childContract, childWorkflowName, options) {
219
+ const work = async () => {
220
+ const validationResult = await getAndValidateChildWorkflow(childContract, childWorkflowName, options.args);
221
+ if (validationResult.isErr()) return err(validationResult.error);
222
+ const { definition: childDefinition, validatedInput, taskQueue } = validationResult.value;
223
+ try {
224
+ const { args: _args, ...temporalOptions } = options;
225
+ return ok(createTypedChildHandle(await startChild(childWorkflowName, {
226
+ ...temporalOptions,
227
+ taskQueue,
228
+ args: [validatedInput]
229
+ }), childDefinition, childWorkflowName));
230
+ } catch (error) {
231
+ return err(classifyChildWorkflowError("startChild", error, String(childWorkflowName)));
232
+ }
233
+ };
234
+ return makeResultAsync(work, (error) => new ChildWorkflowError(`Failed to start child workflow: ${error instanceof Error ? error.message : String(error)}`, error));
235
+ }
236
+ function createExecuteChildWorkflow(childContract, childWorkflowName, options) {
237
+ const work = async () => {
238
+ const validationResult = await getAndValidateChildWorkflow(childContract, childWorkflowName, options.args);
239
+ if (validationResult.isErr()) return err(validationResult.error);
240
+ const { definition: childDefinition, validatedInput, taskQueue } = validationResult.value;
241
+ try {
242
+ const { args: _args, ...temporalOptions } = options;
243
+ const outputValidationResult = await validateChildWorkflowOutput(childDefinition, await executeChild(childWorkflowName, {
244
+ ...temporalOptions,
245
+ taskQueue,
246
+ args: [validatedInput]
247
+ }), childWorkflowName);
248
+ if (outputValidationResult.isErr()) return err(outputValidationResult.error);
249
+ return ok(outputValidationResult.value);
250
+ } catch (error) {
251
+ return err(classifyChildWorkflowError("executeChild", error, String(childWorkflowName)));
252
+ }
253
+ };
254
+ return makeResultAsync(work, (error) => new ChildWorkflowError(`Failed to execute child workflow: ${error instanceof Error ? error.message : String(error)}`, error));
255
+ }
256
+ //#endregion
257
+ //#region src/activities-proxy.ts
258
+ /**
259
+ * Wrap the raw activities proxy with input/output validation against the
260
+ * Standard Schema definitions on the contract. The wrapper enforces data
261
+ * integrity at the workflow → activity boundary in addition to the
262
+ * activity-side validation that `declareActivitiesHandler` already runs.
263
+ */
264
+ function createValidatedActivities(rawActivities, workflowActivitiesDefinition, contractActivitiesDefinition) {
265
+ const validatedActivities = {};
266
+ const allActivitiesDefinition = {
267
+ ...contractActivitiesDefinition,
268
+ ...workflowActivitiesDefinition
269
+ };
270
+ for (const [activityName, activityDef] of Object.entries(allActivitiesDefinition)) {
271
+ const rawActivity = rawActivities[activityName];
272
+ if (!rawActivity) throw new Error(`Activity implementation not found for: "${activityName}". Available activities: ${Object.keys(rawActivities).length > 0 ? Object.keys(rawActivities).join(", ") : "none"}`);
273
+ validatedActivities[activityName] = async (input) => {
274
+ const inputResult = await activityDef.input["~standard"].validate(input);
275
+ if (inputResult.issues) throw new ActivityInputValidationError(activityName, inputResult.issues);
276
+ const result = await rawActivity(inputResult.value);
277
+ const outputResult = await activityDef.output["~standard"].validate(result);
278
+ if (outputResult.issues) throw new ActivityOutputValidationError(activityName, outputResult.issues);
279
+ return outputResult.value;
280
+ };
281
+ }
282
+ return validatedActivities;
283
+ }
144
284
  //#endregion
145
285
  //#region src/workflow.ts
146
286
  /**
@@ -218,13 +358,16 @@ function bindUpdateHandler(workflowDefinition, workflowName, updateName, handler
218
358
  */
219
359
  function declareWorkflow({ workflowName, contract, implementation, activityOptions, activityOptionsByName }) {
220
360
  const definition = contract.workflows[workflowName];
361
+ let contextActivities = {};
362
+ if (definition.activities || contract.activities) {
363
+ contextActivities = createValidatedActivities(buildRawActivitiesProxy(definition.activities, contract.activities, activityOptions, activityOptionsByName), definition.activities, contract.activities);
364
+ Object.freeze(contextActivities);
365
+ }
221
366
  return async (...args) => {
222
367
  const input = extractHandlerInput(args);
223
368
  const inputResult = await definition.input["~standard"].validate(input);
224
- if (inputResult.issues) throw new WorkflowInputValidationError(String(workflowName), inputResult.issues);
369
+ if (inputResult.issues) throw new WorkflowInputValidationError(workflowName, inputResult.issues);
225
370
  const validatedInput = inputResult.value;
226
- let contextActivities = {};
227
- if (definition.activities || contract.activities) contextActivities = createValidatedActivities(buildRawActivitiesProxy(definition.activities, contract.activities, activityOptions, activityOptionsByName), definition.activities, contract.activities);
228
371
  const result = await implementation({
229
372
  activities: contextActivities,
230
373
  info: workflowInfo(),
@@ -232,113 +375,17 @@ function declareWorkflow({ workflowName, contract, implementation, activityOptio
232
375
  executeChildWorkflow: createExecuteChildWorkflow,
233
376
  cancellableScope,
234
377
  nonCancellableScope,
235
- defineSignal: ((signalName, handler) => bindSignalHandler(definition, String(workflowName), signalName, handler)),
236
- defineQuery: ((queryName, handler) => bindQueryHandler(definition, String(workflowName), queryName, handler)),
237
- defineUpdate: ((updateName, handler) => bindUpdateHandler(definition, String(workflowName), updateName, handler)),
378
+ defineSignal: ((signalName, handler) => bindSignalHandler(definition, workflowName, signalName, handler)),
379
+ defineQuery: ((queryName, handler) => bindQueryHandler(definition, workflowName, queryName, handler)),
380
+ defineUpdate: ((updateName, handler) => bindUpdateHandler(definition, workflowName, updateName, handler)),
238
381
  continueAsNew: createContinueAsNew(contract, workflowName)
239
382
  }, validatedInput);
240
383
  const outputResult = await definition.output["~standard"].validate(result);
241
- if (outputResult.issues) throw new WorkflowOutputValidationError(String(workflowName), outputResult.issues);
384
+ if (outputResult.issues) throw new WorkflowOutputValidationError(workflowName, outputResult.issues);
242
385
  return outputResult.value;
243
386
  };
244
387
  }
245
- /**
246
- * Create a validated activities proxy that parses inputs and outputs
247
- *
248
- * This wrapper ensures data integrity across the network boundary between
249
- * workflow and activity execution.
250
- */
251
- function createValidatedActivities(rawActivities, workflowActivitiesDefinition, contractActivitiesDefinition) {
252
- const validatedActivities = {};
253
- const allActivitiesDefinition = {
254
- ...contractActivitiesDefinition,
255
- ...workflowActivitiesDefinition
256
- };
257
- for (const [activityName, activityDef] of Object.entries(allActivitiesDefinition)) {
258
- const rawActivity = rawActivities[activityName];
259
- if (!rawActivity) throw new Error(`Activity implementation not found for: "${activityName}". Available activities: ${Object.keys(rawActivities).length > 0 ? Object.keys(rawActivities).join(", ") : "none"}`);
260
- validatedActivities[activityName] = async (input) => {
261
- const inputResult = await activityDef.input["~standard"].validate(input);
262
- if (inputResult.issues) throw new ActivityInputValidationError(activityName, inputResult.issues);
263
- const result = await rawActivity(inputResult.value);
264
- const outputResult = await activityDef.output["~standard"].validate(result);
265
- if (outputResult.issues) throw new ActivityOutputValidationError(activityName, outputResult.issues);
266
- return outputResult.value;
267
- };
268
- }
269
- return validatedActivities;
270
- }
271
- async function validateChildWorkflowOutput(childDefinition, result, childWorkflowName) {
272
- const outputResult = await childDefinition.output["~standard"].validate(result);
273
- if (outputResult.issues) return err(new ChildWorkflowError(formatChildWorkflowValidationMessage(childWorkflowName, "output", outputResult.issues)));
274
- return ok(outputResult.value);
275
- }
276
- async function getAndValidateChildWorkflow(childContract, childWorkflowName, args) {
277
- const childDefinition = childContract.workflows[childWorkflowName];
278
- if (!childDefinition) return err(new ChildWorkflowNotFoundError(String(childWorkflowName), Object.keys(childContract.workflows)));
279
- const inputResult = await childDefinition.input["~standard"].validate(args);
280
- if (inputResult.issues) return err(new ChildWorkflowError(formatChildWorkflowValidationMessage(String(childWorkflowName), "input", inputResult.issues)));
281
- const validatedInput = inputResult.value;
282
- return ok({
283
- definition: childDefinition,
284
- validatedInput,
285
- taskQueue: childContract.taskQueue
286
- });
287
- }
288
- function createTypedChildHandle(handle, childDefinition, childWorkflowName) {
289
- return {
290
- workflowId: handle.workflowId,
291
- result: () => {
292
- const work = async () => {
293
- try {
294
- return validateChildWorkflowOutput(childDefinition, await handle.result(), childWorkflowName);
295
- } catch (error) {
296
- return err(new ChildWorkflowError(`Child workflow execution failed: ${error instanceof Error ? error.message : String(error)}`, error));
297
- }
298
- };
299
- return new ResultAsync(work());
300
- }
301
- };
302
- }
303
- function createStartChildWorkflow(childContract, childWorkflowName, options) {
304
- const work = async () => {
305
- const validationResult = await getAndValidateChildWorkflow(childContract, childWorkflowName, options.args);
306
- if (validationResult.isErr()) return err(validationResult.error);
307
- const { definition: childDefinition, validatedInput, taskQueue } = validationResult.value;
308
- try {
309
- const { args: _args, ...temporalOptions } = options;
310
- return ok(createTypedChildHandle(await startChild(childWorkflowName, {
311
- ...temporalOptions,
312
- taskQueue,
313
- args: [validatedInput]
314
- }), childDefinition, String(childWorkflowName)));
315
- } catch (error) {
316
- return err(new ChildWorkflowError(`Failed to start child workflow: ${error instanceof Error ? error.message : String(error)}`, error));
317
- }
318
- };
319
- return new ResultAsync(work());
320
- }
321
- function createExecuteChildWorkflow(childContract, childWorkflowName, options) {
322
- const work = async () => {
323
- const validationResult = await getAndValidateChildWorkflow(childContract, childWorkflowName, options.args);
324
- if (validationResult.isErr()) return err(validationResult.error);
325
- const { definition: childDefinition, validatedInput, taskQueue } = validationResult.value;
326
- try {
327
- const { args: _args, ...temporalOptions } = options;
328
- const outputValidationResult = await validateChildWorkflowOutput(childDefinition, await executeChild(childWorkflowName, {
329
- ...temporalOptions,
330
- taskQueue,
331
- args: [validatedInput]
332
- }), String(childWorkflowName));
333
- if (outputValidationResult.isErr()) return err(outputValidationResult.error);
334
- return ok(outputValidationResult.value);
335
- } catch (error) {
336
- return err(new ChildWorkflowError(`Failed to execute child workflow: ${error instanceof Error ? error.message : String(error)}`, error));
337
- }
338
- };
339
- return new ResultAsync(work());
340
- }
341
388
  //#endregion
342
- export { ActivityInputValidationError, ActivityOutputValidationError, ChildWorkflowError, ChildWorkflowNotFoundError, QueryInputValidationError, QueryOutputValidationError, SignalInputValidationError, UpdateInputValidationError, UpdateOutputValidationError, WorkflowCancelledError, WorkflowInputValidationError, WorkflowOutputValidationError, declareWorkflow };
389
+ export { ActivityInputValidationError, ActivityOutputValidationError, ChildWorkflowCancelledError, ChildWorkflowError, ChildWorkflowNotFoundError, QueryInputValidationError, QueryOutputValidationError, SignalInputValidationError, UpdateInputValidationError, UpdateOutputValidationError, WorkflowCancelledError, WorkflowInputValidationError, WorkflowOutputValidationError, WorkflowScopeError, declareWorkflow };
343
390
 
344
391
  //# sourceMappingURL=workflow.mjs.map