workflow 5.0.0-beta.5 → 5.0.0-beta.51

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 (256) hide show
  1. package/README.md +68 -23
  2. package/dist/api-workflow.d.ts +1 -1
  3. package/dist/api-workflow.d.ts.map +1 -1
  4. package/dist/api-workflow.js +1 -1
  5. package/dist/api.d.ts +3 -3
  6. package/dist/api.d.ts.map +1 -1
  7. package/dist/api.js +5 -7
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +6 -1
  11. package/dist/internal/builtins.d.ts +20 -3
  12. package/dist/internal/builtins.d.ts.map +1 -1
  13. package/dist/internal/builtins.js +68 -4
  14. package/dist/internal/errors.d.ts +1 -1
  15. package/dist/internal/errors.d.ts.map +1 -1
  16. package/dist/internal/errors.js +2 -2
  17. package/dist/nest-builder.d.ts +2 -0
  18. package/dist/nest-builder.d.ts.map +1 -0
  19. package/dist/nest-builder.js +2 -0
  20. package/dist/nest-vercel-builder.d.ts +2 -0
  21. package/dist/nest-vercel-builder.d.ts.map +1 -0
  22. package/dist/nest-vercel-builder.js +2 -0
  23. package/dist/observability.d.ts +1 -1
  24. package/dist/observability.js +2 -2
  25. package/dist/runtime.d.ts +2 -1
  26. package/dist/runtime.d.ts.map +1 -1
  27. package/dist/runtime.js +4 -1
  28. package/docs/ai/chat-session-modeling.mdx +176 -422
  29. package/docs/ai/defining-tools.mdx +6 -7
  30. package/docs/ai/human-in-the-loop.mdx +11 -11
  31. package/docs/ai/index.mdx +71 -75
  32. package/docs/ai/message-queueing.mdx +71 -110
  33. package/docs/ai/meta.json +1 -0
  34. package/docs/ai/resumable-streams.mdx +40 -28
  35. package/docs/ai/sleep-and-delays.mdx +10 -10
  36. package/docs/ai/streaming-updates-from-tools.mdx +6 -6
  37. package/docs/api-reference/index.mdx +25 -1
  38. package/docs/api-reference/meta.json +8 -0
  39. package/docs/api-reference/vitest/index.mdx +9 -15
  40. package/docs/api-reference/workflow/create-hook.mdx +89 -10
  41. package/docs/api-reference/workflow/create-webhook.mdx +16 -15
  42. package/docs/api-reference/workflow/define-hook.mdx +35 -33
  43. package/docs/api-reference/workflow/fatal-error.mdx +30 -8
  44. package/docs/api-reference/workflow/fetch.mdx +14 -10
  45. package/docs/api-reference/workflow/get-step-metadata.mdx +2 -2
  46. package/docs/api-reference/workflow/get-workflow-metadata.mdx +3 -3
  47. package/docs/api-reference/workflow/get-writable.mdx +7 -7
  48. package/docs/api-reference/workflow/index.mdx +4 -1
  49. package/docs/api-reference/workflow/retryable-error.mdx +1 -1
  50. package/docs/api-reference/workflow/set-attributes.mdx +61 -0
  51. package/docs/api-reference/workflow/sleep.mdx +4 -4
  52. package/docs/api-reference/workflow-ai/durable-agent.mdx +63 -101
  53. package/docs/api-reference/workflow-ai/index.mdx +5 -5
  54. package/docs/api-reference/workflow-ai/workflow-chat-transport.mdx +67 -24
  55. package/docs/api-reference/workflow-api/get-hook-by-token.mdx +26 -12
  56. package/docs/api-reference/workflow-api/get-run.mdx +43 -8
  57. package/docs/api-reference/workflow-api/index.mdx +6 -10
  58. package/docs/api-reference/workflow-api/resume-hook.mdx +73 -12
  59. package/docs/api-reference/workflow-api/resume-webhook.mdx +11 -9
  60. package/docs/api-reference/workflow-api/start.mdx +60 -13
  61. package/docs/api-reference/workflow-astro/index.mdx +18 -0
  62. package/docs/api-reference/workflow-astro/meta.json +4 -0
  63. package/docs/api-reference/workflow-astro/workflow.mdx +45 -0
  64. package/docs/api-reference/workflow-errors/entity-conflict-error.mdx +4 -4
  65. package/docs/api-reference/workflow-errors/hook-conflict-error.mdx +60 -0
  66. package/docs/api-reference/workflow-errors/hook-not-found-error.mdx +8 -8
  67. package/docs/api-reference/workflow-errors/index.mdx +88 -0
  68. package/docs/api-reference/workflow-errors/meta.json +6 -0
  69. package/docs/api-reference/workflow-errors/precondition-failed-error.mdx +68 -0
  70. package/docs/api-reference/workflow-errors/run-expired-error.mdx +2 -2
  71. package/docs/api-reference/workflow-errors/run-not-supported-error.mdx +58 -0
  72. package/docs/api-reference/workflow-errors/step-not-registered-error.mdx +5 -5
  73. package/docs/api-reference/workflow-errors/throttle-error.mdx +2 -2
  74. package/docs/api-reference/workflow-errors/too-early-error.mdx +2 -2
  75. package/docs/api-reference/workflow-errors/workflow-error.mdx +52 -0
  76. package/docs/api-reference/workflow-errors/workflow-not-registered-error.mdx +5 -6
  77. package/docs/api-reference/workflow-errors/workflow-run-cancelled-error.mdx +6 -6
  78. package/docs/api-reference/workflow-errors/workflow-run-failed-error.mdx +5 -5
  79. package/docs/api-reference/workflow-errors/workflow-run-not-completed-error.mdx +58 -0
  80. package/docs/api-reference/workflow-errors/workflow-run-not-found-error.mdx +4 -4
  81. package/docs/api-reference/workflow-errors/workflow-runtime-error.mdx +58 -0
  82. package/docs/api-reference/workflow-errors/workflow-world-error.mdx +8 -8
  83. package/docs/api-reference/workflow-globals.mdx +14 -10
  84. package/docs/api-reference/workflow-nest/configure-workflow-controller.mdx +33 -0
  85. package/docs/api-reference/workflow-nest/index.mdx +31 -0
  86. package/docs/api-reference/workflow-nest/meta.json +9 -0
  87. package/docs/api-reference/workflow-nest/nest-local-builder.mdx +64 -0
  88. package/docs/api-reference/workflow-nest/workflow-controller.mdx +40 -0
  89. package/docs/api-reference/workflow-nest/workflow-module.mdx +74 -0
  90. package/docs/api-reference/workflow-next/with-workflow.mdx +39 -17
  91. package/docs/api-reference/workflow-nitro/index.mdx +60 -0
  92. package/docs/api-reference/workflow-nuxt/index.mdx +48 -0
  93. package/docs/api-reference/workflow-observability/hydrate-data.mdx +35 -0
  94. package/docs/api-reference/workflow-observability/hydrate-resource-io.mdx +62 -0
  95. package/docs/api-reference/workflow-observability/index.mdx +62 -0
  96. package/docs/api-reference/workflow-observability/meta.json +11 -0
  97. package/docs/api-reference/workflow-observability/observability-revivers.mdx +50 -0
  98. package/docs/api-reference/workflow-observability/parse-class-name.mdx +41 -0
  99. package/docs/api-reference/workflow-observability/parse-step-name.mdx +40 -0
  100. package/docs/api-reference/workflow-observability/parse-workflow-name.mdx +55 -0
  101. package/docs/api-reference/workflow-runtime/create-world.mdx +39 -0
  102. package/docs/api-reference/workflow-runtime/get-world-handlers.mdx +44 -0
  103. package/docs/api-reference/{workflow-api → workflow-runtime}/get-world.mdx +11 -14
  104. package/docs/api-reference/workflow-runtime/health-check.mdx +50 -0
  105. package/docs/api-reference/workflow-runtime/index.mdx +41 -0
  106. package/docs/api-reference/workflow-runtime/meta.json +12 -0
  107. package/docs/api-reference/workflow-runtime/set-world.mdx +51 -0
  108. package/docs/api-reference/workflow-runtime/workflow-entrypoint.mdx +43 -0
  109. package/docs/api-reference/workflow-runtime/world/analytics.mdx +315 -0
  110. package/docs/api-reference/workflow-runtime/world/index.mdx +60 -0
  111. package/docs/api-reference/workflow-runtime/world/meta.json +4 -0
  112. package/docs/api-reference/workflow-runtime/world/queue.mdx +88 -0
  113. package/docs/api-reference/{workflow-api → workflow-runtime}/world/storage.mdx +98 -34
  114. package/docs/api-reference/{workflow-api → workflow-runtime}/world/streams.mdx +8 -8
  115. package/docs/api-reference/workflow-serde/index.mdx +1 -2
  116. package/docs/api-reference/workflow-serde/workflow-deserialize.mdx +3 -4
  117. package/docs/api-reference/workflow-serde/workflow-serialize.mdx +8 -8
  118. package/docs/api-reference/workflow-sveltekit/index.mdx +18 -0
  119. package/docs/api-reference/workflow-sveltekit/meta.json +4 -0
  120. package/docs/api-reference/workflow-sveltekit/workflow-plugin.mdx +42 -0
  121. package/docs/api-reference/workflow-vite/index.mdx +18 -0
  122. package/docs/api-reference/workflow-vite/meta.json +4 -0
  123. package/docs/api-reference/workflow-vite/workflow.mdx +48 -0
  124. package/docs/changelog/attributes-mvp.mdx +380 -0
  125. package/docs/changelog/batched-event-writes.mdx +79 -0
  126. package/docs/changelog/eager-processing.mdx +110 -436
  127. package/docs/changelog/index.mdx +4 -2
  128. package/docs/changelog/lazy-event-creation.md +127 -0
  129. package/docs/changelog/lazy-hook-resume.mdx +78 -0
  130. package/docs/changelog/meta.json +11 -1
  131. package/docs/changelog/resilient-resume.mdx +32 -0
  132. package/docs/changelog/resilient-start.mdx +33 -285
  133. package/docs/changelog/step-message-ownership.mdx +360 -0
  134. package/docs/changelog/turbo-mode.md +87 -0
  135. package/docs/comparisons/index.mdx +66 -0
  136. package/docs/comparisons/meta.json +11 -0
  137. package/docs/comparisons/workflow-sdk-vs-aws-agentcore.mdx +55 -0
  138. package/docs/comparisons/workflow-sdk-vs-aws-step-functions.mdx +111 -0
  139. package/docs/comparisons/workflow-sdk-vs-cloudflare-workflows.mdx +71 -0
  140. package/docs/comparisons/workflow-sdk-vs-inngest.mdx +102 -0
  141. package/docs/comparisons/workflow-sdk-vs-temporal.mdx +123 -0
  142. package/docs/comparisons/workflow-sdk-vs-trigger-dev.mdx +104 -0
  143. package/docs/configuration/build-and-diagnostics.mdx +70 -0
  144. package/docs/configuration/cli-and-web-ui.mdx +241 -0
  145. package/docs/configuration/framework-options.mdx +165 -0
  146. package/docs/configuration/index.mdx +32 -0
  147. package/docs/configuration/meta.json +12 -0
  148. package/docs/configuration/runtime-tuning.mdx +381 -0
  149. package/docs/configuration/worlds.mdx +313 -0
  150. package/docs/cookbook/advanced/child-workflows.mdx +211 -264
  151. package/docs/cookbook/advanced/meta.json +6 -1
  152. package/docs/cookbook/advanced/publishing-libraries.mdx +65 -56
  153. package/docs/cookbook/advanced/serializable-steps.mdx +48 -68
  154. package/docs/cookbook/advanced/upgrading-workflows.mdx +199 -0
  155. package/docs/cookbook/agent-patterns/agent-cancellation.mdx +78 -60
  156. package/docs/cookbook/agent-patterns/durable-agent.mdx +23 -131
  157. package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +180 -195
  158. package/docs/cookbook/common-patterns/batching.mdx +18 -14
  159. package/docs/cookbook/common-patterns/idempotency.mdx +41 -53
  160. package/docs/cookbook/common-patterns/rate-limiting.mdx +8 -4
  161. package/docs/cookbook/common-patterns/saga.mdx +23 -19
  162. package/docs/cookbook/common-patterns/scheduling.mdx +34 -22
  163. package/docs/cookbook/common-patterns/sequential-and-parallel.mdx +29 -25
  164. package/docs/cookbook/common-patterns/timeouts.mdx +26 -21
  165. package/docs/cookbook/common-patterns/webhooks.mdx +10 -6
  166. package/docs/cookbook/common-patterns/workflow-composition.mdx +30 -27
  167. package/docs/cookbook/index.mdx +22 -21
  168. package/docs/cookbook/integrations/ai-sdk.mdx +86 -48
  169. package/docs/cookbook/integrations/chat-sdk.mdx +50 -33
  170. package/docs/cookbook/integrations/sandbox.mdx +62 -45
  171. package/docs/deploying.mdx +95 -0
  172. package/docs/errors/abort-signal-timeout-in-workflow.mdx +16 -12
  173. package/docs/errors/corrupted-event-log.mdx +39 -18
  174. package/docs/errors/deployment-mismatch.mdx +71 -0
  175. package/docs/errors/fetch-in-workflow.mdx +15 -14
  176. package/docs/errors/hook-conflict.mdx +69 -13
  177. package/docs/errors/index.mdx +2 -36
  178. package/docs/errors/node-js-module-in-workflow.mdx +9 -5
  179. package/docs/errors/replay-divergence.mdx +27 -0
  180. package/docs/errors/run-expired.mdx +85 -0
  181. package/docs/errors/runtime-decryption-failed.mdx +77 -0
  182. package/docs/errors/serialization-failed.mdx +44 -12
  183. package/docs/errors/start-invalid-workflow-function.mdx +9 -5
  184. package/docs/errors/step-executed-multiple-times.mdx +23 -0
  185. package/docs/errors/step-not-registered.mdx +6 -6
  186. package/docs/errors/timeout-in-workflow.mdx +12 -8
  187. package/docs/errors/webhook-invalid-respond-with-value.mdx +18 -18
  188. package/docs/errors/webhook-response-not-sent.mdx +20 -16
  189. package/docs/errors/workflow-not-registered.mdx +5 -5
  190. package/docs/foundations/cancellation.mdx +31 -32
  191. package/docs/foundations/errors-and-retries.mdx +42 -11
  192. package/docs/foundations/hooks.mdx +64 -35
  193. package/docs/foundations/idempotency.mdx +244 -12
  194. package/docs/foundations/index.mdx +1 -23
  195. package/docs/foundations/meta.json +2 -1
  196. package/docs/foundations/serialization.mdx +21 -22
  197. package/docs/foundations/starting-workflows.mdx +106 -30
  198. package/docs/foundations/streaming.mdx +108 -60
  199. package/docs/foundations/versioning.mdx +263 -0
  200. package/docs/foundations/workflows-and-steps.mdx +9 -9
  201. package/docs/getting-started/astro.mdx +22 -18
  202. package/docs/getting-started/express.mdx +15 -11
  203. package/docs/getting-started/fastify.mdx +15 -11
  204. package/docs/getting-started/hono.mdx +15 -11
  205. package/docs/getting-started/index.mdx +10 -3
  206. package/docs/getting-started/meta.json +3 -1
  207. package/docs/getting-started/nestjs.mdx +87 -20
  208. package/docs/getting-started/next.mdx +22 -16
  209. package/docs/getting-started/nitro.mdx +22 -18
  210. package/docs/getting-started/nuxt.mdx +15 -11
  211. package/docs/getting-started/python.mdx +190 -41
  212. package/docs/getting-started/react-router/index.mdx +33 -0
  213. package/docs/getting-started/react-router/meta.json +5 -0
  214. package/docs/getting-started/react-router/v7.mdx +237 -0
  215. package/docs/getting-started/react-router/v8.mdx +232 -0
  216. package/docs/getting-started/sveltekit.mdx +20 -16
  217. package/docs/getting-started/tanstack-start.mdx +17 -13
  218. package/docs/getting-started/vite.mdx +15 -11
  219. package/docs/how-it-works/cancellation.mdx +63 -63
  220. package/docs/how-it-works/code-transform.mdx +83 -67
  221. package/docs/how-it-works/encryption.mdx +30 -26
  222. package/docs/how-it-works/event-sourcing.mdx +125 -34
  223. package/docs/how-it-works/framework-integrations.mdx +96 -337
  224. package/docs/how-it-works/understanding-directives.mdx +22 -22
  225. package/docs/internal/index.mdx +6 -4
  226. package/docs/internal/meta.json +6 -1
  227. package/docs/internal/nitro-native-build.mdx +38 -0
  228. package/docs/internal/nitro-web-ui.mdx +24 -0
  229. package/docs/internal/serializable-abort-controller.mdx +7 -7
  230. package/docs/meta.json +3 -2
  231. package/docs/observability/attributes.mdx +134 -0
  232. package/docs/observability/index.mdx +32 -10
  233. package/docs/observability/meta.json +1 -1
  234. package/docs/observability/retention.mdx +95 -0
  235. package/docs/observability/tracing.mdx +124 -0
  236. package/docs/testing/index.mdx +36 -36
  237. package/docs/testing/server-based.mdx +10 -10
  238. package/docs/whats-new.mdx +186 -0
  239. package/package.json +17 -14
  240. package/docs/api-reference/workflow-api/world/index.mdx +0 -58
  241. package/docs/api-reference/workflow-api/world/meta.json +0 -4
  242. package/docs/api-reference/workflow-api/world/observability.mdx +0 -164
  243. package/docs/api-reference/workflow-api/world/queue.mdx +0 -86
  244. package/docs/deploying/building-a-world.mdx +0 -251
  245. package/docs/deploying/index.mdx +0 -95
  246. package/docs/deploying/meta.json +0 -4
  247. package/docs/deploying/world/local-world.mdx +0 -84
  248. package/docs/deploying/world/meta.json +0 -4
  249. package/docs/deploying/world/postgres-world.mdx +0 -224
  250. package/docs/deploying/world/vercel-world.mdx +0 -179
  251. package/docs/migration-guides/index.mdx +0 -34
  252. package/docs/migration-guides/meta.json +0 -9
  253. package/docs/migration-guides/migrating-from-aws-step-functions.mdx +0 -363
  254. package/docs/migration-guides/migrating-from-inngest.mdx +0 -314
  255. package/docs/migration-guides/migrating-from-temporal.mdx +0 -318
  256. package/docs/migration-guides/migrating-from-trigger-dev.mdx +0 -337
@@ -9,6 +9,10 @@ related:
9
9
  - /docs/foundations/workflows-and-steps
10
10
  ---
11
11
 
12
+ <CopyPrompt
13
+ text="In this Vite app, run `npm i workflow nitro`. In `vite.config.ts`, import `nitro` from `nitro/vite`, `workflow` from `workflow/vite`, and configure `plugins: [nitro(), workflow()]` plus `nitro: { serverDir: &quot;./&quot; }`. Add the TypeScript plugin `{ &quot;name&quot;: &quot;workflow&quot; }` to `tsconfig.json` if TypeScript is used. Create `workflows/user-signup.ts` with `&quot;use workflow&quot;`, `sleep`, and `&quot;use step&quot;` helpers. Add `api/signup.post.ts` using `defineEventHandler` from `nitro/h3` and `start` from `workflow/api`. Run `npm run dev`, call `curl -X POST --json '{&quot;email&quot;:&quot;hello@example.com&quot;}' http://localhost:3000/api/signup`, and inspect with `npx workflow web` or `npx workflow inspect runs`."
14
+ />
15
+
12
16
  This guide will walk through setting up your first workflow in a Vite app. Along the way, you'll learn more about the concepts that are fundamental to using the Workflow SDK in your own projects.
13
17
 
14
18
  ---
@@ -16,7 +20,7 @@ This guide will walk through setting up your first workflow in a Vite app. Along
16
20
  <Steps>
17
21
 
18
22
  <Step>
19
- ## Create Your Vite Project
23
+ ## Create your Vite project
20
24
 
21
25
  Start by creating a new Vite project. This command will create a new directory named `my-workflow-app` with a minimal setup and setup a Vite project inside it.
22
26
 
@@ -87,7 +91,7 @@ To enable helpful hints in your IDE, setup the workflow plugin in `tsconfig.json
87
91
 
88
92
  <Step>
89
93
 
90
- ## Create Your First Workflow
94
+ ## Create your first workflow
91
95
 
92
96
  Create a new file for our first workflow:
93
97
 
@@ -108,14 +112,14 @@ export async function handleUserSignup(email: string) {
108
112
 
109
113
  ```
110
114
 
111
- We'll fill in those functions next, but let's take a look at this code:
115
+ We'll fill in those functions next. The current code does the following:
112
116
 
113
117
  * We define a **workflow** function with the directive `"use workflow"`. Think of the workflow function as the _orchestrator_ of individual **steps**.
114
118
  * The Workflow SDK's `sleep` function allows us to suspend execution of the workflow without using up any resources. A sleep can be a few seconds, hours, days, or even months long.
115
119
 
116
- ## Create Your Workflow Steps
120
+ ## Create your workflow steps
117
121
 
118
- Let's now define those missing functions.
122
+ Define the missing functions.
119
123
 
120
124
  ```typescript title="workflows/user-signup.ts" lineNumbers
121
125
  import { FatalError } from "workflow"
@@ -156,7 +160,7 @@ async function sendOnboardingEmail(user: { id: string; email: string}) {
156
160
 
157
161
  Taking a look at this code:
158
162
 
159
- * Business logic lives inside **steps**. When a step is invoked inside a **workflow**, it gets enqueued to run on a separate request while the workflow is suspended, just like `sleep`.
163
+ * Business logic lives inside **steps**. When a workflow invokes a step, the workflow suspends while the step runs with full Node.js access. The combined handler may execute it inline; queued retries and continuations return through the same flow route.
160
164
  * If a step throws an error, like in `sendWelcomeEmail`, the step will automatically be retried until it succeeds (or hits the step's max retry count).
161
165
  * Steps can throw a `FatalError` if an error is intentional and should not be retried.
162
166
 
@@ -168,7 +172,7 @@ We'll dive deeper into workflows, steps, and other ways to suspend or handle eve
168
172
 
169
173
  <Step>
170
174
 
171
- ## Create Your Route Handler
175
+ ## Create your route handler
172
176
 
173
177
  To invoke your new workflow, we'll have to add your workflow to a `POST` API route handler, `api/signup.post.ts` with the following code:
174
178
 
@@ -228,7 +232,7 @@ npx workflow inspect runs
228
232
 
229
233
  ## Deploying to production
230
234
 
231
- Workflow SDK apps currently work best when deployed to [Vercel](https://vercel.com/home) and needs no special configuration.
235
+ Workflow SDK apps currently work best when deployed to [Vercel](https://vercel.com/home) and need no special configuration.
232
236
 
233
237
  <FluidComputeCallout />
234
238
 
@@ -240,8 +244,8 @@ Check the [Deploying](/docs/deploying) section to learn how your workflows can b
240
244
 
241
245
  If you see this error:
242
246
 
243
- ```
244
- 'start' received an invalid workflow function. Ensure the Workflow Development Kit is configured correctly and the function includes a 'use workflow' directive.
247
+ ```text
248
+ 'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive.
245
249
  ```
246
250
 
247
251
  Check both of these first:
@@ -251,7 +255,7 @@ Check both of these first:
251
255
 
252
256
  See [start-invalid-workflow-function](/docs/errors/start-invalid-workflow-function) for full examples and fixes.
253
257
 
254
- ## Next Steps
258
+ ## Next steps
255
259
 
256
260
  * Learn more about the [Foundations](/docs/foundations).
257
261
  * Check [Errors](/docs/errors) if you encounter issues.
@@ -18,30 +18,30 @@ This guide explains how cancellation works internally. Understanding these detai
18
18
 
19
19
  When you write `new AbortController()` in a workflow function, Workflow DevKit creates a durable controller backed by two existing primitives: a [hook](/docs/foundations/hooks) and a [stream](/docs/foundations/streaming). This page explains why both are needed and how they work together.
20
20
 
21
- ## The Problem
21
+ ## The problem
22
22
 
23
- `AbortController` and `AbortSignal` are inherently stateful an abort happens once and is permanent. In a durable workflow, this state must:
23
+ `AbortController` and `AbortSignal` are inherently stateful: an abort happens once and is permanent. In a durable workflow, this state must:
24
24
 
25
- 1. **Survive replay** If `abort()` was called, `signal.aborted` must return `true` on every subsequent replay of the workflow.
26
- 2. **Propagate in real-time** A running step on a different compute instance must receive the abort immediately, not on the next replay.
25
+ 1. **Survive replay**: If `abort()` was called, `signal.aborted` must return `true` on every subsequent replay of the workflow.
26
+ 2. **Propagate in real-time**: A running step on a different compute instance must receive the abort immediately, not on the next replay.
27
27
 
28
28
  No single primitive solves both. Hooks provide durable event log state but can't reach into a running step. Streams provide real-time cross-process communication but aren't part of the event log. The solution is to use both.
29
29
 
30
- ## Dual Backing: Hook + Stream
30
+ ## Dual backing: hook + stream
31
31
 
32
32
  Every `AbortController` in the workflow context is backed by:
33
33
 
34
- ### Hook (Durable State)
34
+ ### Hook (durable state)
35
35
 
36
- When `new AbortController()` is called in a workflow, an internal hook is created similar to calling `createHook()`. This hook is registered in the workflow's invocations queue and produces events in the [event log](/docs/how-it-works/event-sourcing):
36
+ When `new AbortController()` is called in a workflow, an internal hook is created, similar to calling `createHook()`. This hook is registered in the workflow's invocations queue and produces events in the [event log](/docs/how-it-works/event-sourcing):
37
37
 
38
38
  - **On creation**: A `hook_created` event records that the controller exists
39
39
  - **On abort**: The hook is resumed (producing a `hook_received` event), recording the abort permanently
40
40
  - **On replay**: The event consumer processes the `hook_received` event and updates `signal.aborted` to `true` at the same point in the replay as the original abort
41
41
 
42
- This gives the workflow deterministic access to the abort state `controller.signal.aborted` always returns the correct value, even after cold starts.
42
+ This gives the workflow deterministic access to the abort state: `controller.signal.aborted` always returns the correct value, even after cold starts.
43
43
 
44
- ### Stream (Real-Time Propagation)
44
+ ### Stream (real-time propagation)
45
45
 
46
46
  When `controller.signal` is serialized as a step argument, a stream name is included in the serialized form. Inside the step, the deserialized `AbortSignal` listens on this stream:
47
47
 
@@ -50,7 +50,7 @@ When `controller.signal` is serialized as a step argument, a stream name is incl
50
50
 
51
51
  This gives steps real-time cancellation without waiting for the workflow to replay.
52
52
 
53
- ### Why Both?
53
+ ### Why both?
54
54
 
55
55
  | Mechanism | Solves | Doesn't Solve |
56
56
  |---|---|---|
@@ -60,18 +60,18 @@ This gives steps real-time cancellation without waiting for the workflow to repl
60
60
 
61
61
  ## Lifecycle
62
62
 
63
- ### 1. Controller Created in Workflow
63
+ ### 1. Controller created in workflow
64
64
 
65
- ```
65
+ ```text
66
66
  new AbortController()
67
67
 
68
68
  ├─→ Internal hook created (registered in invocations queue)
69
69
  └─→ Stream name generated (deterministic ULID)
70
70
  ```
71
71
 
72
- ### 2. Signal Passed to Step
72
+ ### 2. Signal passed to step
73
73
 
74
- ```
74
+ ```text
75
75
  stepFunction(controller.signal)
76
76
 
77
77
  ├─→ Signal serialized as { streamName, hookToken, aborted }
@@ -80,9 +80,9 @@ stepFunction(controller.signal)
80
80
  └─→ Background reader listens on stream for abort packet
81
81
  ```
82
82
 
83
- ### 3. abort() Called in Workflow
83
+ ### 3. abort() called in workflow
84
84
 
85
- ```
85
+ ```text
86
86
  controller.abort()
87
87
 
88
88
  ├─→ signal.aborted set to true (synchronous, local state)
@@ -92,13 +92,13 @@ controller.abort()
92
92
  ├─→ Suspension handler creates hook_received event
93
93
  ├─→ Suspension handler writes cancellation packet to stream
94
94
  │ │
95
- │ └─→ Step receives packet → local signal fires → fetch cancelled
95
+ │ └─→ Step receives packet → local signal fires → fetch canceled
96
96
  └─→ Workflow re-enqueued for replay
97
97
  ```
98
98
 
99
- ### 4. Workflow Replays After Abort
99
+ ### 4. Workflow replays after abort
100
100
 
101
- ```
101
+ ```text
102
102
  Replay starts → events loaded
103
103
 
104
104
  ├─→ new AbortController() → hook created → event consumer subscribes
@@ -107,34 +107,34 @@ Replay starts → events loaded
107
107
  └─→ Workflow code sees signal.aborted === true at the correct point in replay
108
108
  ```
109
109
 
110
- On replay, the events consumer re-applies the abort by calling `_setAborted` when it encounters the `hook_received` event in the log at the same point in execution where the original `abort()` happened. This is what makes the abort deterministic across replays.
110
+ On replay, the events consumer re-applies the abort by calling `_setAborted` when it encounters the `hook_received` event in the log, at the same point in execution where the original `abort()` happened. This is what makes the abort deterministic across replays.
111
111
 
112
- ## Where the Hook Is Created
112
+ ## Where the hook is created
113
113
 
114
114
  The backing hook is set up whenever an `AbortController` or `AbortSignal` enters the workflow context:
115
115
 
116
- **`new AbortController()` in a workflow function** The workflow VM provides a durable `AbortController` implementation (similar to how it provides deterministic `Date` and serializable `Request`/`Response`). The hook is created in the constructor using the orchestrator context injected via VM globals.
116
+ **`new AbortController()` in a workflow function**: The workflow VM provides a durable `AbortController` implementation (similar to how it provides deterministic `Date` and serializable `Request`/`Response`). The hook is created in the constructor using the orchestrator context injected via VM globals.
117
117
 
118
- **Returned from a step** A step can create a plain `new AbortController()` and return it. The step-side serializer generates a stream name and hook token (using a random ULID) and includes them in the serialized payload. When the return value is deserialized into the workflow via `hydrateStepReturnValue`, the workflow reviver reads the token from the payload and sets up the hook with that token. Since the serialized payload is stored in the event log (as part of the `step_completed` event), the same token is used on every replay no deterministic generation needed in the workflow.
118
+ **Returned from a step**: A step can create a plain `new AbortController()` and return it. The step-side serializer generates a stream name and hook token (using a random ULID) and includes them in the serialized payload. When the return value is deserialized into the workflow via `hydrateStepReturnValue`, the workflow reviver reads the token from the payload and sets up the hook with that token. Since the serialized payload is stored in the event log (as part of the `step_completed` event), the same token is used on every replay, so no deterministic generation is needed in the workflow.
119
119
 
120
- **Passed as workflow input** Conceptually the same as "returned from a step". The **external reducer** handles it at serialization time:
120
+ **Passed as workflow input**: Conceptually the same as "returned from a step". The **external reducer** handles it at serialization time:
121
121
 
122
122
  1. Generates a stream name and hook token (random ULID)
123
123
  2. Attaches an `abort` event listener on the source signal: when the external code calls `controller.abort()`, the listener writes the cancellation packet to the stream
124
124
  3. Pushes the listener's async work into `ops` (awaited via `waitUntil`)
125
125
  4. Serializes the reference as `{ streamName, hookToken, aborted }`
126
126
 
127
- The serialized payload (including the generated token) is stored in the event log as part of the workflow's input. When the workflow deserializes the input, the reviver reads the token from the payload and creates the hook identical to the "returned from a step" case. On replay, the same token is read from the event log, so the hook matches the same events.
127
+ The serialized payload (including the generated token) is stored in the event log as part of the workflow's input. When the workflow deserializes the input, the reviver reads the token from the payload and creates the hook, identical to the "returned from a step" case. On replay, the same token is read from the event log, so the hook matches the same events.
128
128
 
129
129
  If the external code calls `abort()` while the process is still alive (within the `waitUntil` window), the stream packet arrives in the workflow, and the workflow can resume the hook to record it in the event log.
130
130
 
131
131
  <Callout type="info">
132
- Since the external `AbortController` is a plain JavaScript object (not the workflow VM's durable version), the stream write depends on the originating process still being alive. This is the same constraint that applies to passing a `ReadableStream` as a workflow argument the stream pipe runs via `waitUntil` and requires the process to remain active until the data is written.
132
+ Since the external `AbortController` is a plain JavaScript object (not the workflow VM's durable version), the stream write depends on the originating process still being alive. This is the same constraint that applies to passing a `ReadableStream` as a workflow argument: the stream pipe runs via `waitUntil` and requires the process to remain active until the data is written.
133
133
  </Callout>
134
134
 
135
- ## Serialization & Deserialization
135
+ ## Serialization & deserialization
136
136
 
137
- ### Serialized Form
137
+ ### Serialized form
138
138
 
139
139
  An `AbortController` or `AbortSignal` is serialized as:
140
140
 
@@ -148,23 +148,23 @@ An `AbortController` or `AbortSignal` is serialized as:
148
148
  }
149
149
  ```
150
150
 
151
- The `streamName` and `hookToken` are generated once at serialization time (in the step or external context) and stored in the event log as part of the serialized payload. On replay, the workflow reviver reads them from the payload it never generates them itself. This is the same pattern used by `ReadableStream` and `WritableStream` serialization.
151
+ The `streamName` and `hookToken` are generated once at serialization time (in the step or external context) and stored in the event log as part of the serialized payload. On replay, the workflow reviver reads them from the payload; it never generates them itself. This is the same pattern used by `ReadableStream` and `WritableStream` serialization.
152
152
 
153
- ### Reducers (Serialization)
153
+ ### Reducers (serialization)
154
154
 
155
155
  **In step context** (`getStepReducers`): When a step returns an `AbortController`, the reducer captures the stream name. If `abort()` was called in the step, `aborted: true` is recorded.
156
156
 
157
- **In workflow context** (`getWorkflowReducers`): The reducer captures the stream name and hook token. These are handles no I/O happens during serialization in the workflow.
157
+ **In workflow context** (`getWorkflowReducers`): The reducer captures the stream name and hook token. These are handles; no I/O happens during serialization in the workflow.
158
158
 
159
159
  **In external context** (`getExternalReducers`): When an `AbortController` is passed as a workflow argument from outside, the reducer creates the backing stream and serializes the reference.
160
160
 
161
- ### Revivers (Deserialization)
161
+ ### Revivers (deserialization)
162
162
 
163
163
  **Into step context** (`getStepRevivers`): Creates a real `AbortController`. If `aborted: true`, calls `abort()` immediately. Otherwise, pushes a stream reader into the step's `ops` array that listens for the cancellation packet and calls `abort()` when received.
164
164
 
165
165
  **Into workflow context** (`getWorkflowRevivers`): Creates the durable AbortController with hook backing. Subscribes to the events consumer for the hook's correlation ID. If the event log contains a `hook_received` event, `signal.aborted` is `true`.
166
166
 
167
- ### abort() in a Step
167
+ ### abort() in a step
168
168
 
169
169
  When `abort()` is called on a deserialized `AbortController` inside a step:
170
170
 
@@ -172,18 +172,18 @@ When `abort()` is called on a deserialized `AbortController` inside a step:
172
172
  2. The stream write (cancellation packet) is pushed into `ctx.ops`
173
173
  3. The hook resume (`resumeHook`) is pushed into `ctx.ops`
174
174
 
175
- The step's `ops` array is awaited via `waitUntil(Promise.all(ops))` after the step function returns the same mechanism used by [`getWritable()`](/docs/api-reference/workflow/get-writable). This keeps `abort()` synchronous from the caller's perspective while ensuring the async work completes.
175
+ The step's `ops` array is awaited via `waitUntil(Promise.all(ops))` after the step function returns, the same mechanism used by [`getWritable()`](/docs/api-reference/workflow/get-writable). This keeps `abort()` synchronous from the caller's perspective while ensuring the async work completes.
176
176
 
177
- ### Abort Errors Are Wrapped in FatalError
177
+ ### Abort errors are wrapped in FatalError
178
178
 
179
- When a step throws due to an abort whether from `fetch` throwing `AbortError`, `signal.throwIfAborted()`, or any other abort-induced error the step handler wraps the error in `FatalError` before recording it in the event log. This ensures:
179
+ When a step throws due to an abort (whether from `fetch` throwing `AbortError`, `signal.throwIfAborted()`, or any other abort-induced error), the step executor wraps the error in `FatalError` before recording it in the event log. This ensures:
180
180
 
181
- - **No retries**: An abort is intentional cancellation, not a transient failure. Retrying would just abort again.
181
+ - **No retries**: An abort is intentional cancellation, not a transient failure. Retrying would abort again.
182
182
  - **Immediate propagation**: The error bubbles up to the workflow as a `FatalError`, which the workflow can catch with `FatalError.is(err)`.
183
183
 
184
- The wrapping happens at the step handler level (`runtime/step-handler.ts`), during error hydration. When the step's thrown error is an `AbortError` (checked via `err.name === 'AbortError'`), it is treated as fatal regardless of the step's `maxRetries` configuration.
184
+ The wrapping happens in `runtime/step-executor.ts` during error hydration. When the step's thrown error is an `AbortError` (checked via `err.name === 'AbortError'`), it is treated as fatal regardless of the step's `maxRetries` configuration.
185
185
 
186
- ### abort() in the Workflow
186
+ ### abort() in the workflow
187
187
 
188
188
  When `abort()` is called in the workflow context:
189
189
 
@@ -194,7 +194,7 @@ When `abort()` is called in the workflow context:
194
194
  - Creates a `hook_received` event in the event log
195
195
  - Writes the cancellation packet to the stream (for real-time step propagation)
196
196
  - Re-enqueues the workflow for replay
197
- 4. On replay, the event consumer processes the `hook_received` event, updating `signal.aborted` to `true` at the deterministically correct point
197
+ 5. On replay, the event consumer processes the `hook_received` event, updating `signal.aborted` to `true` at the deterministically correct point
198
198
 
199
199
  `signal.aborted` is updated synchronously so that the workflow can immediately check the state and serialization captures `aborted: true` when passing the signal to steps. On replay, the event consumer also processes the `hook_received` event, ensuring the state is consistent.
200
200
 
@@ -203,55 +203,55 @@ For abort specifically, this ensures that:
203
203
  - The abort's `hook_received` event is created in the event log
204
204
  - The cancellation stream packet is written to propagate to running steps
205
205
 
206
- ## Race Conditions
206
+ ## Race conditions
207
207
 
208
- ### Abort Before Hook Exists
208
+ ### Abort before hook exists
209
209
 
210
210
  When an `AbortSignal` is passed as a workflow argument via `start()`, the external reducer attaches a listener at serialization time. If the external code calls `abort()` before the workflow has started and created the internal hook, the stream packet is written but the hook doesn't exist yet.
211
211
 
212
212
  This is resolved through eventual consistency:
213
213
 
214
- 1. The stream packet is durable it persists in storage
214
+ 1. The stream packet is durable; it persists in storage
215
215
  2. When the workflow runs and passes the signal to a step, the step's reviver reads from the stream starting at index 0
216
216
  3. The step sees the existing packet, aborts locally, and resumes the hook (via `ops`)
217
217
  4. On the next workflow replay, the hook event is in the log and `signal.aborted` is `true`
218
218
 
219
- **Important:** There is a window where the workflow's `signal.aborted` returns `false` even though the external code has already called `abort()`. This lasts until a step processes the stream packet and resumes the hook. This is analogous to hooks `resumeHook()` doesn't take effect until the workflow replays.
219
+ **Important:** There is a window where the workflow's `signal.aborted` returns `false` even though the external code has already called `abort()`. This lasts until a step processes the stream packet and resumes the hook. This is analogous to hooks: `resumeHook()` doesn't take effect until the workflow replays.
220
220
 
221
- ### Abort at Serialization Time
221
+ ### Abort at serialization time
222
222
 
223
223
  To prevent a micro-window where `abort()` is called between checking `signal.aborted` and attaching the listener, the external reducer uses this order:
224
224
 
225
225
  1. Attach the `abort` event listener first
226
- 2. Then check `signal.aborted` if already `true`, the listener won't fire, so handle immediately
226
+ 2. Then check `signal.aborted`; if already `true`, the listener won't fire, so handle immediately
227
227
 
228
228
  This ensures no abort events are missed regardless of timing.
229
229
 
230
- ## Stream/Hook Consistency
230
+ ## Stream/hook consistency
231
231
 
232
232
  Since abort involves two operations (stream write + hook resume), partial failure is possible:
233
233
 
234
- ### Stream Succeeds, Hook Fails
234
+ ### Stream succeeds, hook fails
235
235
 
236
236
  - Steps see the abort and throw `AbortError` (stream worked)
237
237
  - Workflow doesn't see `signal.aborted === true` on the next replay (hook not resumed)
238
238
  - The workflow sees the step failure as an error, which it can handle with try/catch
239
- - **Recovery:** The step-side `resumeHook` call is best-effort if it throws, the failure is swallowed. Convergence comes from the next replay: when the step's reviver re-reads the stream, it sees the abort packet and calls `resumeHook` again. There's no in-process retry loop; the dual-mechanism design relies on either the stream or the hook eventually landing.
239
+ - **Recovery:** The step-side `resumeHook` call is best-effort: if it throws, the failure is swallowed. Convergence comes from the next replay: when the step's reviver re-reads the stream, it sees the abort packet and calls `resumeHook` again. There's no in-process retry loop; the dual-mechanism design relies on either the stream or the hook eventually landing.
240
240
 
241
- ### Hook Succeeds, Stream Fails
241
+ ### Hook succeeds, stream fails
242
242
 
243
243
  - Workflow sees `signal.aborted === true` on replay (hook worked)
244
- - Steps don't receive real-time cancellation (stream failed) they run to completion
244
+ - Steps don't receive real-time cancellation (stream failed), so they run to completion
245
245
  - On the next suspension, the workflow knows the abort happened and can stop calling more steps
246
- - **Recovery:** Natural convergence no active harm, just missed real-time cancellation for in-flight steps.
246
+ - **Recovery:** Natural convergence. No active harm, only missed real-time cancellation for in-flight steps.
247
247
 
248
- ### Both Fail
248
+ ### Both fail
249
249
 
250
- - Abort is lost no propagation
251
- - No crash or corruption the system continues as if abort was never called
250
+ - Abort is lost; no propagation
251
+ - No crash or corruption; the system continues as if abort was never called
252
252
  - **Recovery:** The caller can retry the abort. If using a hook for external cancellation, the hook's retry semantics apply.
253
253
 
254
- The dual mechanism provides natural resilience if either one succeeds, the system converges on the correct state.
254
+ The dual mechanism provides natural resilience: if either one succeeds, the system converges on the correct state.
255
255
 
256
256
  ## `AbortSignal.timeout()` in Workflow VM
257
257
 
@@ -264,7 +264,7 @@ The dual mechanism provides natural resilience — if either one succeeds, the s
264
264
  A `Request`'s `.signal` is forwarded by the `Request` reducer in two cases:
265
265
 
266
266
  1. **The signal is already aborted.** The serialized payload preserves `aborted: true` and the abort `reason`, so the deserialized step sees the cancellation that happened before the boundary.
267
- 2. **The signal is workflow-managed** (i.e., it has the `ABORT_STREAM_NAME` symbol produced by a workflow-context `AbortController`). Its hook + stream backing carries through, and the deserialized step listens on the stream as usual.
267
+ 2. **The signal is workflow-managed** (i.e., it has the `ABORT_STREAM_NAME` symbol, produced by a workflow-context `AbortController`). Its hook + stream backing carries through, and the deserialized step listens on the stream as usual.
268
268
 
269
269
  Plain non-aborted native signals are intentionally dropped, including the auto-generated signal that `new Request(url)` synthesizes when no `signal` is passed. Forwarding every Request signal would mint stream infrastructure for the throwaway auto-signals on every Request, even ones the caller never intended to use for cancellation.
270
270
 
@@ -278,10 +278,10 @@ await fetchStep(req); // signal carries through
278
278
  controller.abort(); // step-side fetch sees the abort
279
279
  ```
280
280
 
281
- ## Related Documentation
281
+ ## Related documentation
282
282
 
283
- - [Cancellation](/docs/foundations/cancellation) Usage patterns and API
284
- - [Event Sourcing](/docs/how-it-works/event-sourcing) How the event log works
285
- - [Hooks](/docs/foundations/hooks) The hook primitive
286
- - [Streaming](/docs/foundations/streaming) The stream primitive
287
- - [Serialization](/docs/foundations/serialization) Serializable types
283
+ - [Cancellation](/docs/foundations/cancellation): Usage patterns and API
284
+ - [Event Sourcing](/docs/how-it-works/event-sourcing): How the event log works
285
+ - [Hooks](/docs/foundations/hooks): The hook primitive
286
+ - [Streaming](/docs/foundations/streaming): The stream primitive
287
+ - [Serialization](/docs/foundations/serialization): Serializable types