workflow 5.0.0-beta.4 → 5.0.0-beta.40

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 (227) hide show
  1. package/README.md +63 -20
  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 +5 -1
  6. package/dist/api.d.ts.map +1 -1
  7. package/dist/api.js +12 -2
  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 +17 -0
  12. package/dist/internal/builtins.d.ts.map +1 -1
  13. package/dist/internal/builtins.js +65 -1
  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 +5 -2
  28. package/docs/ai/chat-session-modeling.mdx +7 -3
  29. package/docs/ai/defining-tools.mdx +2 -2
  30. package/docs/ai/index.mdx +30 -25
  31. package/docs/ai/message-queueing.mdx +10 -10
  32. package/docs/ai/resumable-streams.mdx +9 -1
  33. package/docs/api-reference/index.mdx +24 -0
  34. package/docs/api-reference/meta.json +8 -0
  35. package/docs/api-reference/vitest/index.mdx +28 -7
  36. package/docs/api-reference/workflow/create-hook.mdx +79 -0
  37. package/docs/api-reference/workflow/create-webhook.mdx +1 -0
  38. package/docs/api-reference/workflow/define-hook.mdx +26 -24
  39. package/docs/api-reference/workflow/fatal-error.mdx +29 -7
  40. package/docs/api-reference/workflow/fetch.mdx +8 -4
  41. package/docs/api-reference/workflow/index.mdx +3 -0
  42. package/docs/api-reference/workflow/set-attributes.mdx +61 -0
  43. package/docs/api-reference/workflow/sleep.mdx +1 -1
  44. package/docs/api-reference/workflow-ai/durable-agent.mdx +7 -45
  45. package/docs/api-reference/workflow-ai/index.mdx +2 -2
  46. package/docs/api-reference/workflow-ai/workflow-chat-transport.mdx +44 -1
  47. package/docs/api-reference/workflow-api/get-hook-by-token.mdx +9 -0
  48. package/docs/api-reference/workflow-api/get-run.mdx +25 -0
  49. package/docs/api-reference/workflow-api/index.mdx +6 -8
  50. package/docs/api-reference/workflow-api/resume-hook.mdx +60 -1
  51. package/docs/api-reference/workflow-api/resume-webhook.mdx +6 -4
  52. package/docs/api-reference/workflow-api/start.mdx +39 -6
  53. package/docs/api-reference/workflow-astro/index.mdx +18 -0
  54. package/docs/api-reference/workflow-astro/meta.json +4 -0
  55. package/docs/api-reference/workflow-astro/workflow.mdx +45 -0
  56. package/docs/api-reference/workflow-errors/hook-conflict-error.mdx +60 -0
  57. package/docs/api-reference/workflow-errors/index.mdx +88 -0
  58. package/docs/api-reference/workflow-errors/meta.json +6 -0
  59. package/docs/api-reference/workflow-errors/precondition-failed-error.mdx +68 -0
  60. package/docs/api-reference/workflow-errors/run-not-supported-error.mdx +58 -0
  61. package/docs/api-reference/workflow-errors/workflow-error.mdx +52 -0
  62. package/docs/api-reference/workflow-errors/workflow-run-failed-error.mdx +16 -6
  63. package/docs/api-reference/workflow-errors/workflow-run-not-completed-error.mdx +58 -0
  64. package/docs/api-reference/workflow-errors/workflow-runtime-error.mdx +58 -0
  65. package/docs/api-reference/workflow-globals.mdx +4 -1
  66. package/docs/api-reference/workflow-nest/configure-workflow-controller.mdx +33 -0
  67. package/docs/api-reference/workflow-nest/index.mdx +31 -0
  68. package/docs/api-reference/workflow-nest/meta.json +9 -0
  69. package/docs/api-reference/workflow-nest/nest-local-builder.mdx +64 -0
  70. package/docs/api-reference/workflow-nest/workflow-controller.mdx +40 -0
  71. package/docs/api-reference/workflow-nest/workflow-module.mdx +74 -0
  72. package/docs/api-reference/workflow-next/with-workflow.mdx +56 -2
  73. package/docs/api-reference/workflow-nitro/index.mdx +60 -0
  74. package/docs/api-reference/workflow-nuxt/index.mdx +48 -0
  75. package/docs/api-reference/workflow-observability/hydrate-data.mdx +35 -0
  76. package/docs/api-reference/workflow-observability/hydrate-resource-io.mdx +62 -0
  77. package/docs/api-reference/workflow-observability/index.mdx +64 -0
  78. package/docs/api-reference/workflow-observability/meta.json +11 -0
  79. package/docs/api-reference/workflow-observability/observability-revivers.mdx +50 -0
  80. package/docs/api-reference/workflow-observability/parse-class-name.mdx +41 -0
  81. package/docs/api-reference/workflow-observability/parse-step-name.mdx +40 -0
  82. package/docs/api-reference/workflow-observability/parse-workflow-name.mdx +55 -0
  83. package/docs/api-reference/workflow-runtime/create-world.mdx +39 -0
  84. package/docs/api-reference/workflow-runtime/get-world-handlers.mdx +44 -0
  85. package/docs/api-reference/{workflow-api → workflow-runtime}/get-world.mdx +7 -10
  86. package/docs/api-reference/workflow-runtime/health-check.mdx +50 -0
  87. package/docs/api-reference/workflow-runtime/index.mdx +43 -0
  88. package/docs/api-reference/workflow-runtime/meta.json +12 -0
  89. package/docs/api-reference/workflow-runtime/set-world.mdx +49 -0
  90. package/docs/api-reference/workflow-runtime/workflow-entrypoint.mdx +42 -0
  91. package/docs/api-reference/workflow-runtime/world/analytics.mdx +138 -0
  92. package/docs/api-reference/{workflow-api → workflow-runtime}/world/index.mdx +10 -10
  93. package/docs/api-reference/workflow-runtime/world/meta.json +4 -0
  94. package/docs/api-reference/{workflow-api → workflow-runtime}/world/queue.mdx +8 -6
  95. package/docs/api-reference/{workflow-api → workflow-runtime}/world/storage.mdx +18 -5
  96. package/docs/api-reference/{workflow-api → workflow-runtime}/world/streams.mdx +2 -2
  97. package/docs/api-reference/workflow-serde/index.mdx +0 -1
  98. package/docs/api-reference/workflow-serde/workflow-deserialize.mdx +1 -2
  99. package/docs/api-reference/workflow-serde/workflow-serialize.mdx +2 -2
  100. package/docs/api-reference/workflow-sveltekit/index.mdx +18 -0
  101. package/docs/api-reference/workflow-sveltekit/meta.json +4 -0
  102. package/docs/api-reference/workflow-sveltekit/workflow-plugin.mdx +42 -0
  103. package/docs/api-reference/workflow-vite/index.mdx +18 -0
  104. package/docs/api-reference/workflow-vite/meta.json +4 -0
  105. package/docs/api-reference/workflow-vite/workflow.mdx +48 -0
  106. package/docs/changelog/attributes-mvp.mdx +380 -0
  107. package/docs/changelog/eager-processing.mdx +269 -0
  108. package/docs/changelog/index.mdx +3 -1
  109. package/docs/changelog/lazy-event-creation.md +127 -0
  110. package/docs/changelog/meta.json +9 -1
  111. package/docs/changelog/resilient-resume.mdx +22 -0
  112. package/docs/changelog/resilient-start.mdx +31 -283
  113. package/docs/changelog/step-message-ownership.mdx +360 -0
  114. package/docs/changelog/turbo-mode.md +87 -0
  115. package/docs/comparisons/index.mdx +66 -0
  116. package/docs/comparisons/meta.json +11 -0
  117. package/docs/comparisons/workflow-sdk-vs-aws-agentcore.mdx +55 -0
  118. package/docs/comparisons/workflow-sdk-vs-aws-step-functions.mdx +111 -0
  119. package/docs/comparisons/workflow-sdk-vs-cloudflare-workflows.mdx +71 -0
  120. package/docs/comparisons/workflow-sdk-vs-inngest.mdx +102 -0
  121. package/docs/comparisons/workflow-sdk-vs-temporal.mdx +123 -0
  122. package/docs/comparisons/workflow-sdk-vs-trigger-dev.mdx +103 -0
  123. package/docs/configuration/build-and-diagnostics.mdx +70 -0
  124. package/docs/configuration/cli-and-web-ui.mdx +182 -0
  125. package/docs/configuration/framework-options.mdx +165 -0
  126. package/docs/configuration/index.mdx +32 -0
  127. package/docs/configuration/meta.json +12 -0
  128. package/docs/configuration/runtime-tuning.mdx +276 -0
  129. package/docs/configuration/worlds.mdx +275 -0
  130. package/docs/cookbook/advanced/child-workflows.mdx +203 -256
  131. package/docs/cookbook/advanced/meta.json +1 -1
  132. package/docs/cookbook/advanced/publishing-libraries.mdx +33 -24
  133. package/docs/cookbook/advanced/serializable-steps.mdx +12 -4
  134. package/docs/cookbook/advanced/upgrading-workflows.mdx +199 -0
  135. package/docs/cookbook/agent-patterns/agent-cancellation.mdx +34 -75
  136. package/docs/cookbook/agent-patterns/durable-agent.mdx +14 -142
  137. package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +11 -3
  138. package/docs/cookbook/common-patterns/batching.mdx +4 -0
  139. package/docs/cookbook/common-patterns/idempotency.mdx +40 -52
  140. package/docs/cookbook/common-patterns/rate-limiting.mdx +5 -1
  141. package/docs/cookbook/common-patterns/saga.mdx +6 -2
  142. package/docs/cookbook/common-patterns/scheduling.mdx +12 -0
  143. package/docs/cookbook/common-patterns/sequential-and-parallel.mdx +4 -0
  144. package/docs/cookbook/common-patterns/timeouts.mdx +6 -1
  145. package/docs/cookbook/common-patterns/webhooks.mdx +4 -0
  146. package/docs/cookbook/common-patterns/workflow-composition.mdx +19 -16
  147. package/docs/cookbook/index.mdx +3 -3
  148. package/docs/cookbook/integrations/ai-sdk.mdx +64 -28
  149. package/docs/cookbook/integrations/chat-sdk.mdx +17 -0
  150. package/docs/cookbook/integrations/sandbox.mdx +17 -0
  151. package/docs/{deploying/index.mdx → deploying.mdx} +7 -7
  152. package/docs/errors/abort-signal-timeout-in-workflow.mdx +84 -0
  153. package/docs/errors/corrupted-event-log.mdx +5 -5
  154. package/docs/errors/deployment-mismatch.mdx +71 -0
  155. package/docs/errors/fetch-in-workflow.mdx +4 -0
  156. package/docs/errors/hook-conflict.mdx +60 -4
  157. package/docs/errors/index.mdx +1 -35
  158. package/docs/errors/node-js-module-in-workflow.mdx +4 -0
  159. package/docs/errors/replay-divergence.mdx +27 -0
  160. package/docs/errors/runtime-decryption-failed.mdx +77 -0
  161. package/docs/errors/serialization-failed.mdx +4 -0
  162. package/docs/errors/start-invalid-workflow-function.mdx +4 -0
  163. package/docs/errors/step-executed-multiple-times.mdx +23 -0
  164. package/docs/errors/step-not-registered.mdx +1 -1
  165. package/docs/errors/timeout-in-workflow.mdx +4 -0
  166. package/docs/errors/webhook-response-not-sent.mdx +4 -0
  167. package/docs/foundations/cancellation.mdx +459 -0
  168. package/docs/foundations/errors-and-retries.mdx +7 -3
  169. package/docs/foundations/hooks.mdx +29 -0
  170. package/docs/foundations/idempotency.mdx +243 -11
  171. package/docs/foundations/index.mdx +1 -23
  172. package/docs/foundations/meta.json +3 -1
  173. package/docs/foundations/serialization.mdx +77 -41
  174. package/docs/foundations/starting-workflows.mdx +79 -2
  175. package/docs/foundations/streaming.mdx +14 -23
  176. package/docs/foundations/versioning.mdx +263 -0
  177. package/docs/getting-started/astro.mdx +12 -2
  178. package/docs/getting-started/express.mdx +6 -2
  179. package/docs/getting-started/fastify.mdx +6 -2
  180. package/docs/getting-started/hono.mdx +6 -2
  181. package/docs/getting-started/index.mdx +16 -10
  182. package/docs/getting-started/meta.json +4 -1
  183. package/docs/getting-started/nestjs.mdx +77 -3
  184. package/docs/getting-started/next.mdx +12 -6
  185. package/docs/getting-started/nitro.mdx +28 -2
  186. package/docs/getting-started/nuxt.mdx +6 -2
  187. package/docs/getting-started/python.mdx +24 -18
  188. package/docs/getting-started/react-router/index.mdx +33 -0
  189. package/docs/getting-started/react-router/meta.json +5 -0
  190. package/docs/getting-started/react-router/v7.mdx +237 -0
  191. package/docs/getting-started/react-router/v8.mdx +232 -0
  192. package/docs/getting-started/sveltekit.mdx +12 -2
  193. package/docs/getting-started/tanstack-start.mdx +245 -0
  194. package/docs/getting-started/vite.mdx +6 -2
  195. package/docs/how-it-works/cancellation.mdx +287 -0
  196. package/docs/how-it-works/code-transform.mdx +21 -17
  197. package/docs/how-it-works/encryption.mdx +5 -5
  198. package/docs/how-it-works/event-sourcing.mdx +6 -6
  199. package/docs/how-it-works/framework-integrations.mdx +96 -337
  200. package/docs/how-it-works/meta.json +2 -1
  201. package/docs/internal/index.mdx +21 -0
  202. package/docs/internal/meta.json +10 -0
  203. package/docs/internal/nitro-native-build.mdx +38 -0
  204. package/docs/internal/nitro-web-ui.mdx +24 -0
  205. package/docs/internal/serializable-abort-controller.mdx +148 -0
  206. package/docs/meta.json +2 -2
  207. package/docs/observability/attributes.mdx +112 -0
  208. package/docs/observability/index.mdx +20 -1
  209. package/docs/observability/meta.json +1 -1
  210. package/docs/observability/tracing.mdx +124 -0
  211. package/docs/testing/index.mdx +2 -2
  212. package/package.json +17 -13
  213. package/docs/api-reference/workflow-api/world/meta.json +0 -4
  214. package/docs/api-reference/workflow-api/world/observability.mdx +0 -164
  215. package/docs/cookbook/advanced/distributed-abort-controller.mdx +0 -318
  216. package/docs/deploying/building-a-world.mdx +0 -251
  217. package/docs/deploying/meta.json +0 -4
  218. package/docs/deploying/world/local-world.mdx +0 -84
  219. package/docs/deploying/world/meta.json +0 -4
  220. package/docs/deploying/world/postgres-world.mdx +0 -222
  221. package/docs/deploying/world/vercel-world.mdx +0 -179
  222. package/docs/migration-guides/index.mdx +0 -34
  223. package/docs/migration-guides/meta.json +0 -9
  224. package/docs/migration-guides/migrating-from-aws-step-functions.mdx +0 -363
  225. package/docs/migration-guides/migrating-from-inngest.mdx +0 -314
  226. package/docs/migration-guides/migrating-from-temporal.mdx +0 -318
  227. package/docs/migration-guides/migrating-from-trigger-dev.mdx +0 -337
@@ -0,0 +1,24 @@
1
+ ---
2
+ title: Local web UI in Nitro dev
3
+ description: Inspect, monitor, and debug your workflow runs from the /_workflow route during Nitro development.
4
+ type: overview
5
+ ---
6
+
7
+ # Local web UI in Nitro dev
8
+
9
+ {/* TODO: unreleased — changeset .changeset/nitro-dashboard-route.md is pending; ships in the next @workflow/nitro beta (5.0.0-beta.12). Update this date on publish. */}
10
+ <span className="text-sm text-fd-muted-foreground">June 2, 2026</span>
11
+
12
+ The Workflow SDK web UI is now built into the Nitro dev server. During development, open `/_workflow` in your browser to inspect, monitor, and debug your workflow runs.
13
+
14
+ ## What's new
15
+
16
+ - **Built-in `/_workflow` route in development.** The route starts the local web UI and redirects to it — no separate command or process required.
17
+ - **Inspect runs in place.** Inspect, monitor, and debug your workflow runs directly from the dev server you're already running.
18
+
19
+ ![Workflow SDK web UI on the /_workflow route](/local-web-ui.png)
20
+
21
+ ## Learn more
22
+
23
+ - [Observability](/docs/observability) — Inspect runs with the web UI and CLI
24
+ - [Nitro](/docs/getting-started/nitro) — Set up Workflow SDK in a Nitro v3 app
@@ -0,0 +1,148 @@
1
+ ---
2
+ title: Serializable AbortController and AbortSignal
3
+ description: AbortController and AbortSignal now work across workflow and step boundaries using the standard Web API.
4
+ type: overview
5
+ ---
6
+
7
+ # Serializable AbortController and AbortSignal
8
+
9
+ <span className="text-sm text-fd-muted-foreground">March 12, 2026</span>
10
+
11
+ `AbortController` and `AbortSignal` now work natively in workflow functions. Create a controller, pass its signal to steps, and call `abort()` — no special imports or wrapper functions needed.
12
+
13
+ ## What's new
14
+
15
+ - **Standard API, zero boilerplate.** `new AbortController()` works inside `"use workflow"` functions. The controller and its signal are automatically serialized across workflow and step boundaries.
16
+ - **Dual hook + stream backing for durability.** Under the hood, each controller is backed by a durable [hook](/docs/foundations/hooks) (for replay correctness) and a [stream](/docs/foundations/streaming) (for real-time propagation to running steps). This means aborts survive cold starts, replays, and scale events.
17
+ - **Cooperative cancellation.** Steps receive the abort in real time and can respond by checking `signal.aborted`, calling `signal.throwIfAborted()`, or passing the signal to APIs like `fetch`.
18
+ - **Abort errors skip retries.** When a step throws due to an abort (e.g., `fetch` throws `AbortError`), the error is automatically wrapped in `FatalError` so it skips retries and bubbles up immediately.
19
+ - **`AbortSignal.timeout()` blocked in workflow VM.** Because it relies on real-time timers that break deterministic replay, `AbortSignal.timeout()` throws a helpful error pointing to the `sleep()` + `AbortController` pattern instead.
20
+ - **`Request.signal` preserved when it carries abort state.** A `Request`'s `.signal` is serialized when it's already aborted (so the cancellation that happened pre-serialization is preserved) or when it's a workflow-managed signal (so its hook + stream backing carries through). Plain non-aborted native signals — including the auto-generated signal on `new Request(url)` — are dropped to avoid minting stream infrastructure for every `Request`. To get cross-boundary cancellation through a `Request`, build it with the signal from a workflow-context `AbortController`.
21
+ - **Pending queue items drain on completion.** If you call `abort()` (or `dispose` a hook, or kick off a `void sleep('1d')`, or fire a `void someStep()`) without a suspension point between that call and the workflow's return, the runtime now treats end-of-run as a final suspension and commits all pending operations before the run is marked terminal. This matches normal JS semantics — `setTimeout` etc. continue running after the surrounding function returns. The most important case: `controller.abort()` called as the last statement of a workflow now actually propagates to in-flight steps on other compute instances.
22
+
23
+ ## Timeout with cancellation
24
+
25
+ Race a step against a durable `sleep()`, and cancel the step if the timeout wins:
26
+
27
+ ```typescript
28
+ import { sleep } from "workflow";
29
+
30
+ export async function fetchWithTimeout(url: string) {
31
+ "use workflow";
32
+
33
+ const controller = new AbortController();
34
+
35
+ const result = await Promise.race([
36
+ fetchUrl(url, controller.signal),
37
+ sleep("10s").then(() => null),
38
+ ]);
39
+
40
+ if (result === null) {
41
+ controller.abort();
42
+ throw new Error(`Request to ${url} timed out after 10s`);
43
+ }
44
+
45
+ return result;
46
+ }
47
+
48
+ async function fetchUrl(url: string, signal: AbortSignal) {
49
+ "use step";
50
+ const response = await fetch(url, { signal });
51
+ return response.json();
52
+ }
53
+ ```
54
+
55
+ ## Cancelling parallel work
56
+
57
+ When racing multiple steps, cancel the losers:
58
+
59
+ ```typescript
60
+ declare function fetchUrl(url: string, signal: AbortSignal): Promise<{ url: string; data: unknown }>; // @setup
61
+
62
+ export async function firstResponder(urls: string[]) {
63
+ "use workflow";
64
+
65
+ const controller = new AbortController();
66
+
67
+ const result = await Promise.race(
68
+ urls.map((url) => fetchUrl(url, controller.signal))
69
+ );
70
+
71
+ controller.abort(); // Cancel remaining fetches
72
+
73
+ return result;
74
+ }
75
+ ```
76
+
77
+ ## User-triggered cancellation with hooks
78
+
79
+ Combine hooks with abort controllers to let users cancel work from an external API:
80
+
81
+ ```typescript
82
+ declare function doExpensiveWork(signal: AbortSignal): Promise<unknown>; // @setup
83
+ import { createHook } from "workflow";
84
+
85
+ export async function userCancellableWorkflow(jobId: string) {
86
+ "use workflow";
87
+
88
+ using cancelHook = createHook<{ reason: string }>({
89
+ token: `cancel:${jobId}`,
90
+ });
91
+
92
+ const controller = new AbortController();
93
+ const workPromise = doExpensiveWork(controller.signal);
94
+
95
+ const result = await Promise.race([
96
+ workPromise.then((data) => ({ status: "completed", data })),
97
+ cancelHook.then((payload) => {
98
+ controller.abort();
99
+ return { status: "cancelled", reason: payload.reason };
100
+ }),
101
+ ]);
102
+
103
+ return result;
104
+ }
105
+ ```
106
+
107
+ ## Step-initiated abort
108
+
109
+ A step can receive the full `AbortController` and call `abort()` to cancel parallel work — useful for watchdog patterns like quota monitoring:
110
+
111
+ ```typescript
112
+ declare function processData(url: string, signal: AbortSignal): Promise<{ processed: boolean }>; // @setup
113
+
114
+ export async function processWithQuotaCheck(userId: string, dataUrl: string) {
115
+ "use workflow";
116
+
117
+ const controller = new AbortController();
118
+
119
+ const [result] = await Promise.all([
120
+ processData(dataUrl, controller.signal),
121
+ monitorQuota(userId, controller),
122
+ ]);
123
+
124
+ return result;
125
+ }
126
+
127
+ async function monitorQuota(userId: string, controller: AbortController) {
128
+ "use step";
129
+
130
+ while (!controller.signal.aborted) {
131
+ const quota = await fetch(`https://api.example.com/quota/${userId}`);
132
+ const { exceeded } = await quota.json();
133
+
134
+ if (exceeded) {
135
+ controller.abort("Quota exceeded"); // Cancels processData
136
+ return;
137
+ }
138
+
139
+ await new Promise((resolve) => setTimeout(resolve, 5000));
140
+ }
141
+ }
142
+ ```
143
+
144
+ ## Learn more
145
+
146
+ - [Cancellation](/docs/foundations/cancellation) — Full guide with all usage patterns
147
+ - [How Cancellation Works](/docs/how-it-works/cancellation) — Hook and stream internals
148
+ - [AbortSignal.timeout() in Workflow](/docs/errors/abort-signal-timeout-in-workflow) — Why `AbortSignal.timeout()` is blocked and what to use instead
package/docs/meta.json CHANGED
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "pages": [
3
- "introduction",
4
3
  "---",
5
4
  "getting-started",
6
5
  "foundations",
@@ -10,7 +9,8 @@
10
9
  "testing",
11
10
  "deploying",
12
11
  "errors",
13
- "migration-guides",
12
+ "comparisons",
13
+ "configuration",
14
14
  "api-reference"
15
15
  ]
16
16
  }
@@ -0,0 +1,112 @@
1
+ ---
2
+ title: Attributes
3
+ description: Attach metadata to workflow runs for observability.
4
+ type: reference
5
+ summary: Add string attributes to a workflow run.
6
+ prerequisites:
7
+ - /docs/foundations/workflows-and-steps
8
+ related:
9
+ - /docs/observability
10
+ - /docs/api-reference/workflow/set-attributes
11
+ - /docs/api-reference/workflow-errors/workflow-world-error
12
+ ---
13
+
14
+ [`setAttributes`](/docs/api-reference/workflow/set-attributes) attaches plaintext string metadata to the current workflow run. These attributes are displayed in observability CLI/UI, and can be used to search and filter runs through the [Analytics API](/docs/api-reference/workflow-runtime/world/analytics).
15
+
16
+ You can also seed any attributes directly when starting a run:
17
+
18
+ {/* @skip-typecheck: abbreviated usage; orderWorkflow is defined below */}
19
+ ```typescript lineNumbers
20
+ const run = await start(orderWorkflow, ["ord_123"], {
21
+ attributes: { source: "checkout" }, // [!code highlight]
22
+ })
23
+ ```
24
+
25
+ ```typescript lineNumbers
26
+ import { setAttributes } from "workflow"
27
+
28
+ export async function orderWorkflow(orderId: string) {
29
+ "use workflow"
30
+
31
+ await setAttributes({ // [!code highlight]
32
+ phase: "received", // [!code highlight]
33
+ orderId, // [!code highlight]
34
+ }) // [!code highlight]
35
+
36
+ // ...work...
37
+
38
+ await setAttributes({ phase: "complete" }) // [!code highlight]
39
+ }
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ Call [`setAttributes`](/docs/api-reference/workflow/set-attributes) from a `"use workflow"` function or a `"use step"` function. Plain application code is not supported because there is no active workflow run to attach attributes to.
45
+
46
+ Values must be strings. Pass `undefined` to remove a key:
47
+
48
+ ```typescript lineNumbers
49
+ import { setAttributes } from "workflow"
50
+
51
+ export async function cleanupAttributes() {
52
+ "use workflow"
53
+
54
+ await setAttributes({ staleKey: undefined }) // [!code highlight]
55
+ }
56
+ ```
57
+
58
+ Attribute keys must be 1-256 characters, values must be strings up to 256 bytes, and each run can have up to 64 attributes. Keys that start with `$` are reserved for framework and library code.
59
+
60
+ ## Reserved keys
61
+
62
+ When `start()` is called from inside a running workflow or step, the new run is automatically tagged with two reserved attributes:
63
+
64
+ - `$parentRunId`: the run that started it.
65
+ - `$rootRunId`: the root of the chain. It is inherited, so every run in a daisy chain or fan-out shares one root id.
66
+
67
+ Top-level runs (started outside any workflow or step) are not tagged.
68
+
69
+ ## Viewing attributes
70
+
71
+ The run details panel in the observability UI shows the run's current attributes as key-value rows. Reserved `$`-prefixed keys are marked with a badge and sorted after user keys:
72
+
73
+ ![Run details panel showing the Attributes card with reserved keys badged](/screenshots/attributes/run-details-attributes.png)
74
+
75
+ Each `setAttributes` call appears on the trace timeline as a diamond marker at the moment the attributes were written:
76
+
77
+ ![Trace timeline with attr_set diamond markers on the run row](/screenshots/attributes/trace-timeline.png)
78
+
79
+ Expanding an `attr_set` event — in the run sidebar or the Events tab — shows the changed keys, removed keys, and whether the write came from the workflow body or a step (with the attempt number):
80
+
81
+ ![Expanded attr_set events showing changes and the writer](/screenshots/attributes/run-details-attr-set-events.png)
82
+
83
+ ## Searching and filtering by attributes
84
+
85
+ The [Analytics API](/docs/api-reference/workflow-runtime/world/analytics) can discover which attribute keys exist and filter run listings by them. The `analytics` namespace is optional on `World` — feature-detect it before use; it is absent on local, Postgres, and other custom Worlds:
86
+
87
+ ```typescript lineNumbers
88
+ import { getWorld } from "workflow/runtime";
89
+
90
+ const world = await getWorld();
91
+ if (!world.analytics) {
92
+ throw new Error("This World does not support analytics queries"); // [!code highlight]
93
+ }
94
+
95
+ // Which attribute keys exist, and on how many runs?
96
+ const keys = await world.analytics.attributes.list();
97
+
98
+ // List runs whose latest attributes match every pair
99
+ const stuck = await world.analytics.runs.list({
100
+ attributes: { phase: "received" }, // [!code highlight]
101
+ });
102
+ ```
103
+
104
+ Matching is latest-write-wins: once the run above writes `phase: "complete"`, it stops matching `phase: "received"`.
105
+
106
+ ## Behavior
107
+
108
+ - Attributes require a World implementing spec version 4 or later.
109
+ - Writes from workflow and step bodies append native `attr_set` events and immediately materialize `run.attributes`.
110
+ - Storage errors surface rather than being silently ignored: transient errors on workflow-body writes are retried, and a write the World rejects as invalid (for example, exceeding the per-run attribute cap across multiple calls) fails the run with the validation error.
111
+ - Step-body storage errors throw from `setAttributes` like any other step-side network write. Catch the error inside the step if the attribute is best-effort.
112
+ - Reading and querying: each run's current attributes are returned on the run objects from the [Storage](/docs/api-reference/workflow-runtime/world/storage) and [Analytics](/docs/api-reference/workflow-runtime/world/analytics) APIs, and the Analytics API supports discovering attribute keys and filtering run listings by key=value pairs (see [Searching and filtering by attributes](#searching-and-filtering-by-attributes)). On Worlds without the optional `analytics` namespace, attributes are readable on run objects but not searchable.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: Observability
3
3
  description: Inspect, monitor, and debug workflows through the CLI and Web UI with powerful observability tools.
4
- type: guide
4
+ type: overview
5
5
  summary: Inspect and debug workflow runs using the CLI and Web UI.
6
6
  prerequisites:
7
7
  - /docs/foundations
@@ -42,6 +42,21 @@ npx workflow inspect runs --web
42
42
 
43
43
  ![Workflow SDK Web UI](/o11y-ui.png)
44
44
 
45
+ In the runs table, select one or more runs and choose **Cancel** to cancel the batch in a single request. Runs that fail with a retryable error stay selected so you can retry them.
46
+
47
+ To share a link to a specific run without opening a browser, use the `--url`
48
+ flag. It prints the dashboard deep link to stdout and exits (no browser, no
49
+ local server) — useful for scripts, PR comments, or automation. Add `--json` to
50
+ get `{ "url": "..." }`.
51
+
52
+ ```bash
53
+ # Print the deep-link URL for a run (no browser, no server)
54
+ npx workflow inspect run <run_id> --url
55
+
56
+ # Vercel runs: add the backend (and --env preview for preview deployments)
57
+ npx workflow inspect run <run_id> --backend vercel --url
58
+ ```
59
+
45
60
  ## Backends
46
61
 
47
62
  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
@@ -63,3 +78,7 @@ npx workflow inspect runs --backend vercel
63
78
  ```
64
79
 
65
80
  When deployed to Vercel, workflow data is [encrypted end-to-end](/docs/how-it-works/encryption). Encrypted fields display as locked placeholders until you choose to decrypt them using the **Decrypt** button in the web UI or the `--decrypt` flag in the CLI.
81
+
82
+ ## More Observability Features
83
+
84
+ <AutoCards />
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "title": "Observability",
3
- "pages": ["observability"]
3
+ "pages": ["tracing", "attributes"]
4
4
  }
@@ -0,0 +1,124 @@
1
+ ---
2
+ title: Tracing
3
+ description: Distributed tracing with OpenTelemetry for workflow runs, steps, and queue deliveries.
4
+ type: guide
5
+ summary: Trace workflow execution end to end with OpenTelemetry.
6
+ prerequisites:
7
+ - /docs/foundations/workflows-and-steps
8
+ related:
9
+ - /docs/observability
10
+ - /docs/observability/attributes
11
+ - /docs/how-it-works/event-sourcing
12
+ ---
13
+
14
+ The Workflow SDK is instrumented with [OpenTelemetry](https://opentelemetry.io) out of the box. It emits spans for workflow starts, every workflow and step invocation, and the HTTP calls it makes to the workflow backend — and it propagates trace context across queue deliveries so a run remains traceable end to end.
15
+
16
+ The SDK only depends on the OpenTelemetry **API**, never on an SDK or exporter. If your application does not register an OpenTelemetry SDK, all tracing code is a silent no-op with no overhead and no behavior change.
17
+
18
+ ## Enabling tracing
19
+
20
+ Register any OpenTelemetry Node SDK in your application. On Vercel with Next.js, the simplest setup is [`@vercel/otel`](https://vercel.com/docs/tracing/instrumentation) in `instrumentation.ts`:
21
+
22
+ ```typescript title="instrumentation.ts" lineNumbers
23
+ import { registerOTel } from "@vercel/otel"
24
+
25
+ export function register() {
26
+ registerOTel({ serviceName: "my-app" })
27
+ }
28
+ ```
29
+
30
+ No workflow-specific configuration is required. As soon as a tracer provider and propagator are registered, the SDK's spans, context propagation, and span links activate automatically.
31
+
32
+ <Callout>
33
+ `@opentelemetry/api` is an **optional peer dependency**. An OpenTelemetry SDK such as `@vercel/otel` normally pulls it in transitively, but installing it directly (`npm i @opentelemetry/api`) guarantees it is present in your build — particularly for bundled or serverless targets where the SDK's tracing is inlined at build time. If it can't be resolved, tracing is a silent no-op.
34
+ </Callout>
35
+
36
+ ## Spans
37
+
38
+ | Span name | Kind | Emitted when |
39
+ | --- | --- | --- |
40
+ | `workflow.start <name>` | internal | `start()` is called in your application code |
41
+ | `workflow.execute <name>` | consumer (root) | a queue delivery invokes the workflow — replay, orchestration, and inline steps run under it |
42
+ | `step.execute <name>` | internal (inline) / consumer + root (queue-delivered) | a step function executes |
43
+ | `http <method>` | client | the SDK calls the workflow backend (event reads/writes) |
44
+ | `workflow.stream.write` | client | a stream chunk (or the stream close) is flushed to the backend |
45
+ | `workflow.stream.flush` | client | a buffered batch of stream writes settles; back-dated to the batch's first `write()`, so its duration is the app-perceived batch latency (buffer dwell + RPC) |
46
+ | `workflow.stream.close` | client | the stream-close RPC; its duration is the close round trip |
47
+ | `workflow.stream.read.complete` | client | a stream read drains; back-dated to the read dispatch, so its duration is the total read (`workflow.stream.read.chunks` / `.bytes` carry throughput counts) |
48
+ | `workflow.stream.read.connect` | client | a live stream read opens; the span covers dispatch → response headers (network connect) |
49
+ | `workflow.stream.read` | client | a live stream read receives its first chunk; the span's duration is the end-to-end time-to-first-chunk (see `workflow.stream.read.ttfc_ms`) |
50
+
51
+ `<name>` is the short function name (for example `processOrder`); the full machine name, including the source module, is available in the `workflow.name` / `step.name` attributes.
52
+
53
+ Stream spans are emitted by the SDK's world backend on the client that writes or reads the stream, and (like all SDK spans) are no-ops when no OpenTelemetry SDK is registered. The `workflow.stream.read` span only appears once the first non-empty chunk arrives.
54
+
55
+ ## Key attributes
56
+
57
+ | Attribute | Description |
58
+ | --- | --- |
59
+ | `workflow.run.id` | The run ID (`wrun_...`). Present on every workflow and step span — the primary key for finding all spans of a run. |
60
+ | `workflow.name` | The workflow function name. |
61
+ | `workflow.trace.mode` | The active trace mode (`linked` or `continuous`). |
62
+ | `workflow.trace.propagated` | Whether the invocation received trace context from the queue message. |
63
+ | `workflow.queue.overhead_ms` | Time between the message being enqueued and the handler starting — queue dwell plus any cold start. |
64
+ | `workflow.stream.name` | The stream name, on stream write/read spans. |
65
+ | `workflow.stream.operation` | The stream operation: `write`, `write_multi`, `close`, `read`, or `flush`. |
66
+ | `workflow.stream.write.chunk_rtt` | Time between emissions of a chunk to the wire, and receiving the `ack` message for that chunk. Also stamped on `workflow.stream.flush` (the batch's write RPC duration, network included). |
67
+ | `workflow.stream.flush.buffer_dwell_ms` | On `workflow.stream.flush`: time the batch's first chunk waited in the client-side write buffer (flush timer, run-ready barrier) before the request was dispatched. `workflow.stream.flush.chunks` / `.bytes` carry the batch shape. |
68
+ | `workflow.stream.read.ttfc_ms` | Time between opening a read connection and observing and receiving the first chunk back. |
69
+ | `workflow.stream.read.connect_ms` | On `workflow.stream.read`: the connect portion (read dispatch → stream handle/response headers), network included. |
70
+
71
+ ## Trace shape: one trace per invocation
72
+
73
+ A single workflow run can span hours or days across many separate function invocations: every step completion, `sleep()` wake-up, and retry is a new queue delivery. Stitching all of that into one trace produces giant, slow-loading traces that most tracing backends truncate.
74
+
75
+ Instead, the SDK creates **one bounded trace per invocation**. Each `workflow.execute` (or background `step.execute`) span starts a new trace root and attaches two **span links**:
76
+
77
+ - a link to the **enqueue site** — the span that queued the message which triggered this invocation, and
78
+ - a link to the **run origin** — the trace in which `start()` was originally called.
79
+
80
+ A span link is OpenTelemetry's relationship for "causally related, but in a different trace." It is the standard pattern for asynchronous messaging, where producing and consuming a message can be separated by arbitrary time.
81
+
82
+ ```mermaid
83
+ flowchart LR
84
+ O["start() request trace"]
85
+ A["invocation 1"]
86
+ B["invocation 2"]
87
+ C["invocation 3 ..."]
88
+ A -. "link" .-> O
89
+ B -. "link" .-> O
90
+ C -. "link" .-> O
91
+ B -. "link" .-> A
92
+ C -. "link" .-> B
93
+
94
+ style O fill:#a78bfa,stroke:#8b5cf6,color:#000
95
+ ```
96
+
97
+ Each invocation links back to the trace that enqueued it and to the run origin.
98
+
99
+ To see a whole run, query by attribute rather than by trace ID — for example `workflow.run.id = wrun_...` in your tracing backend — or follow the span links between invocation traces.
100
+
101
+ ## Trace modes
102
+
103
+ The `WORKFLOW_TRACE_MODE` environment variable controls the shape:
104
+
105
+ | Mode | Behavior |
106
+ | --- | --- |
107
+ | `linked` (default) | Each invocation is its own trace root with span links to the enqueue site and the run origin. Traces stay small; sampling is decided per invocation. |
108
+ | `continuous` | The run-origin context becomes the **parent** of every invocation, so the entire run shares one trace ID. |
109
+
110
+ <Callout type="warn">
111
+ This is a behavior change from v4, which always used `continuous`-style tracing. If you have dashboards or queries that assume one trace ID per run, either update them to use `workflow.run.id` and span links, or set `WORKFLOW_TRACE_MODE=continuous` to restore the previous shape. Note that in `linked` mode each invocation root makes its own sampling decision, and the number of root spans increases to one per invocation.
112
+ </Callout>
113
+
114
+ ## Context propagation
115
+
116
+ When tracing is enabled, the SDK propagates [W3C Trace Context](https://www.w3.org/TR/trace-context/) on its outbound calls:
117
+
118
+ - **Backend requests** carry `traceparent`, `tracestate`, and `baggage` headers, so backend spans can join your trace.
119
+ - **Queue messages** carry the run-origin trace context in the message payload, and the queue re-delivers the producer's context to the workflow handler, where it becomes the enqueue-site span link.
120
+ - **Baggage** carries `workflow.run_id` and `workflow.name` entries during workflow execution, allowing downstream services you call from steps to tag their own telemetry with the run ID.
121
+
122
+ <Callout>
123
+ Baggage entries set by your application are propagated as a `baggage` HTTP header on the SDK's backend requests, like any other OpenTelemetry-instrumented HTTP call. Avoid placing sensitive values in baggage.
124
+ </Callout>
@@ -109,7 +109,7 @@ That's it. The plugin automatically:
109
109
 
110
110
  1. Transforms `"use workflow"` and `"use step"` directives via SWC
111
111
  2. Builds workflow and step bundles before tests run
112
- 3. Sets up an in-process workflow runtime using a fresh [Local World](/docs/worlds/local) instance in each test worker — all workflow data is cleared automatically between test files for full isolation
112
+ 3. Sets up an in-process workflow runtime using a fresh [Local World](/worlds/local) instance in each test worker — all workflow data is cleared automatically between test files for full isolation
113
113
 
114
114
  <Callout type="info">
115
115
  Use a separate Vitest configuration and a distinct file naming convention (e.g. `*.integration.test.ts`) to keep unit tests and integration tests separate. Unit tests run with a standard Vitest config without the workflow plugin, while integration tests use the config above.
@@ -117,7 +117,7 @@ Use a separate Vitest configuration and a distinct file naming convention (e.g.
117
117
 
118
118
  ### Writing Integration Tests
119
119
 
120
- Use [`start()`](/docs/api-reference/workflow-api/start) to trigger a workflow and [`run.returnValue`](/docs/api-reference/workflow-api/start#returnvalue) to get the result. `returnValue` is a promise that blocks until the workflow completes (or throws if it fails):
120
+ Use [`start()`](/docs/api-reference/workflow-api/start) to trigger a workflow and [`run.returnValue`](/docs/api-reference/workflow-api/start#returns) to get the result. `returnValue` is a promise that blocks until the workflow completes (or throws if it fails):
121
121
 
122
122
  ```typescript title="workflows/calculate.integration.test.ts" lineNumbers
123
123
  import { describe, it, expect } from "vitest";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workflow",
3
- "version": "5.0.0-beta.4",
3
+ "version": "5.0.0-beta.40",
4
4
  "description": "Workflow SDK - Build durable, resilient, and observable workflows",
5
5
  "main": "dist/typescript-plugin.cjs",
6
6
  "type": "module",
@@ -29,6 +29,7 @@
29
29
  ".": {
30
30
  "types": "./dist/index.d.ts",
31
31
  "workflow": "./dist/workflow.js",
32
+ "node": "./dist/index.js",
32
33
  "require": "./dist/typescript-plugin.cjs",
33
34
  "default": "./dist/index.js"
34
35
  },
@@ -48,6 +49,8 @@
48
49
  "./astro": "./dist/astro.js",
49
50
  "./vite": "./dist/vite.js",
50
51
  "./nest": "./dist/nest.js",
52
+ "./nest/builder": "./dist/nest-builder.js",
53
+ "./nest/vercel-builder": "./dist/nest-vercel-builder.js",
51
54
  "./runtime": "./dist/runtime.js",
52
55
  "./observability": {
53
56
  "types": "./dist/observability.d.ts",
@@ -56,22 +59,23 @@
56
59
  },
57
60
  "dependencies": {
58
61
  "ms": "2.1.3",
59
- "@workflow/astro": "5.0.0-beta.4",
60
- "@workflow/cli": "5.0.0-beta.4",
61
- "@workflow/core": "5.0.0-beta.4",
62
- "@workflow/errors": "5.0.0-beta.1",
63
- "@workflow/typescript-plugin": "5.0.0-beta.3",
64
- "@workflow/utils": "5.0.0-beta.1",
65
- "@workflow/next": "5.0.0-beta.4",
66
- "@workflow/nest": "5.0.0-beta.4",
67
- "@workflow/nuxt": "5.0.0-beta.4",
68
- "@workflow/sveltekit": "5.0.0-beta.4",
69
- "@workflow/rollup": "5.0.0-beta.4",
70
- "@workflow/nitro": "5.0.0-beta.4"
62
+ "@workflow/astro": "5.0.0-beta.40",
63
+ "@workflow/cli": "5.0.0-beta.40",
64
+ "@workflow/core": "5.0.0-beta.40",
65
+ "@workflow/errors": "5.0.0-beta.16",
66
+ "@workflow/typescript-plugin": "5.0.0-beta.5",
67
+ "@workflow/utils": "5.0.0-beta.8",
68
+ "@workflow/next": "5.0.0-beta.40",
69
+ "@workflow/nest": "5.0.0-beta.40",
70
+ "@workflow/nitro": "5.0.0-beta.40",
71
+ "@workflow/nuxt": "5.0.0-beta.40",
72
+ "@workflow/sveltekit": "5.0.0-beta.40",
73
+ "@workflow/rollup": "5.0.0-beta.40"
71
74
  },
72
75
  "devDependencies": {
73
76
  "@types/ms": "2.1.0",
74
77
  "@types/node": "22.19.0",
78
+ "typescript": "^6.0.3",
75
79
  "@workflow/tsconfig": "5.0.0-beta.0"
76
80
  },
77
81
  "peerDependencies": {
@@ -1,4 +0,0 @@
1
- {
2
- "title": "World SDK",
3
- "pages": ["storage", "streams", "queue", "observability"]
4
- }