workflow 5.0.0-beta.1 → 5.0.0-beta.11

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 (113) hide show
  1. package/README.md +4 -4
  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 +2 -2
  5. package/dist/api.d.ts +5 -1
  6. package/dist/api.d.ts.map +1 -1
  7. package/dist/api.js +14 -2
  8. package/dist/astro.js +1 -1
  9. package/dist/index.js +1 -1
  10. package/dist/internal/builtins.d.ts +17 -0
  11. package/dist/internal/builtins.d.ts.map +1 -1
  12. package/dist/internal/builtins.js +65 -1
  13. package/dist/internal/class-serialization.js +1 -1
  14. package/dist/internal/errors.js +1 -1
  15. package/dist/nest.js +1 -1
  16. package/dist/next.cjs +1 -1
  17. package/dist/nitro.js +1 -1
  18. package/dist/nuxt.js +1 -1
  19. package/dist/observability.d.ts +1 -1
  20. package/dist/observability.js +2 -2
  21. package/dist/runtime.d.ts +1 -1
  22. package/dist/runtime.d.ts.map +1 -1
  23. package/dist/runtime.js +2 -2
  24. package/dist/stdlib.js +1 -1
  25. package/dist/sveltekit.js +1 -1
  26. package/dist/typescript-plugin.cjs +1 -1
  27. package/dist/vite.js +1 -1
  28. package/dist/workflow.js +1 -1
  29. package/docs/ai/index.mdx +6 -5
  30. package/docs/ai/resumable-streams.mdx +1 -1
  31. package/docs/api-reference/vitest/index.mdx +28 -1
  32. package/docs/api-reference/workflow/create-webhook.mdx +37 -18
  33. package/docs/api-reference/workflow/experimental-set-attributes.mdx +63 -0
  34. package/docs/api-reference/workflow/get-workflow-metadata.mdx +34 -0
  35. package/docs/api-reference/workflow/index.mdx +3 -0
  36. package/docs/api-reference/workflow-ai/durable-agent.mdx +0 -4
  37. package/docs/api-reference/workflow-ai/index.mdx +0 -5
  38. package/docs/api-reference/workflow-ai/workflow-chat-transport.mdx +0 -4
  39. package/docs/api-reference/workflow-api/start.mdx +5 -4
  40. package/docs/api-reference/workflow-errors/workflow-run-failed-error.mdx +16 -6
  41. package/docs/api-reference/workflow-next/with-workflow.mdx +34 -2
  42. package/docs/changelog/attributes-mvp.mdx +368 -0
  43. package/docs/changelog/eager-processing.mdx +595 -0
  44. package/docs/changelog/index.mdx +2 -1
  45. package/docs/cookbook/advanced/child-workflows.mdx +313 -0
  46. package/docs/cookbook/advanced/meta.json +9 -0
  47. package/docs/cookbook/advanced/publishing-libraries.mdx +336 -0
  48. package/docs/cookbook/advanced/serializable-steps.mdx +147 -0
  49. package/docs/cookbook/advanced/upgrading-workflows.mdx +195 -0
  50. package/docs/cookbook/agent-patterns/agent-cancellation.mdx +156 -0
  51. package/docs/cookbook/agent-patterns/durable-agent.mdx +154 -0
  52. package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +255 -0
  53. package/docs/cookbook/agent-patterns/meta.json +4 -0
  54. package/docs/cookbook/common-patterns/batching.mdx +105 -0
  55. package/docs/cookbook/common-patterns/idempotency.mdx +107 -0
  56. package/docs/cookbook/common-patterns/meta.json +15 -0
  57. package/docs/cookbook/common-patterns/rate-limiting.mdx +228 -0
  58. package/docs/cookbook/common-patterns/saga.mdx +247 -0
  59. package/docs/cookbook/common-patterns/scheduling.mdx +129 -0
  60. package/docs/cookbook/common-patterns/sequential-and-parallel.mdx +155 -0
  61. package/docs/cookbook/common-patterns/timeouts.mdx +99 -0
  62. package/docs/cookbook/common-patterns/webhooks.mdx +185 -0
  63. package/docs/cookbook/common-patterns/workflow-composition.mdx +111 -0
  64. package/docs/cookbook/index.mdx +38 -0
  65. package/docs/cookbook/integrations/ai-sdk.mdx +383 -0
  66. package/docs/cookbook/integrations/chat-sdk.mdx +307 -0
  67. package/docs/cookbook/integrations/meta.json +4 -0
  68. package/docs/cookbook/integrations/sandbox.mdx +520 -0
  69. package/docs/cookbook/meta.json +5 -0
  70. package/docs/deploying/building-a-world.mdx +1 -1
  71. package/docs/deploying/world/local-world.mdx +1 -1
  72. package/docs/deploying/world/postgres-world.mdx +6 -4
  73. package/docs/deploying/world/vercel-world.mdx +3 -1
  74. package/docs/errors/abort-signal-timeout-in-workflow.mdx +80 -0
  75. package/docs/errors/hook-conflict.mdx +56 -4
  76. package/docs/errors/index.mdx +3 -0
  77. package/docs/errors/runtime-decryption-failed.mdx +77 -0
  78. package/docs/errors/start-invalid-workflow-function.mdx +1 -1
  79. package/docs/foundations/cancellation.mdx +460 -0
  80. package/docs/foundations/errors-and-retries.mdx +7 -3
  81. package/docs/foundations/index.mdx +3 -3
  82. package/docs/foundations/meta.json +3 -2
  83. package/docs/foundations/serialization.mdx +78 -42
  84. package/docs/foundations/starting-workflows.mdx +6 -2
  85. package/docs/foundations/versioning.mdx +263 -0
  86. package/docs/getting-started/astro.mdx +6 -0
  87. package/docs/getting-started/index.mdx +13 -7
  88. package/docs/getting-started/meta.json +3 -1
  89. package/docs/getting-started/nestjs.mdx +8 -0
  90. package/docs/getting-started/next.mdx +5 -3
  91. package/docs/getting-started/nitro.mdx +22 -0
  92. package/docs/getting-started/python.mdx +165 -0
  93. package/docs/getting-started/sveltekit.mdx +6 -0
  94. package/docs/getting-started/tanstack-start.mdx +241 -0
  95. package/docs/how-it-works/cancellation.mdx +287 -0
  96. package/docs/how-it-works/code-transform.mdx +2 -2
  97. package/docs/how-it-works/event-sourcing.mdx +2 -2
  98. package/docs/how-it-works/meta.json +2 -1
  99. package/docs/internal/index.mdx +19 -0
  100. package/docs/internal/meta.json +5 -0
  101. package/docs/internal/serializable-abort-controller.mdx +148 -0
  102. package/docs/meta.json +1 -0
  103. package/docs/migration-guides/index.mdx +34 -0
  104. package/docs/migration-guides/meta.json +9 -0
  105. package/docs/migration-guides/migrating-from-aws-step-functions.mdx +358 -0
  106. package/docs/migration-guides/migrating-from-inngest.mdx +304 -0
  107. package/docs/migration-guides/migrating-from-temporal.mdx +313 -0
  108. package/docs/migration-guides/migrating-from-trigger-dev.mdx +328 -0
  109. package/docs/observability/attributes.mdx +66 -0
  110. package/docs/observability/index.mdx +9 -1
  111. package/docs/observability/meta.json +1 -1
  112. package/package.json +14 -13
  113. package/docs/foundations/common-patterns.mdx +0 -265
@@ -3,12 +3,13 @@
3
3
  "pages": [
4
4
  "workflows-and-steps",
5
5
  "starting-workflows",
6
- "common-patterns",
7
6
  "errors-and-retries",
8
7
  "hooks",
9
8
  "streaming",
9
+ "cancellation",
10
10
  "serialization",
11
- "idempotency"
11
+ "idempotency",
12
+ "versioning"
12
13
  ],
13
14
  "defaultOpen": true
14
15
  }
@@ -55,6 +55,50 @@ These types have special handling and are explained in detail in the sections be
55
55
  - `Response`
56
56
  - `ReadableStream<Serializable>`
57
57
  - `WritableStream<Serializable>`
58
+ - `AbortController`
59
+ - `AbortSignal`
60
+
61
+ ## Pass-by-Value Semantics
62
+
63
+ **Parameters are passed by value, not by reference.** Steps receive deserialized copies of data. Mutations inside a step won't affect the original in the workflow.
64
+
65
+ **Incorrect:**
66
+
67
+ ```typescript title="workflows/incorrect-mutation.ts" lineNumbers
68
+ export async function updateUserWorkflow(userId: string) {
69
+ "use workflow";
70
+
71
+ let user = { id: userId, name: "John", email: "john@example.com" };
72
+ await updateUserStep(user);
73
+
74
+ // user.email is still "john@example.com" // [!code highlight]
75
+ console.log(user.email); // [!code highlight]
76
+ }
77
+
78
+ async function updateUserStep(user: { id: string; name: string; email: string }) {
79
+ "use step";
80
+ user.email = "newemail@example.com"; // Changes are lost // [!code highlight]
81
+ }
82
+ ```
83
+
84
+ **Correct - return the modified data:**
85
+
86
+ ```typescript title="workflows/correct-mutation.ts" lineNumbers
87
+ export async function updateUserWorkflow(userId: string) {
88
+ "use workflow";
89
+
90
+ let user = { id: userId, name: "John", email: "john@example.com" };
91
+ user = await updateUserStep(user); // Reassign the return value // [!code highlight]
92
+
93
+ console.log(user.email); // "newemail@example.com"
94
+ }
95
+
96
+ async function updateUserStep(user: { id: string; name: string; email: string }) {
97
+ "use step";
98
+ user.email = "newemail@example.com";
99
+ return user; // [!code highlight]
100
+ }
101
+ ```
58
102
 
59
103
  **Custom Classes:**
60
104
 
@@ -125,6 +169,39 @@ export async function fetch(...args: Parameters<typeof globalThis.fetch>) {
125
169
 
126
170
  This allows you to make HTTP requests directly in workflow functions while maintaining deterministic replay behavior through automatic caching.
127
171
 
172
+ ## AbortController & AbortSignal
173
+
174
+ `AbortController` and `AbortSignal` are serializable types that enable cooperative cancellation across workflow and step boundaries. Inside a workflow function, `new AbortController()` creates a durable controller that works across suspensions and step boundaries:
175
+
176
+ ```typescript lineNumbers
177
+ import { sleep } from "workflow";
178
+
179
+ export async function cancellableWorkflow() {
180
+ "use workflow";
181
+
182
+ const controller = new AbortController(); // [!code highlight]
183
+
184
+ const result = await Promise.race([
185
+ fetchData(controller.signal), // [!code highlight]
186
+ sleep("10s").then(() => null),
187
+ ]);
188
+
189
+ if (result === null) {
190
+ controller.abort(); // [!code highlight]
191
+ }
192
+
193
+ return result;
194
+ }
195
+
196
+ async function fetchData(signal: AbortSignal) {
197
+ "use step";
198
+ const response = await fetch("https://api.example.com/data", { signal });
199
+ return response.json();
200
+ }
201
+ ```
202
+
203
+ For usage patterns including timeouts, parallel cancellation, user-triggered cancellation, and run cancellation, see the [Cancellation Guide](/docs/foundations/cancellation). For details on the hook and stream backing that makes this work, see [How Cancellation Works](/docs/how-it-works/cancellation).
204
+
128
205
  ## Custom Class Serialization
129
206
 
130
207
  By default, custom class instances cannot be serialized because the serialization system doesn't know how to reconstruct them. You can make your classes serializable by implementing two static methods using special symbols from the `@workflow/serde` package.
@@ -185,7 +262,7 @@ async function doublePoint(point: Point) {
185
262
 
186
263
  2. **`WORKFLOW_DESERIALIZE`**: A static method that receives the serialized data and returns a new class instance
187
264
 
188
- 3. **Automatic Registration**: The SWC compiler plugin automatically detects classes that implement these symbols and registers them for serialization
265
+ 3. **Automatic Registration**: The SWC compiler plugin automatically detects classes that implement these symbols and registers them for serialization. Each class receives a deterministic `classId` derived from its file path and class name, and is registered into the global `Symbol.for("workflow-class-registry")` registry at build time — no manual registration step is required
189
266
 
190
267
  ### Requirements
191
268
 
@@ -332,44 +409,3 @@ export async function processOrderWorkflow() {
332
409
  }
333
410
  ```
334
411
 
335
- ## Pass-by-Value Semantics
336
-
337
- **Parameters are passed by value, not by reference.** Steps receive deserialized copies of data. Mutations inside a step won't affect the original in the workflow.
338
-
339
- **Incorrect:**
340
-
341
- ```typescript title="workflows/incorrect-mutation.ts" lineNumbers
342
- export async function updateUserWorkflow(userId: string) {
343
- "use workflow";
344
-
345
- let user = { id: userId, name: "John", email: "john@example.com" };
346
- await updateUserStep(user);
347
-
348
- // user.email is still "john@example.com" // [!code highlight]
349
- console.log(user.email); // [!code highlight]
350
- }
351
-
352
- async function updateUserStep(user: { id: string; name: string; email: string }) {
353
- "use step";
354
- user.email = "newemail@example.com"; // Changes are lost // [!code highlight]
355
- }
356
- ```
357
-
358
- **Correct - return the modified data:**
359
-
360
- ```typescript title="workflows/correct-mutation.ts" lineNumbers
361
- export async function updateUserWorkflow(userId: string) {
362
- "use workflow";
363
-
364
- let user = { id: userId, name: "John", email: "john@example.com" };
365
- user = await updateUserStep(user); // Reassign the return value // [!code highlight]
366
-
367
- console.log(user.email); // "newemail@example.com"
368
- }
369
-
370
- async function updateUserStep(user: { id: string; name: string; email: string }) {
371
- "use step";
372
- user.email = "newemail@example.com";
373
- return user; // [!code highlight]
374
- }
375
- ```
@@ -13,7 +13,7 @@ Once you've defined your workflow functions, you need to trigger them to begin e
13
13
 
14
14
  ## The `start()` Function
15
15
 
16
- The [`start()`](/docs/api-reference/workflow-api/start) function is used to programmatically trigger workflow executions from runtime contexts like API routes, Server Actions, or any server-side code.
16
+ The [`start()`](/docs/api-reference/workflow-api/start) function is used to programmatically trigger workflow executions from runtime contexts like API routes, Server Actions, or any server-side code. In v5, you can also call `start()` from inside a workflow function when you want to spawn a child run or continue work in a new run.
17
17
 
18
18
  ```typescript lineNumbers
19
19
  import { start } from "workflow/api";
@@ -41,6 +41,10 @@ export async function POST(request: Request) {
41
41
 
42
42
  **Learn more**: [`start()` API Reference](/docs/api-reference/workflow-api/start)
43
43
 
44
+ <Callout type="info">
45
+ For parent-child workflow patterns, see [Workflow Composition](/cookbook/common-patterns/workflow-composition). For long-lived workflows that intentionally hand off to newer deployments with `deploymentId: "latest"`, see [Versioning](/docs/foundations/versioning).
46
+ </Callout>
47
+
44
48
  ## The `Run` Object
45
49
 
46
50
  When you call `start()`, it returns a [`Run`](/docs/api-reference/workflow-api/start#returns) object that provides access to the workflow's status and results.
@@ -213,6 +217,6 @@ export async function GET(request: Request) {
213
217
 
214
218
  Now that you understand how to start workflows and track their execution:
215
219
 
216
- - Learn about [Common Patterns](/docs/foundations/common-patterns) for organizing complex workflows
220
+ - Browse the [Cookbook](/cookbook) for copy-paste recipes covering composition, scheduling, timeouts, and more
217
221
  - Explore [Errors & Retrying](/docs/foundations/errors-and-retries) to handle failures gracefully
218
222
  - Check the [`start()` API Reference](/docs/api-reference/workflow-api/start) for complete details
@@ -0,0 +1,263 @@
1
+ ---
2
+ title: Versioning
3
+ description: Understand how workflow runs are pinned to deployments, how to recover runs after a fix, and how to opt in to newer code explicitly.
4
+ type: guide
5
+ summary: Keep in-flight runs stable by default, then choose explicit upgrade boundaries when you need them.
6
+ prerequisites:
7
+ - /docs/foundations/starting-workflows
8
+ related:
9
+ - /docs/api-reference/workflow-api/start
10
+ - /docs/foundations/cancellation
11
+ - /cookbook/common-patterns/workflow-composition
12
+ ---
13
+
14
+ Workflow runs are pinned to the deployment that starts them. When a run begins, Workflow SDK records the deployment for that run and continues executing the run on that same copy of your code.
15
+
16
+ That default is intentional. Durable workflows can pause for minutes, days, or months. If the code underneath a paused run changed every time you deployed, an in-flight run could resume into a different function body, different step names, or different input types than the ones it started with. That can make type safety fragile and can break long-running work in hard-to-debug ways.
17
+
18
+ With Workflow SDK, you can keep shipping. New runs use new deployments, while existing runs keep the version they already understand.
19
+
20
+ ## Default behavior
21
+
22
+ Start a workflow normally:
23
+
24
+ ```typescript title="app/api/orders/route.ts" lineNumbers
25
+ import { start } from "workflow/api";
26
+ import { fulfillOrder } from "@/workflows/fulfill-order";
27
+
28
+ export async function POST(request: Request) {
29
+ const { orderId } = await request.json();
30
+
31
+ const run = await start(fulfillOrder, [orderId]); // [!code highlight]
32
+
33
+ return Response.json({ runId: run.runId });
34
+ }
35
+ ```
36
+
37
+ The run is tied to the deployment that handled this request. If you deploy a new version while the workflow is [sleeping](/docs/api-reference/workflow/sleep), [waiting on a hook](/docs/foundations/hooks), [retrying a step](/docs/foundations/errors-and-retries), or processing later queue messages, that existing run still resumes on the original deployment.
38
+
39
+ ```typescript title="workflows/fulfill-order.ts" lineNumbers
40
+ import { sleep } from "workflow";
41
+
42
+ export async function fulfillOrder(orderId: string) {
43
+ "use workflow";
44
+
45
+ await reserveInventory(orderId);
46
+ await sleep("2d");
47
+ await chargeCustomer(orderId);
48
+ await shipOrder(orderId);
49
+ }
50
+
51
+ async function reserveInventory(orderId: string) {
52
+ "use step";
53
+ // ...
54
+ }
55
+
56
+ async function chargeCustomer(orderId: string) {
57
+ "use step";
58
+ // ...
59
+ }
60
+
61
+ async function shipOrder(orderId: string) {
62
+ "use step";
63
+ // ...
64
+ }
65
+ ```
66
+
67
+ If you deploy a change to `chargeCustomer()` while a run is in the two-day sleep, the existing run does not suddenly resume into the new implementation. It continues on the deployment it started on. The next order starts on the latest deployment and uses the new code from the beginning.
68
+
69
+ ## Fixing in-flight runs
70
+
71
+ Sometimes you deploy because the old code had a bug. The safest fix is usually explicit:
72
+
73
+ 1. Deploy the fixed code.
74
+ 2. Find the affected runs in [observability](/docs/observability) or with the CLI.
75
+ 3. Cancel the old runs if they are still running.
76
+ 4. Rerun them on the latest deployment with the same inputs.
77
+
78
+ This keeps the version boundary visible. The old run ends as cancelled or failed, and the replacement run starts fresh on the fixed deployment. This is a good fit for one-off, ad-hoc upgrades where you explicitly opt in to moving affected runs onto a new version.
79
+
80
+ ```bash
81
+ # Inspect affected runs and copy the exact workflowName value.
82
+ npx workflow inspect runs \
83
+ --backend vercel \
84
+ --status running
85
+
86
+ # Cancel one run.
87
+ npx workflow cancel <run-id> \
88
+ --backend vercel
89
+
90
+ # Or bulk-cancel matching running runs.
91
+ npx workflow cancel \
92
+ --status running \
93
+ --workflowName "workflow//./workflows/fulfill-order//fulfillOrder" \
94
+ --backend vercel
95
+ ```
96
+
97
+ The `--workflowName` filter expects the generated workflow ID, not only the exported function's short name. Use the `workflowName` value from `workflow inspect runs`, and use [`parseWorkflowName()`](/docs/api-reference/workflow-api/world/observability) when you need display-friendly names.
98
+
99
+ In the [observability UI](/docs/observability), use **Rerun on latest** to enqueue the workflow again with the same inputs against the latest deployment.
100
+
101
+ If you are writing your own recovery route, call `start()` with the same arguments and `deploymentId: "latest"`:
102
+
103
+ ```typescript title="app/api/orders/rerun/route.ts" lineNumbers
104
+ import { start } from "workflow/api";
105
+ import { fulfillOrder } from "@/workflows/fulfill-order";
106
+
107
+ export async function POST(request: Request) {
108
+ const { orderId } = await request.json();
109
+
110
+ const run = await start(fulfillOrder, [orderId], {
111
+ deploymentId: "latest", // [!code highlight]
112
+ });
113
+
114
+ return Response.json({ runId: run.runId });
115
+ }
116
+ ```
117
+
118
+ <Callout type="warn">
119
+ `deploymentId: "latest"` 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. Because the caller and target deployment can be different, keep the [workflow function name and file path](/docs/errors/workflow-not-registered), arguments, and return value backward-compatible across the deployments you plan to bridge.
120
+ </Callout>
121
+
122
+ ## Self upgrading workflows
123
+
124
+ Some workflows are expected to run for a very long time. Scheduled loops, recurring jobs, agents, and chat sessions often should not stay on one deployment forever.
125
+
126
+ Model those as a sequence of runs. Each run does a bounded piece of work, then starts the next run on the latest deployment and exits. This is similar to `continueAsNew` in other durable execution systems, but in Workflow SDK it is just [explicit recursion through `start()`](/cookbook/common-patterns/workflow-composition).
127
+
128
+ ```typescript title="workflows/daily-digest.ts" lineNumbers
129
+ import { sleep } from "workflow";
130
+ import { start } from "workflow/api";
131
+
132
+ type DigestState = {
133
+ userId: string;
134
+ lastSentAt?: string;
135
+ };
136
+
137
+ export async function dailyDigest(state: DigestState) {
138
+ "use workflow";
139
+
140
+ const sentAt = await sendDigest(state.userId);
141
+ await sleep("1d");
142
+
143
+ const run = await start(
144
+ dailyDigest,
145
+ [{ ...state, lastSentAt: sentAt }],
146
+ {
147
+ deploymentId: "latest", // [!code highlight]
148
+ }
149
+ );
150
+
151
+ return { continuedAs: run.runId };
152
+ }
153
+
154
+ async function sendDigest(userId: string) {
155
+ "use step";
156
+ // ...
157
+ return new Date().toISOString();
158
+ }
159
+ ```
160
+
161
+ This pattern gives every run a clear lifecycle:
162
+
163
+ - The current run stays on its original deployment.
164
+ - The next run starts on the latest deployment.
165
+ - The [serialized `state`](/docs/foundations/serialization) is the migration boundary between versions.
166
+ - Observability can link parent and child runs when a workflow starts another run.
167
+
168
+ ## Carrying context forward
169
+
170
+ Anything that is [serializable by Workflow SDK](/docs/foundations/serialization) can be passed from one run to the next as an argument. That includes plain state objects, `ReadableStream`, `WritableStream`, `AbortSignal`, and other supported serialized values.
171
+
172
+ For example, a long export can register its [output stream](/docs/foundations/streaming) once, write progress from each run, and pass the same stream plus updated state into the next run:
173
+
174
+ ```typescript title="workflows/export-report.ts" lineNumbers
175
+ import { getWritable } from "workflow";
176
+ import { start } from "workflow/api";
177
+
178
+ type ExportState = {
179
+ exportId: string;
180
+ page: number;
181
+ };
182
+
183
+ export async function exportReport(
184
+ state: ExportState,
185
+ progress?: WritableStream<string>
186
+ ) {
187
+ "use workflow";
188
+
189
+ // Register the stream once. Continuation runs receive this same stream
190
+ // as an argument and keep writing to it.
191
+ const stream =
192
+ progress !== undefined ? progress : getWritable<string>();
193
+
194
+ const hasMore = await exportPage(state, stream);
195
+
196
+ if (!hasMore) {
197
+ await writeProgress(stream, { type: "done", totalPages: state.page });
198
+ return { totalPages: state.page };
199
+ }
200
+
201
+ const run = await start(exportReport, [
202
+ { ...state, page: state.page + 1 },
203
+ stream,
204
+ ], {
205
+ deploymentId: "latest", // [!code highlight]
206
+ });
207
+
208
+ return { continuedAs: run.runId };
209
+ }
210
+
211
+ async function exportPage(
212
+ state: ExportState,
213
+ stream: WritableStream<string>
214
+ ) {
215
+ "use step";
216
+
217
+ // Do work for this version boundary.
218
+ const hasMore = state.page < 10;
219
+ const writer = stream.getWriter();
220
+
221
+ try {
222
+ await writer.write(
223
+ JSON.stringify({ type: "page", page: state.page }) + "\n"
224
+ );
225
+ return hasMore;
226
+ } finally {
227
+ writer.releaseLock();
228
+ }
229
+ }
230
+
231
+ async function writeProgress(
232
+ stream: WritableStream<string>,
233
+ event: { type: "done"; totalPages: number }
234
+ ) {
235
+ "use step";
236
+
237
+ const writer = stream.getWriter();
238
+ try {
239
+ await writer.write(JSON.stringify(event) + "\n");
240
+ } finally {
241
+ writer.releaseLock();
242
+ }
243
+ }
244
+ ```
245
+
246
+ ```typescript title="app/api/export/route.ts" lineNumbers
247
+ import { start } from "workflow/api";
248
+ import { exportReport } from "@/workflows/export-report";
249
+
250
+ export async function POST(request: Request) {
251
+ const { exportId } = await request.json();
252
+
253
+ const run = await start(exportReport, [{ exportId, page: 1 }]);
254
+
255
+ // Linked continuation runs keep writing to the stream registered by
256
+ // the parent run, because that stream is passed forward as an argument.
257
+ return new Response(run.readable, {
258
+ headers: { "Content-Type": "application/jsonl" },
259
+ });
260
+ }
261
+ ```
262
+
263
+ Each run still has one clear version boundary: the current run stays on its original deployment, the next run starts on the latest deployment, and only the explicit state and stream handle are carried forward.
@@ -51,6 +51,12 @@ export default defineConfig({
51
51
  });
52
52
  ```
53
53
 
54
+ `workflow()` accepts an options object:
55
+
56
+ | Option | Type | Default | Description |
57
+ | --- | --- | --- | --- |
58
+ | `sourcemap` | `boolean \| 'inline' \| 'linked' \| 'external' \| 'both'` | `'inline'` | Controls source maps on generated workflow bundles. Accepts the same values as esbuild's `sourcemap` option. Set to `false` for smaller function bundles (useful for staying under the Vercel 250MB function size limit) at the cost of stack traces pointing at generated code. Can also be set via the `WORKFLOW_SOURCEMAP` environment variable. |
59
+
54
60
  <Accordion type="single" collapsible>
55
61
  <AccordionItem value="typescript-intellisense" className="[&_h3]:my-0">
56
62
  <AccordionTrigger className="text-sm">
@@ -8,7 +8,7 @@ related:
8
8
  - /docs/foundations/workflows-and-steps
9
9
  ---
10
10
 
11
- import { Next, Nitro, SvelteKit, Nuxt, Hono, Bun, AstroDark, AstroLight, TanStack, Vite, Express, Nest, Fastify } from "@/app/[lang]/(home)/components/frameworks";
11
+ import { Next, Nitro, SvelteKit, Nuxt, Hono, Bun, AstroDark, AstroLight, TanStack, Vite, Express, Nest, Fastify, Python } from "@/app/[lang]/(home)/components/frameworks";
12
12
 
13
13
  <Cards>
14
14
  <Card href="/docs/getting-started/next">
@@ -63,17 +63,23 @@ import { Next, Nitro, SvelteKit, Nuxt, Hono, Bun, AstroDark, AstroLight, TanStac
63
63
  <span className="font-medium">SvelteKit</span>
64
64
  </div>
65
65
  </Card>
66
- <Card className="opacity-50">
66
+ <Card href="/docs/getting-started/tanstack-start" >
67
+ <div className="flex flex-col items-center justify-center gap-2">
68
+ <TanStack className="size-16 dark:invert" />
69
+ <span className="font-medium">TanStack Start</span>
70
+ </div>
71
+ </Card>
72
+ <Card href="/docs/getting-started/python">
67
73
  <div className="flex flex-col items-center justify-center gap-2">
68
- <Nest className="size-16 dark:invert grayscale" />
69
- <span className="font-medium">NestJS</span>
70
- <Badge variant="secondary">Coming soon</Badge>
74
+ <Python className="size-16" />
75
+ <span className="font-medium">Python</span>
76
+ <Badge variant="secondary">Beta</Badge>
71
77
  </div>
72
78
  </Card>
73
79
  <Card className="opacity-50">
74
80
  <div className="flex flex-col items-center justify-center gap-2">
75
- <TanStack className="size-16 dark:invert grayscale" />
76
- <span className="font-medium">TanStack Start</span>
81
+ <Nest className="size-16 dark:invert grayscale" />
82
+ <span className="font-medium">NestJS</span>
77
83
  <Badge variant="secondary">Coming soon</Badge>
78
84
  </div>
79
85
  </Card>
@@ -9,7 +9,9 @@
9
9
  "nitro",
10
10
  "nuxt",
11
11
  "sveltekit",
12
- "vite"
12
+ "tanstack-start",
13
+ "vite",
14
+ "python"
13
15
  ],
14
16
  "defaultOpen": true
15
17
  }
@@ -386,6 +386,14 @@ WorkflowModule.forRoot({
386
386
  // Only used when moduleType is 'commonjs'
387
387
  // Should match the outDir in your tsconfig.json
388
388
  distDir: 'dist',
389
+
390
+ // Source maps on generated workflow bundles (default: 'inline').
391
+ // Accepts the same values as esbuild's sourcemap option: true, false,
392
+ // 'inline', 'linked', 'external', 'both'. Set to false for smaller
393
+ // function bundles (useful for staying under the Vercel 250MB function
394
+ // size limit) at the cost of stack traces pointing at generated code.
395
+ // Can also be set via the WORKFLOW_SOURCEMAP environment variable.
396
+ sourcemap: 'inline',
389
397
  });
390
398
  ```
391
399
 
@@ -75,9 +75,9 @@ To enable helpful hints in your IDE, setup the workflow plugin in `tsconfig.json
75
75
  </Accordion>
76
76
 
77
77
  <Accordion type="single" collapsible>
78
- <AccordionItem value="typescript-intellisense" className="[&_h3]:my-0">
78
+ <AccordionItem value="configure-proxy-handler" className="[&_h3]:my-0">
79
79
  <AccordionTrigger className="text-sm">
80
- ### Configure Proxy Handler (if applicable)
80
+ <h3 id="configure-proxy-handler">Configure Proxy Handler (if applicable)</h3>
81
81
  </AccordionTrigger>
82
82
  <AccordionContent className="[&_p]:my-2">
83
83
 
@@ -85,7 +85,9 @@ If your Next.js app has a [proxy handler](https://nextjs.org/docs/app/api-refere
85
85
  (formerly known as "middleware"), you'll need to update the matcher pattern to exclude Workflow's
86
86
  internal paths to prevent the proxy handler from running on them.
87
87
 
88
- Add `.well-known/workflow/*` to your middleware's exclusion list:
88
+ If you see `[local world] Queue operation failed` with `Cannot perform ArrayBuffer.prototype.slice on a detached ArrayBuffer`, your proxy matcher is still intercepting Workflow's internal `POST /.well-known/workflow/v1/flow` request. This is especially easy to miss in Next.js 16, where `proxy.ts` replaced `middleware.ts`.
89
+
90
+ Add `.well-known/workflow/*` to your matcher exclusion list:
89
91
 
90
92
  ```typescript title="proxy.ts" lineNumbers
91
93
  import { NextResponse } from "next/server";
@@ -46,6 +46,28 @@ export default defineConfig({
46
46
 
47
47
  ```
48
48
 
49
+ ### Module options
50
+
51
+ The `workflow/nitro` module reads its options from `workflow` on your Nitro config.
52
+
53
+ ```typescript title="nitro.config.ts" lineNumbers
54
+ import { defineConfig } from "nitro";
55
+
56
+ export default defineConfig({
57
+ modules: ["workflow/nitro"],
58
+ workflow: {
59
+ runtime: "nodejs22.x",
60
+ sourcemap: "inline",
61
+ },
62
+ });
63
+ ```
64
+
65
+ | Option | Type | Default | Description |
66
+ | --- | --- | --- | --- |
67
+ | `dirs` | `string[]` | — | Directories to scan for workflows and steps. By default, `workflows/` is scanned from the project root and all layer source directories. |
68
+ | `runtime` | `string` | `'nodejs22.x'` | Node.js runtime version for Vercel Functions (e.g. `'nodejs22.x'`, `'nodejs24.x'`). |
69
+ | `sourcemap` | `boolean \| 'inline' \| 'linked' \| 'external' \| 'both'` | `'inline'` | Controls source maps on generated workflow bundles. Accepts the same values as esbuild's `sourcemap` option. Set to `false` for smaller function bundles (useful for staying under the Vercel 250MB function size limit) at the cost of stack traces pointing at generated code. Can also be set via the `WORKFLOW_SOURCEMAP` environment variable. |
70
+
49
71
  <Accordion type="single" collapsible>
50
72
  <AccordionItem value="typescript-intellisense" className="[&_h3]:my-0">
51
73
  <AccordionTrigger className="[&_p]:my-0 text-lg [&_p]:text-foreground">