workflow 5.0.0-beta.5 → 5.0.0-beta.51
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 +68 -23
- package/dist/api-workflow.d.ts +1 -1
- package/dist/api-workflow.d.ts.map +1 -1
- package/dist/api-workflow.js +1 -1
- package/dist/api.d.ts +3 -3
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +5 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/internal/builtins.d.ts +20 -3
- package/dist/internal/builtins.d.ts.map +1 -1
- package/dist/internal/builtins.js +68 -4
- package/dist/internal/errors.d.ts +1 -1
- package/dist/internal/errors.d.ts.map +1 -1
- package/dist/internal/errors.js +2 -2
- package/dist/nest-builder.d.ts +2 -0
- package/dist/nest-builder.d.ts.map +1 -0
- package/dist/nest-builder.js +2 -0
- package/dist/nest-vercel-builder.d.ts +2 -0
- package/dist/nest-vercel-builder.d.ts.map +1 -0
- package/dist/nest-vercel-builder.js +2 -0
- package/dist/observability.d.ts +1 -1
- package/dist/observability.js +2 -2
- package/dist/runtime.d.ts +2 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +4 -1
- package/docs/ai/chat-session-modeling.mdx +176 -422
- package/docs/ai/defining-tools.mdx +6 -7
- package/docs/ai/human-in-the-loop.mdx +11 -11
- package/docs/ai/index.mdx +71 -75
- package/docs/ai/message-queueing.mdx +71 -110
- package/docs/ai/meta.json +1 -0
- package/docs/ai/resumable-streams.mdx +40 -28
- package/docs/ai/sleep-and-delays.mdx +10 -10
- package/docs/ai/streaming-updates-from-tools.mdx +6 -6
- package/docs/api-reference/index.mdx +25 -1
- package/docs/api-reference/meta.json +8 -0
- package/docs/api-reference/vitest/index.mdx +9 -15
- package/docs/api-reference/workflow/create-hook.mdx +89 -10
- package/docs/api-reference/workflow/create-webhook.mdx +16 -15
- package/docs/api-reference/workflow/define-hook.mdx +35 -33
- package/docs/api-reference/workflow/fatal-error.mdx +30 -8
- package/docs/api-reference/workflow/fetch.mdx +14 -10
- package/docs/api-reference/workflow/get-step-metadata.mdx +2 -2
- package/docs/api-reference/workflow/get-workflow-metadata.mdx +3 -3
- package/docs/api-reference/workflow/get-writable.mdx +7 -7
- package/docs/api-reference/workflow/index.mdx +4 -1
- package/docs/api-reference/workflow/retryable-error.mdx +1 -1
- package/docs/api-reference/workflow/set-attributes.mdx +61 -0
- package/docs/api-reference/workflow/sleep.mdx +4 -4
- package/docs/api-reference/workflow-ai/durable-agent.mdx +63 -101
- package/docs/api-reference/workflow-ai/index.mdx +5 -5
- package/docs/api-reference/workflow-ai/workflow-chat-transport.mdx +67 -24
- package/docs/api-reference/workflow-api/get-hook-by-token.mdx +26 -12
- package/docs/api-reference/workflow-api/get-run.mdx +43 -8
- package/docs/api-reference/workflow-api/index.mdx +6 -10
- package/docs/api-reference/workflow-api/resume-hook.mdx +73 -12
- package/docs/api-reference/workflow-api/resume-webhook.mdx +11 -9
- package/docs/api-reference/workflow-api/start.mdx +60 -13
- package/docs/api-reference/workflow-astro/index.mdx +18 -0
- package/docs/api-reference/workflow-astro/meta.json +4 -0
- package/docs/api-reference/workflow-astro/workflow.mdx +45 -0
- package/docs/api-reference/workflow-errors/entity-conflict-error.mdx +4 -4
- package/docs/api-reference/workflow-errors/hook-conflict-error.mdx +60 -0
- package/docs/api-reference/workflow-errors/hook-not-found-error.mdx +8 -8
- package/docs/api-reference/workflow-errors/index.mdx +88 -0
- package/docs/api-reference/workflow-errors/meta.json +6 -0
- package/docs/api-reference/workflow-errors/precondition-failed-error.mdx +68 -0
- package/docs/api-reference/workflow-errors/run-expired-error.mdx +2 -2
- package/docs/api-reference/workflow-errors/run-not-supported-error.mdx +58 -0
- package/docs/api-reference/workflow-errors/step-not-registered-error.mdx +5 -5
- package/docs/api-reference/workflow-errors/throttle-error.mdx +2 -2
- package/docs/api-reference/workflow-errors/too-early-error.mdx +2 -2
- package/docs/api-reference/workflow-errors/workflow-error.mdx +52 -0
- package/docs/api-reference/workflow-errors/workflow-not-registered-error.mdx +5 -6
- package/docs/api-reference/workflow-errors/workflow-run-cancelled-error.mdx +6 -6
- package/docs/api-reference/workflow-errors/workflow-run-failed-error.mdx +5 -5
- package/docs/api-reference/workflow-errors/workflow-run-not-completed-error.mdx +58 -0
- package/docs/api-reference/workflow-errors/workflow-run-not-found-error.mdx +4 -4
- package/docs/api-reference/workflow-errors/workflow-runtime-error.mdx +58 -0
- package/docs/api-reference/workflow-errors/workflow-world-error.mdx +8 -8
- package/docs/api-reference/workflow-globals.mdx +14 -10
- package/docs/api-reference/workflow-nest/configure-workflow-controller.mdx +33 -0
- package/docs/api-reference/workflow-nest/index.mdx +31 -0
- package/docs/api-reference/workflow-nest/meta.json +9 -0
- package/docs/api-reference/workflow-nest/nest-local-builder.mdx +64 -0
- package/docs/api-reference/workflow-nest/workflow-controller.mdx +40 -0
- package/docs/api-reference/workflow-nest/workflow-module.mdx +74 -0
- package/docs/api-reference/workflow-next/with-workflow.mdx +39 -17
- package/docs/api-reference/workflow-nitro/index.mdx +60 -0
- package/docs/api-reference/workflow-nuxt/index.mdx +48 -0
- package/docs/api-reference/workflow-observability/hydrate-data.mdx +35 -0
- package/docs/api-reference/workflow-observability/hydrate-resource-io.mdx +62 -0
- package/docs/api-reference/workflow-observability/index.mdx +62 -0
- package/docs/api-reference/workflow-observability/meta.json +11 -0
- package/docs/api-reference/workflow-observability/observability-revivers.mdx +50 -0
- package/docs/api-reference/workflow-observability/parse-class-name.mdx +41 -0
- package/docs/api-reference/workflow-observability/parse-step-name.mdx +40 -0
- package/docs/api-reference/workflow-observability/parse-workflow-name.mdx +55 -0
- package/docs/api-reference/workflow-runtime/create-world.mdx +39 -0
- package/docs/api-reference/workflow-runtime/get-world-handlers.mdx +44 -0
- package/docs/api-reference/{workflow-api → workflow-runtime}/get-world.mdx +11 -14
- package/docs/api-reference/workflow-runtime/health-check.mdx +50 -0
- package/docs/api-reference/workflow-runtime/index.mdx +41 -0
- package/docs/api-reference/workflow-runtime/meta.json +12 -0
- package/docs/api-reference/workflow-runtime/set-world.mdx +51 -0
- package/docs/api-reference/workflow-runtime/workflow-entrypoint.mdx +43 -0
- package/docs/api-reference/workflow-runtime/world/analytics.mdx +315 -0
- package/docs/api-reference/workflow-runtime/world/index.mdx +60 -0
- package/docs/api-reference/workflow-runtime/world/meta.json +4 -0
- package/docs/api-reference/workflow-runtime/world/queue.mdx +88 -0
- package/docs/api-reference/{workflow-api → workflow-runtime}/world/storage.mdx +98 -34
- package/docs/api-reference/{workflow-api → workflow-runtime}/world/streams.mdx +8 -8
- package/docs/api-reference/workflow-serde/index.mdx +1 -2
- package/docs/api-reference/workflow-serde/workflow-deserialize.mdx +3 -4
- package/docs/api-reference/workflow-serde/workflow-serialize.mdx +8 -8
- package/docs/api-reference/workflow-sveltekit/index.mdx +18 -0
- package/docs/api-reference/workflow-sveltekit/meta.json +4 -0
- package/docs/api-reference/workflow-sveltekit/workflow-plugin.mdx +42 -0
- package/docs/api-reference/workflow-vite/index.mdx +18 -0
- package/docs/api-reference/workflow-vite/meta.json +4 -0
- package/docs/api-reference/workflow-vite/workflow.mdx +48 -0
- package/docs/changelog/attributes-mvp.mdx +380 -0
- package/docs/changelog/batched-event-writes.mdx +79 -0
- package/docs/changelog/eager-processing.mdx +110 -436
- package/docs/changelog/index.mdx +4 -2
- package/docs/changelog/lazy-event-creation.md +127 -0
- package/docs/changelog/lazy-hook-resume.mdx +78 -0
- package/docs/changelog/meta.json +11 -1
- package/docs/changelog/resilient-resume.mdx +32 -0
- package/docs/changelog/resilient-start.mdx +33 -285
- package/docs/changelog/step-message-ownership.mdx +360 -0
- package/docs/changelog/turbo-mode.md +87 -0
- package/docs/comparisons/index.mdx +66 -0
- package/docs/comparisons/meta.json +11 -0
- package/docs/comparisons/workflow-sdk-vs-aws-agentcore.mdx +55 -0
- package/docs/comparisons/workflow-sdk-vs-aws-step-functions.mdx +111 -0
- package/docs/comparisons/workflow-sdk-vs-cloudflare-workflows.mdx +71 -0
- package/docs/comparisons/workflow-sdk-vs-inngest.mdx +102 -0
- package/docs/comparisons/workflow-sdk-vs-temporal.mdx +123 -0
- package/docs/comparisons/workflow-sdk-vs-trigger-dev.mdx +104 -0
- package/docs/configuration/build-and-diagnostics.mdx +70 -0
- package/docs/configuration/cli-and-web-ui.mdx +241 -0
- package/docs/configuration/framework-options.mdx +165 -0
- package/docs/configuration/index.mdx +32 -0
- package/docs/configuration/meta.json +12 -0
- package/docs/configuration/runtime-tuning.mdx +381 -0
- package/docs/configuration/worlds.mdx +313 -0
- package/docs/cookbook/advanced/child-workflows.mdx +211 -264
- package/docs/cookbook/advanced/meta.json +6 -1
- package/docs/cookbook/advanced/publishing-libraries.mdx +65 -56
- package/docs/cookbook/advanced/serializable-steps.mdx +48 -68
- package/docs/cookbook/advanced/upgrading-workflows.mdx +199 -0
- package/docs/cookbook/agent-patterns/agent-cancellation.mdx +78 -60
- package/docs/cookbook/agent-patterns/durable-agent.mdx +23 -131
- package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +180 -195
- package/docs/cookbook/common-patterns/batching.mdx +18 -14
- package/docs/cookbook/common-patterns/idempotency.mdx +41 -53
- package/docs/cookbook/common-patterns/rate-limiting.mdx +8 -4
- package/docs/cookbook/common-patterns/saga.mdx +23 -19
- package/docs/cookbook/common-patterns/scheduling.mdx +34 -22
- package/docs/cookbook/common-patterns/sequential-and-parallel.mdx +29 -25
- package/docs/cookbook/common-patterns/timeouts.mdx +26 -21
- package/docs/cookbook/common-patterns/webhooks.mdx +10 -6
- package/docs/cookbook/common-patterns/workflow-composition.mdx +30 -27
- package/docs/cookbook/index.mdx +22 -21
- package/docs/cookbook/integrations/ai-sdk.mdx +86 -48
- package/docs/cookbook/integrations/chat-sdk.mdx +50 -33
- package/docs/cookbook/integrations/sandbox.mdx +62 -45
- package/docs/deploying.mdx +95 -0
- package/docs/errors/abort-signal-timeout-in-workflow.mdx +16 -12
- package/docs/errors/corrupted-event-log.mdx +39 -18
- package/docs/errors/deployment-mismatch.mdx +71 -0
- package/docs/errors/fetch-in-workflow.mdx +15 -14
- package/docs/errors/hook-conflict.mdx +69 -13
- package/docs/errors/index.mdx +2 -36
- package/docs/errors/node-js-module-in-workflow.mdx +9 -5
- package/docs/errors/replay-divergence.mdx +27 -0
- package/docs/errors/run-expired.mdx +85 -0
- package/docs/errors/runtime-decryption-failed.mdx +77 -0
- package/docs/errors/serialization-failed.mdx +44 -12
- package/docs/errors/start-invalid-workflow-function.mdx +9 -5
- package/docs/errors/step-executed-multiple-times.mdx +23 -0
- package/docs/errors/step-not-registered.mdx +6 -6
- package/docs/errors/timeout-in-workflow.mdx +12 -8
- package/docs/errors/webhook-invalid-respond-with-value.mdx +18 -18
- package/docs/errors/webhook-response-not-sent.mdx +20 -16
- package/docs/errors/workflow-not-registered.mdx +5 -5
- package/docs/foundations/cancellation.mdx +31 -32
- package/docs/foundations/errors-and-retries.mdx +42 -11
- package/docs/foundations/hooks.mdx +64 -35
- package/docs/foundations/idempotency.mdx +244 -12
- package/docs/foundations/index.mdx +1 -23
- package/docs/foundations/meta.json +2 -1
- package/docs/foundations/serialization.mdx +21 -22
- package/docs/foundations/starting-workflows.mdx +106 -30
- package/docs/foundations/streaming.mdx +108 -60
- package/docs/foundations/versioning.mdx +263 -0
- package/docs/foundations/workflows-and-steps.mdx +9 -9
- package/docs/getting-started/astro.mdx +22 -18
- package/docs/getting-started/express.mdx +15 -11
- package/docs/getting-started/fastify.mdx +15 -11
- package/docs/getting-started/hono.mdx +15 -11
- package/docs/getting-started/index.mdx +10 -3
- package/docs/getting-started/meta.json +3 -1
- package/docs/getting-started/nestjs.mdx +87 -20
- package/docs/getting-started/next.mdx +22 -16
- package/docs/getting-started/nitro.mdx +22 -18
- package/docs/getting-started/nuxt.mdx +15 -11
- package/docs/getting-started/python.mdx +190 -41
- package/docs/getting-started/react-router/index.mdx +33 -0
- package/docs/getting-started/react-router/meta.json +5 -0
- package/docs/getting-started/react-router/v7.mdx +237 -0
- package/docs/getting-started/react-router/v8.mdx +232 -0
- package/docs/getting-started/sveltekit.mdx +20 -16
- package/docs/getting-started/tanstack-start.mdx +17 -13
- package/docs/getting-started/vite.mdx +15 -11
- package/docs/how-it-works/cancellation.mdx +63 -63
- package/docs/how-it-works/code-transform.mdx +83 -67
- package/docs/how-it-works/encryption.mdx +30 -26
- package/docs/how-it-works/event-sourcing.mdx +125 -34
- package/docs/how-it-works/framework-integrations.mdx +96 -337
- package/docs/how-it-works/understanding-directives.mdx +22 -22
- package/docs/internal/index.mdx +6 -4
- package/docs/internal/meta.json +6 -1
- package/docs/internal/nitro-native-build.mdx +38 -0
- package/docs/internal/nitro-web-ui.mdx +24 -0
- package/docs/internal/serializable-abort-controller.mdx +7 -7
- package/docs/meta.json +3 -2
- package/docs/observability/attributes.mdx +134 -0
- package/docs/observability/index.mdx +32 -10
- package/docs/observability/meta.json +1 -1
- package/docs/observability/retention.mdx +95 -0
- package/docs/observability/tracing.mdx +124 -0
- package/docs/testing/index.mdx +36 -36
- package/docs/testing/server-based.mdx +10 -10
- package/docs/whats-new.mdx +186 -0
- package/package.json +17 -14
- package/docs/api-reference/workflow-api/world/index.mdx +0 -58
- package/docs/api-reference/workflow-api/world/meta.json +0 -4
- package/docs/api-reference/workflow-api/world/observability.mdx +0 -164
- package/docs/api-reference/workflow-api/world/queue.mdx +0 -86
- package/docs/deploying/building-a-world.mdx +0 -251
- package/docs/deploying/index.mdx +0 -95
- package/docs/deploying/meta.json +0 -4
- package/docs/deploying/world/local-world.mdx +0 -84
- package/docs/deploying/world/meta.json +0 -4
- package/docs/deploying/world/postgres-world.mdx +0 -224
- package/docs/deploying/world/vercel-world.mdx +0 -179
- package/docs/migration-guides/index.mdx +0 -34
- package/docs/migration-guides/meta.json +0 -9
- package/docs/migration-guides/migrating-from-aws-step-functions.mdx +0 -363
- package/docs/migration-guides/migrating-from-inngest.mdx +0 -314
- package/docs/migration-guides/migrating-from-temporal.mdx +0 -318
- package/docs/migration-guides/migrating-from-trigger-dev.mdx +0 -337
|
@@ -9,10 +9,14 @@ related:
|
|
|
9
9
|
- /docs/foundations/workflows-and-steps
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
<CopyPrompt
|
|
13
|
+
text="In this Nuxt app, run `npm i workflow`. In `nuxt.config.ts`, add `modules: ["workflow/nuxt"]` and keep `compatibilityDate: "latest"`. Create `workflows/user-signup.ts` exporting `handleUserSignup(email)` with `"use workflow"`, `sleep` from `workflow`, and `"use step"` helpers that create a user and send emails. Add `server/api/signup.post.ts` using `defineEventHandler` from `h3` or `nitro/h3` and `start` from `workflow/api` to read `{ email }` and start the workflow. Run `npm run dev`, call `curl -X POST --json '{"email":"hello@example.com"}' http://localhost:3000/api/signup`, and inspect with `npx workflow web` or `npx workflow inspect runs`."
|
|
14
|
+
/>
|
|
15
|
+
|
|
12
16
|
<Steps>
|
|
13
17
|
|
|
14
18
|
<Step>
|
|
15
|
-
## Create
|
|
19
|
+
## Create your Nuxt project
|
|
16
20
|
|
|
17
21
|
Start by creating a new Nuxt project. This command will create a new directory named `nuxt-app` and setup a Nuxt project inside it.
|
|
18
22
|
|
|
@@ -75,7 +79,7 @@ export default defineNuxtConfig({
|
|
|
75
79
|
|
|
76
80
|
<Step>
|
|
77
81
|
|
|
78
|
-
## Create
|
|
82
|
+
## Create your first workflow
|
|
79
83
|
|
|
80
84
|
Create a new file for our first workflow:
|
|
81
85
|
|
|
@@ -97,14 +101,14 @@ export async function handleUserSignup(email: string) {
|
|
|
97
101
|
}
|
|
98
102
|
```
|
|
99
103
|
|
|
100
|
-
We'll fill in those functions next
|
|
104
|
+
We'll fill in those functions next. The current code does the following:
|
|
101
105
|
|
|
102
106
|
- We define a **workflow** function with the directive `"use workflow"`. Think of the workflow function as the _orchestrator_ of individual **steps**.
|
|
103
107
|
- The Workflow SDK's `sleep` function allows us to suspend execution of the workflow without using up any resources. A sleep can be a few seconds, hours, days, or even months long.
|
|
104
108
|
|
|
105
|
-
## Create
|
|
109
|
+
## Create your workflow steps
|
|
106
110
|
|
|
107
|
-
|
|
111
|
+
Define the missing functions.
|
|
108
112
|
|
|
109
113
|
```typescript title="server/workflows/user-signup.ts" lineNumbers
|
|
110
114
|
import { FatalError } from "workflow";
|
|
@@ -145,7 +149,7 @@ async function sendOnboardingEmail(user: { id: string; email: string }) {
|
|
|
145
149
|
|
|
146
150
|
Taking a look at this code:
|
|
147
151
|
|
|
148
|
-
- Business logic lives inside **steps**. When a
|
|
152
|
+
- Business logic lives inside **steps**. When a workflow invokes a step, the workflow suspends while the step runs with full Node.js access. The combined handler may execute it inline; queued retries and continuations return through the same flow route.
|
|
149
153
|
- If a step throws an error, like in `sendWelcomeEmail`, the step will automatically be retried until it succeeds (or hits the step's max retry count).
|
|
150
154
|
- Steps can throw a `FatalError` if an error is intentional and should not be retried.
|
|
151
155
|
|
|
@@ -158,7 +162,7 @@ Taking a look at this code:
|
|
|
158
162
|
|
|
159
163
|
<Step>
|
|
160
164
|
|
|
161
|
-
## Create
|
|
165
|
+
## Create your API route
|
|
162
166
|
|
|
163
167
|
To invoke your new workflow, we'll create a new API route handler at `server/api/signup.post.ts` with the following code:
|
|
164
168
|
|
|
@@ -223,7 +227,7 @@ npx workflow inspect runs
|
|
|
223
227
|
|
|
224
228
|
## Deploying to production
|
|
225
229
|
|
|
226
|
-
Workflow SDK apps currently work best when deployed to [Vercel](https://vercel.com/home) and
|
|
230
|
+
Workflow SDK apps currently work best when deployed to [Vercel](https://vercel.com/home) and need no special configuration.
|
|
227
231
|
|
|
228
232
|
<FluidComputeCallout />
|
|
229
233
|
|
|
@@ -235,8 +239,8 @@ Check the [Deploying](/docs/deploying) section to learn how your workflows can b
|
|
|
235
239
|
|
|
236
240
|
If you see this error:
|
|
237
241
|
|
|
238
|
-
```
|
|
239
|
-
'start' received an invalid workflow function. Ensure the Workflow
|
|
242
|
+
```text
|
|
243
|
+
'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive.
|
|
240
244
|
```
|
|
241
245
|
|
|
242
246
|
Check both of these first:
|
|
@@ -246,7 +250,7 @@ Check both of these first:
|
|
|
246
250
|
|
|
247
251
|
See [start-invalid-workflow-function](/docs/errors/start-invalid-workflow-function) for full examples and fixes.
|
|
248
252
|
|
|
249
|
-
## Next
|
|
253
|
+
## Next steps
|
|
250
254
|
|
|
251
255
|
- Learn more about the [Foundations](/docs/foundations).
|
|
252
256
|
- Check [Errors](/docs/errors) if you encounter issues.
|
|
@@ -10,48 +10,50 @@ related:
|
|
|
10
10
|
- /docs/foundations/workflows-and-steps
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
<CopyPrompt
|
|
14
|
+
text="Set up Workflow in this Python project. In `pyproject.toml`, add `requires-python = ">=3.12"` and `dependencies = ["vercel-workflow"]` under `[project]`, then add `[[tool.vercel.workflows]]` with `entrypoint = "app.workflows:wf"`. Create `app/workflow.py` with `from vercel import workflow` and `wf = workflow.Workflows(sandbox_policy=workflow.SandboxPolicy(share_sandboxes=True))`. Create `app/steps/generate_draft.py`, import `wf`, and define async step functions such as `generate_draft` and `summarize_draft`, decorating each with `@wf.step`. Then create `app/workflows/ai_content_workflow.py`, import `wf` and those step functions, and define `@wf.workflow async def ai_content_workflow(*, topic: str)` to orchestrate them and return the result. In `app/workflows/__init__.py`, export `wf` and import the workflow module so its definitions are registered. From server-side code, start it with `await workflow.start(ai_content_workflow, topic=...)`; use the returned `Run` to access its ID, check its status, or await its return value. Where the workflow needs a durable delay, use `await workflow.sleep(timedelta(days=7))` after importing `timedelta` from `datetime`. Where it needs an external approval event, define a Pydantic model that also extends `workflow.BaseHook`, wait with `.wait(token=...)`, and resume it from server-side code with `.resume(token)`."
|
|
15
|
+
/>
|
|
16
|
+
|
|
13
17
|
<Callout type="warn">
|
|
14
|
-
The Python SDK is currently in **beta**. APIs and behavior may change.
|
|
18
|
+
The Python SDK is currently in **beta**. APIs and behavior may change.
|
|
15
19
|
</Callout>
|
|
16
20
|
|
|
17
|
-
You can build durable workflows in Python using the [`vercel`
|
|
18
|
-
|
|
19
|
-
## Getting Started
|
|
21
|
+
You can build durable workflows in Python using the [`vercel-workflow` SDK](https://pypi.org/project/vercel-workflow/). Your workflow code can pause, resume, and maintain state, just like the JavaScript and TypeScript Workflow SDK.
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
## Getting started
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
pip install vercel
|
|
25
|
-
```
|
|
25
|
+
Add the `vercel-workflow` package and workflow entrypoint to `pyproject.toml`:
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
```toml filename="pyproject.toml"
|
|
28
|
+
[project]
|
|
29
|
+
requires-python = ">=3.12"
|
|
30
|
+
dependencies = ["vercel-workflow"]
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"experimentalServices": {
|
|
32
|
-
"ai_content_workflow": {
|
|
33
|
-
"type": "worker",
|
|
34
|
-
"entrypoint": "app/workflows/ai_content_workflow.py",
|
|
35
|
-
"topics": ["__wkf_*"]
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
32
|
+
[[tool.vercel.workflows]]
|
|
33
|
+
entrypoint = "app.workflows:wf"
|
|
39
34
|
```
|
|
40
35
|
|
|
36
|
+
The workflow `entrypoint` uses the `module:object` format and points to the exported `Workflows` registry.
|
|
37
|
+
|
|
41
38
|
## Workflows
|
|
42
39
|
|
|
43
40
|
A workflow is a stateful function that coordinates multi-step logic over time. Create a `Workflows` instance and use the `@wf.workflow` decorator to mark a function as durable:
|
|
44
41
|
|
|
45
|
-
```python filename="app/workflow.py"
|
|
42
|
+
```python filename="app/workflow.py"
|
|
46
43
|
from vercel import workflow
|
|
47
44
|
|
|
48
|
-
wf = workflow.Workflows(
|
|
45
|
+
wf = workflow.Workflows(
|
|
46
|
+
sandbox_policy=workflow.SandboxPolicy(share_sandboxes=True), # [!code highlight]
|
|
47
|
+
)
|
|
49
48
|
```
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
`share_sandboxes=True` reuses a sandbox across workflow runs for faster startup. Module globals are shared between those runs, so workflow code should not mutate global state.
|
|
51
|
+
|
|
52
|
+
```python filename="app/workflows/ai_content_workflow.py"
|
|
52
53
|
from app.workflow import wf
|
|
54
|
+
from app.steps.generate_draft import generate_draft, summarize_draft
|
|
53
55
|
|
|
54
|
-
@wf.workflow
|
|
56
|
+
@wf.workflow # [!code highlight]
|
|
55
57
|
async def ai_content_workflow(*, topic: str):
|
|
56
58
|
draft = await generate_draft(topic=topic)
|
|
57
59
|
summary = await summarize_draft(draft=draft)
|
|
@@ -62,21 +64,30 @@ async def ai_content_workflow(*, topic: str):
|
|
|
62
64
|
}
|
|
63
65
|
```
|
|
64
66
|
|
|
67
|
+
Export the registry from the workflow package and import the module containing your workflow so its definitions are registered:
|
|
68
|
+
|
|
69
|
+
```python filename="app/workflows/__init__.py"
|
|
70
|
+
from app.workflow import wf
|
|
71
|
+
from app.workflows import ai_content_workflow
|
|
72
|
+
|
|
73
|
+
__all__ = ["ai_content_workflow", "wf"]
|
|
74
|
+
```
|
|
75
|
+
|
|
65
76
|
Under the hood, the workflow compiles into a route that orchestrates execution. All inputs and outputs are recorded in an event log. If a deploy or crash happens, the system replays execution deterministically from where it stopped.
|
|
66
77
|
|
|
67
78
|
## Steps
|
|
68
79
|
|
|
69
80
|
A step is a stateless function that runs a unit of durable work inside a workflow. Use `@wf.step` to mark a function as a step:
|
|
70
81
|
|
|
71
|
-
```python filename="app/steps/generate_draft.py"
|
|
82
|
+
```python filename="app/steps/generate_draft.py"
|
|
72
83
|
import random
|
|
73
84
|
from app.workflow import wf
|
|
74
85
|
|
|
75
|
-
@wf.step
|
|
86
|
+
@wf.step # [!code highlight]
|
|
76
87
|
async def generate_draft(*, topic: str):
|
|
77
88
|
return await ai_generate(prompt=f"Write a blog post about {topic}")
|
|
78
89
|
|
|
79
|
-
@wf.step
|
|
90
|
+
@wf.step # [!code highlight]
|
|
80
91
|
async def summarize_draft(*, draft: str):
|
|
81
92
|
summary = await ai_summarize(text=draft)
|
|
82
93
|
|
|
@@ -87,20 +98,71 @@ async def summarize_draft(*, draft: str):
|
|
|
87
98
|
return summary
|
|
88
99
|
```
|
|
89
100
|
|
|
90
|
-
Each step
|
|
101
|
+
Each step executes separately from the workflow orchestrator. While the step executes, the workflow suspends without consuming resources. When the step completes, the workflow resumes automatically where it left off.
|
|
102
|
+
|
|
103
|
+
### Cancellable steps
|
|
104
|
+
|
|
105
|
+
Pass `cancellable=True` when a workflow may need to stop a running step. This makes normal Python `asyncio` cancellation apply to the step: if a task awaiting the step is cancelled in the workflow, then the task running the step will be cancelled as well. For example, `asyncio.timeout()` cancels the step if it does not finish within the allotted time:
|
|
106
|
+
|
|
107
|
+
```python filename="app/workflows/report.py"
|
|
108
|
+
import asyncio
|
|
109
|
+
from datetime import timedelta
|
|
110
|
+
|
|
111
|
+
from app.workflow import wf
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@wf.step(cancellable=True) # [!code highlight]
|
|
115
|
+
async def generate_report(*, account_id: str) -> dict[str, str]:
|
|
116
|
+
return await reporting_service.generate(account_id=account_id)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@wf.workflow
|
|
120
|
+
async def report_workflow(*, account_id: str) -> dict[str, str]:
|
|
121
|
+
try:
|
|
122
|
+
async with asyncio.timeout(timedelta(minutes=10).total_seconds()): # [!code highlight]
|
|
123
|
+
return await generate_report(account_id=account_id)
|
|
124
|
+
except TimeoutError:
|
|
125
|
+
return {"status": "timed_out"}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Cancellation is a request, so the workflow waits for the step to terminate before continuing. If the step suppresses its cancellation, it can still return normally.
|
|
129
|
+
|
|
130
|
+
## Starting a workflow
|
|
131
|
+
|
|
132
|
+
Call `workflow.start()` from server-side code to start a workflow. It returns a `Run` that you can use to identify the run, check its status, and wait for its result:
|
|
133
|
+
|
|
134
|
+
```python filename="app/api/generate.py"
|
|
135
|
+
from app.workflows.ai_content_workflow import ai_content_workflow
|
|
136
|
+
from vercel import workflow
|
|
137
|
+
|
|
138
|
+
@app.post("/api/generate")
|
|
139
|
+
async def generate_content(*, topic: str):
|
|
140
|
+
run = await workflow.start(ai_content_workflow, topic=topic) # [!code highlight]
|
|
141
|
+
|
|
142
|
+
print(run.run_id)
|
|
143
|
+
print(await run.status()) # [!code highlight]
|
|
144
|
+
|
|
145
|
+
# Wait until the workflow completes and return its result.
|
|
146
|
+
return await run.return_value() # [!code highlight]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Starting a workflow only waits until the run has been created and queued. Await `return_value()` to wait for the workflow to finish, or save its `run_id` and recreate the handle later with `workflow.Run(run_id)`.
|
|
91
150
|
|
|
92
151
|
## Sleep
|
|
93
152
|
|
|
94
153
|
Sleep pauses a workflow for a specified duration without consuming compute resources:
|
|
95
154
|
|
|
96
|
-
```python filename="app/workflows/ai_refine.py"
|
|
155
|
+
```python filename="app/workflows/ai_refine.py"
|
|
156
|
+
from datetime import timedelta
|
|
157
|
+
|
|
158
|
+
from app.workflow import wf
|
|
97
159
|
from vercel import workflow
|
|
98
160
|
|
|
99
161
|
@wf.workflow
|
|
100
162
|
async def ai_refine_workflow(*, draft_id: str):
|
|
101
163
|
draft = await fetch_draft(draft_id)
|
|
102
164
|
|
|
103
|
-
await workflow.sleep(
|
|
165
|
+
await workflow.sleep(timedelta(days=7)) # Wait 7 days to gather more signals. # [!code highlight]
|
|
104
166
|
|
|
105
167
|
refined = await refine_draft(draft)
|
|
106
168
|
|
|
@@ -110,7 +172,43 @@ async def ai_refine_workflow(*, draft_id: str):
|
|
|
110
172
|
}
|
|
111
173
|
```
|
|
112
174
|
|
|
113
|
-
The
|
|
175
|
+
The parameter accepts four forms:
|
|
176
|
+
|
|
177
|
+
| Form | Description | Example |
|
|
178
|
+
| --- | --- | --- |
|
|
179
|
+
| `str` | Human-readable duration string | `"2 days"`, `"1w"`, `"1h 30m"` |
|
|
180
|
+
| `int` or `float` | Seconds from now | `5` (5 seconds) |
|
|
181
|
+
| `datetime.timedelta` | Duration from now | `timedelta(days=7)` |
|
|
182
|
+
| `datetime.datetime` | Absolute wake-up time (must be timezone-aware) | `datetime(2025, 1, 1, tzinfo=UTC)` |
|
|
183
|
+
|
|
184
|
+
The string form accepts one or more `<value><unit>` pairs. Supported units:
|
|
185
|
+
|
|
186
|
+
| Duration | Unit |
|
|
187
|
+
| --- | --- |
|
|
188
|
+
| Milliseconds | `ms` |
|
|
189
|
+
| Seconds | `s`, `second`, `seconds` |
|
|
190
|
+
| Minutes | `m`, `minute`, `minutes` |
|
|
191
|
+
| Hours | `h`, `hour`, `hours` |
|
|
192
|
+
| Days | `d`, `day`, `days` |
|
|
193
|
+
| Weeks | `w`, `week`, `weeks` |
|
|
194
|
+
|
|
195
|
+
<Callout>
|
|
196
|
+
`sleep()` must be called from the workflow body, not from inside a step. Calling it from a step raises a `RuntimeError`.
|
|
197
|
+
</Callout>
|
|
198
|
+
|
|
199
|
+
The sleep consumes no resources. The workflow resumes automatically when the time expires.
|
|
200
|
+
|
|
201
|
+
`asyncio.sleep()` may also be used to sleep.
|
|
202
|
+
|
|
203
|
+
## Deterministic workflow helpers
|
|
204
|
+
|
|
205
|
+
Workflow bodies must be fully deterministic, and so are not allowed to perform operations like reading the system clock or generating randomness using the default generator. Deterministic replacements are provided for use in workflow bodies:
|
|
206
|
+
|
|
207
|
+
- `workflow.now()` is a deterministic substitute for `datetime.datetime.now`. It returns the time that the last workflow event occurred at.
|
|
208
|
+
- `workflow.time_ns()` is a deterministic substitute for `time.time_ns`.
|
|
209
|
+
- `workflow.random()` returns a `random.Random` instance with a seed based on the run id.
|
|
210
|
+
|
|
211
|
+
These helpers can only be called from a workflow body. Steps should use the normal system functions.
|
|
114
212
|
|
|
115
213
|
## Hooks
|
|
116
214
|
|
|
@@ -118,21 +216,25 @@ A hook lets a workflow wait for external events such as user actions, webhooks,
|
|
|
118
216
|
|
|
119
217
|
Define a hook model with Pydantic and `workflow.BaseHook`:
|
|
120
218
|
|
|
121
|
-
```python filename="app/workflows/approval.py"
|
|
219
|
+
```python filename="app/workflows/approval.py"
|
|
220
|
+
import typing
|
|
221
|
+
|
|
222
|
+
import pydantic
|
|
223
|
+
from app.workflow import wf
|
|
122
224
|
from vercel import workflow
|
|
123
225
|
|
|
124
|
-
class Approval(BaseModel, workflow.BaseHook):
|
|
226
|
+
class Approval(pydantic.BaseModel, workflow.BaseHook): # [!code highlight]
|
|
125
227
|
"""Human approval for AI-generated drafts"""
|
|
126
228
|
|
|
127
|
-
decision: Literal["approved", "changes"]
|
|
229
|
+
decision: typing.Literal["approved", "changes"]
|
|
128
230
|
notes: str | None = None
|
|
129
231
|
|
|
130
232
|
@wf.workflow
|
|
131
|
-
async def ai_approval_workflow(*, topic: str):
|
|
233
|
+
async def ai_approval_workflow(*, topic: str) -> None:
|
|
132
234
|
draft = await generate_draft(topic=topic)
|
|
133
235
|
|
|
134
236
|
# Wait for human approval events
|
|
135
|
-
async for event in Approval.wait(token="draft-123"):
|
|
237
|
+
async for event in Approval.wait(token="draft-123"): # [!code highlight]
|
|
136
238
|
if event.decision == "approved":
|
|
137
239
|
await publish_draft(draft)
|
|
138
240
|
break
|
|
@@ -143,22 +245,69 @@ async def ai_approval_workflow(*, topic: str):
|
|
|
143
245
|
|
|
144
246
|
Resume the workflow when data arrives:
|
|
145
247
|
|
|
146
|
-
```python filename="app/api/resume.py"
|
|
248
|
+
```python filename="app/api/resume.py"
|
|
249
|
+
from app.workflows.approval import Approval
|
|
250
|
+
|
|
147
251
|
@app.post("/api/resume")
|
|
148
|
-
async def resume(approval: Approval):
|
|
252
|
+
async def resume(approval: Approval): # [!code highlight]
|
|
149
253
|
"""Resume the workflow when an approval is received"""
|
|
150
254
|
|
|
151
|
-
await approval.resume("draft-123")
|
|
255
|
+
hook = await approval.resume("draft-123") # [!code highlight]
|
|
256
|
+
print(f"Resumed workflow run: {hook.run_id}") # [!code highlight]
|
|
152
257
|
return {"ok": True}
|
|
153
258
|
```
|
|
154
259
|
|
|
155
260
|
When a hook receives data, the workflow resumes automatically. You don't need polling, message queues, or manual state management.
|
|
156
261
|
|
|
157
|
-
##
|
|
262
|
+
## Streaming
|
|
263
|
+
|
|
264
|
+
Steps can stream progress while a workflow is running. Streams are associated with workflow runs, can be acquired from inside a workflow or inside a step, and can be passed to workflows and steps as arguments. If a type is specified when getting the stream (or on the type annotation of a step or workflow it is passed to), then Pydantic will be used to encode and decode the type.
|
|
265
|
+
|
|
266
|
+
```python filename="app/workflows/streaming.py"
|
|
267
|
+
import pydantic
|
|
268
|
+
|
|
269
|
+
from app.workflow import wf
|
|
270
|
+
from vercel import workflow
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class Progress(pydantic.BaseModel):
|
|
274
|
+
message: str
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@wf.step
|
|
278
|
+
async def write_progress(
|
|
279
|
+
writable: workflow.WorkflowWritable[Progress], # [!code highlight]
|
|
280
|
+
):
|
|
281
|
+
for message in ["Drafting", "Reviewing", "Complete"]:
|
|
282
|
+
await writable.write(Progress(message=message)) # [!code highlight]
|
|
283
|
+
|
|
284
|
+
await writable.close()
|
|
285
|
+
|
|
286
|
+
@wf.workflow
|
|
287
|
+
async def streaming_workflow():
|
|
288
|
+
writable = workflow.get_writable(type=Progress)
|
|
289
|
+
await write_progress(writable) # [!code highlight]
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Read and validate the typed values from the returned `Run` as they arrive:
|
|
293
|
+
|
|
294
|
+
```python filename="app/api/stream.py"
|
|
295
|
+
from app.workflows.streaming import Progress, streaming_workflow
|
|
296
|
+
from vercel import workflow
|
|
297
|
+
|
|
298
|
+
@app.post("/api/stream")
|
|
299
|
+
async def stream_progress():
|
|
300
|
+
run = await workflow.start(streaming_workflow)
|
|
301
|
+
|
|
302
|
+
async for progress in run.readable(type=Progress): # [!code highlight]
|
|
303
|
+
print(progress.message)
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Streams are not closed automatically. It can be manually closed when it is finished so that readers know to terminate.
|
|
158
307
|
|
|
159
|
-
|
|
308
|
+
`readable` and `get_writable` also take a `namespace` parameter, allowing each run to have many distinct streams.
|
|
160
309
|
|
|
161
|
-
## Next
|
|
310
|
+
## Next steps
|
|
162
311
|
|
|
163
312
|
- Learn more about the [Foundations](/docs/foundations).
|
|
164
313
|
- Check [Errors](/docs/errors) if you encounter issues.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: React Router
|
|
3
|
+
description: Run durable workflows in a React Router framework-mode app using Nitro.
|
|
4
|
+
type: overview
|
|
5
|
+
summary: Choose your React Router version and connect React Router, Nitro, and Workflow SDK.
|
|
6
|
+
related:
|
|
7
|
+
- /docs/getting-started/nitro
|
|
8
|
+
- /docs/getting-started/vite
|
|
9
|
+
- /docs/foundations/workflows-and-steps
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
React Router framework mode builds the browser application and its server-rendering code, but it still needs a server to receive requests. [Nitro](https://v3.nitro.build) provides that server. Workflow SDK integrates with Nitro to add the durable workflow routes and build artifacts.
|
|
13
|
+
|
|
14
|
+
The three pieces share one Vite build:
|
|
15
|
+
|
|
16
|
+
1. **React Router** builds your routes, loaders, actions, and browser assets.
|
|
17
|
+
2. **Nitro** runs the React Router request handler and any routes in `server/routes`.
|
|
18
|
+
3. **Workflow SDK** finds files with `"use workflow"` and `"use step"`, then adds its runtime routes to Nitro.
|
|
19
|
+
|
|
20
|
+
Choose the guide that matches your React Router major version:
|
|
21
|
+
|
|
22
|
+
<AutoCards />
|
|
23
|
+
|
|
24
|
+
<Callout>
|
|
25
|
+
These guides require **Nitro v3**. Nitro v2 does not provide the Vite
|
|
26
|
+
environment integration used by this setup.
|
|
27
|
+
</Callout>
|
|
28
|
+
|
|
29
|
+
## What the bridge does
|
|
30
|
+
|
|
31
|
+
The setup adds a small `server/ssr.ts` file. It turns React Router's generated server build into a standard Fetch API handler that Nitro can run. The Vite config then points Nitro's server and public output at the same `build` directory React Router uses.
|
|
32
|
+
|
|
33
|
+
This is configuration in your application, not a separate React Router adapter. Your React Router routes remain React Router routes, while Nitro owns the HTTP server and Workflow SDK uses Nitro's lifecycle and routing.
|