workflow 4.4.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/docs/ai/resumable-streams.mdx +4 -0
  2. package/docs/api-reference/index.mdx +24 -0
  3. package/docs/api-reference/meta.json +8 -0
  4. package/docs/api-reference/vitest/index.mdx +0 -6
  5. package/docs/api-reference/workflow/create-hook.mdx +32 -0
  6. package/docs/api-reference/workflow/create-webhook.mdx +1 -0
  7. package/docs/api-reference/workflow-ai/workflow-chat-transport.mdx +39 -0
  8. package/docs/api-reference/workflow-api/index.mdx +6 -8
  9. package/docs/api-reference/workflow-api/start.mdx +2 -0
  10. package/docs/api-reference/workflow-errors/meta.json +5 -0
  11. package/docs/api-reference/workflow-next/with-workflow.mdx +26 -4
  12. package/docs/api-reference/workflow-serde/index.mdx +0 -1
  13. package/docs/api-reference/workflow-serde/workflow-deserialize.mdx +1 -2
  14. package/docs/api-reference/workflow-serde/workflow-serialize.mdx +1 -2
  15. package/docs/deploying/world/postgres-world.mdx +33 -1
  16. package/docs/deploying/world/vercel-world.mdx +2 -0
  17. package/docs/errors/index.mdx +3 -0
  18. package/docs/foundations/hooks.mdx +29 -0
  19. package/docs/foundations/streaming.mdx +7 -1
  20. package/docs/foundations/versioning.mdx +1 -1
  21. package/docs/how-it-works/encryption.mdx +2 -2
  22. package/docs/how-it-works/event-sourcing.mdx +2 -2
  23. package/docs/observability/index.mdx +13 -0
  24. package/docs/v4/api-reference/workflow-astro/index.mdx +18 -0
  25. package/docs/v4/api-reference/workflow-astro/meta.json +4 -0
  26. package/docs/v4/api-reference/workflow-astro/workflow.mdx +37 -0
  27. package/docs/v4/api-reference/workflow-errors/hook-conflict-error.mdx +60 -0
  28. package/docs/v4/api-reference/workflow-errors/run-not-supported-error.mdx +58 -0
  29. package/docs/v4/api-reference/workflow-errors/workflow-error.mdx +52 -0
  30. package/docs/v4/api-reference/workflow-errors/workflow-run-not-completed-error.mdx +58 -0
  31. package/docs/v4/api-reference/workflow-errors/workflow-runtime-error.mdx +58 -0
  32. package/docs/v4/api-reference/workflow-nest/configure-workflow-controller.mdx +33 -0
  33. package/docs/v4/api-reference/workflow-nest/index.mdx +31 -0
  34. package/docs/v4/api-reference/workflow-nest/meta.json +9 -0
  35. package/docs/v4/api-reference/workflow-nest/nest-local-builder.mdx +63 -0
  36. package/docs/v4/api-reference/workflow-nest/workflow-controller.mdx +40 -0
  37. package/docs/v4/api-reference/workflow-nest/workflow-module.mdx +73 -0
  38. package/docs/v4/api-reference/workflow-nitro/index.mdx +58 -0
  39. package/docs/v4/api-reference/workflow-nuxt/index.mdx +48 -0
  40. package/docs/v4/api-reference/workflow-observability/hydrate-data.mdx +35 -0
  41. package/docs/v4/api-reference/workflow-observability/hydrate-resource-io.mdx +62 -0
  42. package/docs/v4/api-reference/workflow-observability/index.mdx +64 -0
  43. package/docs/v4/api-reference/workflow-observability/meta.json +11 -0
  44. package/docs/v4/api-reference/workflow-observability/observability-revivers.mdx +50 -0
  45. package/docs/v4/api-reference/workflow-observability/parse-class-name.mdx +41 -0
  46. package/docs/v4/api-reference/workflow-observability/parse-step-name.mdx +40 -0
  47. package/docs/v4/api-reference/workflow-observability/parse-workflow-name.mdx +55 -0
  48. package/docs/v4/api-reference/workflow-runtime/create-world.mdx +43 -0
  49. package/docs/v4/api-reference/workflow-runtime/get-world-handlers.mdx +44 -0
  50. package/docs/v4/api-reference/workflow-runtime/get-world.mdx +124 -0
  51. package/docs/v4/api-reference/workflow-runtime/health-check.mdx +50 -0
  52. package/docs/v4/api-reference/workflow-runtime/index.mdx +46 -0
  53. package/docs/v4/api-reference/workflow-runtime/meta.json +13 -0
  54. package/docs/v4/api-reference/workflow-runtime/set-world.mdx +49 -0
  55. package/docs/v4/api-reference/workflow-runtime/step-entrypoint.mdx +39 -0
  56. package/docs/v4/api-reference/workflow-runtime/workflow-entrypoint.mdx +42 -0
  57. package/docs/{api-reference/workflow-api → v4/api-reference/workflow-runtime}/world/index.mdx +5 -8
  58. package/docs/v4/api-reference/workflow-runtime/world/meta.json +4 -0
  59. package/docs/v4/api-reference/workflow-runtime/world/queue.mdx +86 -0
  60. package/docs/{api-reference/workflow-api → v4/api-reference/workflow-runtime}/world/storage.mdx +4 -4
  61. package/docs/v4/api-reference/workflow-runtime/world/streams.mdx +216 -0
  62. package/docs/v4/api-reference/workflow-sveltekit/index.mdx +18 -0
  63. package/docs/v4/api-reference/workflow-sveltekit/meta.json +4 -0
  64. package/docs/v4/api-reference/workflow-sveltekit/workflow-plugin.mdx +34 -0
  65. package/docs/v4/api-reference/workflow-vite/index.mdx +18 -0
  66. package/docs/v4/api-reference/workflow-vite/meta.json +4 -0
  67. package/docs/v4/api-reference/workflow-vite/workflow.mdx +47 -0
  68. package/docs/v4/errors/step-executed-multiple-times.mdx +23 -0
  69. package/docs/v5/api-reference/workflow-astro/index.mdx +18 -0
  70. package/docs/v5/api-reference/workflow-astro/meta.json +4 -0
  71. package/docs/v5/api-reference/workflow-astro/workflow.mdx +45 -0
  72. package/docs/v5/api-reference/workflow-errors/hook-conflict-error.mdx +60 -0
  73. package/docs/v5/api-reference/workflow-errors/run-not-supported-error.mdx +58 -0
  74. package/docs/v5/api-reference/workflow-errors/workflow-error.mdx +52 -0
  75. package/docs/v5/api-reference/workflow-errors/workflow-run-not-completed-error.mdx +58 -0
  76. package/docs/v5/api-reference/workflow-errors/workflow-runtime-error.mdx +58 -0
  77. package/docs/v5/api-reference/workflow-nest/configure-workflow-controller.mdx +33 -0
  78. package/docs/v5/api-reference/workflow-nest/index.mdx +31 -0
  79. package/docs/v5/api-reference/workflow-nest/meta.json +9 -0
  80. package/docs/v5/api-reference/workflow-nest/nest-local-builder.mdx +64 -0
  81. package/docs/v5/api-reference/workflow-nest/workflow-controller.mdx +40 -0
  82. package/docs/v5/api-reference/workflow-nest/workflow-module.mdx +74 -0
  83. package/docs/v5/api-reference/workflow-nitro/index.mdx +60 -0
  84. package/docs/v5/api-reference/workflow-nuxt/index.mdx +48 -0
  85. package/docs/v5/api-reference/workflow-observability/hydrate-data.mdx +35 -0
  86. package/docs/v5/api-reference/workflow-observability/hydrate-resource-io.mdx +62 -0
  87. package/docs/v5/api-reference/workflow-observability/index.mdx +64 -0
  88. package/docs/v5/api-reference/workflow-observability/meta.json +11 -0
  89. package/docs/v5/api-reference/workflow-observability/observability-revivers.mdx +50 -0
  90. package/docs/v5/api-reference/workflow-observability/parse-class-name.mdx +41 -0
  91. package/docs/v5/api-reference/workflow-observability/parse-step-name.mdx +40 -0
  92. package/docs/v5/api-reference/workflow-observability/parse-workflow-name.mdx +55 -0
  93. package/docs/v5/api-reference/workflow-runtime/create-world.mdx +39 -0
  94. package/docs/v5/api-reference/workflow-runtime/get-world-handlers.mdx +44 -0
  95. package/docs/{api-reference/workflow-api → v5/api-reference/workflow-runtime}/get-world.mdx +7 -10
  96. package/docs/v5/api-reference/workflow-runtime/health-check.mdx +50 -0
  97. package/docs/v5/api-reference/workflow-runtime/index.mdx +43 -0
  98. package/docs/v5/api-reference/workflow-runtime/meta.json +12 -0
  99. package/docs/v5/api-reference/workflow-runtime/set-world.mdx +49 -0
  100. package/docs/v5/api-reference/workflow-runtime/workflow-entrypoint.mdx +42 -0
  101. package/docs/v5/api-reference/workflow-runtime/world/index.mdx +55 -0
  102. package/docs/v5/api-reference/workflow-runtime/world/meta.json +4 -0
  103. package/docs/{api-reference/workflow-api → v5/api-reference/workflow-runtime}/world/queue.mdx +2 -2
  104. package/docs/v5/api-reference/workflow-runtime/world/storage.mdx +409 -0
  105. package/docs/{api-reference/workflow-api → v5/api-reference/workflow-runtime}/world/streams.mdx +2 -2
  106. package/docs/v5/api-reference/workflow-sveltekit/index.mdx +18 -0
  107. package/docs/v5/api-reference/workflow-sveltekit/meta.json +4 -0
  108. package/docs/v5/api-reference/workflow-sveltekit/workflow-plugin.mdx +42 -0
  109. package/docs/v5/api-reference/workflow-vite/index.mdx +18 -0
  110. package/docs/v5/api-reference/workflow-vite/meta.json +4 -0
  111. package/docs/v5/api-reference/workflow-vite/workflow.mdx +48 -0
  112. package/docs/v5/errors/index.mdx +3 -0
  113. package/docs/v5/errors/step-executed-multiple-times.mdx +23 -0
  114. package/package.json +10 -10
  115. package/docs/api-reference/workflow-api/world/meta.json +0 -4
  116. package/docs/api-reference/workflow-api/world/observability.mdx +0 -164
@@ -194,6 +194,10 @@ This avoids replaying potentially thousands of chunks and lets the UI render fas
194
194
  When using a negative `initialStartIndex`, the reconnection endpoint **must** return the `x-workflow-stream-tail-index` header (as shown in [Step 2](#add-a-stream-reconnection-endpoint) above). The transport uses this header to compute absolute chunk positions so that retries after a disconnect resume from the correct position. If the header is missing, the transport falls back to `startIndex: 0` (replaying the entire stream) and logs a warning.
195
195
  </Callout>
196
196
 
197
+ ### Mid-part resumes
198
+
199
+ A workflow stream is a flat sequence of chunks, but the AI SDK's UI protocol groups chunks into logical parts (`text-*`, `reasoning-*`, `tool-input-*`) that must be opened with a `*-start` before any `*-delta` or `*-end`. A non-zero `startIndex` can land in the middle of an open part. See [`WorkflowChatTransport` → Mid-part resumes](/docs/api-reference/workflow-ai/workflow-chat-transport#mid-part-resumes) for how this is handled and an example of rewinding to a step boundary on the server.
200
+
197
201
  ## Related Documentation
198
202
 
199
203
  - [`WorkflowChatTransport` API Reference](/docs/api-reference/workflow-ai/workflow-chat-transport) - Full configuration options
@@ -17,9 +17,33 @@ All the functions and primitives that come with Workflow SDK by package.
17
17
  <Card title="workflow/api" href="/docs/api-reference/workflow-api">
18
18
  API reference for runtime functions from the `workflow/api` package.
19
19
  </Card>
20
+ <Card title="workflow/runtime" href="/docs/api-reference/workflow-runtime">
21
+ Runtime functions for resolving the World instance and the low-level World SDK.
22
+ </Card>
23
+ <Card title="workflow/observability" href="/docs/api-reference/workflow-observability">
24
+ Utilities to hydrate step I/O, parse display names, and decrypt workflow data.
25
+ </Card>
20
26
  <Card title="workflow/next" href="/docs/api-reference/workflow-next">
21
27
  Next.js integration for Workflow SDK that automatically configures bundling and runtime support.
22
28
  </Card>
29
+ <Card title="workflow/nitro" href="/docs/api-reference/workflow-nitro">
30
+ Nitro module for workflow bundling and runtime support.
31
+ </Card>
32
+ <Card title="workflow/nuxt" href="/docs/api-reference/workflow-nuxt">
33
+ Nuxt module for workflow bundling and runtime support.
34
+ </Card>
35
+ <Card title="workflow/sveltekit" href="/docs/api-reference/workflow-sveltekit">
36
+ SvelteKit Vite plugin for workflow bundling and runtime support.
37
+ </Card>
38
+ <Card title="workflow/astro" href="/docs/api-reference/workflow-astro">
39
+ Astro integration for workflow bundling and runtime support.
40
+ </Card>
41
+ <Card title="workflow/vite" href="/docs/api-reference/workflow-vite">
42
+ Standalone Vite plugin for workflow bundling and runtime support.
43
+ </Card>
44
+ <Card title="workflow/nest" href="/docs/api-reference/workflow-nest">
45
+ NestJS module for workflow bundling and runtime support.
46
+ </Card>
23
47
  <Card title="workflow/errors" href="/docs/api-reference/workflow-errors">
24
48
  Semantic error types for handling workflow storage backend failures.
25
49
  </Card>
@@ -4,7 +4,15 @@
4
4
  "workflow-globals",
5
5
  "workflow",
6
6
  "workflow-api",
7
+ "workflow-runtime",
8
+ "workflow-observability",
7
9
  "workflow-next",
10
+ "workflow-nitro",
11
+ "workflow-nuxt",
12
+ "workflow-sveltekit",
13
+ "workflow-astro",
14
+ "workflow-vite",
15
+ "workflow-nest",
8
16
  "workflow-errors",
9
17
  "workflow-serde",
10
18
  "workflow-ai",
@@ -11,7 +11,6 @@ The `@workflow/vitest` package provides a Vitest plugin and test helpers for run
11
11
 
12
12
  Returns a Vite plugin array that handles SWC transforms, bundle building, and in-process handler registration automatically.
13
13
 
14
- {/* @skip-typecheck - @workflow/vitest not available in docs-typecheck */}
15
14
 
16
15
  ```typescript
17
16
  import { defineConfig } from "vitest/config";
@@ -24,7 +23,6 @@ export default defineConfig({
24
23
 
25
24
  Pass a [`WorkflowTestOptions`](#workflowtestoptions) object when your project uses a non-standard layout — for example, a monorepo where `workflows/` does not live at the Vitest config's directory, or when the default `.workflow-data` / `.workflow-vitest` output locations need to move. The plugin forwards these paths to `buildWorkflowTests()` and `setupWorkflowTests()` through Vitest's per-project provided context, so each Vitest workspace project stays isolated.
26
25
 
27
- {/* @skip-typecheck - @workflow/vitest not available in docs-typecheck */}
28
26
 
29
27
  ```typescript
30
28
  import { defineConfig } from "vitest/config";
@@ -54,7 +52,6 @@ export default defineConfig({
54
52
 
55
53
  Builds workflow and step bundles to disk. Called automatically by the `workflow()` plugin in `globalSetup`. Use directly only for [manual setup](/docs/testing#manual-setup).
56
54
 
57
- {/* @skip-typecheck - @workflow/vitest not available in docs-typecheck */}
58
55
 
59
56
  ```typescript
60
57
  import { buildWorkflowTests } from "@workflow/vitest";
@@ -76,7 +73,6 @@ Sets up an in-process workflow runtime in each test worker. Imports pre-built bu
76
73
 
77
74
  Called automatically by the `workflow()` plugin in `setupFiles`. Use directly only for [manual setup](/docs/testing#manual-setup).
78
75
 
79
- {/* @skip-typecheck - @workflow/vitest not available in docs-typecheck */}
80
76
 
81
77
  ```typescript
82
78
  import { beforeAll, afterAll } from "vitest";
@@ -118,7 +114,6 @@ Tears down the workflow test world. Clears the global world and closes the Local
118
114
 
119
115
  Polls the event log until the workflow has a pending `sleep()` call — one with a `wait_created` event but no corresponding `wait_completed` event. Returns the correlation ID of the pending sleep, which can be passed to [`wakeUp()`](/docs/api-reference/workflow-api/get-run) to target a specific sleep.
120
116
 
121
- {/* @skip-typecheck - @workflow/vitest not available in docs-typecheck */}
122
117
 
123
118
  ```typescript
124
119
  import { waitForSleep } from "@workflow/vitest"; // [!code highlight]
@@ -147,7 +142,6 @@ await getRun(run.runId).wakeUp({ correlationIds: [sleepId] }); // [!code highlig
147
142
 
148
143
  Polls the hook list and event log until a hook matching the optional `token` filter exists that hasn't been received yet. Returns the matching hook object.
149
144
 
150
- {/* @skip-typecheck - @workflow/vitest not available in docs-typecheck */}
151
145
 
152
146
  ```typescript
153
147
  import { waitForHook } from "@workflow/vitest"; // [!code highlight]
@@ -65,6 +65,8 @@ export default Hook;`}
65
65
 
66
66
  The returned `Hook` object also implements `AsyncIterable<T>`, which allows you to iterate over incoming payloads using `for await...of` syntax.
67
67
 
68
+ Use `hook.getConflict()` (available starting in `workflow@4.5.0`) to check whether the hook token is already claimed by another active hook, without waiting for hook payload data. Calling `createHook()` on its own does not register the hook — registration is only committed when the workflow suspends. Awaiting `hook.getConflict()` suspends the workflow to commit the registration, then resolves with `null` once `hook_created` is recorded, or with `{ runId }` identifying the conflicting run if another active hook already owns the same token.
69
+
68
70
  ## Examples
69
71
 
70
72
  ### Basic Usage
@@ -112,6 +114,36 @@ export async function slackBotWorkflow(channelId: string) {
112
114
  }
113
115
  ```
114
116
 
117
+ ### Detecting Token Conflicts
118
+
119
+ Use `hook.getConflict()` (available starting in `workflow@4.5.0`) when the workflow needs to claim a hook token before doing other work, but does not need a payload yet:
120
+
121
+ ```typescript lineNumbers
122
+ import { createHook } from "workflow";
123
+
124
+ declare function chargeOrder(orderId: string): Promise<void>; // @setup
125
+
126
+ async function processOrder(orderId: string) {
127
+ "use workflow";
128
+
129
+ using hook = createHook({ // [!code highlight]
130
+ token: `order:${orderId}` // [!code highlight]
131
+ }); // [!code highlight]
132
+
133
+ const conflict = await hook.getConflict(); // [!code highlight]
134
+ if (conflict) { // [!code highlight]
135
+ // Another active workflow run already owns this token.
136
+ return { dedupedTo: conflict.runId };
137
+ }
138
+
139
+ await chargeOrder(orderId);
140
+ }
141
+ ```
142
+
143
+ Because `createHook()` alone does not suspend the workflow, awaiting `hook.getConflict()` is what actually suspends the run and commits the hook registration. It only waits for registration — to receive payload data from a future `resumeHook()` call, await the hook itself or iterate it with `for await...of`.
144
+
145
+ On a conflict, the resolved value is `{ runId }` identifying the run that currently owns the token. To act on the owner — inspect its status, wait for its result, or cancel it — pass `conflict.runId` to [`getRun()`](/docs/api-reference/workflow-api/get-run) inside a step. See [Idempotency](/docs/foundations/idempotency) for these strategies in context.
146
+
115
147
  ### Waiting for Multiple Payloads
116
148
 
117
149
  You can also wait for multiple payloads by using the `for await...of` syntax.
@@ -55,6 +55,7 @@ The returned `Webhook` object has:
55
55
 
56
56
  - `url`: The HTTP endpoint URL that external systems can call
57
57
  - `token`: The unique token identifying this webhook
58
+ - `getConflict()`: A promise that resolves with `{ runId }` identifying the conflicting run if another active hook already owns this token, or `null` once the webhook endpoint has been registered
58
59
  - Implements `AsyncIterable<RequestWithResponse>` for handling multiple requests
59
60
 
60
61
  The `RequestWithResponse` type extends the standard `Request` interface with a `respondWith(response: Response)` method for sending custom responses back to the caller.
@@ -254,6 +254,45 @@ export default function ChatWithCustomConfig() {
254
254
  }
255
255
  ```
256
256
 
257
+ ## Mid-part resumes
258
+
259
+ A workflow stream is a flat sequence of chunks, but the AI SDK's UI protocol groups chunks into logical parts: a `text-start` opens a text part that subsequent `text-delta`s extend and a `text-end` closes, and the same shape applies to `reasoning-*` and `tool-input-*`. The AI SDK client enforces that grammar — a `reasoning-delta` whose `reasoning-start` was never seen throws and breaks the chat.
260
+
261
+ A non-zero `startIndex` (in particular a negative `initialStartIndex`) resolves to a chunk offset with no awareness of those part boundaries, so it can land in the middle of an open part. When that happens, `WorkflowChatTransport` will **drop chunks that reference a part it didn't see a start for** and log a one-time warning. The chat keeps working, but any partial part overlapping the resume cursor is discarded. Tool calls are an exception: `tool-input-available` / `tool-input-error` chunks are self-contained (they carry the full input), so a tool call is recovered as soon as one of those chunks appears in the resumed window — only its streamed input deltas are lost.
262
+
263
+ To preserve those partial parts, rewind to a step boundary on the server before returning the readable. `start-step` / `finish-step` chunks are the natural seams — no UI part is ever open across them. Sketch:
264
+
265
+ {/*@skip-typecheck: incomplete code sample*/}
266
+
267
+ ```typescript title="app/api/chat/[id]/stream/route.ts"
268
+ const run = getRun(id);
269
+ const tailIndex = await run.getReadable().getTailIndex();
270
+
271
+ let resolved = startIndex < 0
272
+ ? Math.max(0, tailIndex + 1 + startIndex)
273
+ : startIndex;
274
+
275
+ if (startIndex !== 0) {
276
+ // Walk back from `resolved` to the most recent start-step (or chunk 0),
277
+ // capping the lookback so a single huge step can't trigger an unbounded scan.
278
+ const LOOKBACK = 200;
279
+ const probe = run.getReadable({ startIndex: Math.max(0, resolved - LOOKBACK) });
280
+ let i = Math.max(0, resolved - LOOKBACK);
281
+ let lastBoundary = i;
282
+ for await (const chunk of probe as unknown as AsyncIterable<{ type: string }>) {
283
+ if (i >= resolved) break;
284
+ if (chunk.type === "start-step") lastBoundary = i;
285
+ i++;
286
+ }
287
+ resolved = lastBoundary;
288
+ }
289
+
290
+ return createUIMessageStreamResponse({
291
+ stream: run.getReadable({ startIndex: resolved }),
292
+ headers: { "x-workflow-stream-tail-index": String(tailIndex) },
293
+ });
294
+ ```
295
+
257
296
  ## See Also
258
297
 
259
298
  - [DurableAgent](/docs/api-reference/workflow-ai/durable-agent) - Building durable AI agents within workflows
@@ -1,6 +1,6 @@
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
  ---
@@ -9,7 +9,7 @@ API reference for runtime functions from the `workflow/api` package.
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.
12
+ The API package is for access and introspection of workflow data to inspect runs, start new runs, and manage hooks.
13
13
 
14
14
  <Cards>
15
15
  <Card href="/docs/api-reference/workflow-api/start" title="start()">
@@ -27,10 +27,8 @@ The API package is for access and introspection of workflow data to inspect runs
27
27
  <Card href="/docs/api-reference/workflow-api/get-run" title="getRun()">
28
28
  Get workflow run status and metadata without waiting for completion.
29
29
  </Card>
30
- <Card href="/docs/api-reference/workflow-api/get-world" title="getWorld()">
31
- Get direct access to workflow 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
30
  </Cards>
31
+
32
+ <Callout type="info">
33
+ Looking for `getWorld()` and the World SDK? They are exported from `workflow/runtime` — see the [`workflow/runtime` reference](/docs/api-reference/workflow-runtime).
34
+ </Callout>
@@ -97,6 +97,8 @@ const run = await start(myWorkflow, ["arg1", "arg2"], { // [!code highlight]
97
97
 
98
98
  <Callout type="info">
99
99
  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.
100
+
101
+ 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.
100
102
  </Callout>
101
103
 
102
104
  <Callout type="warn">
@@ -1,16 +1,21 @@
1
1
  {
2
2
  "title": "workflow/errors",
3
3
  "pages": [
4
+ "workflow-error",
4
5
  "hook-not-found-error",
6
+ "hook-conflict-error",
5
7
  "step-not-registered-error",
6
8
  "workflow-not-registered-error",
7
9
  "workflow-run-not-found-error",
8
10
  "workflow-run-failed-error",
9
11
  "workflow-run-cancelled-error",
12
+ "workflow-run-not-completed-error",
13
+ "workflow-runtime-error",
10
14
  "workflow-world-error",
11
15
  "throttle-error",
12
16
  "entity-conflict-error",
13
17
  "run-expired-error",
18
+ "run-not-supported-error",
14
19
  "too-early-error"
15
20
  ]
16
21
  }
@@ -31,11 +31,35 @@ export default withWorkflow(nextConfig, workflowConfig); // [!code highlight]
31
31
  If a package in `serverExternalPackages` contains workflow code (`"use step"`,
32
32
  `"use workflow"`, or serialization classes), `withWorkflow()` automatically
33
33
  removes it from `serverExternalPackages` for the current build and prints a
34
- warning. This ensures the package still gets transformed by the Workflow
35
- compiler. Remove that package from `serverExternalPackages` in your
34
+ warning. Workflow still compiles the package so its directives are transformed.
35
+ Remove that package from `serverExternalPackages` in your
36
36
  `next.config` to silence the warning.
37
37
  </Callout>
38
38
 
39
+ ### Workflow Discovery in Next.js
40
+
41
+ `withWorkflow()` discovers workflows by scanning your Next.js entrypoints — App
42
+ Router `route`, `page`, and `layout` files (under `app/` or `src/app/`) and any
43
+ file under `pages/` or `src/pages/` — for `start()` calls imported from
44
+ `workflow/api`. The workflow and step files themselves can live anywhere (for
45
+ example `src/workflows/`); they are discovered transitively through imports, as
46
+ long as a `start()` call in an entrypoint statically reaches them.
47
+
48
+ <Callout type="info">
49
+ Call `start()` from server-side entrypoints, including Route Handlers and Server
50
+ Actions. Don't call workflow functions directly — that bypasses the workflow
51
+ runtime.
52
+ </Callout>
53
+
54
+ ### Next.js Server Actions and `"use server"`
55
+
56
+ Don't put a top-level `"use server"` directive in modules imported by workflow
57
+ or step functions. Workflow transformation wraps imported modules in synchronous
58
+ initializers, and Next.js rejects a `"use server"` directive inside that wrapper
59
+ with errors like `Server Actions must be async functions`. Keep `"use server"`
60
+ on the files that define your Server Actions, and move shared logic into
61
+ separate modules that don't carry the directive.
62
+
39
63
  ### Monorepos and Workspace Imports
40
64
 
41
65
  By default, Next.js detects the correct workspace root automatically. If your Next.js app lives in a subdirectory such as `apps/web` and workspace resolution is not working correctly, you can set `outputFileTracingRoot` as a workaround:
@@ -68,7 +92,6 @@ const nextConfig: NextConfig = {};
68
92
 
69
93
  export default withWorkflow(nextConfig, {
70
94
  workflows: {
71
- lazyDiscovery: true,
72
95
  local: {
73
96
  port: 4000,
74
97
  },
@@ -78,7 +101,6 @@ export default withWorkflow(nextConfig, {
78
101
 
79
102
  | Option | Type | Default | Description |
80
103
  | --- | --- | --- | --- |
81
- | `workflows.lazyDiscovery` | `boolean` | `false` | When `true`, defers workflow discovery until files are requested instead of scanning eagerly at startup. Useful for large projects where startup time matters. |
82
104
  | `workflows.local.port` | `number` | — | Overrides the `PORT` environment variable for local development. Has no effect when deployed to Vercel. |
83
105
 
84
106
  <Callout type="info">
@@ -29,7 +29,6 @@ The `@workflow/serde` package provides two symbols that allow you to define cust
29
29
 
30
30
  ## Quick Example
31
31
 
32
- {/* @expect-error:2351 */}
33
32
 
34
33
  ```typescript lineNumbers
35
34
  import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde";
@@ -6,7 +6,6 @@ A symbol used to define custom deserialization for user-defined class instances.
6
6
 
7
7
  ## Usage
8
8
 
9
- {/* @expect-error:2351 */}
10
9
 
11
10
  ```typescript lineNumbers
12
11
  import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde";
@@ -26,7 +25,7 @@ class Point {
26
25
 
27
26
  ## API Signature
28
27
 
29
- {/* @skip-typecheck */}
28
+ {/* @skip-typecheck: type-only signature snippet, not compilable code */}
30
29
 
31
30
  ```typescript
32
31
  static [WORKFLOW_DESERIALIZE](data: SerializableData): T
@@ -6,7 +6,6 @@ A symbol used to define custom serialization for user-defined class instances. T
6
6
 
7
7
  ## Usage
8
8
 
9
- {/* @expect-error:2351 */}
10
9
 
11
10
  ```typescript lineNumbers
12
11
  import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde";
@@ -26,7 +25,7 @@ class Point {
26
25
 
27
26
  ## API Signature
28
27
 
29
- {/* @skip-typecheck */}
28
+ {/* @skip-typecheck: type-only signature snippet, not compilable code */}
30
29
 
31
30
  ```typescript
32
31
  static [WORKFLOW_SERIALIZE](instance: T): SerializableData
@@ -31,10 +31,42 @@ WORKFLOW_POSTGRES_URL="postgres://user:password@host:5432/database"
31
31
 
32
32
  Run the migration script to create the necessary tables in your database. Ensure `WORKFLOW_POSTGRES_URL` is set when running this command:
33
33
 
34
+ <Tabs items={["npm", "pnpm", "Yarn", "Bun"]}>
35
+
36
+ <Tab value="npm">
37
+
38
+ ```bash
39
+ npx --package=@workflow/world-postgres bootstrap
40
+ ```
41
+
42
+ </Tab>
43
+
44
+ <Tab value="pnpm">
45
+
46
+ ```bash
47
+ pnpm dlx --package @workflow/world-postgres bootstrap
48
+ ```
49
+
50
+ </Tab>
51
+
52
+ <Tab value="Yarn">
53
+
54
+ ```bash
55
+ yarn dlx --package @workflow/world-postgres bootstrap
56
+ ```
57
+
58
+ </Tab>
59
+
60
+ <Tab value="Bun">
61
+
34
62
  ```bash
35
- npx workflow-postgres-setup
63
+ bunx --package @workflow/world-postgres bootstrap
36
64
  ```
37
65
 
66
+ </Tab>
67
+
68
+ </Tabs>
69
+
38
70
  <Callout type="info">
39
71
  The migration is idempotent and can safely be run as a post-deployment lifecycle script.
40
72
  </Callout>
@@ -48,6 +48,8 @@ For self-hosted deployments, use the [Postgres World](/worlds/postgres). For loc
48
48
 
49
49
  - **Data residency** - The Vercel World is currently deployed in the `iad1` region. This means independently of the deployment location of your application, the data for your workflows will be stored in the `iad1` region.
50
50
 
51
+ - **Stream routes need `supportsCancellation`** - Routes that pipe `run.getReadable()` back to a client keep running — and billing — until the function's max duration, even after the client disconnects. Set [`supportsCancellation`](https://vercel.com/docs/functions/request-cancellation) in `vercel.json` for those routes so client aborts tear the invocation down. See [Streaming — Resuming Streams from a Specific Point](/docs/foundations/streaming#resuming-streams-from-a-specific-point).
52
+
51
53
  ## Observability
52
54
 
53
55
  Workflow observability is built into the Vercel dashboard on your project page. It respects your existing authentication and project permission settings.
@@ -43,6 +43,9 @@ Fix common mistakes when creating and executing workflows in the **Workflow SDK*
43
43
  <Card href="/docs/errors/step-not-registered" title="step-not-registered">
44
44
  Resolve step not registered errors caused by deployment mismatches.
45
45
  </Card>
46
+ <Card href="/docs/errors/step-executed-multiple-times" title="Step executed multiple times">
47
+ Diagnose duplicate step_started events from function crashes, timeouts, or OOMs.
48
+ </Card>
46
49
  <Card href="/docs/errors/workflow-not-registered" title="workflow-not-registered">
47
50
  Resolve workflow not registered errors caused by deployment mismatches.
48
51
  </Card>
@@ -85,6 +85,35 @@ The key points:
85
85
  - You need the hook's `token` to resume it
86
86
  - The workflow will resume execution right where it left off
87
87
 
88
+ ### Checking for Token Conflicts
89
+
90
+ Sometimes you need to know that a hook token has been claimed, but you do not want to wait for external data yet. Await `hook.getConflict()` (available starting in `workflow@4.5.0`) for that:
91
+
92
+ ```typescript lineNumbers
93
+ import { createHook } from "workflow";
94
+
95
+ declare function processOrder(orderId: string): Promise<void>; // @setup
96
+
97
+ export async function orderWorkflow(orderId: string) {
98
+ "use workflow";
99
+
100
+ using hook = createHook({
101
+ token: `order:${orderId}`
102
+ });
103
+
104
+ const conflict = await hook.getConflict(); // [!code highlight]
105
+ if (conflict) { // [!code highlight]
106
+ // Another active run already owns this token.
107
+ return { dedupedTo: conflict.runId };
108
+ }
109
+
110
+ // The hook token is registered and reserved here.
111
+ await processOrder(orderId);
112
+ }
113
+ ```
114
+
115
+ Calling `createHook()` on its own does not register the hook — registration is only committed when the workflow suspends. Awaiting `hook.getConflict()` suspends the workflow to commit the hook registration, then resolves with `null` once the hook is registered and ready to receive payloads, or with `{ runId }` identifying the run that owns the token if another active hook already claimed it (see [`HookConflictError`](/docs/errors/hook-conflict)). For `hook_conflict` events persisted by older worlds that did not record the owning run's ID, `getConflict()` rejects with `HookConflictError` instead of resolving with an incomplete handle. To act on the owner — inspect its status, wait for its result, or cancel it — pass `conflict.runId` to [`getRun()`](/docs/api-reference/workflow-api/get-run) inside a step. See [Idempotency](/docs/foundations/idempotency) for these strategies.
116
+
88
117
  ### Custom Tokens for Deterministic Hooks
89
118
 
90
119
  By default, hooks generate a random token. However, you often want to use a **custom token** that external systems can reconstruct. This is especially useful for long-running workflows where the same workflow instance should handle multiple events.
@@ -87,6 +87,12 @@ export async function GET(
87
87
 
88
88
  This allows clients to reconnect and continue receiving data from where they left off, rather than restarting from the beginning.
89
89
 
90
+ <Callout type="warn">
91
+ **Vercel: long-lived stream routes need `supportsCancellation`**
92
+
93
+ When a route like the one above pipes `run.getReadable()` out to a client on Vercel, the function keeps running — and billing — until the function's configured max duration, even after the client disconnects. Set [`supportsCancellation`](https://vercel.com/docs/functions/request-cancellation) in `vercel.json` for routes that stream workflow output so Vercel forwards the client abort signal and tears the invocation down when the client goes away.
94
+ </Callout>
95
+
90
96
  `startIndex` also supports **negative values** to read relative to the end of the stream. For example, `startIndex: -5` starts 5 chunks before the current end. This is useful when you want to show the most recent output without reading the entire stream history.
91
97
 
92
98
  On an active (not-yet-closed) stream, the negative index resolves relative to the chunk count at connection time; any chunks written afterward are still delivered normally.
@@ -593,7 +599,7 @@ Stream errors don't trigger automatic retries for the producer step. Design your
593
599
  - [`sleep()` API Reference](/docs/api-reference/workflow/sleep) - Pause workflow execution for a duration
594
600
  - [`start()` API Reference](/docs/api-reference/workflow-api/start) - Start workflows and access the `Run` object
595
601
  - [`getRun()` API Reference](/docs/api-reference/workflow-api/get-run) - Retrieve runs and their streams later
596
- - [world.streams](/docs/api-reference/workflow-api/world/streams) - Low-level stream read/write/close via World SDK
602
+ - [world.streams](/docs/api-reference/workflow-runtime/world/streams) - Low-level stream read/write/close via World SDK
597
603
  - [DurableAgent](/docs/api-reference/workflow-ai/durable-agent) - AI agents with built-in streaming support
598
604
  - [Errors and Retries](/docs/foundations/errors-and-retries) - Understanding error handling and retry behavior
599
605
  - [Serialization](/docs/foundations/serialization) - Understanding what data types can be passed in workflows
@@ -93,7 +93,7 @@ npx workflow cancel \
93
93
  --backend vercel
94
94
  ```
95
95
 
96
- 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.
96
+ 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-observability/parse-workflow-name) when you need display-friendly names.
97
97
 
98
98
  In the [observability UI](/docs/observability), use **Rerun on latest** to enqueue the workflow again with the same inputs against the latest deployment.
99
99
 
@@ -105,8 +105,8 @@ To add encryption support to a custom `World`:
105
105
  import type { WorkflowRun, World } from "@workflow/world";
106
106
 
107
107
  export const getEncryptionKeyForRun: World["getEncryptionKeyForRun"] = async (
108
- run,
109
- context
108
+ run: WorkflowRun | string,
109
+ context?: Record<string, unknown>
110
110
  ) => {
111
111
  const runId = typeof run === "string" ? run : run.runId;
112
112
  const deploymentId =
@@ -127,7 +127,7 @@ flowchart TD
127
127
 
128
128
  Unlike other entities, hooks don't have a `status` field—the states above are conceptual. An "active" hook is one that exists in storage, while "disposed" means the hook has been deleted. When a `hook_disposed` event is created, the hook record is removed rather than updated.
129
129
 
130
- While a hook is active, its token is reserved and cannot be used by other workflows. If a workflow attempts to create a hook with a token that is already in use by another active hook, a `hook_conflict` event is recorded instead of `hook_created`. Current worlds include the token and the run ID that currently owns it, though older persisted events or world implementations may only include the token. This causes the hook's promise to reject with a `HookConflictError`, which you can detect with `HookConflictError.is(error)`. See the [hook-conflict error](/docs/errors/hook-conflict) documentation for more details.
130
+ While a hook is active, its token is reserved and cannot be used by other workflows. If a workflow attempts to create a hook with a token that is already in use by another active hook, a `hook_conflict` event is recorded instead of `hook_created`. Current worlds include the token and the run ID that currently owns it, though older persisted events or world implementations may only include the token. This causes `hook.getConflict()` to resolve with the conflicting run and the hook's payload promise to reject with a `HookConflictError`, which you can detect with `HookConflictError.is(error)`. See the [hook-conflict error](/docs/errors/hook-conflict) documentation for more details.
131
131
 
132
132
  When a hook is disposed (either explicitly or when its workflow completes), the token is released and can be claimed by future workflows. Hooks are automatically disposed when a workflow reaches a terminal state (`completed`, `failed`, or `cancelled`). The `hook_disposed` event is only needed for explicit disposal before workflow completion.
133
133
 
@@ -188,7 +188,7 @@ Events are categorized by the entity type they affect. Each event contains metad
188
188
  | Event | Description |
189
189
  |-------|-------------|
190
190
  | `hook_created` | Creates a new hook in `active` state. Contains the hook token and optional metadata. |
191
- | `hook_conflict` | Records that hook creation failed because the token is already in use by another active hook. Contains the token and, for current worlds, the active hook owner's run ID. The hook is not created, and awaiting the hook will reject with a `HookConflictError`. |
191
+ | `hook_conflict` | Records that hook creation failed because the token is already in use by another active hook. Contains the token and, for current worlds, the active hook owner's run ID. The hook is not created: `hook.getConflict()` resolves with the conflicting run, and awaiting the hook payload rejects with a `HookConflictError`. |
192
192
  | `hook_received` | Records that a payload was delivered to the hook. The hook remains `active` and can receive more payloads. |
193
193
  | `hook_disposed` | Deletes the hook from storage (conceptually transitioning to `disposed` state). The token is released for reuse by future workflows. |
194
194
 
@@ -42,6 +42,19 @@ npx workflow inspect runs --web
42
42
 
43
43
  ![Workflow SDK Web UI](/o11y-ui.png)
44
44
 
45
+ To share a link to a specific run without opening a browser, use the `--url`
46
+ flag. It prints the dashboard deep link to stdout and exits (no browser, no
47
+ local server) — useful for scripts, PR comments, or automation. Add `--json` to
48
+ get `{ "url": "..." }`.
49
+
50
+ ```bash
51
+ # Print the deep-link URL for a run (no browser, no server)
52
+ npx workflow inspect run <run_id> --url
53
+
54
+ # Vercel runs: add the backend (and --env preview for preview deployments)
55
+ npx workflow inspect run <run_id> --backend vercel --url
56
+ ```
57
+
45
58
  ## Backends
46
59
 
47
60
  The Workflow SDK CLI can inspect data from any [World](/docs/deploying). By default, it inspects data in your local development environment. For example, if you are using Next.js to develop workflows locally, the
@@ -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,37 @@
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
+ This function does not accept any parameters in workflow 4.x. (5.x adds an options object with a `sourcemap` setting.)
34
+
35
+ ### Returns
36
+
37
+ Returns an `AstroIntegration` object to include in the `integrations` array of your Astro config.