workflow 5.0.0-beta.5 → 5.0.0-beta.50

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 +29 -26
  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 +50 -45
  32. package/docs/ai/message-queueing.mdx +16 -16
  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 +24 -0
  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 +48 -86
  53. package/docs/api-reference/workflow-ai/index.mdx +3 -3
  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 +376 -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 +28 -20
  154. package/docs/cookbook/advanced/upgrading-workflows.mdx +199 -0
  155. package/docs/cookbook/agent-patterns/agent-cancellation.mdx +27 -19
  156. package/docs/cookbook/agent-patterns/durable-agent.mdx +14 -142
  157. package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +30 -22
  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 +85 -47
  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 +29 -18
  174. package/docs/errors/deployment-mismatch.mdx +71 -0
  175. package/docs/errors/fetch-in-workflow.mdx +11 -7
  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 +107 -59
  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 +135 -40
  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 +98 -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 +93 -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
@@ -1,16 +1,14 @@
1
1
  ---
2
2
  title: "workflow/api"
3
- description: Runtime functions to inspect runs, start workflows, and access world data.
3
+ description: Runtime functions to inspect runs, start workflows, and manage hooks.
4
4
  type: overview
5
5
  summary: Explore runtime functions for starting workflows, inspecting runs, and managing hooks.
6
6
  ---
7
7
 
8
- API reference for runtime functions from the `workflow/api` package.
8
+ The `workflow/api` package provides runtime functions to inspect runs, start new runs, and manage hooks.
9
9
 
10
10
  ## Functions
11
11
 
12
- The API package is for access and introspection of workflow data to inspect runs, start new runs, or access anything else directly accessible by the world.
13
-
14
12
  <Cards>
15
13
  <Card href="/docs/api-reference/workflow-api/start" title="start()">
16
14
  Start/enqueue a new workflow run.
@@ -27,10 +25,8 @@ The API package is for access and introspection of workflow data to inspect runs
27
25
  <Card href="/docs/api-reference/workflow-api/get-run" title="getRun()">
28
26
  Get workflow run status and metadata without waiting for completion.
29
27
  </Card>
30
- <Card href="/docs/api-reference/workflow-api/get-world" title="getWorld()">
31
- Async: resolve the World instance for storage, queuing, and streaming backends.
32
- </Card>
33
- <Card href="/docs/api-reference/workflow-api/world" title="World SDK">
34
- Low-level API for inspecting runs, steps, events, hooks, streams, and queues.
35
- </Card>
36
28
  </Cards>
29
+
30
+ <Callout type="info">
31
+ Looking for `getWorld()` and the World SDK? They are exported from `workflow/runtime`. See the [`workflow/runtime` reference](/docs/api-reference/workflow-runtime).
32
+ </Callout>
@@ -7,11 +7,16 @@ prerequisites:
7
7
  - /docs/foundations/hooks
8
8
  related:
9
9
  - /docs/api-reference/workflow-api/resume-webhook
10
+ - /docs/foundations/idempotency
10
11
  ---
11
12
 
12
13
  Resumes a workflow run by sending a payload to a hook identified by its token.
13
14
 
14
- It creates a `hook_received` event and re-triggers the workflow to continue execution.
15
+ It durably writes the `hook_received` event and only then publishes a workflow wake. The call resolves only after both operations succeed, in that order.
16
+
17
+ `resumeHook()` throws `HookNotFoundError` when no hook holds the token or when its `hook_received` write is refused because the hook was disposed or the run ended. See [durable hook resume](/docs/changelog/lazy-hook-resume).
18
+
19
+ If `resumeHook()` throws any other error, the outcome is ambiguous only in dispatch, never in durability: the event may already be durable even though the workflow wake failed, and any later wake of the run delivers it. Calling `resumeHook()` again creates a new `resumeId` and can append a second `hook_received`. Callers that need at-most-once behavior across separate invocations must retain and deduplicate their own request key.
15
20
 
16
21
  <Callout type="warn">
17
22
  `resumeHook` is a runtime function that must be called from outside a workflow function.
@@ -34,7 +39,7 @@ export async function POST(request: Request) {
34
39
  }
35
40
  ```
36
41
 
37
- ## API Signature
42
+ ## API signature
38
43
 
39
44
  ### Parameters
40
45
 
@@ -47,18 +52,18 @@ showSections={["parameters"]}
47
52
 
48
53
  ### Returns
49
54
 
50
- Returns a `Promise<Hook>` that resolves to:
55
+ Returns a `Promise<ResumedHook>`, a `Hook` (from `workflow/api`) extended with an optional `resilientResume` flag. Resolving means the payload is durably recorded as `hook_received` and the workflow wake was accepted. `resilientResume` is retained for source compatibility and is no longer set by any path. Resuming never reads the hook's metadata, so the resolved hook's `metadata` is a Promise that hydrates on first access, exactly as with [`getHookByToken()`](/docs/api-reference/workflow-api/get-hook-by-token): `await hook.metadata` to read it. The resolved hook:
51
56
 
52
57
  <TSDoc
53
58
  definition={`
54
- import type { Hook } from "@workflow/world";
59
+ import type { Hook } from "workflow/api";
55
60
  export default Hook;`}
56
61
  showSections={["returns"]}
57
62
  />
58
63
 
59
64
  ## Examples
60
65
 
61
- ### Basic API Route
66
+ ### Basic API route
62
67
 
63
68
  Using `resumeHook` in a basic API route to resume a hook:
64
69
 
@@ -81,7 +86,7 @@ export async function POST(request: Request) {
81
86
  }
82
87
  ```
83
88
 
84
- ### With Type Safety
89
+ ### With type safety
85
90
 
86
91
  Defining a payload type and using `resumeHook` to resume a hook with type safety:
87
92
 
@@ -109,7 +114,7 @@ export async function POST(request: Request) {
109
114
  }
110
115
  ```
111
116
 
112
- ### Server Action (Next.js)
117
+ ### Server action (Next.js)
113
118
 
114
119
  Using `resumeHook` in Next.js server actions to resume a hook:
115
120
 
@@ -128,7 +133,7 @@ export async function approveRequest(token: string, approved: boolean) {
128
133
  }
129
134
  ```
130
135
 
131
- ### Webhook Handler
136
+ ### Webhook handler
132
137
 
133
138
  Using `resumeHook` in a generic webhook handler to resume a hook:
134
139
 
@@ -155,8 +160,64 @@ export async function POST(request: Request) {
155
160
  }
156
161
  ```
157
162
 
158
- ## Related Functions
163
+ ### Resume or start
164
+
165
+ A common endpoint shape is "resume or start": one route that resumes the active workflow run for a business key if one exists, or starts a new run otherwise. This comes up when the workflow uses a deterministic hook token as its idempotency key, for example, one active run per order or conversation.
166
+
167
+ `resumeHook()` is the resume half of that flow. Try it first; if it throws `HookNotFoundError`, no active run owns the token yet, so start the workflow. One subtlety: `start()` returns before the new run executes and registers its hook, so you cannot resume immediately after starting. Retry the resume until the hook is registered: if you drop the payload and only start the workflow, the data from this request is lost.
168
+
169
+ ```typescript lineNumbers
170
+ import { resumeHook, start } from "workflow/api";
171
+ import { HookNotFoundError } from "workflow/errors";
172
+ import { processOrder } from "./workflows/process-order";
173
+
174
+ type OrderRequest = { confirmed: boolean };
175
+
176
+ async function resumeWithRetry(token: string, payload: OrderRequest) {
177
+ for (let attempt = 0; attempt < 5; attempt++) {
178
+ try {
179
+ return await resumeHook(token, payload); // [!code highlight]
180
+ } catch (error) {
181
+ if (!HookNotFoundError.is(error)) throw error;
182
+ await new Promise((resolve) => setTimeout(resolve, 100));
183
+ }
184
+ }
185
+
186
+ throw new Error("Workflow did not register its hook in time");
187
+ }
188
+
189
+ export async function POST(request: Request) {
190
+ const { orderId, confirmed } = await request.json();
191
+ const token = `order:${orderId}`;
192
+ const payload = { confirmed };
193
+
194
+ try {
195
+ // An active run already owns this token: resume it.
196
+ const hook = await resumeHook(token, payload); // [!code highlight]
197
+ return Response.json({ runId: hook.runId, reused: true });
198
+ } catch (error) {
199
+ if (!HookNotFoundError.is(error)) throw error;
200
+ }
201
+
202
+ // No hook yet: start a new run, then retry the resume so this
203
+ // request's payload still reaches the workflow.
204
+ const run = await start(processOrder, [orderId]); // [!code highlight]
205
+ const resumed = await resumeWithRetry(token, payload);
206
+
207
+ // A concurrent request can win the race between `start()` and hook
208
+ // registration; the resume always reaches the actual active owner.
209
+ return Response.json({
210
+ runId: resumed.runId,
211
+ reused: resumed.runId !== run.runId,
212
+ });
213
+ }
214
+ ```
215
+
216
+ See [Run idempotency](/docs/foundations/idempotency#run-idempotency) for the full pattern, including how the workflow claims the token with `hook.getConflict()` and how concurrent starts converge on one active owner.
217
+
218
+ ## Related functions
159
219
 
160
- - [`getHookByToken()`](/docs/api-reference/workflow-api/get-hook-by-token) - Get hook details before resuming.
161
- - [`createHook()`](/docs/api-reference/workflow/create-hook) - Create a hook in a workflow.
162
- - [`defineHook()`](/docs/api-reference/workflow/define-hook) - Type-safe hook helper.
220
+ - [`getHookByToken()`](/docs/api-reference/workflow-api/get-hook-by-token): Get hook details before resuming.
221
+ - [`createHook()`](/docs/api-reference/workflow/create-hook): Create a hook in a workflow.
222
+ - [`defineHook()`](/docs/api-reference/workflow/define-hook): Type-safe hook helper.
223
+ - [Idempotency](/docs/foundations/idempotency): Deduplicate step side effects and workflow starts.
@@ -11,7 +11,7 @@ related:
11
11
 
12
12
  Resumes a workflow run by sending an HTTP `Request` to a webhook identified by its token.
13
13
 
14
- This function creates a `hook_received` event and re-triggers the workflow to continue execution. It's designed to be called from API routes or server actions that receive external HTTP requests.
14
+ This function publishes a workflow invocation carrying the request; the runtime creates the `hook_received` event from it and continues execution. It's designed to be called from API routes or server actions that receive external HTTP requests.
15
15
 
16
16
  <Callout type="warn">
17
17
  `resumeWebhook` is a runtime function that must be called from outside a workflow function.
@@ -37,7 +37,7 @@ export async function POST(request: Request) {
37
37
  }
38
38
  ```
39
39
 
40
- ## API Signature
40
+ ## API signature
41
41
 
42
42
  ### Parameters
43
43
 
@@ -50,16 +50,18 @@ 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
- ## Usage Note
61
+ ## Usage note
60
62
 
61
63
  <Callout type="warn">
62
- In most cases, you should not need to call `resumeWebhook()` directly. When you use `createWebhook()`, the framework automatically generates a random webhook token and provides a public URL at `/.well-known/workflow/v1/webhook/:token`. External systems can send HTTP requests directly to that URL.
64
+ In most cases, you should not need to call `resumeWebhook()` directly. When you use `createWebhook()`, the framework automatically generates a webhook token and provides a public URL at `/.well-known/workflow/v1/webhook/:token`. External systems can send HTTP requests directly to that URL.
63
65
 
64
66
  For server-side hook resumption with deterministic tokens, use [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) with [`createHook()`](/docs/api-reference/workflow/create-hook) instead.
65
67
  </Callout>
@@ -81,14 +83,14 @@ 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
  }
88
90
  }
89
91
  ```
90
92
 
91
- ## Related Functions
93
+ ## Related functions
92
94
 
93
95
  - [`createWebhook()`](/docs/api-reference/workflow/create-webhook) - Create a webhook in a workflow
94
96
  - [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) - Resume a hook with arbitrary payload
@@ -2,9 +2,11 @@
2
2
  title: start
3
3
  description: Start and enqueue a new workflow run.
4
4
  type: reference
5
- summary: Use start to programmatically enqueue a new workflow run from outside a workflow function.
5
+ summary: Use start to programmatically enqueue a new workflow run.
6
6
  prerequisites:
7
7
  - /docs/foundations/starting-workflows
8
+ related:
9
+ - /docs/foundations/idempotency
8
10
  ---
9
11
 
10
12
  Start/enqueue a new workflow run.
@@ -16,7 +18,7 @@ import { myWorkflow } from "./workflows/my-workflow";
16
18
  const run = await start(myWorkflow); // [!code highlight]
17
19
  ```
18
20
 
19
- ## API Signature
21
+ ## API signature
20
22
 
21
23
  ### Parameters
22
24
 
@@ -48,21 +50,26 @@ showSections={["returns"]}
48
50
 
49
51
  Learn more about [`WorkflowReadableStreamOptions`](/docs/api-reference/workflow-api/get-run#workflowreadablestreamoptions).
50
52
 
51
- ## Good to Know
53
+ ## Good to know
52
54
 
53
- * The `start()` function is used in runtime/non-workflow contexts to programmatically trigger workflow executions.
55
+ * Use the `start()` function in runtime contexts to programmatically trigger workflow executions.
56
+ * In v5, you can also call `start()` directly from a workflow function to spawn a child run or continue work in a new run. See [Workflow Composition](/cookbook/common-patterns/workflow-composition) and [Versioning](/docs/foundations/versioning).
54
57
  * This is different from calling workflow functions directly, which is the typical pattern in Next.js applications.
55
- * The function returns immediately after enqueuing the workflow - it doesn't wait for the workflow to complete.
58
+ * The function returns immediately after enqueuing the workflow. It doesn't wait for the workflow to complete.
59
+ * Each call to `start()` creates a new workflow run. If retried requests must route to one active workflow, have the workflow create a deterministic hook token and use [`getHookByToken()`](/docs/api-reference/workflow-api/get-hook-by-token) to reuse an already-registered active hook. The lookup is not atomic with `start()`, so concurrent callers can still create extra runs before the hook is registered. Handle that race inside the workflow by checking `await hook.getConflict()` before duplicate-sensitive work. On a conflict, it resolves with the run that owns the token, so the duplicate can return the active owner to the caller. If duplicates must be rejected before a workflow body runs, keep a durable request record until native atomic start-and-hook registration exists. See [Idempotency](/docs/foundations/idempotency#run-idempotency).
56
60
  * All arguments must be [serializable](/docs/foundations/serialization).
57
- * When `deploymentId` is provided, the argument types and return type become `unknown` since there is no guarantee the workflow function's types will be consistent across different deployments.
61
+ * When you provide `deploymentId`, the argument types and return type become `unknown` because the workflow function's types may differ across deployments.
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
+ * `region` pins the new run to a specific region on Worlds with a regional dimension. The [Vercel World](/worlds/vercel#explicit-region-selection) then serves the run's storage, queue dispatch, and streams from that region. When you omit `region`, the run is pinned to the region where it was created. Worlds without regions ignore the option.
64
+ * `experimental_retention` asks the World to delete the run's user data as soon as the run completes or fails, instead of keeping it for the World's default window. `0` requests immediate deletion; `'default'` is identical to omitting the option. These are the only two values accepted — the value is a duration and zero is the only one implemented, and its unit is not yet decided. Recorded as the reserved `$retention` attribute, so it needs a World implementing spec version 4 or later. Retention is enforced by the World, not the SDK: the first-party Worlds implement it and a World that does not keeps the data. Note that `await run.returnValue` on a run started with `experimental_retention: 0` usually throws [`RunExpiredError`](/docs/errors/run-expired) rather than resolving, because the deletion races the read. See [Data retention](/docs/observability/retention).
58
65
 
59
66
  <Callout type="info">
60
- 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).
67
+ 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 compiler did not transform the passed function 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).
61
68
  </Callout>
62
69
 
63
70
  ## Examples
64
71
 
65
- ### With Arguments
72
+ ### With arguments
66
73
 
67
74
  ```typescript
68
75
  import { start } from "workflow/api";
@@ -78,13 +85,14 @@ import { start } from "workflow/api";
78
85
  import { myWorkflow } from "./workflows/my-workflow";
79
86
 
80
87
  const run = await start(myWorkflow, ["arg1", "arg2"], { // [!code highlight]
81
- deploymentId: "custom-deployment-id" // [!code highlight]
88
+ deploymentId: "custom-deployment-id", // [!code highlight]
89
+ attributes: { source: "checkout" } // [!code highlight]
82
90
  }); // [!code highlight]
83
91
  ```
84
92
 
85
93
  ### Using `deploymentId: "latest"`
86
94
 
87
- Set `deploymentId` to `"latest"` to automatically resolve the most recent deployment for the current environment. This is useful when you want to ensure a workflow run targets the latest deployed version of your application rather than the deployment that initiated the call.
95
+ Set `deploymentId` to `"latest"` to automatically resolve the most recent deployment for the current environment. This is useful when you want to ensure a workflow run targets the latest deployed version of your application rather than the deployment that initiated the call. For when to use this and how it fits with default run pinning, see [Versioning](/docs/foundations/versioning).
88
96
 
89
97
  ```typescript
90
98
  import { start } from "workflow/api";
@@ -96,12 +104,51 @@ const run = await start(myWorkflow, ["arg1", "arg2"], { // [!code highlight]
96
104
  ```
97
105
 
98
106
  <Callout type="info">
99
- The `deploymentId` option is currently a Vercel-specific feature. The `"latest"` value resolves to the most recent deployment matching your current environment the same production target for production deployments, or the same git branch for preview deployments.
107
+ The `deploymentId` option is currently a Vercel-specific feature. Other Worlds may implement this option differently to match their own deployment runtimes, and the World spec may rename it from `deploymentId` to `version` in a future SDK version. On Vercel, `"latest"` resolves to the most recent deployment matching your current environment: the same production target for production deployments, or the same git branch for preview deployments.
108
+
109
+ In Worlds without atomic, immutable deployments (such as local development or self-hosted Postgres), there is no notion of multiple deployments to resolve between, so `deploymentId: "latest"` has no effect: the SDK logs a warning and the run targets the current deployment. This means a workflow that opts into `"latest"` on Vercel still runs unchanged in local development.
110
+ </Callout>
111
+
112
+ <Callout type="info">
113
+ Resolving `"latest"` is the one `start()` path that calls the Vercel API, so it
114
+ needs an identity that can see the calling deployment. Inside a Vercel
115
+ deployment the SDK authenticates with the deployment's own OIDC token, which
116
+ carries the owning team, and this takes precedence over a `VERCEL_TOKEN` set in
117
+ the function's environment. A `VERCEL_TOKEN` belongs to a *user* and carries no
118
+ team, so authenticating with it scopes the lookup to that user's default team
119
+ and fails with a 404 whenever that is not the team that owns the deployment.
120
+ Outside a deployment (CLI, CI, the dashboard) `VERCEL_TOKEN` is still used;
121
+ configure the World's `teamId` so the request is scoped explicitly.
100
122
  </Callout>
101
123
 
102
124
  <Callout type="warn">
103
125
  When using `deploymentId: "latest"`, the workflow run will execute on a potentially different deployment than the one calling `start()`. Be mindful of forward and backward compatibility:
104
126
 
105
- - **Workflow identity**: The workflow ID is derived from the function name and file path. If the latest deployment has renamed the workflow function or moved it to a different directory, the workflow ID will no longer match and the run will fail to start.
106
- - **Input and output compatibility**: The arguments passed to `start()` are serialized by the calling deployment but deserialized by the target deployment. Similarly, the workflow's return value is serialized by the target deployment but deserialized by the caller. If the workflow's expected arguments or return type have changed (e.g. added required fields, removed fields, or changed types), the run may fail or behave unexpectedly. Ensure that input and output schemas remain backward-compatible across deployments.
127
+ - **Workflow identity**: The function name and file path determine the workflow ID. If the latest deployment has renamed the workflow function or moved it to a different directory, the workflow ID will no longer match and the run will fail to start.
128
+ - **Input and output compatibility**: The calling deployment serializes the arguments passed to `start()`, and the target deployment deserializes them. Similarly, the target deployment serializes the workflow's return value, and the caller deserializes it. If the workflow's expected arguments or return type have changed (for example, added required fields, removed fields, or changed types), the run may fail or behave unexpectedly. Ensure that input and output schemas remain backward-compatible across deployments.
129
+ </Callout>
130
+
131
+ ### Inside a workflow function
132
+
133
+ Call `start()` directly from a workflow function to spawn a child run. It is step-backed, so the spawn records a deterministic step boundary in the parent's event log.
134
+
135
+ ```typescript
136
+ import { start } from "workflow/api";
137
+ import { childWorkflow } from "./workflows/child";
138
+
139
+ export async function parentWorkflow(value: number) {
140
+ "use workflow";
141
+
142
+ const childRun = await start(childWorkflow, [value]); // [!code highlight]
143
+ const result = await childRun.returnValue; // [!code highlight]
144
+ return { childRunId: childRun.runId, result };
145
+ }
146
+ ```
147
+
148
+ <Callout type="info">
149
+ The returned `Run` object is fully functional inside a workflow. Each property access or method call (`.status`, `.returnValue`, `.cancel()`) executes as a separate step. See [Workflow Composition](/cookbook/common-patterns/workflow-composition) for choosing between spawning a child run and awaiting a workflow function directly.
150
+ </Callout>
151
+
152
+ <Callout type="warn">
153
+ `returnValue` polls the child run every second and holds the polling step's worker slot open for as long as the child takes to finish. For long-running children, spawn without awaiting `returnValue` and have the child resume a [hook](/docs/foundations/hooks) when it completes. See the [`startAndWait()` pattern](/cookbook/advanced/child-workflows).
107
154
  </Callout>
@@ -0,0 +1,18 @@
1
+ ---
2
+ title: "workflow/astro"
3
+ description: Astro integration for automatic workflow bundling and route registration.
4
+ type: overview
5
+ summary: Explore the Astro integration for automatic workflow bundling and runtime support.
6
+ related:
7
+ - /docs/getting-started/astro
8
+ ---
9
+
10
+ Astro integration for Workflow SDK that transforms workflow code and builds the workflow bundles.
11
+
12
+ ## Functions
13
+
14
+ <Cards>
15
+ <Card title="workflow()" href="/docs/api-reference/workflow-astro/workflow">
16
+ Astro integration that transforms workflow code (`"use step"`/`"use workflow"` directives)
17
+ </Card>
18
+ </Cards>
@@ -0,0 +1,4 @@
1
+ {
2
+ "title": "workflow/astro",
3
+ "pages": ["workflow"]
4
+ }
@@ -0,0 +1,45 @@
1
+ ---
2
+ title: workflow
3
+ description: Configure Astro to transform workflow directives.
4
+ type: reference
5
+ summary: Add the workflow integration to your Astro config to enable workflow directive transformation.
6
+ prerequisites:
7
+ - /docs/getting-started/astro
8
+ ---
9
+
10
+ Returns an Astro integration that transforms workflow code (`"use step"`/`"use workflow"` directives) and builds the workflow bundles.
11
+
12
+ ## Usage
13
+
14
+ To enable `"use step"` and `"use workflow"` directives while developing locally or deploying to production, add `workflow()` to the `integrations` array of your Astro config.
15
+
16
+ ```typescript title="astro.config.mjs" lineNumbers
17
+ // @ts-check
18
+ import { defineConfig } from "astro/config";
19
+ import { workflow } from "workflow/astro"; // [!code highlight]
20
+
21
+ // https://astro.build/config
22
+ export default defineConfig({
23
+ integrations: [workflow()], // [!code highlight]
24
+ });
25
+ ```
26
+
27
+ The integration registers the workflow Vite transform plugins during `astro:config:setup` and builds the workflow bundles: locally during config setup, or via the Vercel builder after `astro:build:done` when deploying to Vercel.
28
+
29
+ ## API signature
30
+
31
+ ### Parameters
32
+
33
+ | Parameter | Type | Description |
34
+ | --- | --- | --- |
35
+ | `options` | `WorkflowPluginOptions` | Optional. Configures the workflow build. |
36
+
37
+ #### WorkflowPluginOptions
38
+
39
+ | Option | Type | Default | Description |
40
+ | --- | --- | --- | --- |
41
+ | `sourcemap` | `boolean \| 'inline' \| 'linked' \| 'external' \| 'both'` | `'inline'` (dev) / `false` (prod) | Controls source maps on generated workflow bundles. Accepts the same values as esbuild's `sourcemap` option. Defaults to `'inline'` in development and `false` in production (smaller function bundles, which helps stay under the Vercel 250 MB function size limit). Set it explicitly, or use the `WORKFLOW_SOURCEMAP` environment variable, to override in either environment. |
42
+
43
+ ### Returns
44
+
45
+ Returns an `AstroIntegration` object to include in the `integrations` array of your Astro config.
@@ -26,12 +26,12 @@ try {
26
26
  await world.events.create(runId, event);
27
27
  } catch (error) {
28
28
  if (EntityConflictError.is(error)) { // [!code highlight]
29
- // Event already exists safe to ignore during replay
29
+ // Event already exists, safe to ignore during replay
30
30
  }
31
31
  }
32
32
  ```
33
33
 
34
- ## API Signature
34
+ ## API signature
35
35
 
36
36
  ### Properties
37
37
 
@@ -44,11 +44,11 @@ interface EntityConflictError {
44
44
  export default EntityConflictError;`}
45
45
  />
46
46
 
47
- ### Static Methods
47
+ ### Static methods
48
48
 
49
49
  #### `EntityConflictError.is(value)`
50
50
 
51
- Type-safe check for `EntityConflictError` instances. Preferred over `instanceof` because it works across module boundaries and VM contexts.
51
+ Type-safe check for `EntityConflictError` instances. Preferred over `instanceof` because it works across module boundaries and virtual machine contexts.
52
52
 
53
53
  ```typescript
54
54
  import { EntityConflictError } from "workflow/errors"
@@ -0,0 +1,60 @@
1
+ ---
2
+ title: HookConflictError
3
+ description: Thrown when creating a hook with a token that is already in use by another workflow run.
4
+ type: reference
5
+ summary: Catch HookConflictError when a hook token is already claimed by another active workflow run.
6
+ related:
7
+ - /docs/api-reference/workflow/create-hook
8
+ - /docs/foundations/hooks
9
+ - /docs/errors/hook-conflict
10
+ ---
11
+
12
+ `HookConflictError` is thrown when creating a hook with a token that is already in use by another active workflow run. Hook tokens must be unique across all running workflows. See the [hook-conflict](/docs/errors/hook-conflict) error guide for resolution strategies.
13
+
14
+ ```typescript lineNumbers
15
+ import { HookConflictError } from "workflow/errors"
16
+ declare function startApprovalWorkflow(token: string): Promise<void>; // @setup
17
+ declare const token: string; // @setup
18
+
19
+ try {
20
+ await startApprovalWorkflow(token);
21
+ } catch (error) {
22
+ if (HookConflictError.is(error)) { // [!code highlight]
23
+ console.error(
24
+ `Token "${error.token}" already in use by run ${error.conflictingRunId}`
25
+ );
26
+ }
27
+ }
28
+ ```
29
+
30
+ ## API signature
31
+
32
+ ### Properties
33
+
34
+ <TSDoc
35
+ definition={`
36
+ interface HookConflictError {
37
+ /** The hook token that conflicted. */
38
+ token: string;
39
+ /** The run ID of the workflow currently holding the token, when known. */
40
+ conflictingRunId?: string;
41
+ /** The error message. */
42
+ message: string;
43
+ }
44
+ export default HookConflictError;`}
45
+ />
46
+
47
+ ### Static methods
48
+
49
+ #### `HookConflictError.is(value)`
50
+
51
+ Type-safe check for `HookConflictError` instances. Preferred over `instanceof` because it works across module boundaries and virtual machine contexts.
52
+
53
+ ```typescript
54
+ import { HookConflictError } from "workflow/errors"
55
+ declare const error: unknown; // @setup
56
+
57
+ if (HookConflictError.is(error)) {
58
+ // error is typed as HookConflictError
59
+ }
60
+ ```
@@ -10,9 +10,9 @@ related:
10
10
 
11
11
  `HookNotFoundError` is thrown when calling `resumeHook()` or `resumeWebhook()` with a token that does not match any active hook. This typically happens when:
12
12
 
13
- - The hook has expired (past its TTL)
14
- - The hook was already consumed and disposed
15
- - The workflow has not started yet, so the hook does not exist
13
+ - The hook's time to live (TTL) has expired.
14
+ - The hook was already consumed and disposed.
15
+ - The workflow has not started yet, so the hook does not exist.
16
16
 
17
17
  ```typescript lineNumbers
18
18
  import { HookNotFoundError } from "workflow/errors"
@@ -29,7 +29,7 @@ try {
29
29
  }
30
30
  ```
31
31
 
32
- ## API Signature
32
+ ## API signature
33
33
 
34
34
  ### Properties
35
35
 
@@ -44,11 +44,11 @@ interface HookNotFoundError {
44
44
  export default HookNotFoundError;`}
45
45
  />
46
46
 
47
- ### Static Methods
47
+ ### Static methods
48
48
 
49
49
  #### `HookNotFoundError.is(value)`
50
50
 
51
- Type-safe check for `HookNotFoundError` instances. Preferred over `instanceof` because it works across module boundaries and VM contexts.
51
+ Type-safe check for `HookNotFoundError` instances. Preferred over `instanceof` because it works across module boundaries and virtual machine contexts.
52
52
 
53
53
  ```typescript
54
54
  import { HookNotFoundError } from "workflow/errors"
@@ -66,7 +66,7 @@ if (HookNotFoundError.is(error)) {
66
66
  A common pattern for idempotent workflows is to try resuming a hook, and if it doesn't exist yet, start a new workflow run with the input data.
67
67
 
68
68
  <Callout>
69
- This "resume or start" pattern is not atomic there is a small window where a race condition is possible. A better native approach is being worked on, but this pattern works well for many use cases.
69
+ This "resume or start" pattern is not atomic: there is a small window where a race condition is possible. A better native approach is being worked on, but this pattern works well for many use cases.
70
70
  </Callout>
71
71
 
72
72
  ```typescript lineNumbers
@@ -80,7 +80,7 @@ async function handleIncomingEvent(token: string, data: unknown) {
80
80
  await resumeHook(token, data);
81
81
  } catch (error) {
82
82
  if (HookNotFoundError.is(error)) { // [!code highlight]
83
- // Hook doesn't exist yet start a new workflow run
83
+ // Hook doesn't exist yet, so start a new workflow run
84
84
  await startWorkflow("processEvent", data); // [!code highlight]
85
85
  } else {
86
86
  throw error;