workflow 5.0.0-beta.1 → 5.0.0-beta.10
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.
- package/README.md +4 -4
- package/dist/api-workflow.d.ts +1 -1
- package/dist/api-workflow.d.ts.map +1 -1
- package/dist/api-workflow.js +2 -2
- package/dist/api.d.ts +5 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +14 -2
- package/dist/astro.js +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/builtins.d.ts +17 -0
- package/dist/internal/builtins.d.ts.map +1 -1
- package/dist/internal/builtins.js +65 -1
- package/dist/internal/class-serialization.js +1 -1
- package/dist/internal/errors.js +1 -1
- package/dist/nest.js +1 -1
- package/dist/next.cjs +1 -1
- package/dist/nitro.js +1 -1
- package/dist/nuxt.js +1 -1
- package/dist/observability.d.ts +1 -1
- package/dist/observability.js +2 -2
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +2 -2
- package/dist/stdlib.js +1 -1
- package/dist/sveltekit.js +1 -1
- package/dist/typescript-plugin.cjs +1 -1
- package/dist/vite.js +1 -1
- package/dist/workflow.js +1 -1
- package/docs/ai/index.mdx +6 -5
- package/docs/ai/resumable-streams.mdx +1 -1
- package/docs/api-reference/vitest/index.mdx +28 -1
- package/docs/api-reference/workflow/create-webhook.mdx +37 -18
- package/docs/api-reference/workflow/experimental-set-attributes.mdx +63 -0
- package/docs/api-reference/workflow/get-workflow-metadata.mdx +34 -0
- package/docs/api-reference/workflow/index.mdx +3 -0
- package/docs/api-reference/workflow-ai/durable-agent.mdx +0 -4
- package/docs/api-reference/workflow-ai/index.mdx +0 -5
- package/docs/api-reference/workflow-ai/workflow-chat-transport.mdx +0 -4
- package/docs/api-reference/workflow-api/start.mdx +5 -4
- package/docs/api-reference/workflow-errors/workflow-run-failed-error.mdx +16 -6
- package/docs/api-reference/workflow-next/with-workflow.mdx +34 -2
- package/docs/changelog/attributes-mvp.mdx +368 -0
- package/docs/changelog/eager-processing.mdx +595 -0
- package/docs/changelog/index.mdx +2 -1
- package/docs/cookbook/advanced/child-workflows.mdx +313 -0
- package/docs/cookbook/advanced/meta.json +9 -0
- package/docs/cookbook/advanced/publishing-libraries.mdx +336 -0
- package/docs/cookbook/advanced/serializable-steps.mdx +147 -0
- package/docs/cookbook/advanced/upgrading-workflows.mdx +195 -0
- package/docs/cookbook/agent-patterns/agent-cancellation.mdx +156 -0
- package/docs/cookbook/agent-patterns/durable-agent.mdx +154 -0
- package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +255 -0
- package/docs/cookbook/agent-patterns/meta.json +4 -0
- package/docs/cookbook/common-patterns/batching.mdx +105 -0
- package/docs/cookbook/common-patterns/idempotency.mdx +107 -0
- package/docs/cookbook/common-patterns/meta.json +15 -0
- package/docs/cookbook/common-patterns/rate-limiting.mdx +228 -0
- package/docs/cookbook/common-patterns/saga.mdx +247 -0
- package/docs/cookbook/common-patterns/scheduling.mdx +129 -0
- package/docs/cookbook/common-patterns/sequential-and-parallel.mdx +155 -0
- package/docs/cookbook/common-patterns/timeouts.mdx +99 -0
- package/docs/cookbook/common-patterns/webhooks.mdx +185 -0
- package/docs/cookbook/common-patterns/workflow-composition.mdx +111 -0
- package/docs/cookbook/index.mdx +38 -0
- package/docs/cookbook/integrations/ai-sdk.mdx +383 -0
- package/docs/cookbook/integrations/chat-sdk.mdx +307 -0
- package/docs/cookbook/integrations/meta.json +4 -0
- package/docs/cookbook/integrations/sandbox.mdx +520 -0
- package/docs/cookbook/meta.json +5 -0
- package/docs/deploying/building-a-world.mdx +1 -1
- package/docs/deploying/world/local-world.mdx +1 -1
- package/docs/deploying/world/postgres-world.mdx +6 -4
- package/docs/deploying/world/vercel-world.mdx +3 -1
- package/docs/errors/abort-signal-timeout-in-workflow.mdx +80 -0
- package/docs/errors/hook-conflict.mdx +56 -4
- package/docs/errors/index.mdx +3 -0
- package/docs/errors/runtime-decryption-failed.mdx +77 -0
- package/docs/errors/start-invalid-workflow-function.mdx +1 -1
- package/docs/foundations/cancellation.mdx +460 -0
- package/docs/foundations/errors-and-retries.mdx +7 -3
- package/docs/foundations/index.mdx +3 -3
- package/docs/foundations/meta.json +3 -2
- package/docs/foundations/serialization.mdx +78 -42
- package/docs/foundations/starting-workflows.mdx +6 -2
- package/docs/foundations/versioning.mdx +263 -0
- package/docs/getting-started/astro.mdx +6 -0
- package/docs/getting-started/index.mdx +13 -7
- package/docs/getting-started/meta.json +3 -1
- package/docs/getting-started/nestjs.mdx +8 -0
- package/docs/getting-started/next.mdx +5 -3
- package/docs/getting-started/nitro.mdx +22 -0
- package/docs/getting-started/python.mdx +165 -0
- package/docs/getting-started/sveltekit.mdx +6 -0
- package/docs/getting-started/tanstack-start.mdx +241 -0
- package/docs/how-it-works/cancellation.mdx +287 -0
- package/docs/how-it-works/code-transform.mdx +2 -2
- package/docs/how-it-works/event-sourcing.mdx +2 -2
- package/docs/how-it-works/meta.json +2 -1
- package/docs/internal/index.mdx +19 -0
- package/docs/internal/meta.json +5 -0
- package/docs/internal/serializable-abort-controller.mdx +148 -0
- package/docs/meta.json +1 -0
- package/docs/migration-guides/index.mdx +34 -0
- package/docs/migration-guides/meta.json +9 -0
- package/docs/migration-guides/migrating-from-aws-step-functions.mdx +358 -0
- package/docs/migration-guides/migrating-from-inngest.mdx +304 -0
- package/docs/migration-guides/migrating-from-temporal.mdx +313 -0
- package/docs/migration-guides/migrating-from-trigger-dev.mdx +328 -0
- package/docs/observability/attributes.mdx +66 -0
- package/docs/observability/index.mdx +9 -1
- package/docs/observability/meta.json +1 -1
- package/package.json +14 -13
- package/docs/foundations/common-patterns.mdx +0 -265
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Attributes
|
|
3
|
+
description: Attach experimental 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/experimental-set-attributes
|
|
11
|
+
- /docs/api-reference/workflow-errors/workflow-world-error
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<Callout type="warn">
|
|
15
|
+
This feature is experimental and may change before the stable attributes API is released.
|
|
16
|
+
</Callout>
|
|
17
|
+
|
|
18
|
+
[`experimental_setAttributes`](/docs/api-reference/workflow/experimental-set-attributes) attaches plaintext string metadata to the current workflow run. These attributes are displayed in observability CLI/UI.
|
|
19
|
+
In the future, you'll be able to search and filter runs by attributes.
|
|
20
|
+
|
|
21
|
+
```typescript lineNumbers
|
|
22
|
+
import { experimental_setAttributes } from "workflow"
|
|
23
|
+
|
|
24
|
+
export async function orderWorkflow(orderId: string) {
|
|
25
|
+
"use workflow"
|
|
26
|
+
|
|
27
|
+
await experimental_setAttributes({ // [!code highlight]
|
|
28
|
+
phase: "received", // [!code highlight]
|
|
29
|
+
orderId, // [!code highlight]
|
|
30
|
+
}) // [!code highlight]
|
|
31
|
+
|
|
32
|
+
// ...work...
|
|
33
|
+
|
|
34
|
+
await experimental_setAttributes({ phase: "complete" }) // [!code highlight]
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
Call [`experimental_setAttributes`](/docs/api-reference/workflow/experimental-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.
|
|
41
|
+
|
|
42
|
+
Values must be strings. Pass `undefined` to remove a key:
|
|
43
|
+
|
|
44
|
+
```typescript lineNumbers
|
|
45
|
+
import { experimental_setAttributes } from "workflow"
|
|
46
|
+
|
|
47
|
+
export async function cleanupAttributes() {
|
|
48
|
+
"use workflow"
|
|
49
|
+
|
|
50
|
+
await experimental_setAttributes({ staleKey: undefined }) // [!code highlight]
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
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.
|
|
55
|
+
|
|
56
|
+
## Experimental Behavior
|
|
57
|
+
|
|
58
|
+
While attributes are experimental:
|
|
59
|
+
|
|
60
|
+
- Worlds that do not support attributes log a warning and ignore the call.
|
|
61
|
+
- Workflow-body storage errors are logged after retries, but do not fail the workflow run.
|
|
62
|
+
- Step-body storage errors throw from `experimental_setAttributes` like any other step-side network write. Catch the error inside the step if the attribute is best-effort.
|
|
63
|
+
- Setting attributes from a workflow body is currently slower than the final API will be, because each write goes through an internal workflow step. Step-body calls post directly to the World. Prefer batching related attributes in one call.
|
|
64
|
+
- Reading and querying attributes is not available yet. A query API is planned.
|
|
65
|
+
|
|
66
|
+
In a future release, using attributes with a World that does not support them, or when the World fails to store them, will fail with a [world error](/docs/api-reference/workflow-errors/workflow-world-error). This can be caught and handled to prevent failing a run.
|
|
@@ -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:
|
|
4
|
+
type: overview
|
|
5
5
|
summary: Inspect and debug workflow runs using the CLI and Web UI.
|
|
6
6
|
prerequisites:
|
|
7
7
|
- /docs/foundations
|
|
@@ -63,3 +63,11 @@ npx workflow inspect runs --backend vercel
|
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
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.
|
|
66
|
+
|
|
67
|
+
## More Observability Features
|
|
68
|
+
|
|
69
|
+
<Cards>
|
|
70
|
+
<Card href="/docs/observability/attributes" title="Attributes">
|
|
71
|
+
Attach experimental metadata to workflow runs for observability.
|
|
72
|
+
</Card>
|
|
73
|
+
</Cards>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "workflow",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.10",
|
|
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
|
},
|
|
@@ -56,18 +57,18 @@
|
|
|
56
57
|
},
|
|
57
58
|
"dependencies": {
|
|
58
59
|
"ms": "2.1.3",
|
|
59
|
-
"@workflow/astro": "5.0.0-beta.
|
|
60
|
-
"@workflow/cli": "5.0.0-beta.
|
|
61
|
-
"@workflow/core": "5.0.0-beta.
|
|
62
|
-
"@workflow/errors": "5.0.0-beta.
|
|
63
|
-
"@workflow/typescript-plugin": "5.0.0-beta.
|
|
64
|
-
"@workflow/utils": "5.0.0-beta.
|
|
65
|
-
"@workflow/next": "5.0.0-beta.
|
|
66
|
-
"@workflow/nest": "5.0.0-beta.
|
|
67
|
-
"@workflow/nitro": "5.0.0-beta.
|
|
68
|
-
"@workflow/nuxt": "5.0.0-beta.
|
|
69
|
-
"@workflow/sveltekit": "5.0.0-beta.
|
|
70
|
-
"@workflow/rollup": "5.0.0-beta.
|
|
60
|
+
"@workflow/astro": "5.0.0-beta.10",
|
|
61
|
+
"@workflow/cli": "5.0.0-beta.10",
|
|
62
|
+
"@workflow/core": "5.0.0-beta.10",
|
|
63
|
+
"@workflow/errors": "5.0.0-beta.6",
|
|
64
|
+
"@workflow/typescript-plugin": "5.0.0-beta.4",
|
|
65
|
+
"@workflow/utils": "5.0.0-beta.3",
|
|
66
|
+
"@workflow/next": "5.0.0-beta.10",
|
|
67
|
+
"@workflow/nest": "5.0.0-beta.10",
|
|
68
|
+
"@workflow/nitro": "5.0.0-beta.10",
|
|
69
|
+
"@workflow/nuxt": "5.0.0-beta.10",
|
|
70
|
+
"@workflow/sveltekit": "5.0.0-beta.10",
|
|
71
|
+
"@workflow/rollup": "5.0.0-beta.10"
|
|
71
72
|
},
|
|
72
73
|
"devDependencies": {
|
|
73
74
|
"@types/ms": "2.1.0",
|
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Common Patterns
|
|
3
|
-
description: Implement distributed patterns using familiar async/await syntax with no new APIs to learn.
|
|
4
|
-
type: guide
|
|
5
|
-
summary: Apply sequential, parallel, timeout, and composition patterns in workflows.
|
|
6
|
-
prerequisites:
|
|
7
|
-
- /docs/foundations/workflows-and-steps
|
|
8
|
-
related:
|
|
9
|
-
- /docs/foundations/errors-and-retries
|
|
10
|
-
- /docs/foundations/hooks
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
Common distributed patterns are simple to implement in workflows and require learning no new syntax. You can just use familiar async/await patterns.
|
|
14
|
-
|
|
15
|
-
## Sequential Execution
|
|
16
|
-
|
|
17
|
-
The simplest way to orchestrate steps is to execute them one after another, where each step can be dependent on the previous step.
|
|
18
|
-
|
|
19
|
-
```typescript lineNumbers
|
|
20
|
-
declare function validateData(data: unknown): Promise<string>; // @setup
|
|
21
|
-
declare function processData(data: string): Promise<string>; // @setup
|
|
22
|
-
declare function storeData(data: string): Promise<string>; // @setup
|
|
23
|
-
|
|
24
|
-
export async function dataPipelineWorkflow(data: unknown) {
|
|
25
|
-
"use workflow";
|
|
26
|
-
|
|
27
|
-
const validated = await validateData(data);
|
|
28
|
-
const processed = await processData(validated);
|
|
29
|
-
const stored = await storeData(processed);
|
|
30
|
-
|
|
31
|
-
return stored;
|
|
32
|
-
}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Parallel Execution
|
|
36
|
-
|
|
37
|
-
When you need to execute multiple steps in parallel, you can use `Promise.all` to run them all at the same time.
|
|
38
|
-
|
|
39
|
-
```typescript lineNumbers
|
|
40
|
-
declare function fetchUser(userId: string): Promise<{ name: string }>; // @setup
|
|
41
|
-
declare function fetchOrders(userId: string): Promise<{ items: string[] }>; // @setup
|
|
42
|
-
declare function fetchPreferences(userId: string): Promise<{ theme: string }>; // @setup
|
|
43
|
-
|
|
44
|
-
export async function fetchUserData(userId: string) {
|
|
45
|
-
"use workflow";
|
|
46
|
-
|
|
47
|
-
const [user, orders, preferences] = await Promise.all([ // [!code highlight]
|
|
48
|
-
fetchUser(userId), // [!code highlight]
|
|
49
|
-
fetchOrders(userId), // [!code highlight]
|
|
50
|
-
fetchPreferences(userId) // [!code highlight]
|
|
51
|
-
]); // [!code highlight]
|
|
52
|
-
|
|
53
|
-
return { user, orders, preferences };
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
This not only applies to steps - since [`sleep()`](/docs/api-reference/workflow/sleep) and [`webhook`](/docs/api-reference/workflow/create-webhook) are also just promises, we can await those in parallel too.
|
|
58
|
-
We can also use `Promise.race` instead of `Promise.all` to stop executing promises after the first one completes.
|
|
59
|
-
|
|
60
|
-
```typescript lineNumbers
|
|
61
|
-
import { sleep, createWebhook } from "workflow";
|
|
62
|
-
declare function executeExternalTask(webhookUrl: string): Promise<void>; // @setup
|
|
63
|
-
|
|
64
|
-
export async function runExternalTask(userId: string) {
|
|
65
|
-
"use workflow";
|
|
66
|
-
|
|
67
|
-
const webhook = createWebhook();
|
|
68
|
-
await executeExternalTask(webhook.url); // Send the webhook somewhere
|
|
69
|
-
|
|
70
|
-
// Wait for the external webhook to be hit, with a timeout of 1 day,
|
|
71
|
-
// whichever comes first
|
|
72
|
-
await Promise.race([ // [!code highlight]
|
|
73
|
-
webhook, // [!code highlight]
|
|
74
|
-
sleep("1 day"), // [!code highlight]
|
|
75
|
-
]); // [!code highlight]
|
|
76
|
-
|
|
77
|
-
console.log("Done")
|
|
78
|
-
}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## A Full Example
|
|
82
|
-
|
|
83
|
-
Here's a simplified example taken from the [birthday card generator demo](https://github.com/vercel/workflow-examples/tree/main/birthday-card-generator), to illustrate how sequential and parallel execution can be combined.
|
|
84
|
-
|
|
85
|
-
```typescript lineNumbers
|
|
86
|
-
import { createWebhook, sleep, type Webhook } from "workflow"
|
|
87
|
-
declare function makeCardText(prompt: string): Promise<string>; // @setup
|
|
88
|
-
declare function makeCardImage(text: string): Promise<string>; // @setup
|
|
89
|
-
declare function sendRSVPEmail(friend: string, webhook: Webhook): Promise<void>; // @setup
|
|
90
|
-
declare function sendBirthdayCard(text: string, image: string, rsvps: unknown[], email: string): Promise<void>; // @setup
|
|
91
|
-
|
|
92
|
-
async function birthdayWorkflow(
|
|
93
|
-
prompt: string,
|
|
94
|
-
email: string,
|
|
95
|
-
friends: string[],
|
|
96
|
-
birthday: Date
|
|
97
|
-
) {
|
|
98
|
-
"use workflow";
|
|
99
|
-
|
|
100
|
-
// Generate a birthday card with sequential steps
|
|
101
|
-
const text = await makeCardText(prompt)
|
|
102
|
-
const image = await makeCardImage(text)
|
|
103
|
-
|
|
104
|
-
// Create webhooks for each friend who's invited to the birthday party
|
|
105
|
-
const webhooks = friends.map(_ => createWebhook())
|
|
106
|
-
|
|
107
|
-
// Send out all the RSVP invites in parallel steps
|
|
108
|
-
await Promise.all(
|
|
109
|
-
friends.map(
|
|
110
|
-
(friend, i) => sendRSVPEmail(friend, webhooks[i])
|
|
111
|
-
)
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
// Collect RSVPs as they are made without blocking the workflow
|
|
115
|
-
let rsvps = []
|
|
116
|
-
webhooks.map(
|
|
117
|
-
webhook => webhook
|
|
118
|
-
.then(req => req.json())
|
|
119
|
-
.then(( { rsvp } ) => rsvps.push(rsvp))
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
// Wait until the birthday
|
|
123
|
-
await sleep(birthday)
|
|
124
|
-
|
|
125
|
-
// Send birthday card with as many rsvps were collected
|
|
126
|
-
await sendBirthdayCard(text, image, rsvps, email)
|
|
127
|
-
|
|
128
|
-
return { text, image, status: "Sent" }
|
|
129
|
-
}
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
## Timeout Pattern
|
|
133
|
-
|
|
134
|
-
A common requirement is adding timeouts to operations that might take too long. Use `Promise.race` with `sleep()` to implement this pattern.
|
|
135
|
-
|
|
136
|
-
```typescript lineNumbers
|
|
137
|
-
import { sleep } from "workflow";
|
|
138
|
-
declare function processData(data: string): Promise<string>; // @setup
|
|
139
|
-
|
|
140
|
-
export async function processWithTimeout(data: string) {
|
|
141
|
-
"use workflow";
|
|
142
|
-
|
|
143
|
-
const result = await Promise.race([ // [!code highlight]
|
|
144
|
-
processData(data), // [!code highlight]
|
|
145
|
-
sleep("30s").then(() => "timeout" as const), // [!code highlight]
|
|
146
|
-
]); // [!code highlight]
|
|
147
|
-
|
|
148
|
-
if (result === "timeout") {
|
|
149
|
-
// In workflows, any thrown error exits the workflow (FatalError is for steps)
|
|
150
|
-
throw new Error("Processing timed out after 30 seconds");
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return result;
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
This pattern works with any promise-returning operation including steps, hooks, and webhooks. For example, you can add a timeout to a webhook that waits for external input:
|
|
158
|
-
|
|
159
|
-
```typescript lineNumbers
|
|
160
|
-
import { sleep, createWebhook } from "workflow";
|
|
161
|
-
declare function sendApprovalRequest(requestId: string, webhookUrl: string): Promise<void>; // @setup
|
|
162
|
-
|
|
163
|
-
export async function waitForApproval(requestId: string) {
|
|
164
|
-
"use workflow";
|
|
165
|
-
|
|
166
|
-
const webhook = createWebhook<{ approved: boolean }>();
|
|
167
|
-
await sendApprovalRequest(requestId, webhook.url);
|
|
168
|
-
|
|
169
|
-
const result = await Promise.race([ // [!code highlight]
|
|
170
|
-
webhook.then((req) => req.json()), // [!code highlight]
|
|
171
|
-
sleep("7 days").then(() => ({ timedOut: true }) as const), // [!code highlight]
|
|
172
|
-
]); // [!code highlight]
|
|
173
|
-
|
|
174
|
-
if ("timedOut" in result) {
|
|
175
|
-
throw new Error("Approval request expired after 7 days");
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return result.approved;
|
|
179
|
-
}
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
## Workflow Composition
|
|
183
|
-
|
|
184
|
-
Workflows can call other workflows, enabling you to break complex processes into reusable building blocks. There are two approaches depending on your needs.
|
|
185
|
-
|
|
186
|
-
### Direct Await (Flattening)
|
|
187
|
-
|
|
188
|
-
Call a child workflow directly using `await`. This "flattens" the child workflow into the parent - the child's steps execute inline within the parent workflow's context.
|
|
189
|
-
|
|
190
|
-
```typescript lineNumbers
|
|
191
|
-
declare function sendEmail(userId: string): Promise<void>; // @setup
|
|
192
|
-
declare function sendPushNotification(userId: string): Promise<void>; // @setup
|
|
193
|
-
declare function createAccount(userId: string): Promise<void>; // @setup
|
|
194
|
-
declare function setupPreferences(userId: string): Promise<void>; // @setup
|
|
195
|
-
|
|
196
|
-
// Child workflow
|
|
197
|
-
export async function sendNotifications(userId: string) {
|
|
198
|
-
"use workflow";
|
|
199
|
-
|
|
200
|
-
await sendEmail(userId);
|
|
201
|
-
await sendPushNotification(userId);
|
|
202
|
-
return { notified: true };
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Parent workflow calls child directly
|
|
206
|
-
export async function onboardUser(userId: string) {
|
|
207
|
-
"use workflow";
|
|
208
|
-
|
|
209
|
-
await createAccount(userId);
|
|
210
|
-
await sendNotifications(userId); // [!code highlight]
|
|
211
|
-
await setupPreferences(userId);
|
|
212
|
-
|
|
213
|
-
return { userId, status: "onboarded" };
|
|
214
|
-
}
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
With direct await, the parent workflow waits for the child to complete before continuing. The child's steps appear in the parent's event log as if they were called directly from the parent.
|
|
218
|
-
|
|
219
|
-
### Background Execution via Step
|
|
220
|
-
|
|
221
|
-
To run a child workflow independently without blocking the parent, use a step that calls [`start()`](/docs/api-reference/workflow-api/start). This launches the child workflow in the background.
|
|
222
|
-
|
|
223
|
-
```typescript lineNumbers
|
|
224
|
-
import { start } from "workflow/api";
|
|
225
|
-
declare function generateReport(reportId: string): Promise<void>; // @setup
|
|
226
|
-
declare function fulfillOrder(orderId: string): Promise<{ id: string }>; // @setup
|
|
227
|
-
declare function sendConfirmation(orderId: string): Promise<void>; // @setup
|
|
228
|
-
|
|
229
|
-
// Step that starts a workflow in the background
|
|
230
|
-
async function triggerReportGeneration(reportId: string) {
|
|
231
|
-
"use step";
|
|
232
|
-
|
|
233
|
-
const run = await start(generateReport, [reportId]); // [!code highlight]
|
|
234
|
-
return run.runId;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Parent workflow
|
|
238
|
-
export async function processOrder(orderId: string) {
|
|
239
|
-
"use workflow";
|
|
240
|
-
|
|
241
|
-
const order = await fulfillOrder(orderId);
|
|
242
|
-
|
|
243
|
-
// Fire off report generation without waiting
|
|
244
|
-
const reportRunId = await triggerReportGeneration(orderId); // [!code highlight]
|
|
245
|
-
|
|
246
|
-
// Continue immediately - report generates in background
|
|
247
|
-
await sendConfirmation(orderId);
|
|
248
|
-
|
|
249
|
-
return { orderId, reportRunId };
|
|
250
|
-
}
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
With background execution, the parent workflow continues immediately after starting the child. The child workflow runs independently with its own event log and can be monitored separately using the returned `runId`.
|
|
254
|
-
|
|
255
|
-
<Callout type="info">
|
|
256
|
-
If you want the child workflow to run on the latest deployment rather than the current one, you can pass [`deploymentId: "latest"`](/docs/api-reference/workflow-api/start#using-deploymentid-latest) in the `start()` options. This is currently a Vercel-specific feature. Be aware that the child workflow's function name, file path, argument types, and return type must remain compatible across deployments — renaming the function or changing its location will change the workflow ID, and modifying expected inputs or outputs can cause serialization failures.
|
|
257
|
-
</Callout>
|
|
258
|
-
|
|
259
|
-
**Choose direct await when:**
|
|
260
|
-
- The parent needs the child's result before continuing
|
|
261
|
-
- You want a single, unified event log
|
|
262
|
-
|
|
263
|
-
**Choose background execution when:**
|
|
264
|
-
- The parent doesn't need to wait for the result
|
|
265
|
-
- You want separate workflow runs for observability
|