workflow 5.0.0-beta.14 → 5.0.0-beta.16

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 (91) hide show
  1. package/docs/api-reference/index.mdx +24 -0
  2. package/docs/api-reference/meta.json +8 -0
  3. package/docs/api-reference/vitest/index.mdx +1 -7
  4. package/docs/api-reference/workflow/create-hook.mdx +38 -0
  5. package/docs/api-reference/workflow/create-webhook.mdx +1 -0
  6. package/docs/api-reference/workflow/experimental-set-attributes.mdx +3 -1
  7. package/docs/api-reference/workflow/fetch.mdx +5 -0
  8. package/docs/api-reference/workflow-api/get-hook-by-token.mdx +7 -0
  9. package/docs/api-reference/workflow-api/get-run.mdx +6 -0
  10. package/docs/api-reference/workflow-api/index.mdx +6 -8
  11. package/docs/api-reference/workflow-api/resume-hook.mdx +57 -0
  12. package/docs/api-reference/workflow-api/start.mdx +6 -1
  13. package/docs/api-reference/workflow-astro/index.mdx +18 -0
  14. package/docs/api-reference/workflow-astro/meta.json +4 -0
  15. package/docs/api-reference/workflow-astro/workflow.mdx +45 -0
  16. package/docs/api-reference/workflow-errors/hook-conflict-error.mdx +60 -0
  17. package/docs/api-reference/workflow-errors/index.mdx +85 -0
  18. package/docs/api-reference/workflow-errors/meta.json +5 -0
  19. package/docs/api-reference/workflow-errors/run-not-supported-error.mdx +58 -0
  20. package/docs/api-reference/workflow-errors/workflow-error.mdx +52 -0
  21. package/docs/api-reference/workflow-errors/workflow-run-not-completed-error.mdx +58 -0
  22. package/docs/api-reference/workflow-errors/workflow-runtime-error.mdx +58 -0
  23. package/docs/api-reference/workflow-nest/configure-workflow-controller.mdx +33 -0
  24. package/docs/api-reference/workflow-nest/index.mdx +31 -0
  25. package/docs/api-reference/workflow-nest/meta.json +9 -0
  26. package/docs/api-reference/workflow-nest/nest-local-builder.mdx +64 -0
  27. package/docs/api-reference/workflow-nest/workflow-controller.mdx +40 -0
  28. package/docs/api-reference/workflow-nest/workflow-module.mdx +74 -0
  29. package/docs/api-reference/workflow-nitro/index.mdx +59 -0
  30. package/docs/api-reference/workflow-nuxt/index.mdx +47 -0
  31. package/docs/api-reference/workflow-observability/hydrate-data.mdx +35 -0
  32. package/docs/api-reference/workflow-observability/hydrate-resource-io.mdx +62 -0
  33. package/docs/api-reference/workflow-observability/index.mdx +64 -0
  34. package/docs/api-reference/workflow-observability/meta.json +11 -0
  35. package/docs/api-reference/workflow-observability/observability-revivers.mdx +50 -0
  36. package/docs/api-reference/workflow-observability/parse-class-name.mdx +41 -0
  37. package/docs/api-reference/workflow-observability/parse-step-name.mdx +40 -0
  38. package/docs/api-reference/workflow-observability/parse-workflow-name.mdx +55 -0
  39. package/docs/api-reference/workflow-runtime/create-world.mdx +39 -0
  40. package/docs/api-reference/workflow-runtime/get-world-handlers.mdx +44 -0
  41. package/docs/api-reference/{workflow-api → workflow-runtime}/get-world.mdx +7 -10
  42. package/docs/api-reference/workflow-runtime/health-check.mdx +50 -0
  43. package/docs/api-reference/workflow-runtime/index.mdx +43 -0
  44. package/docs/api-reference/workflow-runtime/meta.json +12 -0
  45. package/docs/api-reference/workflow-runtime/set-world.mdx +49 -0
  46. package/docs/api-reference/workflow-runtime/workflow-entrypoint.mdx +42 -0
  47. package/docs/api-reference/{workflow-api → workflow-runtime}/world/index.mdx +5 -8
  48. package/docs/api-reference/workflow-runtime/world/meta.json +4 -0
  49. package/docs/api-reference/{workflow-api → workflow-runtime}/world/queue.mdx +2 -2
  50. package/docs/api-reference/{workflow-api → workflow-runtime}/world/storage.mdx +11 -4
  51. package/docs/api-reference/{workflow-api → workflow-runtime}/world/streams.mdx +2 -2
  52. package/docs/api-reference/workflow-serde/index.mdx +0 -1
  53. package/docs/api-reference/workflow-serde/workflow-deserialize.mdx +1 -2
  54. package/docs/api-reference/workflow-serde/workflow-serialize.mdx +1 -2
  55. package/docs/api-reference/workflow-sveltekit/index.mdx +18 -0
  56. package/docs/api-reference/workflow-sveltekit/meta.json +4 -0
  57. package/docs/api-reference/workflow-sveltekit/workflow-plugin.mdx +42 -0
  58. package/docs/api-reference/workflow-vite/index.mdx +18 -0
  59. package/docs/api-reference/workflow-vite/meta.json +4 -0
  60. package/docs/api-reference/workflow-vite/workflow.mdx +48 -0
  61. package/docs/changelog/attributes-mvp.mdx +15 -3
  62. package/docs/changelog/eager-processing.mdx +26 -6
  63. package/docs/changelog/index.mdx +1 -1
  64. package/docs/cookbook/advanced/child-workflows.mdx +3 -1
  65. package/docs/cookbook/advanced/publishing-libraries.mdx +13 -12
  66. package/docs/cookbook/advanced/serializable-steps.mdx +3 -3
  67. package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +2 -2
  68. package/docs/cookbook/common-patterns/idempotency.mdx +36 -52
  69. package/docs/cookbook/common-patterns/rate-limiting.mdx +1 -1
  70. package/docs/cookbook/common-patterns/saga.mdx +2 -2
  71. package/docs/cookbook/common-patterns/scheduling.mdx +4 -0
  72. package/docs/cookbook/common-patterns/timeouts.mdx +2 -1
  73. package/docs/cookbook/common-patterns/workflow-composition.mdx +6 -0
  74. package/docs/cookbook/index.mdx +1 -1
  75. package/docs/cookbook/integrations/ai-sdk.mdx +10 -1
  76. package/docs/cookbook/integrations/chat-sdk.mdx +9 -0
  77. package/docs/cookbook/integrations/sandbox.mdx +9 -0
  78. package/docs/errors/step-not-registered.mdx +1 -1
  79. package/docs/foundations/cancellation.mdx +1 -2
  80. package/docs/foundations/hooks.mdx +29 -0
  81. package/docs/foundations/idempotency.mdx +236 -11
  82. package/docs/foundations/streaming.mdx +1 -1
  83. package/docs/foundations/versioning.mdx +1 -1
  84. package/docs/how-it-works/encryption.mdx +2 -2
  85. package/docs/how-it-works/event-sourcing.mdx +2 -2
  86. package/docs/migration-guides/migrating-from-temporal.mdx +1 -1
  87. package/docs/observability/attributes.mdx +26 -5
  88. package/docs/testing/index.mdx +2 -2
  89. package/package.json +10 -10
  90. package/docs/api-reference/workflow-api/world/meta.json +0 -4
  91. package/docs/api-reference/workflow-api/world/observability.mdx +0 -164
@@ -0,0 +1,42 @@
1
+ ---
2
+ title: workflowEntrypoint
3
+ description: Create the HTTP route handler that executes workflow runs from a workflow bundle.
4
+ type: reference
5
+ summary: Use workflowEntrypoint to wire a compiled workflow bundle into an HTTP route in custom server environments.
6
+ prerequisites:
7
+ - /docs/how-it-works/code-transform
8
+ related:
9
+ - /docs/api-reference/workflow-runtime/health-check
10
+ ---
11
+
12
+ Creates the HTTP route handler that executes workflow runs. The handler receives queue messages, replays the workflow from its event log, executes steps inline where possible, and suspends when the workflow waits on sleeps or hooks.
13
+
14
+ Framework adapters (Next.js, Nitro, SvelteKit, etc.) call this for you and mount the result at `/.well-known/workflow/v1/flow` — you only need it when wiring workflow support into a custom server environment.
15
+
16
+ ```typescript lineNumbers
17
+ import { workflowEntrypoint } from "workflow/runtime";
18
+ declare const workflowBundleCode: string; // @setup
19
+
20
+ const handler = workflowEntrypoint(workflowBundleCode); // [!code highlight]
21
+
22
+ // Mount on your server, e.g. a fetch-style route:
23
+ export const POST = (req: Request) => handler(req);
24
+ ```
25
+
26
+ ## API Signature
27
+
28
+ ### Parameters
29
+
30
+ | Parameter | Type | Description |
31
+ |-----------|------|-------------|
32
+ | `workflowCode` | `string` | The compiled workflow bundle code containing all workflow functions |
33
+ | `options` | `{ namespace?: string }` | Optional. `namespace` scopes the queue topics this handler consumes. |
34
+
35
+ ### Returns
36
+
37
+ Returns a fetch-style request handler: `(req: Request) => Promise<Response>`.
38
+
39
+ ## Related Functions
40
+
41
+ - [`getWorldHandlers()`](/docs/api-reference/workflow-runtime/get-world-handlers) - The build-time World access this handler is built on.
42
+ - [`healthCheck()`](/docs/api-reference/workflow-runtime/health-check) - Verify the entrypoint processes queue messages end-to-end.
@@ -4,7 +4,7 @@ description: Low-level API for inspecting and managing workflow runs, steps, eve
4
4
  type: overview
5
5
  summary: Access workflow infrastructure via await getWorld() for building observability dashboards, admin tools, and custom integrations.
6
6
  prerequisites:
7
- - /docs/api-reference/workflow-api/get-world
7
+ - /docs/api-reference/workflow-runtime/get-world
8
8
  keywords:
9
9
  - getWorld
10
10
  - World SDK
@@ -25,18 +25,15 @@ const world = await getWorld(); // [!code highlight]
25
25
  ## Interfaces
26
26
 
27
27
  <Cards>
28
- <Card href="/docs/api-reference/workflow-api/world/storage" title="Storage">
28
+ <Card href="/docs/api-reference/workflow-runtime/world/storage" title="Storage">
29
29
  Query runs, steps, hooks, and the underlying event log.
30
30
  </Card>
31
- <Card href="/docs/api-reference/workflow-api/world/streams" title="Streams">
31
+ <Card href="/docs/api-reference/workflow-runtime/world/streams" title="Streams">
32
32
  Read, write, and manage real-time data streams for workflow runs.
33
33
  </Card>
34
- <Card href="/docs/api-reference/workflow-api/world/queue" title="Queue">
34
+ <Card href="/docs/api-reference/workflow-runtime/world/queue" title="Queue">
35
35
  Low-level queue dispatch (internal SDK infrastructure).
36
36
  </Card>
37
- <Card href="/docs/api-reference/workflow-api/world/observability" title="Observability Utilities">
38
- Hydrate step I/O, parse display names, and decrypt workflow data.
39
- </Card>
40
37
  </Cards>
41
38
 
42
39
  <Callout type="info">
@@ -55,4 +52,4 @@ const hydrated = hydrateResourceIO(step, observabilityRevivers); // [!code highl
55
52
  console.log(hydrated.input, hydrated.output);
56
53
  ```
57
54
 
58
- See [Observability Utilities](/docs/api-reference/workflow-api/world/observability) for the full API.
55
+ See [`workflow/observability`](/docs/api-reference/workflow-observability) for the full API.
@@ -0,0 +1,4 @@
1
+ {
2
+ "title": "World SDK",
3
+ "pages": ["storage", "streams", "queue"]
4
+ }
@@ -4,7 +4,7 @@ description: Low-level queue interface for dispatching workflow and step invocat
4
4
  type: reference
5
5
  summary: "Methods: getDeploymentId(), queue(), createQueueHandler(). Internal queue dispatch — normally handled by the SDK."
6
6
  prerequisites:
7
- - /docs/api-reference/workflow-api/get-world
7
+ - /docs/api-reference/workflow-runtime/get-world
8
8
  related:
9
9
  - /docs/api-reference/workflow-api/start
10
10
  - /docs/foundations/starting-workflows
@@ -83,4 +83,4 @@ const handler = world.createQueueHandler(prefix, callback); // [!code highlight]
83
83
 
84
84
  - [start()](/docs/api-reference/workflow-api/start) — The standard way to start workflow runs
85
85
  - [Starting Workflows](/docs/foundations/starting-workflows) — Core concepts for workflow invocation
86
- - [Storage](/docs/api-reference/workflow-api/world/storage) — Create events that trigger queue dispatch
86
+ - [Storage](/docs/api-reference/workflow-runtime/world/storage) — Create events that trigger queue dispatch
@@ -4,11 +4,11 @@ description: Query workflow runs, steps, hooks, and the underlying event log via
4
4
  type: reference
5
5
  summary: "Interfaces: world.events, world.runs, world.steps, world.hooks. Events are the source of truth; runs, steps, and hooks are materialized views."
6
6
  prerequisites:
7
- - /docs/api-reference/workflow-api/get-world
7
+ - /docs/api-reference/workflow-runtime/get-world
8
8
  related:
9
9
  - /docs/api-reference/workflow-api/get-run
10
10
  - /docs/how-it-works/event-sourcing
11
- - /docs/api-reference/workflow-api/world/observability
11
+ - /docs/api-reference/workflow-observability
12
12
  keywords:
13
13
  - world.events
14
14
  - world.runs
@@ -116,6 +116,7 @@ const result = await world.events.listByCorrelationId({ // [!code highlight]
116
116
  | Category | Types |
117
117
  |----------|-------|
118
118
  | Run | `run_created`, `run_started`, `run_completed`, `run_failed`, `run_cancelled` |
119
+ | Attribute | `attr_set` |
119
120
  | Step | `step_created`, `step_started`, `step_completed`, `step_failed`, `step_retrying` |
120
121
  | Hook | `hook_created`, `hook_received`, `hook_disposed`, `hook_conflict` |
121
122
  | Wait | `wait_created`, `wait_completed` |
@@ -229,7 +230,7 @@ const result = await world.steps.list({ // [!code highlight]
229
230
  | `retryAfter` | `string \| null` | ISO timestamp for next retry attempt |
230
231
 
231
232
  <Callout type="info">
232
- Step I/O is serialized using the [devalue](https://github.com/Rich-Harris/devalue) format. Use `hydrateResourceIO()` from `workflow/observability` to deserialize it for display. See [Observability Utilities](/docs/api-reference/workflow-api/world/observability).
233
+ Step I/O is serialized using the [devalue](https://github.com/Rich-Harris/devalue) format. Use `hydrateResourceIO()` from `workflow/observability` to deserialize it for display. See [`hydrateResourceIO()`](/docs/api-reference/workflow-observability/hydrate-resource-io).
233
234
  </Callout>
234
235
 
235
236
  <Callout type="warn">
@@ -258,6 +259,12 @@ const hook = await world.hooks.get(hookId); // [!code highlight]
258
259
 
259
260
  Look up a hook by its token. Useful in webhook resume flows where you receive a token in the callback URL.
260
261
 
262
+ <Callout type="info">
263
+ For runtime application code, prefer [`getHookByToken()`](/docs/api-reference/workflow-api/get-hook-by-token). Use `world.hooks.getByToken()` when you are working directly with the World storage interface for custom tooling, admin views, or low-level integrations.
264
+
265
+ Hook-token lookup is the low-level form of the recommended idempotency flow: if a hook is already registered for your business key, reuse the hook's `runId` or resume that hook instead of starting another run. If no hook exists yet, start a workflow that creates the deterministic hook near the beginning and checks `await hook.getConflict()` to detect whether another run claimed the token first — on a conflict it resolves with the run that owns the token. See [Run idempotency](/docs/foundations/idempotency#run-idempotency).
266
+ </Callout>
267
+
261
268
  ```typescript lineNumbers
262
269
  const hook = await world.hooks.getByToken(token); // [!code highlight]
263
270
  ```
@@ -402,7 +409,7 @@ for (const event of events.data) {
402
409
 
403
410
  - [Event Sourcing](/docs/how-it-works/event-sourcing) — How the event log powers workflow replay and state
404
411
  - [getRun()](/docs/api-reference/workflow-api/get-run) — Higher-level API for working with individual runs
405
- - [Observability Utilities](/docs/api-reference/workflow-api/world/observability) — Hydrate step I/O, parse display names, decrypt data
412
+ - [`workflow/observability`](/docs/api-reference/workflow-observability) — Hydrate step I/O and parse display names
406
413
  - [resumeHook()](/docs/api-reference/workflow-api/resume-hook) — Resume a workflow by sending a payload to a hook
407
414
  - [Hooks](/docs/foundations/hooks) — Core concepts for hooks and pause points
408
415
  - [Workflows and Steps](/docs/foundations/workflows-and-steps) — Core concepts for steps
@@ -4,7 +4,7 @@ description: Read, write, and manage real-time data streams for workflow runs.
4
4
  type: reference
5
5
  summary: "Methods: streams.write(), streams.writeMulti(), streams.get(), streams.close(), streams.list(), streams.getChunks(), streams.getInfo(). Stream methods live on world.streams."
6
6
  prerequisites:
7
- - /docs/api-reference/workflow-api/get-world
7
+ - /docs/api-reference/workflow-runtime/get-world
8
8
  related:
9
9
  - /docs/foundations/streaming
10
10
  - /docs/api-reference/workflow/get-writable
@@ -213,4 +213,4 @@ do {
213
213
 
214
214
  - [Streaming](/docs/foundations/streaming) — Core concepts for streaming data from workflows
215
215
  - [getWritable()](/docs/api-reference/workflow/get-writable) — The standard way to write to streams from within steps
216
- - [Storage](/docs/api-reference/workflow-api/world/storage) — Query runs, steps, hooks, and events
216
+ - [Storage](/docs/api-reference/workflow-runtime/world/storage) — Query runs, steps, hooks, and events
@@ -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
@@ -0,0 +1,18 @@
1
+ ---
2
+ title: "workflow/sveltekit"
3
+ description: SvelteKit integration for automatic workflow bundling via Vite.
4
+ type: overview
5
+ summary: Explore the SvelteKit integration for automatic workflow bundling and runtime support.
6
+ related:
7
+ - /docs/getting-started/sveltekit
8
+ ---
9
+
10
+ SvelteKit integration for Workflow SDK that configures Vite to transform workflow code and build the workflow bundles.
11
+
12
+ ## Functions
13
+
14
+ <Cards>
15
+ <Card title="workflowPlugin()" href="/docs/api-reference/workflow-sveltekit/workflow-plugin">
16
+ Vite plugin that transforms workflow code (`"use step"`/`"use workflow"` directives) in SvelteKit apps
17
+ </Card>
18
+ </Cards>
@@ -0,0 +1,4 @@
1
+ {
2
+ "title": "workflow/sveltekit",
3
+ "pages": ["workflow-plugin"]
4
+ }
@@ -0,0 +1,42 @@
1
+ ---
2
+ title: workflowPlugin
3
+ description: Configure Vite to transform workflow directives in SvelteKit.
4
+ type: reference
5
+ summary: Add workflowPlugin to your Vite config to enable workflow directive transformation in SvelteKit apps.
6
+ prerequisites:
7
+ - /docs/getting-started/sveltekit
8
+ ---
9
+
10
+ Returns the Vite plugins that transform workflow code (`"use step"`/`"use workflow"` directives) and build the workflow bundles in a SvelteKit app.
11
+
12
+ ## Usage
13
+
14
+ To enable `"use step"` and `"use workflow"` directives while developing locally or deploying to production, add `workflowPlugin()` to the `plugins` array of your Vite config.
15
+
16
+ ```typescript title="vite.config.ts" lineNumbers
17
+ import { sveltekit } from "@sveltejs/kit/vite";
18
+ import { defineConfig } from "vite";
19
+ import { workflowPlugin } from "workflow/sveltekit"; // [!code highlight]
20
+
21
+ export default defineConfig({
22
+ plugins: [sveltekit(), workflowPlugin()], // [!code highlight]
23
+ });
24
+ ```
25
+
26
+ ## API Signature
27
+
28
+ ### Parameters
29
+
30
+ | Parameter | Type | Description |
31
+ | --- | --- | --- |
32
+ | `options` | `WorkflowPluginOptions` | Optional. Configures the workflow build. |
33
+
34
+ #### WorkflowPluginOptions
35
+
36
+ | Option | Type | Default | Description |
37
+ | --- | --- | --- | --- |
38
+ | `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. |
39
+
40
+ ### Returns
41
+
42
+ Returns an array of Vite `Plugin` objects. Spread or pass the array directly to the `plugins` option of your Vite config — Vite flattens nested plugin arrays automatically.
@@ -0,0 +1,18 @@
1
+ ---
2
+ title: "workflow/vite"
3
+ description: Vite plugin for automatic workflow bundling in Vite + Nitro apps.
4
+ type: overview
5
+ summary: Explore the Vite plugin for automatic workflow bundling and runtime support.
6
+ related:
7
+ - /docs/getting-started/vite
8
+ ---
9
+
10
+ Vite integration for Workflow SDK. It wraps the [`workflow/nitro`](/docs/api-reference/workflow-nitro) module as a Vite plugin, for apps using Nitro's Vite plugin (`nitro/vite`).
11
+
12
+ ## Functions
13
+
14
+ <Cards>
15
+ <Card title="workflow()" href="/docs/api-reference/workflow-vite/workflow">
16
+ Vite plugin that transforms workflow code (`"use step"`/`"use workflow"` directives) and configures the Nitro server
17
+ </Card>
18
+ </Cards>
@@ -0,0 +1,4 @@
1
+ {
2
+ "title": "workflow/vite",
3
+ "pages": ["workflow"]
4
+ }
@@ -0,0 +1,48 @@
1
+ ---
2
+ title: workflow
3
+ description: Configure Vite and Nitro to transform workflow directives.
4
+ type: reference
5
+ summary: Add the workflow plugin to your Vite config to enable workflow directive transformation in Vite + Nitro apps.
6
+ prerequisites:
7
+ - /docs/getting-started/vite
8
+ ---
9
+
10
+ Returns the Vite plugins that transform workflow code (`"use step"`/`"use workflow"` directives) and configure the [`workflow/nitro`](/docs/api-reference/workflow-nitro) module on the Nitro server. It is designed to be used alongside `nitro()` from `nitro/vite`, which provides the server framework for API routes and deployment.
11
+
12
+ ## Usage
13
+
14
+ To enable `"use step"` and `"use workflow"` directives while developing locally or deploying to production, add `workflow()` to the `plugins` array of your Vite config, together with `nitro()`.
15
+
16
+ ```typescript title="vite.config.ts" lineNumbers
17
+ import { nitro } from "nitro/vite";
18
+ import { defineConfig } from "vite";
19
+ import { workflow } from "workflow/vite"; // [!code highlight]
20
+
21
+ export default defineConfig({
22
+ plugins: [nitro(), workflow()], // [!code highlight]
23
+ nitro: {
24
+ serverDir: "./",
25
+ },
26
+ });
27
+ ```
28
+
29
+ ## API Signature
30
+
31
+ ### Parameters
32
+
33
+ | Parameter | Type | Description |
34
+ | --- | --- | --- |
35
+ | `options` | `ModuleOptions` | Optional. Forwarded to the `workflow/nitro` module as its module options. |
36
+
37
+ #### ModuleOptions
38
+
39
+ | Option | Type | Default | Description |
40
+ | --- | --- | --- | --- |
41
+ | `dirs` | `string[]` | — | Directories to scan for workflows and steps. By default, the `workflows/` directory is scanned from the project root and all layer source directories. |
42
+ | `typescriptPlugin` | `boolean` | `false` | Adds the `workflow` TypeScript plugin to the generated `tsconfig.json` for IDE IntelliSense. |
43
+ | `runtime` | `string` | — | Node.js runtime version for Vercel Functions (e.g. `'nodejs22.x'`, `'nodejs24.x'`). Only applies when deploying to Vercel. |
44
+ | `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. |
45
+
46
+ ### Returns
47
+
48
+ Returns an array of Vite `Plugin` objects. Spread or pass the array directly to the `plugins` option of your Vite config — Vite flattens nested plugin arrays automatically.
@@ -9,6 +9,18 @@ This is a minimal, **experimental** subset of the [planned Workflow Attributes f
9
9
 
10
10
  The MVP lets workflow code attach plaintext `string → string` metadata to a run, viewable in any observability surface that reads the `WorkflowRun` entity. It is deliberately narrow: write-only, no reads from inside a run, no list/filter endpoints, no event-log representation. The wire format and SDK surface are chosen so the full 5.0.0 implementation replaces this without source-level breaking changes for end users.
11
11
 
12
+ ## Native v4 follow-up
13
+
14
+ The v4 implementation now completes the planned dispatch migration while preserving the experimental SDK name:
15
+
16
+ - `experimental_setAttributes()` appends a native `attr_set` event from workflow and step bodies instead of mutating through the internal step bridge.
17
+ - Native events carry writer attribution and are materialized immediately onto `run.attributes`.
18
+ - `start(workflow, args, { attributes })` seeds attributes on the initial `run_created` event, including the resilient-start creation path.
19
+ - The World spec is bumped to v4; attributes are supported for v4 runs rather than being backported into older run specifications.
20
+ - The direct server attributes endpoint remains available only as a deprecated compatibility path for clients that shipped with the MVP.
21
+
22
+ The remainder of this page documents the original MVP motivation and implementation.
23
+
12
24
  ## What MVP supports
13
25
 
14
26
  - `experimental_setAttributes(record)` callable from a **workflow body** (`"use workflow"` function), dispatched via an internal `__builtin_set_attributes` step bridge so the mutation gets a `step_created → step_completed` event pair
@@ -296,13 +308,13 @@ End-to-end in `workbench/nextjs-turbopack` (exercises the full SWC plugin + work
296
308
  - `Promise.all` of disjoint-key writes — every key persists
297
309
  - Workflow throws after an awaited `experimental_setAttributes` — the attribute persists on the now-`failed` run (the per-run file lock on `run_failed` re-reads inside the critical section so the attribute snapshot survives the lifecycle write)
298
310
 
299
- ## Migration to 5.0.0
311
+ ## Migration to native v4
300
312
 
301
- When the full attributes feature ships:
313
+ The native attributes follow-up applies this migration:
302
314
 
303
315
  - `experimental_setAttributes` (SDK) — unchanged signature, new dispatch path
304
316
  - `runs.experimentalSetAttributes` (world interface) — deprecated, then removed; replaced by `events.create(runId, { eventType: 'attr_set', eventData: { changes, writer } })`
305
- - Wire endpoint — `POST /v2/runs/:runId/attributes` removed; the same `changes` shape posts to `POST /v2/runs/:id/events`
317
+ - Wire endpoint — `POST /v2/runs/:runId/attributes` is deprecated but temporarily retained for older clients; new calls post the same `changes` shape to events as `attr_set`
306
318
  - Pre-existing attribute values on MVP-era runs remain on the run entity but are not represented in the event log
307
319
 
308
320
  Skew protection means workflows started under the MVP will continue to run with the MVP dispatch path on their original deployment. New deployments use the new path. No in-place data migration is needed.
@@ -157,15 +157,35 @@ To prevent deadlock when polling is executed inline by the step executor, `Run#p
157
157
 
158
158
  ### Mixed Suspensions
159
159
 
160
- A suspension may contain steps, hooks, and waits simultaneously. The handler creates events for all, then chooses between inline execution and queue dispatch:
160
+ A suspension may contain steps, hooks, and waits simultaneously. The handler creates events for all, then dispatches everything we are not running inline as a single parallel batch of queue messages:
161
161
 
162
- - **Steps only** (no waits): one owned step is executed inline; the rest are queued. The loop continues after the inline step completes.
163
- - **Steps + at least one wait**: every step is queued (no inline execution). The handler returns with the wait timeout. Whichever lands first — a step's continuation or the wait timer — drives the next replay.
164
- - **Hooks / waits only**: handler returns with the wait timeout (or no timeout, for hook-only suspensions). The next continuation is driven by external resume or the wait timer.
162
+ ```
163
+ ownedPendingSteps = pendingSteps.filter(owned by this handler)
164
+ inlineStep = ownedPendingSteps[0] // optional
165
+
166
+ dispatches = [
167
+ ...for each non-inline pendingStep: queue stepId message (idempotency=correlationId),
168
+ ...if soonest pending wait: queue delayed continuation
169
+ (delaySeconds=min(remaining, maxDelay),
170
+ idempotency=waitCorrelationId[:hop|:secondBucket]),
171
+ ]
172
+ await Promise.all(dispatches)
173
+
174
+ if (!inlineStep) return
175
+ await executeStep(inlineStep)
176
+ ```
177
+
178
+ The wait timer is queued as its own continuation rather than encoded in the handler's return value (`{ timeoutSeconds }`). This is what makes `Promise.race(step, sleep)` behave correctly: even when the inline step blocks the handler for the full step duration, the wait continuation fires in a separate function invocation. If the sleep wins, that parallel invocation observes `wait_completed` via the "complete elapsed waits" pass and finishes the run; if the step wins, the wait continuation fires later and no-ops on the terminal run via the existing terminal-event check.
179
+
180
+ Step queueing remains unconditional (covers crash recovery: if a prior handler wrote `step_created` but crashed before queueing, a later handler will queue it; idempotency keys dedupe redundant queues across concurrent handlers).
181
+
182
+ Wait continuations are likewise deduplicated, keyed on the wait's correlation ID: while a wait is pending, every replay pass over the run re-observes it and would otherwise enqueue another delayed continuation. A key is attached in all cases, since some worlds serialize key-less workflow messages per run, which would park the continuation behind the handler's own inline step execution.
183
+
184
+ Two situations deliver a continuation while its wait is still pending, and each varies the key so the re-enqueue isn't dropped by a world's dedupe window (which outlives the first delivery): waits longer than the maximum queue delay (23h, bounded by VQS's 24h message retention) are clamped and chained across hops, with the hop index suffixed to the key so each hop dedupes within its window but the chain always advances; and near-elapsed waits (≤2s remaining) use a second-bucketed key suffix so a continuation delivered marginally early (clock skew) can enqueue a fresh short-delay retry. See `runtime/wait-continuation.ts` for the full selection logic.
165
185
 
166
- The "no inline when there's a wait" carve-out is necessary to preserve `Promise.race(step, sleep)` semantics. Inline `await executeStep(...)` blocks the handler for the full step duration, and `wait_completed` events are only created on the *next* loop iteration's "complete elapsed waits" pass — so a longer-running step would always swallow the shorter sleep and `Promise.race` would resolve incorrectly. Queueing the step in this case lets the wait timer drive a continuation in parallel.
186
+ The retry/throttle and hook-conflict paths still return `{ timeoutSeconds }` since their semantics are "redeliver THIS message after a delay" rather than "schedule a fresh wait timer." Those can be unified in a follow-up.
167
187
 
168
- Pure step suspensions (without waits) still benefit from inline execution; the carve-out only costs an extra queue roundtrip when a step and a sleep coexist.
188
+ The unified dispatch requires `world-local` to honor `delaySeconds` on the queue (added in the same PR series). Without it, the wait continuation would fire instantly in dev and trigger a spurious replay before the wait elapsed (recoverable via redelivery, but inefficient and observable as duplicate `step_started` events under contention).
169
189
 
170
190
  ### VM Sandboxing
171
191
 
@@ -13,4 +13,4 @@ Stay up to date with the latest changes to Workflow SDK.
13
13
  ## 2026
14
14
 
15
15
  - [Eager processing of steps and incremental event replay](/docs/changelog/eager-processing) - March 2026
16
- - [Serializable AbortController and AbortSignal](/docs/changelog/serializable-abort-controller) — March 12, 2026
16
+ - Serializable AbortController and AbortSignal — March 12, 2026
@@ -3,6 +3,8 @@ title: Child Workflows
3
3
  description: Spawn child workflows from a parent and wait for completion via hook resume.
4
4
  type: guide
5
5
  summary: Orchestrate independent child workflows from a parent using start(), defineHook(), and startAndWait() — the child resumes the parent's hook when done instead of polling getRun().status.
6
+ related:
7
+ - /docs/api-reference/workflow-api/start
6
8
  ---
7
9
 
8
10
  Use child workflows when a single workflow needs to orchestrate many independent units of work. Each child runs as its own workflow with a separate event log, retry boundary, and failure scope -- if one child fails, it doesn't take down the parent or siblings.
@@ -145,7 +147,7 @@ Polling with `getRun().status` in a `sleep()` loop works, but hook resume is pre
145
147
  - **Zero compute while waiting** — the parent suspends on the hook instead of waking every poll interval
146
148
  - **Immediate wake-up** — the parent resumes as soon as the child finishes, not on the next poll tick
147
149
  - **Typed payloads** — the child sends `{ status, value | error }` directly; no separate `returnValue` fetch step
148
- - **No worker-pool pressure** — `Run#returnValue` polling inside steps can hold worker slots while waiting for children (see [Eager Processing](/changelog/eager-processing))
150
+ - **No worker-pool pressure** — `Run#returnValue` polling inside steps can hold worker slots while waiting for children (see [Eager Processing](/docs/changelog/eager-processing))
149
151
 
150
152
  When a parent calls a child workflow inline with `await` (flattened into the same run), the same wrapper and hook handshake still works — pass the token and `await processDocumentWithCompletion(...)` inside `startAndWait()` instead of calling `start()`.
151
153
 
@@ -270,14 +270,14 @@ Declare `workflow` as an **optional** peer so consumers without the runtime aren
270
270
 
271
271
  ### Runtime detection
272
272
 
273
- Wrap a dynamic `import("workflow")` in try/catch. If either the module isn't installed *or* `getWorkflowMetadata()` throws (call site isn't inside a workflow run), fall through to the standalone path.
273
+ Wrap a dynamic `import("workflow")` in try/catch. If either the module isn't installed *or* `getStepMetadata()` throws (call site isn't inside a workflow step), fall through to the standalone path.
274
274
 
275
275
  ```typescript lineNumbers
276
- async function getWorkflowRunId(): Promise<string | null> { // [!code highlight]
276
+ async function getWorkflowStepId(): Promise<string | null> { // [!code highlight]
277
277
  try {
278
278
  const wf = await import("workflow");
279
- const { workflowRunId } = wf.getWorkflowMetadata();
280
- return workflowRunId;
279
+ const { stepId } = wf.getStepMetadata();
280
+ return stepId;
281
281
  } catch {
282
282
  return null;
283
283
  }
@@ -286,13 +286,14 @@ async function getWorkflowRunId(): Promise<string | null> { // [!code highlight]
286
286
 
287
287
  ### A concrete use case: replay-safe idempotency keys
288
288
 
289
- A payments utility that uses the workflow run ID as a Stripe idempotency key when available, and a fresh UUID otherwise:
289
+ A payments utility that uses the current workflow step ID as a Stripe idempotency key when available, and a fresh UUID otherwise:
290
290
 
291
- {/* @skip-typecheck - depends on getWorkflowRunId defined in the previous block */}
292
291
  ```typescript lineNumbers
292
+ declare function getWorkflowStepId(): Promise<string | null>; // @setup (defined in the previous block)
293
+
293
294
  export async function processPayment(amount: number, currency: string) {
294
- const runId = await getWorkflowRunId();
295
- const idempotencyKey = runId ?? crypto.randomUUID(); // [!code highlight]
295
+ const stepId = await getWorkflowStepId();
296
+ const idempotencyKey = stepId ? `payment:${stepId}` : crypto.randomUUID(); // [!code highlight]
296
297
 
297
298
  const res = await fetch("https://api.stripe.com/v1/charges", {
298
299
  method: "POST",
@@ -306,7 +307,7 @@ export async function processPayment(amount: number, currency: string) {
306
307
  }
307
308
  ```
308
309
 
309
- When called from inside a workflow run, the step wrapping this utility gets a stable idempotency key across replays — Stripe dedupes retries for free. When called from a plain Node.js process, it behaves like any other function and a fresh UUID is generated.
310
+ When called from inside a workflow step, the utility gets a stable idempotency key for that step across retries — Stripe dedupes retries for free. When called from a plain Node.js process, it behaves like any other function and a fresh UUID is generated. For more patterns, see [Idempotency](/docs/foundations/idempotency).
310
311
 
311
312
  ### In production
312
313
 
@@ -330,7 +331,7 @@ Before publishing a workflow library:
330
331
 
331
332
  ## Key APIs
332
333
 
333
- - [`"use workflow"`](/docs/api-reference/workflow/use-workflow) — declares the orchestrator function
334
- - [`"use step"`](/docs/api-reference/workflow/use-step) — marks functions for durable execution
335
- - [`start`](/docs/api-reference/workflow/start) — starts a workflow run
334
+ - [`"use workflow"`](/docs/foundations/workflows-and-steps#workflow-functions) — declares the orchestrator function
335
+ - [`"use step"`](/docs/foundations/workflows-and-steps#step-functions) — marks functions for durable execution
336
+ - [`start`](/docs/api-reference/workflow-api/start) — starts a workflow run
336
337
  - [`getWorkflowMetadata`](/docs/api-reference/workflow/get-workflow-metadata) — runtime detection and run ID access
@@ -6,7 +6,7 @@ summary: Return a callback from a step to defer construction of a non-owned clas
6
6
  related:
7
7
  - /docs/foundations/serialization
8
8
  - /docs/foundations/serialization#custom-class-serialization
9
- - /docs/api-reference/workflow/use-step
9
+ - /docs/foundations/workflows-and-steps#step-functions
10
10
  ---
11
11
 
12
12
  <Callout>
@@ -141,7 +141,7 @@ async function uploadFile(
141
141
 
142
142
  ## Key APIs
143
143
 
144
- - [`"use step"`](/docs/api-reference/workflow/use-step) — marks a function for extraction and serialization
145
- - [`"use workflow"`](/docs/api-reference/workflow/use-workflow) — declares the orchestrator function
144
+ - [`"use step"`](/docs/foundations/workflows-and-steps#step-functions) — marks a function for extraction and serialization
145
+ - [`"use workflow"`](/docs/foundations/workflows-and-steps#workflow-functions) — declares the orchestrator function
146
146
  - [`DurableAgent`](/docs/api-reference/workflow-ai/durable-agent) — accepts a model factory for durable AI agent streaming
147
147
  - [Custom class serialization](/docs/foundations/serialization#custom-class-serialization) — the companion pattern for classes you own (`WORKFLOW_SERIALIZE` / `WORKFLOW_DESERIALIZE`)
@@ -247,8 +247,8 @@ const approvalResult = messages
247
247
 
248
248
  ## Key APIs
249
249
 
250
- - [`"use workflow"`](/docs/api-reference/workflow/use-workflow) — declares the orchestrator function
251
- - [`"use step"`](/docs/api-reference/workflow/use-step) — declares step functions with retries
250
+ - [`"use workflow"`](/docs/foundations/workflows-and-steps#workflow-functions) — declares the orchestrator function
251
+ - [`"use step"`](/docs/foundations/workflows-and-steps#step-functions) — declares step functions with retries
252
252
  - [`defineHook()`](/docs/api-reference/workflow/define-hook) — type-safe hook with schema validation
253
253
  - [`sleep()`](/docs/api-reference/workflow/sleep) — durable timeout for approval expiry
254
254
  - [`getWritable()`](/docs/api-reference/workflow/get-writable) — stream custom data parts from steps