workflow 4.3.1 → 4.5.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 (114) 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 +0 -6
  4. package/docs/api-reference/workflow/create-hook.mdx +32 -0
  5. package/docs/api-reference/workflow/create-webhook.mdx +1 -0
  6. package/docs/api-reference/workflow-api/index.mdx +6 -8
  7. package/docs/api-reference/workflow-errors/meta.json +5 -0
  8. package/docs/api-reference/workflow-serde/index.mdx +0 -1
  9. package/docs/api-reference/workflow-serde/workflow-deserialize.mdx +1 -2
  10. package/docs/api-reference/workflow-serde/workflow-serialize.mdx +1 -2
  11. package/docs/deploying/building-a-world.mdx +1 -1
  12. package/docs/deploying/world/vercel-world.mdx +2 -0
  13. package/docs/errors/hook-conflict.mdx +56 -4
  14. package/docs/errors/index.mdx +6 -0
  15. package/docs/foundations/hooks.mdx +29 -0
  16. package/docs/foundations/streaming.mdx +7 -1
  17. package/docs/foundations/versioning.mdx +1 -1
  18. package/docs/how-it-works/encryption.mdx +2 -2
  19. package/docs/how-it-works/event-sourcing.mdx +2 -2
  20. package/docs/v4/api-reference/workflow-astro/index.mdx +18 -0
  21. package/docs/v4/api-reference/workflow-astro/meta.json +4 -0
  22. package/docs/v4/api-reference/workflow-astro/workflow.mdx +37 -0
  23. package/docs/v4/api-reference/workflow-errors/hook-conflict-error.mdx +60 -0
  24. package/docs/v4/api-reference/workflow-errors/run-not-supported-error.mdx +58 -0
  25. package/docs/v4/api-reference/workflow-errors/workflow-error.mdx +52 -0
  26. package/docs/v4/api-reference/workflow-errors/workflow-run-not-completed-error.mdx +58 -0
  27. package/docs/v4/api-reference/workflow-errors/workflow-runtime-error.mdx +58 -0
  28. package/docs/v4/api-reference/workflow-nest/configure-workflow-controller.mdx +33 -0
  29. package/docs/v4/api-reference/workflow-nest/index.mdx +31 -0
  30. package/docs/v4/api-reference/workflow-nest/meta.json +9 -0
  31. package/docs/v4/api-reference/workflow-nest/nest-local-builder.mdx +63 -0
  32. package/docs/v4/api-reference/workflow-nest/workflow-controller.mdx +40 -0
  33. package/docs/v4/api-reference/workflow-nest/workflow-module.mdx +73 -0
  34. package/docs/v4/api-reference/workflow-nitro/index.mdx +57 -0
  35. package/docs/v4/api-reference/workflow-nuxt/index.mdx +47 -0
  36. package/docs/v4/api-reference/workflow-observability/hydrate-data.mdx +35 -0
  37. package/docs/v4/api-reference/workflow-observability/hydrate-resource-io.mdx +62 -0
  38. package/docs/v4/api-reference/workflow-observability/index.mdx +64 -0
  39. package/docs/v4/api-reference/workflow-observability/meta.json +11 -0
  40. package/docs/v4/api-reference/workflow-observability/observability-revivers.mdx +50 -0
  41. package/docs/v4/api-reference/workflow-observability/parse-class-name.mdx +41 -0
  42. package/docs/v4/api-reference/workflow-observability/parse-step-name.mdx +40 -0
  43. package/docs/v4/api-reference/workflow-observability/parse-workflow-name.mdx +55 -0
  44. package/docs/v4/api-reference/workflow-runtime/create-world.mdx +43 -0
  45. package/docs/v4/api-reference/workflow-runtime/get-world-handlers.mdx +44 -0
  46. package/docs/v4/api-reference/workflow-runtime/get-world.mdx +124 -0
  47. package/docs/v4/api-reference/workflow-runtime/health-check.mdx +50 -0
  48. package/docs/v4/api-reference/workflow-runtime/index.mdx +46 -0
  49. package/docs/v4/api-reference/workflow-runtime/meta.json +13 -0
  50. package/docs/v4/api-reference/workflow-runtime/set-world.mdx +49 -0
  51. package/docs/v4/api-reference/workflow-runtime/step-entrypoint.mdx +39 -0
  52. package/docs/v4/api-reference/workflow-runtime/workflow-entrypoint.mdx +42 -0
  53. package/docs/{api-reference/workflow-api → v4/api-reference/workflow-runtime}/world/index.mdx +5 -8
  54. package/docs/v4/api-reference/workflow-runtime/world/meta.json +4 -0
  55. package/docs/v4/api-reference/workflow-runtime/world/queue.mdx +86 -0
  56. package/docs/{api-reference/workflow-api → v4/api-reference/workflow-runtime}/world/storage.mdx +4 -4
  57. package/docs/v4/api-reference/workflow-runtime/world/streams.mdx +216 -0
  58. package/docs/v4/api-reference/workflow-sveltekit/index.mdx +18 -0
  59. package/docs/v4/api-reference/workflow-sveltekit/meta.json +4 -0
  60. package/docs/v4/api-reference/workflow-sveltekit/workflow-plugin.mdx +34 -0
  61. package/docs/v4/api-reference/workflow-vite/index.mdx +18 -0
  62. package/docs/v4/api-reference/workflow-vite/meta.json +4 -0
  63. package/docs/v4/api-reference/workflow-vite/workflow.mdx +47 -0
  64. package/docs/v4/errors/runtime-decryption-failed.mdx +77 -0
  65. package/docs/v4/errors/step-executed-multiple-times.mdx +23 -0
  66. package/docs/v5/api-reference/workflow-astro/index.mdx +18 -0
  67. package/docs/v5/api-reference/workflow-astro/meta.json +4 -0
  68. package/docs/v5/api-reference/workflow-astro/workflow.mdx +45 -0
  69. package/docs/v5/api-reference/workflow-errors/hook-conflict-error.mdx +60 -0
  70. package/docs/v5/api-reference/workflow-errors/run-not-supported-error.mdx +58 -0
  71. package/docs/v5/api-reference/workflow-errors/workflow-error.mdx +52 -0
  72. package/docs/v5/api-reference/workflow-errors/workflow-run-not-completed-error.mdx +58 -0
  73. package/docs/v5/api-reference/workflow-errors/workflow-runtime-error.mdx +58 -0
  74. package/docs/v5/api-reference/workflow-nest/configure-workflow-controller.mdx +33 -0
  75. package/docs/v5/api-reference/workflow-nest/index.mdx +31 -0
  76. package/docs/v5/api-reference/workflow-nest/meta.json +9 -0
  77. package/docs/v5/api-reference/workflow-nest/nest-local-builder.mdx +64 -0
  78. package/docs/v5/api-reference/workflow-nest/workflow-controller.mdx +40 -0
  79. package/docs/v5/api-reference/workflow-nest/workflow-module.mdx +74 -0
  80. package/docs/v5/api-reference/workflow-nitro/index.mdx +59 -0
  81. package/docs/v5/api-reference/workflow-nuxt/index.mdx +47 -0
  82. package/docs/v5/api-reference/workflow-observability/hydrate-data.mdx +35 -0
  83. package/docs/v5/api-reference/workflow-observability/hydrate-resource-io.mdx +62 -0
  84. package/docs/v5/api-reference/workflow-observability/index.mdx +64 -0
  85. package/docs/v5/api-reference/workflow-observability/meta.json +11 -0
  86. package/docs/v5/api-reference/workflow-observability/observability-revivers.mdx +50 -0
  87. package/docs/v5/api-reference/workflow-observability/parse-class-name.mdx +41 -0
  88. package/docs/v5/api-reference/workflow-observability/parse-step-name.mdx +40 -0
  89. package/docs/v5/api-reference/workflow-observability/parse-workflow-name.mdx +55 -0
  90. package/docs/v5/api-reference/workflow-runtime/create-world.mdx +39 -0
  91. package/docs/v5/api-reference/workflow-runtime/get-world-handlers.mdx +44 -0
  92. package/docs/{api-reference/workflow-api → v5/api-reference/workflow-runtime}/get-world.mdx +7 -10
  93. package/docs/v5/api-reference/workflow-runtime/health-check.mdx +50 -0
  94. package/docs/v5/api-reference/workflow-runtime/index.mdx +43 -0
  95. package/docs/v5/api-reference/workflow-runtime/meta.json +12 -0
  96. package/docs/v5/api-reference/workflow-runtime/set-world.mdx +49 -0
  97. package/docs/v5/api-reference/workflow-runtime/workflow-entrypoint.mdx +42 -0
  98. package/docs/v5/api-reference/workflow-runtime/world/index.mdx +55 -0
  99. package/docs/v5/api-reference/workflow-runtime/world/meta.json +4 -0
  100. package/docs/{api-reference/workflow-api → v5/api-reference/workflow-runtime}/world/queue.mdx +2 -2
  101. package/docs/v5/api-reference/workflow-runtime/world/storage.mdx +409 -0
  102. package/docs/{api-reference/workflow-api → v5/api-reference/workflow-runtime}/world/streams.mdx +2 -2
  103. package/docs/v5/api-reference/workflow-sveltekit/index.mdx +18 -0
  104. package/docs/v5/api-reference/workflow-sveltekit/meta.json +4 -0
  105. package/docs/v5/api-reference/workflow-sveltekit/workflow-plugin.mdx +42 -0
  106. package/docs/v5/api-reference/workflow-vite/index.mdx +18 -0
  107. package/docs/v5/api-reference/workflow-vite/meta.json +4 -0
  108. package/docs/v5/api-reference/workflow-vite/workflow.mdx +48 -0
  109. package/docs/v5/errors/index.mdx +59 -0
  110. package/docs/v5/errors/runtime-decryption-failed.mdx +77 -0
  111. package/docs/v5/errors/step-executed-multiple-times.mdx +23 -0
  112. package/package.json +13 -13
  113. package/docs/api-reference/workflow-api/world/meta.json +0 -4
  114. package/docs/api-reference/workflow-api/world/observability.mdx +0 -164
@@ -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()` 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()` 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.
@@ -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>
@@ -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
  }
@@ -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
@@ -93,7 +93,7 @@ interface Storage {
93
93
 
94
94
  **Run Creation:** For `run_created` events, the `runId` parameter may be a client-provided string or `null`. When `null`, your World generates and returns a new `runId`.
95
95
 
96
- **Hook Tokens:** Hook tokens must be unique. If a `hook_created` event conflicts with an existing token, return a `hook_conflict` event instead.
96
+ **Hook Tokens:** Hook tokens must be unique. If a `hook_created` event conflicts with an existing token, return a `hook_conflict` event instead and include the active hook owner's run ID as `eventData.conflictingRunId`.
97
97
 
98
98
  **Automatic Hook Disposal:** When a workflow reaches a terminal state (`completed`, `failed`, or `cancelled`), automatically dispose of all associated hooks to release tokens for reuse.
99
99
 
@@ -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.
@@ -73,9 +73,9 @@ export async function processPayment() {
73
73
  }
74
74
  ```
75
75
 
76
- ## Handling Hook Conflicts in Your Workflow
76
+ ## Handling Hook Conflicts
77
77
 
78
- When a hook conflict occurs, awaiting the hook will throw a `HookConflictError`. You can catch this error to handle the conflict gracefully:
78
+ When a hook conflict occurs, awaiting the hook will throw a `HookConflictError`. The error exposes the token that conflicted and, for current worlds, the run ID that currently owns it. `conflictingRunId` remains optional for compatibility with older persisted events and world implementations, so guard it before delegating:
79
79
 
80
80
  ```typescript lineNumbers
81
81
  import { createHook } from "workflow";
@@ -93,14 +93,64 @@ export async function processPayment(orderId: string) {
93
93
  if (HookConflictError.is(error)) { // [!code highlight]
94
94
  // Another workflow is already processing this order
95
95
  console.log(`Conflicting token: ${error.token}`);
96
- return { success: false, reason: "duplicate-processing" };
96
+ if (error.conflictingRunId) {
97
+ console.log(`Active run: ${error.conflictingRunId}`);
98
+ }
99
+ return {
100
+ success: false,
101
+ reason: "duplicate-processing",
102
+ token: error.token,
103
+ runId: error.conflictingRunId
104
+ };
97
105
  }
98
106
  throw error; // Re-throw other errors
99
107
  }
100
108
  }
101
109
  ```
102
110
 
103
- This pattern is useful when you want to detect and handle duplicate processing attempts instead of letting the workflow fail.
111
+ This pattern is useful when you want to detect duplicate processing inside the workflow. Runtime APIs such as `resumeHook()` and `getRun()` must be called outside workflow functions, for example from an API route or in a step.
112
+
113
+ ### Delegate to the Active Run
114
+
115
+ In idempotency flows, a conflict means another active run already owns the hook token. You can return the duplicate-processing payload from the workflow, resume the active hook to deliver the payload to the existing run, then use `getRun(result.runId)` to wait for, stream, or cancel the active run:
116
+
117
+ ```typescript lineNumbers
118
+ import { getRun, resumeHook, start } from "workflow/api";
119
+ import { processPayment } from "@/workflows/process-payment";
120
+
121
+ type ProcessPaymentResult =
122
+ | { success: true; payment: unknown }
123
+ | {
124
+ success: false;
125
+ reason: "duplicate-processing";
126
+ token: string;
127
+ runId?: string;
128
+ };
129
+
130
+ export async function POST(request: Request) {
131
+ const { orderId, payment } = await request.json();
132
+ const run = await start(processPayment, [orderId]);
133
+ const result = (await run.returnValue) as ProcessPaymentResult;
134
+
135
+ if (
136
+ result.success === false &&
137
+ result.reason === "duplicate-processing" &&
138
+ result.runId
139
+ ) {
140
+ await resumeHook(result.token, payment); // [!code highlight]
141
+ const activeRun = getRun(result.runId); // [!code highlight]
142
+
143
+ return Response.json({
144
+ delegatedToRunId: activeRun.runId,
145
+ result: await activeRun.returnValue
146
+ });
147
+ }
148
+
149
+ return Response.json(result);
150
+ }
151
+ ```
152
+
153
+ If the caller needs live output instead of the final result, return `activeRun.getReadable()` from the same branch. If the duplicate request should replace the active work, call `await activeRun.cancel()` after inspecting the run.
104
154
 
105
155
  ## When Hook Tokens Are Released
106
156
 
@@ -122,4 +172,6 @@ After a workflow completes, its hook tokens become available for reuse by other
122
172
  ## Related
123
173
 
124
174
  - [Hooks](/docs/foundations/hooks) - Learn more about using hooks in workflows
175
+ - [getRun](/docs/api-reference/workflow-api/get-run) - Retrieve or control the active run
176
+ - [resumeHook](/docs/api-reference/workflow-api/resume-hook) - Deliver data to the active hook
125
177
  - [createWebhook](/docs/api-reference/workflow/create-webhook) - Alternative for fixed webhook URLs
@@ -43,9 +43,15 @@ 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>
52
+ <Card href="/docs/errors/runtime-decryption-failed" title="runtime-decryption-failed">
53
+ Resolve runtime decryption failures from the SDK's encryption layer.
54
+ </Card>
49
55
  </Cards>
50
56
 
51
57
  ## Learn More
@@ -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()` 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`. 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. 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
 
@@ -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.
@@ -0,0 +1,60 @@
1
+ ---
2
+ title: HookConflictError
3
+ description: Thrown when creating a hook with a token that is already in use by another workflow run.
4
+ type: reference
5
+ summary: Catch HookConflictError when a hook token is already claimed by another active workflow run.
6
+ related:
7
+ - /docs/api-reference/workflow/create-hook
8
+ - /docs/foundations/hooks
9
+ - /docs/errors/hook-conflict
10
+ ---
11
+
12
+ `HookConflictError` is thrown when creating a hook with a token that is already in use by another active workflow run. Hook tokens must be unique across all running workflows — see the [hook-conflict](/docs/errors/hook-conflict) error guide for resolution strategies.
13
+
14
+ ```typescript lineNumbers
15
+ import { HookConflictError } from "workflow/errors"
16
+ declare function startApprovalWorkflow(token: string): Promise<void>; // @setup
17
+ declare const token: string; // @setup
18
+
19
+ try {
20
+ await startApprovalWorkflow(token);
21
+ } catch (error) {
22
+ if (HookConflictError.is(error)) { // [!code highlight]
23
+ console.error(
24
+ `Token "${error.token}" already in use by run ${error.conflictingRunId}`
25
+ );
26
+ }
27
+ }
28
+ ```
29
+
30
+ ## API Signature
31
+
32
+ ### Properties
33
+
34
+ <TSDoc
35
+ definition={`
36
+ interface HookConflictError {
37
+ /** The hook token that conflicted. */
38
+ token: string;
39
+ /** The run ID of the workflow currently holding the token, when known. */
40
+ conflictingRunId?: string;
41
+ /** The error message. */
42
+ message: string;
43
+ }
44
+ export default HookConflictError;`}
45
+ />
46
+
47
+ ### Static Methods
48
+
49
+ #### `HookConflictError.is(value)`
50
+
51
+ Type-safe check for `HookConflictError` instances. Preferred over `instanceof` because it works across module boundaries and VM contexts.
52
+
53
+ ```typescript
54
+ import { HookConflictError } from "workflow/errors"
55
+ declare const error: unknown; // @setup
56
+
57
+ if (HookConflictError.is(error)) {
58
+ // error is typed as HookConflictError
59
+ }
60
+ ```