workflow 5.0.0-beta.34 → 5.0.0-beta.36

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 (71) hide show
  1. package/dist/internal/errors.d.ts +1 -1
  2. package/dist/internal/errors.d.ts.map +1 -1
  3. package/dist/internal/errors.js +2 -2
  4. package/dist/nest-builder.d.ts +2 -0
  5. package/dist/nest-builder.d.ts.map +1 -0
  6. package/dist/nest-builder.js +2 -0
  7. package/dist/nest-vercel-builder.d.ts +2 -0
  8. package/dist/nest-vercel-builder.d.ts.map +1 -0
  9. package/dist/nest-vercel-builder.js +2 -0
  10. package/docs/api-reference/workflow/create-hook.mdx +43 -2
  11. package/docs/api-reference/workflow/define-hook.mdx +26 -24
  12. package/docs/api-reference/workflow/fatal-error.mdx +29 -7
  13. package/docs/api-reference/workflow/fetch.mdx +3 -4
  14. package/docs/api-reference/workflow/sleep.mdx +1 -1
  15. package/docs/api-reference/workflow-api/get-hook-by-token.mdx +2 -0
  16. package/docs/api-reference/workflow-api/resume-hook.mdx +2 -0
  17. package/docs/api-reference/workflow-api/resume-webhook.mdx +6 -4
  18. package/docs/api-reference/workflow-api/start.mdx +1 -1
  19. package/docs/api-reference/workflow-errors/index.mdx +3 -0
  20. package/docs/api-reference/workflow-errors/meta.json +1 -0
  21. package/docs/api-reference/workflow-errors/precondition-failed-error.mdx +62 -0
  22. package/docs/api-reference/workflow-nest/nest-local-builder.mdx +1 -1
  23. package/docs/comparisons/index.mdx +66 -0
  24. package/docs/comparisons/meta.json +11 -0
  25. package/docs/comparisons/workflow-sdk-vs-aws-agentcore.mdx +55 -0
  26. package/docs/comparisons/workflow-sdk-vs-aws-step-functions.mdx +111 -0
  27. package/docs/comparisons/workflow-sdk-vs-cloudflare-workflows.mdx +71 -0
  28. package/docs/comparisons/workflow-sdk-vs-inngest.mdx +102 -0
  29. package/docs/comparisons/workflow-sdk-vs-temporal.mdx +123 -0
  30. package/docs/comparisons/workflow-sdk-vs-trigger-dev.mdx +103 -0
  31. package/docs/configuration/runtime-tuning.mdx +26 -0
  32. package/docs/configuration/worlds.mdx +6 -7
  33. package/docs/cookbook/common-patterns/idempotency.mdx +1 -1
  34. package/docs/{deploying/index.mdx → deploying.mdx} +6 -8
  35. package/docs/foundations/hooks.mdx +1 -1
  36. package/docs/foundations/idempotency.mdx +16 -9
  37. package/docs/getting-started/astro.mdx +1 -1
  38. package/docs/getting-started/express.mdx +1 -1
  39. package/docs/getting-started/fastify.mdx +1 -1
  40. package/docs/getting-started/hono.mdx +1 -1
  41. package/docs/getting-started/index.mdx +10 -3
  42. package/docs/getting-started/meta.json +3 -1
  43. package/docs/getting-started/nestjs.mdx +63 -2
  44. package/docs/getting-started/next.mdx +2 -2
  45. package/docs/getting-started/nitro.mdx +1 -1
  46. package/docs/getting-started/nuxt.mdx +1 -1
  47. package/docs/getting-started/python.mdx +19 -17
  48. package/docs/getting-started/react-router/index.mdx +33 -0
  49. package/docs/getting-started/react-router/meta.json +5 -0
  50. package/docs/getting-started/react-router/v7.mdx +237 -0
  51. package/docs/getting-started/react-router/v8.mdx +232 -0
  52. package/docs/getting-started/sveltekit.mdx +1 -1
  53. package/docs/getting-started/vite.mdx +1 -1
  54. package/docs/how-it-works/encryption.mdx +3 -3
  55. package/docs/how-it-works/event-sourcing.mdx +6 -6
  56. package/docs/how-it-works/framework-integrations.mdx +3 -3
  57. package/docs/meta.json +1 -1
  58. package/docs/observability/attributes.mdx +9 -0
  59. package/package.json +13 -11
  60. package/docs/deploying/building-a-world.mdx +0 -251
  61. package/docs/deploying/meta.json +0 -4
  62. package/docs/deploying/world/local-world.mdx +0 -101
  63. package/docs/deploying/world/meta.json +0 -4
  64. package/docs/deploying/world/postgres-world.mdx +0 -288
  65. package/docs/deploying/world/vercel-world.mdx +0 -275
  66. package/docs/migration-guides/index.mdx +0 -34
  67. package/docs/migration-guides/meta.json +0 -9
  68. package/docs/migration-guides/migrating-from-aws-step-functions.mdx +0 -362
  69. package/docs/migration-guides/migrating-from-inngest.mdx +0 -308
  70. package/docs/migration-guides/migrating-from-temporal.mdx +0 -317
  71. package/docs/migration-guides/migrating-from-trigger-dev.mdx +0 -332
@@ -1,2 +1,2 @@
1
- export { EntityConflictError, HookConflictError, HookNotFoundError, RunExpiredError, RunNotSupportedError, StepNotRegisteredError, ThrottleError, TooEarlyError, WorkflowError, WorkflowNotRegisteredError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRunNotFoundError, WorkflowRuntimeError, WorkflowWorldError, } from '@workflow/errors';
1
+ export { EntityConflictError, HookConflictError, HookNotFoundError, PreconditionFailedError, RunExpiredError, RunNotSupportedError, StepNotRegisteredError, ThrottleError, TooEarlyError, WorkflowError, WorkflowNotRegisteredError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRunNotFoundError, WorkflowRuntimeError, WorkflowWorldError, } from '@workflow/errors';
2
2
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/internal/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,aAAa,EACb,aAAa,EACb,aAAa,EACb,0BAA0B,EAC1B,yBAAyB,EACzB,sBAAsB,EACtB,4BAA4B,EAC5B,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/internal/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,uBAAuB,EACvB,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,aAAa,EACb,aAAa,EACb,aAAa,EACb,0BAA0B,EAC1B,yBAAyB,EACzB,sBAAsB,EACtB,4BAA4B,EAC5B,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC"}
@@ -1,2 +1,2 @@
1
- export { EntityConflictError, HookConflictError, HookNotFoundError, RunExpiredError, RunNotSupportedError, StepNotRegisteredError, ThrottleError, TooEarlyError, WorkflowError, WorkflowNotRegisteredError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRunNotFoundError, WorkflowRuntimeError, WorkflowWorldError, } from '@workflow/errors';
2
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3JzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2ludGVybmFsL2Vycm9ycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsbUJBQW1CLEVBQ25CLGlCQUFpQixFQUNqQixpQkFBaUIsRUFDakIsZUFBZSxFQUNmLG9CQUFvQixFQUNwQixzQkFBc0IsRUFDdEIsYUFBYSxFQUNiLGFBQWEsRUFDYixhQUFhLEVBQ2IsMEJBQTBCLEVBQzFCLHlCQUF5QixFQUN6QixzQkFBc0IsRUFDdEIsNEJBQTRCLEVBQzVCLHdCQUF3QixFQUN4QixvQkFBb0IsRUFDcEIsa0JBQWtCLEdBQ25CLE1BQU0sa0JBQWtCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQge1xuICBFbnRpdHlDb25mbGljdEVycm9yLFxuICBIb29rQ29uZmxpY3RFcnJvcixcbiAgSG9va05vdEZvdW5kRXJyb3IsXG4gIFJ1bkV4cGlyZWRFcnJvcixcbiAgUnVuTm90U3VwcG9ydGVkRXJyb3IsXG4gIFN0ZXBOb3RSZWdpc3RlcmVkRXJyb3IsXG4gIFRocm90dGxlRXJyb3IsXG4gIFRvb0Vhcmx5RXJyb3IsXG4gIFdvcmtmbG93RXJyb3IsXG4gIFdvcmtmbG93Tm90UmVnaXN0ZXJlZEVycm9yLFxuICBXb3JrZmxvd1J1bkNhbmNlbGxlZEVycm9yLFxuICBXb3JrZmxvd1J1bkZhaWxlZEVycm9yLFxuICBXb3JrZmxvd1J1bk5vdENvbXBsZXRlZEVycm9yLFxuICBXb3JrZmxvd1J1bk5vdEZvdW5kRXJyb3IsXG4gIFdvcmtmbG93UnVudGltZUVycm9yLFxuICBXb3JrZmxvd1dvcmxkRXJyb3IsXG59IGZyb20gJ0B3b3JrZmxvdy9lcnJvcnMnO1xuIl19
1
+ export { EntityConflictError, HookConflictError, HookNotFoundError, PreconditionFailedError, RunExpiredError, RunNotSupportedError, StepNotRegisteredError, ThrottleError, TooEarlyError, WorkflowError, WorkflowNotRegisteredError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRunNotFoundError, WorkflowRuntimeError, WorkflowWorldError, } from '@workflow/errors';
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3JzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2ludGVybmFsL2Vycm9ycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsbUJBQW1CLEVBQ25CLGlCQUFpQixFQUNqQixpQkFBaUIsRUFDakIsdUJBQXVCLEVBQ3ZCLGVBQWUsRUFDZixvQkFBb0IsRUFDcEIsc0JBQXNCLEVBQ3RCLGFBQWEsRUFDYixhQUFhLEVBQ2IsYUFBYSxFQUNiLDBCQUEwQixFQUMxQix5QkFBeUIsRUFDekIsc0JBQXNCLEVBQ3RCLDRCQUE0QixFQUM1Qix3QkFBd0IsRUFDeEIsb0JBQW9CLEVBQ3BCLGtCQUFrQixHQUNuQixNQUFNLGtCQUFrQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IHtcbiAgRW50aXR5Q29uZmxpY3RFcnJvcixcbiAgSG9va0NvbmZsaWN0RXJyb3IsXG4gIEhvb2tOb3RGb3VuZEVycm9yLFxuICBQcmVjb25kaXRpb25GYWlsZWRFcnJvcixcbiAgUnVuRXhwaXJlZEVycm9yLFxuICBSdW5Ob3RTdXBwb3J0ZWRFcnJvcixcbiAgU3RlcE5vdFJlZ2lzdGVyZWRFcnJvcixcbiAgVGhyb3R0bGVFcnJvcixcbiAgVG9vRWFybHlFcnJvcixcbiAgV29ya2Zsb3dFcnJvcixcbiAgV29ya2Zsb3dOb3RSZWdpc3RlcmVkRXJyb3IsXG4gIFdvcmtmbG93UnVuQ2FuY2VsbGVkRXJyb3IsXG4gIFdvcmtmbG93UnVuRmFpbGVkRXJyb3IsXG4gIFdvcmtmbG93UnVuTm90Q29tcGxldGVkRXJyb3IsXG4gIFdvcmtmbG93UnVuTm90Rm91bmRFcnJvcixcbiAgV29ya2Zsb3dSdW50aW1lRXJyb3IsXG4gIFdvcmtmbG93V29ybGRFcnJvcixcbn0gZnJvbSAnQHdvcmtmbG93L2Vycm9ycyc7XG4iXX0=
@@ -0,0 +1,2 @@
1
+ export * from '@workflow/nest/builder';
2
+ //# sourceMappingURL=nest-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nest-builder.d.ts","sourceRoot":"","sources":["../src/nest-builder.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from '@workflow/nest/builder';
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmVzdC1idWlsZGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL25lc3QtYnVpbGRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLHdCQUF3QixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0ICogZnJvbSAnQHdvcmtmbG93L25lc3QvYnVpbGRlcic7XG4iXX0=
@@ -0,0 +1,2 @@
1
+ export * from '@workflow/nest/vercel-builder';
2
+ //# sourceMappingURL=nest-vercel-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nest-vercel-builder.d.ts","sourceRoot":"","sources":["../src/nest-vercel-builder.ts"],"names":[],"mappings":"AAAA,cAAc,+BAA+B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from '@workflow/nest/vercel-builder';
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmVzdC12ZXJjZWwtYnVpbGRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9uZXN0LXZlcmNlbC1idWlsZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsK0JBQStCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgKiBmcm9tICdAd29ya2Zsb3cvbmVzdC92ZXJjZWwtYnVpbGRlcic7XG4iXX0=
@@ -66,7 +66,7 @@ export default Hook;`}
66
66
 
67
67
  The returned `Hook` object also implements `AsyncIterable<T>`, which allows you to iterate over incoming payloads using `for await...of` syntax.
68
68
 
69
- Use `hook.getConflict()` to check whether the hook token is already claimed by another active hook, without waiting for hook payload data. Calling `createHook()` on its own does not register the hook — registration is only committed when the workflow suspends. Awaiting `hook.getConflict()` suspends the workflow to commit the registration, then resolves with `null` once `hook_created` is recorded, or with the conflicting [`Run`](/docs/api-reference/workflow-api/get-run) if another active hook already owns the same token.
69
+ Use `hook.getConflict()` to check whether the hook token is already claimed by another hook, including one kept reserved after its run ends, without waiting for hook payload data. Calling `createHook()` on its own does not register the hook — registration is only committed when the workflow suspends. Awaiting `hook.getConflict()` suspends the workflow to commit the registration, then resolves with `null` once `hook_created` is recorded, or with the conflicting [`Run`](/docs/api-reference/workflow-api/get-run).
70
70
 
71
71
  ## Examples
72
72
 
@@ -143,12 +143,53 @@ async function processOrder(orderId: string) {
143
143
 
144
144
  Because `createHook()` alone does not suspend the workflow, awaiting `hook.getConflict()` is what actually suspends the run and commits the hook registration. It only waits for registration — to receive payload data from a future `resumeHook()` call, await the hook itself or iterate it with `for await...of`.
145
145
 
146
- On a conflict, the resolved value is a `Run` handle for the run that currently owns the token, with durable step-backed accessors. The duplicate run can decide in code how to handle it: return or log `conflict.runId`, inspect `await conflict.status`, wait on `await conflict.returnValue`, or cancel the owner with `await conflict.cancel()` and continue in the current run. See [Run idempotency](/docs/foundations/idempotency#run-idempotency) for these strategies in context.
146
+ On a conflict, the resolved value is a `Run` handle for the run that owns the token, with durable step-backed accessors. The duplicate run can decide in code how to handle it: return or log `conflict.runId`, inspect `await conflict.status`, wait on `await conflict.returnValue`, or cancel the owner with `await conflict.cancel()` and continue in the current run. See [Run idempotency](/docs/foundations/idempotency#run-idempotency) for these strategies in context.
147
147
 
148
148
  <Callout type="info">
149
149
  Custom hook tokens are the recommended way to coordinate active workflow runs. Use a deterministic token from your domain, such as an order ID or conversation ID, create the hook near the beginning of the workflow, and check `await hook.getConflict()` before work that depends on owning the token. See [Run idempotency](/docs/foundations/idempotency#run-idempotency).
150
150
  </Callout>
151
151
 
152
+ ### Keep a Token Unavailable After the Run Ends
153
+
154
+ By default, another Hook can use the token after its workflow ends. Set `experimental_minRetention` to keep the token unavailable for at least a specific time after `createHook()` runs:
155
+
156
+ ```typescript lineNumbers
157
+ import { createHook } from "workflow";
158
+
159
+ declare function processOwnedOrder(orderId: string): Promise<void>; // @setup
160
+
161
+ export async function processOrder(orderId: string) {
162
+ "use workflow";
163
+
164
+ const hook = createHook({ // [!code highlight]
165
+ token: `order:${orderId}`, // [!code highlight]
166
+ experimental_minRetention: "30d", // [!code highlight]
167
+ }); // [!code highlight]
168
+
169
+ const conflict = await hook.getConflict();
170
+ if (conflict) {
171
+ return { status: "duplicate" as const, runId: conflict.runId };
172
+ }
173
+
174
+ await processOwnedOrder(orderId);
175
+ return { status: "processed" as const };
176
+ }
177
+ ```
178
+
179
+ `experimental_minRetention` accepts the same values as [`sleep()`](/docs/api-reference/workflow/sleep): a duration string such as `"30d"`, a number of milliseconds, or an absolute `Date`. Durations start when `createHook()` runs.
180
+
181
+ The Hook remains active until the workflow ends, even if the configured time passes first. Another Hook can use the token only after both the workflow has ended and the configured time has passed. For example, `"30d"` keeps the token unavailable for 29 more days if the workflow ends after 1 day. A workflow that runs for more than 30 days releases the token when it ends.
182
+
183
+ After the workflow ends, [`getHookByToken()`](/docs/api-reference/workflow-api/get-hook-by-token) can still find the Hook until retention ends, but the Hook cannot be resumed.
184
+
185
+ <Callout type="warn">
186
+ `using` auto-disposes the Hook at scope exit, which releases the token immediately and defeats `experimental_minRetention`. Declare retained Hooks with `const` and let the runtime clean them up when the run ends.
187
+ </Callout>
188
+
189
+ <Callout type="warn">
190
+ This option is experimental. If the configured World does not support it, the workflow fails when registering the Hook. `createWebhook()` does not accept this option.
191
+ </Callout>
192
+
152
193
  ### Waiting for Multiple Payloads
153
194
 
154
195
  You can also wait for multiple payloads by using the `for await...of` syntax.
@@ -46,24 +46,21 @@ showSections={['parameters']}
46
46
 
47
47
  ### Returns
48
48
 
49
+ `defineHook()` returns a `TypedHook<TInput, TOutput>`:
50
+
49
51
  <TSDoc
50
52
  definition={`
51
- interface DefineHook<T> {
52
- /**
53
-
54
- * Creates a new hook with the defined payload type.
55
- */
56
- create: (options?: HookOptions) => Hook<T>;
57
-
58
- /**
59
-
60
- * Resumes a hook by sending a payload with the defined type.
61
- */
62
- resume: (token: string, payload: T) => Promise<HookEntity | null>;
53
+ interface TypedHook<TInput, TOutput> {
54
+ /** Creates the hook. Call inside a "use workflow" function. */
55
+ create(options?: HookOptions): Hook<TOutput>;
56
+ /** Resumes the hook from runtime code. Resolves to the resumed hook; throws HookNotFoundError if the token does not match an active hook. */
57
+ resume(token: string, payload: TInput): Promise<HookEntity>;
63
58
  }
64
- export default DefineHook;`}
59
+ export default TypedHook;`}
65
60
  />
66
61
 
62
+ `create()` is called inside a `"use workflow"` function to create the hook; `resume()` is called from runtime code (an API route or server action). When a `schema` is provided, `resume()` accepts the raw input type (`TInput`) and the workflow receives the validated and possibly transformed output type (`TOutput`); without a schema, `TOutput` defaults to `TInput`. `resume()` throws [`HookNotFoundError`](/docs/api-reference/workflow-errors/hook-not-found-error) if the token does not match an active hook — it does not return `null`.
63
+
67
64
  ## Examples
68
65
 
69
66
  ### Basic Type-Safe Hook Definition
@@ -93,24 +90,29 @@ export async function workflowWithApproval() {
93
90
 
94
91
  ### Resuming with Type Safety
95
92
 
96
- Hooks can be resumed using the same defined hook and a token. By using the same hook, you can ensure that the payload matches the defined type when resuming a hook.
93
+ Hooks can be resumed using the same defined hook and a token. By using the same hook, you can ensure that the payload matches the defined type when resuming a hook. `resume()` resolves to the resumed hook and throws [`HookNotFoundError`](/docs/api-reference/workflow-errors/hook-not-found-error) if the token does not match an active hook.
97
94
 
98
95
  ```typescript lineNumbers
96
+ import { HookNotFoundError } from "workflow/errors";
97
+
99
98
  // Use the same defined hook to resume
100
99
  export async function POST(request: Request) {
101
100
  const { token, approved, comment } = await request.json();
102
101
 
103
- // Type-safe resumption - TypeScript ensures the payload matches
104
- const result = await approvalHook.resume(token, { // [!code highlight]
105
- approved, // [!code highlight]
106
- comment, // [!code highlight]
107
- }); // [!code highlight]
108
-
109
- if (!result) {
110
- return Response.json({ error: "Hook not found" }, { status: 404 });
102
+ try {
103
+ // Type-safe resumption - TypeScript ensures the payload matches
104
+ const hook = await approvalHook.resume(token, { // [!code highlight]
105
+ approved, // [!code highlight]
106
+ comment, // [!code highlight]
107
+ }); // [!code highlight]
108
+
109
+ return Response.json({ success: true, runId: hook.runId });
110
+ } catch (error) {
111
+ if (HookNotFoundError.is(error)) { // [!code highlight]
112
+ return Response.json({ error: "Hook not found" }, { status: 404 });
113
+ }
114
+ throw error;
111
115
  }
112
-
113
- return Response.json({ success: true, runId: result.runId });
114
116
  }
115
117
  ```
116
118
 
@@ -29,16 +29,38 @@ async function fallibleStep() {
29
29
 
30
30
  ## API Signature
31
31
 
32
- ### Parameters
32
+ ### Constructor
33
+
34
+ `new FatalError(message)`
35
+
36
+ | Parameter | Type | Description |
37
+ | --------- | -------- | ------------------ |
38
+ | `message` | `string` | The error message. |
39
+
40
+ ### Properties
33
41
 
34
42
  <TSDoc
35
43
  definition={`
36
- interface Error {
37
- /**
38
-
39
- * The error message.
40
- */
44
+ interface FatalError {
45
+ /** The error message. */
41
46
  message: string;
47
+ /** Always true. Marks the error as non-retryable. */
48
+ fatal: boolean;
42
49
  }
43
- export default Error;`}
50
+ export default FatalError;`}
44
51
  />
52
+
53
+ ### Static Methods
54
+
55
+ #### `FatalError.is(value)`
56
+
57
+ Type-safe check for `FatalError` instances. Preferred over `instanceof` because it works across module boundaries and VM contexts.
58
+
59
+ ```typescript
60
+ import { FatalError } from "workflow"
61
+ declare const error: unknown; // @setup
62
+
63
+ if (FatalError.is(error)) {
64
+ // error is typed as FatalError
65
+ }
66
+ ```
@@ -93,7 +93,7 @@ This API is provided as a convenience to easily use `fetch` in workflow, but oft
93
93
 
94
94
  ### Customizing Fetch Behavior
95
95
 
96
- Here's an example of a custom fetch wrapper that provides more sophisticated error handling with custom retry logic:
96
+ Here's an example of a custom fetch wrapper that provides more sophisticated error handling with custom retry logic. Call `globalThis.fetch` inside your own `"use step"` function — calling the workflow `fetch` imported from `workflow` would nest a step inside a step:
97
97
 
98
98
  ```typescript lineNumbers
99
99
  import { FatalError, RetryableError } from "workflow"
@@ -104,7 +104,7 @@ export async function customFetch(
104
104
  ) {
105
105
  "use step"
106
106
 
107
- const response = await fetch(url, init)
107
+ const response = await globalThis.fetch(url, init)
108
108
 
109
109
  // Handle client errors (4xx) - don't retry
110
110
  if (response.status >= 400 && response.status < 500) {
@@ -145,7 +145,6 @@ export async function customFetch(
145
145
 
146
146
  This example demonstrates:
147
147
 
148
- - Setting custom `maxRetries` to 5 retries (6 total attempts including the initial attempt).
149
148
  - Throwing [`FatalError`](/docs/api-reference/workflow/fatal-error) for client errors (400-499) to prevent retries.
150
149
  - Handling 429 rate limiting by reading the `Retry-After` header and using [`RetryableError`](/docs/api-reference/workflow/retryable-error).
151
- - Allowing automatic retries for server errors (5xx).
150
+ - Allowing automatic retries for server errors (5xx) by throwing a plain `Error`.
@@ -14,7 +14,7 @@ Suspends a workflow for a specified duration or until an end date without consum
14
14
  This is useful when you want to resume a workflow after some duration or date.
15
15
 
16
16
  <Callout>
17
- `sleep` is a *special* type of step function and should be called directly inside workflow functions.
17
+ `sleep` is a built-in workflow runtime function (backed by a timer event in the event log, not a step) and should be called directly inside workflow functions.
18
18
  </Callout>
19
19
 
20
20
  ```typescript lineNumbers
@@ -11,6 +11,8 @@ related:
11
11
 
12
12
  Retrieves a hook by its unique token, returning the associated workflow run information and any metadata that was set when the hook was created. This function is useful for inspecting hook details before deciding whether to resume a workflow.
13
13
 
14
+ When `experimental_minRetention` is set, this function continues to return the Hook after its workflow ends until retention ends. That Hook cannot be resumed. Use `getRun(hook.runId)` to inspect the finished run.
15
+
14
16
  <Callout type="warn">
15
17
  `getHookByToken` is a runtime function that must be called from outside a workflow function.
16
18
  </Callout>
@@ -14,6 +14,8 @@ Resumes a workflow run by sending a payload to a hook identified by its token.
14
14
 
15
15
  It creates a `hook_received` event and re-triggers the workflow to continue execution.
16
16
 
17
+ A Hook kept by `experimental_minRetention` after its workflow ends cannot be resumed. `resumeHook()` throws `HookNotFoundError` in that case.
18
+
17
19
  <Callout type="warn">
18
20
  `resumeHook` is a runtime function that must be called from outside a workflow function.
19
21
  </Callout>
@@ -50,11 +50,13 @@ showSections={['parameters']}
50
50
 
51
51
  ### Returns
52
52
 
53
- Returns a `Promise<Response>` that resolves to:
53
+ Returns a `Promise<Response>` that resolves to one of:
54
54
 
55
- - `Response`: The HTTP response from the workflow's `respondWith()` call
55
+ - A `202 Accepted` response when the webhook was created in the default mode (no `respondWith` option).
56
+ - The exact `Response` object configured via `createWebhook({ respondWith: new Response(...) })`.
57
+ - The workflow's manual `Response` when the webhook was created with `createWebhook({ respondWith: "manual" })` and a step calls `request.respondWith(response)`.
56
58
 
57
- Throws an error if the webhook token is not found or invalid.
59
+ Throws [`HookNotFoundError`](/docs/api-reference/workflow-errors/hook-not-found-error) if the token does not match an active webhook.
58
60
 
59
61
  ## Usage Note
60
62
 
@@ -81,7 +83,7 @@ export async function POST(request: Request) {
81
83
 
82
84
  try {
83
85
  const response = await resumeWebhook(token, request); // [!code highlight]
84
- return response; // Returns the workflow's custom response
86
+ return response; // 202 Accepted, a configured static Response, or a manual workflow response
85
87
  } catch (error) {
86
88
  return new Response("Webhook not found", { status: 404 });
87
89
  }
@@ -62,7 +62,7 @@ Learn more about [`WorkflowReadableStreamOptions`](/docs/api-reference/workflow-
62
62
  * `attributes` seeds plaintext run metadata as part of creation and requires a World implementing spec version 4 or later. Keys that start with `$` are reserved for framework and library code; framework-level callers can pass `allowReservedAttributes: true` to seed reserved keys, with the same semantics as the [`setAttributes`](/docs/api-reference/workflow/set-attributes) option of the same name.
63
63
 
64
64
  <Callout type="info">
65
- If `start()` throws `'start' received an invalid workflow function. Ensure the Workflow Development Kit is configured correctly and the function includes a 'use workflow' directive.`, the passed function was not transformed as a workflow. The two most common causes are a missing `"use workflow"` directive or missing framework integration. See [start-invalid-workflow-function](/docs/errors/start-invalid-workflow-function).
65
+ If `start()` throws `'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive.`, the passed function was not transformed as a workflow. The two most common causes are a missing `"use workflow"` directive or missing framework integration. See [start-invalid-workflow-function](/docs/errors/start-invalid-workflow-function).
66
66
  </Callout>
67
67
 
68
68
  ## Examples
@@ -79,6 +79,9 @@ All errors extend [`WorkflowError`](/docs/api-reference/workflow-errors/workflow
79
79
  <Card href="/docs/api-reference/workflow-errors/entity-conflict-error" title="EntityConflictError">
80
80
  Thrown when a storage operation conflicts with the current entity state.
81
81
  </Card>
82
+ <Card href="/docs/api-reference/workflow-errors/precondition-failed-error" title="PreconditionFailedError">
83
+ Thrown when an event creation is rejected because the client's event-log snapshot is stale.
84
+ </Card>
82
85
  <Card href="/docs/api-reference/workflow-errors/too-early-error" title="TooEarlyError">
83
86
  Thrown when a request is made before the system is ready to process it.
84
87
  </Card>
@@ -14,6 +14,7 @@
14
14
  "workflow-world-error",
15
15
  "throttle-error",
16
16
  "entity-conflict-error",
17
+ "precondition-failed-error",
17
18
  "run-expired-error",
18
19
  "run-not-supported-error",
19
20
  "too-early-error"
@@ -0,0 +1,62 @@
1
+ ---
2
+ title: PreconditionFailedError
3
+ description: Thrown when an event creation is rejected because the client's event-log snapshot is stale.
4
+ type: reference
5
+ summary: Catch PreconditionFailedError when a world rejects an event creation made from a stale event-log snapshot.
6
+ related:
7
+ - /docs/api-reference/workflow-errors/workflow-world-error
8
+ - /docs/api-reference/workflow-errors/entity-conflict-error
9
+ ---
10
+
11
+ `PreconditionFailedError` is thrown by world implementations when an event creation is rejected because the client's event-log snapshot is stale — a newer out-of-band event (such as a received hook or a completed step) was recorded after the snapshot the client replayed from. It corresponds to HTTP 412 Precondition Failed semantics.
12
+
13
+ This only occurs when the optimistic-concurrency guard is enabled via `WORKFLOW_PRECONDITION_GUARD=1` (see [Runtime Tuning](/docs/configuration/runtime-tuning)); event creations that carry no snapshot are never rejected with this error.
14
+
15
+ <Callout>
16
+ The Workflow runtime handles this error automatically: it reloads the event log and retries, ultimately re-enqueueing the run for a fresh replay if it cannot catch up. You will only encounter it when interacting with world storage APIs directly.
17
+ </Callout>
18
+
19
+ ```typescript lineNumbers
20
+ import { PreconditionFailedError } from "workflow/errors"
21
+ declare const world: { events: { create(...args: any[]): Promise<any> } }; // @setup
22
+ declare const runId: string; // @setup
23
+ declare const event: any; // @setup
24
+
25
+ try {
26
+ await world.events.create(runId, event);
27
+ } catch (error) {
28
+ if (PreconditionFailedError.is(error)) { // [!code highlight]
29
+ console.log("Snapshot is stale; reload the event log and retry");
30
+ }
31
+ }
32
+ ```
33
+
34
+ ## API Signature
35
+
36
+ ### Properties
37
+
38
+ <TSDoc
39
+ definition={`
40
+ interface PreconditionFailedError {
41
+ /** Delay in seconds before the operation should be retried. Present when the server sends a Retry-After header. */
42
+ retryAfter?: number;
43
+ /** The error message. */
44
+ message: string;
45
+ }
46
+ export default PreconditionFailedError;`}
47
+ />
48
+
49
+ ### Static Methods
50
+
51
+ #### `PreconditionFailedError.is(value)`
52
+
53
+ Type-safe check for `PreconditionFailedError` instances. Preferred over `instanceof` because it works across module boundaries and VM contexts.
54
+
55
+ ```typescript
56
+ import { PreconditionFailedError } from "workflow/errors"
57
+ declare const error: unknown; // @setup
58
+
59
+ if (PreconditionFailedError.is(error)) {
60
+ // error is typed as PreconditionFailedError
61
+ }
62
+ ```
@@ -12,7 +12,7 @@ Builder that scans a NestJS project for workflow files and compiles them into th
12
12
  ## Usage
13
13
 
14
14
  ```typescript title="scripts/build-workflows.ts" lineNumbers
15
- import { NestLocalBuilder } from "workflow/nest"; // [!code highlight]
15
+ import { NestLocalBuilder } from "workflow/nest/builder"; // [!code highlight]
16
16
 
17
17
  const builder = new NestLocalBuilder({
18
18
  dirs: ["src"],
@@ -0,0 +1,66 @@
1
+ ---
2
+ title: Comparisons
3
+ description: How the Workflow SDK compares to other durable execution, workflow, and AI-agent frameworks — Temporal, Cloudflare Workflows, AWS Step Functions, AWS Bedrock AgentCore, Inngest, and trigger.dev.
4
+ type: overview
5
+ summary: Side-by-side comparisons of the Workflow SDK against Temporal, Cloudflare Workflows, AWS Step Functions, AWS Bedrock AgentCore, Inngest, and trigger.dev.
6
+ related:
7
+ - /docs/foundations/workflows-and-steps
8
+ - /docs/how-it-works/event-sourcing
9
+ - /worlds/vercel
10
+ ---
11
+
12
+ The Workflow SDK overlaps with several categories: durable execution engines, background-job platforms, managed orchestrators, and AI-agent runtimes. This section compares it against the most common alternatives, one deep dive per tool.
13
+
14
+ ## What makes the Workflow SDK different
15
+
16
+ - **It's an open-source SDK, not a hosted product.** Your workflows are plain TypeScript in your existing app. Run them on the managed [Vercel World](/worlds/vercel), or self-host on the [Postgres World](/worlds/postgres) — the [World abstraction](/worlds/building-a-world) lets you own and swap the storage, queue, and streaming layers independently.
17
+ - **Versioning is safe by default.** Runs are pinned to the immutable deployment that started them, so shipping new code never disturbs in-flight runs. Upgrading a run is explicit and opt-in. See [Versioning](/docs/comparisons/workflow-sdk-vs-temporal#versioning).
18
+ - **Realtime durable streaming is built in.** Stream partial output (LLM tokens, progress) to clients with [streams](/docs/foundations/streaming) that survive reconnects, cold starts, and replays — essential for chat and agent UIs.
19
+ - **First-class AI agents.** `WorkflowAgent` ships inside the [AI SDK](/docs/ai), turning an agent loop into a durable workflow with automatic step retries and human-in-the-loop pauses.
20
+
21
+ ## Snapshot
22
+
23
+ <Callout type="info">
24
+ These comparisons are compiled from each product's public documentation and are **not** based on head-to-head benchmarks — durable engines differ enough that a single number rarely compares cleanly. Treat them as directional and verify current pricing and limits against each vendor's docs.
25
+ </Callout>
26
+
27
+ | Tool | Category | Durability model | Open source / self-host | Language(s) |
28
+ | --- | --- | --- | --- | --- |
29
+ | **Workflow SDK** | Durable functions SDK | Event-log + deterministic replay | ✅ Apache-2.0 — self-host (Postgres) or Vercel | TypeScript (Python beta) |
30
+ | [Temporal](/docs/comparisons/workflow-sdk-vs-temporal) | Durable execution platform | Event-sourced replay | ✅ MIT server — self-host or Temporal Cloud | Go, Java, TS, Python, .NET, PHP, Ruby |
31
+ | [Cloudflare Workflows](/docs/comparisons/workflow-sdk-vs-cloudflare-workflows) | Durable execution engine | Step-result memoization + replay | ❌ Cloudflare-only | TypeScript (Python beta) |
32
+ | [AWS Step Functions](/docs/comparisons/workflow-sdk-vs-aws-step-functions) | Managed state-machine orchestrator | Declarative ASL state machine | ❌ AWS-only | ASL JSON (tasks: any language) |
33
+ | [AWS Bedrock AgentCore](/docs/comparisons/workflow-sdk-vs-aws-agentcore) | AI-agent hosting platform | Not durable execution (ephemeral sessions) | ❌ AWS-only | Python, Node.js |
34
+ | [Inngest](/docs/comparisons/workflow-sdk-vs-inngest) | Durable functions / event platform | Step-result memoization | ◑ SSPL — self-host (community/best-effort) or SaaS | TypeScript (Python/Go pre-1.0) |
35
+ | [trigger.dev](/docs/comparisons/workflow-sdk-vs-trigger-dev) | Durable task platform | Process checkpoint/restore (CRIU) | ✅ Apache-2.0 — self-host or Cloud | TypeScript only |
36
+
37
+ ## Deep dives
38
+
39
+ <Cards>
40
+ <Card href="/docs/comparisons/workflow-sdk-vs-temporal" title="vs Temporal">
41
+ The mature, language-agnostic durable-execution platform. You run the workers; Workflow SDK runs in your app.
42
+ </Card>
43
+ <Card href="/docs/comparisons/workflow-sdk-vs-cloudflare-workflows" title="vs Cloudflare Workflows">
44
+ A durable engine on Workers + Durable Objects. Both replay; they handle versioning and encryption very differently.
45
+ </Card>
46
+ <Card href="/docs/comparisons/workflow-sdk-vs-aws-step-functions" title="vs AWS Step Functions">
47
+ Declarative ASL JSON state machines vs. plain TypeScript control flow.
48
+ </Card>
49
+ <Card href="/docs/comparisons/workflow-sdk-vs-aws-agentcore" title="vs AWS Bedrock AgentCore">
50
+ An AI-agent hosting platform — not a durable-execution engine. Different axis.
51
+ </Card>
52
+ <Card href="/docs/comparisons/workflow-sdk-vs-inngest" title="vs Inngest">
53
+ Event-driven durable functions that run on your own infra over HTTP.
54
+ </Card>
55
+ <Card href="/docs/comparisons/workflow-sdk-vs-trigger-dev" title="vs trigger.dev">
56
+ A TypeScript task platform that achieves durability by snapshotting the process (CRIU).
57
+ </Card>
58
+ </Cards>
59
+
60
+ <Callout type="info">
61
+ Moving an existing system over? Each deep dive includes a concept-mapping section, and the Workflow SDK migration skill can translate code for you:
62
+
63
+ ```bash
64
+ npx skills add https://github.com/vercel/workflow --skill migrating-to-workflow-sdk
65
+ ```
66
+ </Callout>
@@ -0,0 +1,11 @@
1
+ {
2
+ "title": "Comparisons",
3
+ "pages": [
4
+ "workflow-sdk-vs-temporal",
5
+ "workflow-sdk-vs-cloudflare-workflows",
6
+ "workflow-sdk-vs-aws-step-functions",
7
+ "workflow-sdk-vs-aws-agentcore",
8
+ "workflow-sdk-vs-inngest",
9
+ "workflow-sdk-vs-trigger-dev"
10
+ ]
11
+ }
@@ -0,0 +1,55 @@
1
+ ---
2
+ title: Workflow SDK vs AWS Bedrock AgentCore
3
+ description: How the Workflow SDK compares to AWS Bedrock AgentCore — a durable-execution framework versus an AI-agent hosting platform. They solve different problems.
4
+ type: conceptual
5
+ summary: AgentCore hosts and operates AI agents in isolated microVMs but is not a durable-execution engine. The Workflow SDK provides durable orchestration and resumable streaming for agents.
6
+ prerequisites:
7
+ - /docs/foundations/workflows-and-steps
8
+ related:
9
+ - /docs/ai
10
+ - /docs/ai/resumable-streams
11
+ - /docs/foundations/streaming
12
+ ---
13
+
14
+ [Amazon Bedrock AgentCore](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/what-is-bedrock-agentcore.html) is AWS's platform for **hosting and operating AI agents** — secure microVM runtime, plus building blocks for Memory, tool Gateways, and Identity. It is *not* a durable-execution engine, which makes this less a head-to-head and more a "different axis" comparison.
15
+
16
+ <Callout type="info">
17
+ **These solve different problems.** AgentCore answers "where do I securely run and operate an agent on AWS?" The Workflow SDK answers "how do I make a multi-step, tool-calling agent loop durable, resumable, and streamable?" AWS itself pairs AgentCore with a durable layer (Step Functions, or Temporal) for resumability — the Workflow SDK provides that durable layer natively, in your own app.
18
+ </Callout>
19
+
20
+ ## At a glance
21
+
22
+ | | Workflow SDK | AWS Bedrock AgentCore |
23
+ | --- | --- | --- |
24
+ | **Category** | Open-source durable-functions SDK | AI-agent hosting & operations platform |
25
+ | **Durable execution?** | ✅ Event-log replay; the agent loop resumes from its last checkpoint after a crash | ❌ Not built-in — sessions are ephemeral microVMs; durability is opt-in via Memory or a framework checkpointer |
26
+ | **What it gives you** | Durable orchestration, steps, hooks, streaming, observability | Runtime (microVM hosting), Harness (managed agent loop), Memory, Gateway (tools/MCP), Identity, Browser, Code Interpreter, Observability, Policy, Evaluations |
27
+ | **Languages** | TypeScript / JS (Python beta) | Python-first for authoring; TypeScript or Python project scaffolding via the `@aws/agentcore` CLI; framework-agnostic (LangGraph, CrewAI, Strands, etc.) |
28
+ | **Isolation** | VM-sandboxed workflow code + full-Node steps | Dedicated Firecracker microVM per session (strong hardware isolation) |
29
+ | **Max duration** | No limit ([Vercel World limits](https://vercel.com/docs/workflows/pricing)) | 8-hour hard cap per session |
30
+ | **AI streaming** | Native **durable, resumable** streaming (survives reconnect, cold start, replay) | Live SSE / WebSocket only — no buffering or replay of missed tokens |
31
+ | **Versioning** | Runs pinned to immutable deployment | Immutable runtime versions + endpoints; in-flight sessions stay on their launch version (deployment/rollback only — no replay) |
32
+ | **Portability** | Apache-2.0; runs anywhere Node runs; World abstraction | AWS-only; agent *code* is portable, the operating platform is not |
33
+ | **Compliance** | Inherits your platform | HIPAA-eligible; SOC/PCI/ISO not third-party certified; not FedRAMP-authorized |
34
+ | **Pricing** | SDK free; pay your platform | Per-module consumption (vCPU-hr / GB-hr, etc.) + model inference billed via Bedrock; no minimums |
35
+
36
+ **What the limits mean in practice:** AgentCore's 8-hour session cap means an agent that waits on a human, a long-running job, or a slow external system can't span that wait in one session. Workflow SDK runs have [no duration cap](https://vercel.com/docs/workflows/pricing) — they suspend durably at `sleep()` and hooks for hours or weeks.
37
+
38
+ ## The core distinction: durability
39
+
40
+ AgentCore Runtime gives each session an isolated microVM with up to 8 hours of runtime, but the compute is **ephemeral** — on a crash or stop, the next invocation gets a fresh microVM with no automatic replay of the agent loop. AWS's own guidance is to use AgentCore Memory or a framework checkpointer for state durability, and to layer a workflow engine (Step Functions or Temporal) on top when you need durable orchestration.
41
+
42
+ The Workflow SDK *is* that durable layer. With `WorkflowAgent` (in the [AI SDK](/docs/ai)), the agent loop becomes a durable workflow: each model call and tool execution is a checkpointed step, the run resumes mid-loop after a failure, and partial output streams to the client through [resumable streams](/docs/ai/resumable-streams) that survive disconnects and cold starts.
43
+
44
+ ## Where AgentCore leads
45
+
46
+ AgentCore is purpose-built for operating agents on AWS, and brings things the Workflow SDK doesn't try to be:
47
+
48
+ - **MicroVM isolation per session** — the strongest hardware isolation among the tools in this section.
49
+ - **Managed agent infrastructure** — Memory, a tool Gateway (turn APIs/Lambda/MCP servers into tools), and Identity (credential vaulting, OAuth) as first-class managed services.
50
+ - **Enterprise/AWS compliance breadth** and VPC/PrivateLink networking.
51
+
52
+ If your priority is running agents inside AWS with managed memory, tools, and identity, AgentCore is a strong fit. If your priority is **durable, resumable, streamable** agent execution that lives in your own TypeScript app and isn't tied to AWS, the Workflow SDK fits better — and the two can be combined (host on AgentCore, orchestrate durably with the Workflow SDK).
53
+
54
+ ---
55
+ *Compiled from public documentation. AgentCore cold-start figures are community-sourced (no published SLA). Verify against [the AgentCore docs](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/what-is-bedrock-agentcore.html). Not based on head-to-head benchmarks.*