workflow 5.0.0-beta.5 → 5.0.0-beta.50
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 +29 -26
- package/docs/ai/defining-tools.mdx +6 -7
- package/docs/ai/human-in-the-loop.mdx +11 -11
- package/docs/ai/index.mdx +50 -45
- package/docs/ai/message-queueing.mdx +16 -16
- 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 +24 -0
- 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 +48 -86
- package/docs/api-reference/workflow-ai/index.mdx +3 -3
- 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 +376 -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 +28 -20
- package/docs/cookbook/advanced/upgrading-workflows.mdx +199 -0
- package/docs/cookbook/agent-patterns/agent-cancellation.mdx +27 -19
- package/docs/cookbook/agent-patterns/durable-agent.mdx +14 -142
- package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +30 -22
- 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 +85 -47
- 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 +29 -18
- package/docs/errors/deployment-mismatch.mdx +71 -0
- package/docs/errors/fetch-in-workflow.mdx +11 -7
- 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 +107 -59
- 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 +135 -40
- 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 +98 -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 +93 -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
|
@@ -10,48 +10,46 @@ 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()`. 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() # [!code highlight]
|
|
49
46
|
```
|
|
50
47
|
|
|
51
|
-
```python filename="app/workflows/ai_content_workflow.py"
|
|
48
|
+
```python filename="app/workflows/ai_content_workflow.py"
|
|
52
49
|
from app.workflow import wf
|
|
50
|
+
from app.steps.generate_draft import generate_draft, summarize_draft
|
|
53
51
|
|
|
54
|
-
@wf.workflow
|
|
52
|
+
@wf.workflow # [!code highlight]
|
|
55
53
|
async def ai_content_workflow(*, topic: str):
|
|
56
54
|
draft = await generate_draft(topic=topic)
|
|
57
55
|
summary = await summarize_draft(draft=draft)
|
|
@@ -62,21 +60,30 @@ async def ai_content_workflow(*, topic: str):
|
|
|
62
60
|
}
|
|
63
61
|
```
|
|
64
62
|
|
|
63
|
+
Export the registry from the workflow package and import the module containing your workflow so its definitions are registered:
|
|
64
|
+
|
|
65
|
+
```python filename="app/workflows/__init__.py"
|
|
66
|
+
from app.workflow import wf
|
|
67
|
+
from app.workflows import ai_content_workflow
|
|
68
|
+
|
|
69
|
+
__all__ = ["ai_content_workflow", "wf"]
|
|
70
|
+
```
|
|
71
|
+
|
|
65
72
|
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
73
|
|
|
67
74
|
## Steps
|
|
68
75
|
|
|
69
76
|
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
77
|
|
|
71
|
-
```python filename="app/steps/generate_draft.py"
|
|
78
|
+
```python filename="app/steps/generate_draft.py"
|
|
72
79
|
import random
|
|
73
80
|
from app.workflow import wf
|
|
74
81
|
|
|
75
|
-
@wf.step
|
|
82
|
+
@wf.step # [!code highlight]
|
|
76
83
|
async def generate_draft(*, topic: str):
|
|
77
84
|
return await ai_generate(prompt=f"Write a blog post about {topic}")
|
|
78
85
|
|
|
79
|
-
@wf.step
|
|
86
|
+
@wf.step # [!code highlight]
|
|
80
87
|
async def summarize_draft(*, draft: str):
|
|
81
88
|
summary = await ai_summarize(text=draft)
|
|
82
89
|
|
|
@@ -87,20 +94,44 @@ async def summarize_draft(*, draft: str):
|
|
|
87
94
|
return summary
|
|
88
95
|
```
|
|
89
96
|
|
|
90
|
-
Each step
|
|
97
|
+
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.
|
|
98
|
+
|
|
99
|
+
## Starting a workflow
|
|
100
|
+
|
|
101
|
+
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:
|
|
102
|
+
|
|
103
|
+
```python filename="app/api/generate.py"
|
|
104
|
+
from app.workflows.ai_content_workflow import ai_content_workflow
|
|
105
|
+
from vercel import workflow
|
|
106
|
+
|
|
107
|
+
@app.post("/api/generate")
|
|
108
|
+
async def generate_content(*, topic: str):
|
|
109
|
+
run = await workflow.start(ai_content_workflow, topic=topic) # [!code highlight]
|
|
110
|
+
|
|
111
|
+
print(run.run_id)
|
|
112
|
+
print(await run.status()) # [!code highlight]
|
|
113
|
+
|
|
114
|
+
# Wait until the workflow completes and return its result.
|
|
115
|
+
return await run.return_value() # [!code highlight]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
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
119
|
|
|
92
120
|
## Sleep
|
|
93
121
|
|
|
94
122
|
Sleep pauses a workflow for a specified duration without consuming compute resources:
|
|
95
123
|
|
|
96
|
-
```python filename="app/workflows/ai_refine.py"
|
|
124
|
+
```python filename="app/workflows/ai_refine.py"
|
|
125
|
+
from datetime import timedelta
|
|
126
|
+
|
|
127
|
+
from app.workflow import wf
|
|
97
128
|
from vercel import workflow
|
|
98
129
|
|
|
99
130
|
@wf.workflow
|
|
100
131
|
async def ai_refine_workflow(*, draft_id: str):
|
|
101
132
|
draft = await fetch_draft(draft_id)
|
|
102
133
|
|
|
103
|
-
await workflow.sleep(
|
|
134
|
+
await workflow.sleep(timedelta(days=7)) # Wait 7 days to gather more signals. # [!code highlight]
|
|
104
135
|
|
|
105
136
|
refined = await refine_draft(draft)
|
|
106
137
|
|
|
@@ -110,7 +141,31 @@ async def ai_refine_workflow(*, draft_id: str):
|
|
|
110
141
|
}
|
|
111
142
|
```
|
|
112
143
|
|
|
113
|
-
The
|
|
144
|
+
The parameter accepts four forms:
|
|
145
|
+
|
|
146
|
+
| Form | Description | Example |
|
|
147
|
+
| --- | --- | --- |
|
|
148
|
+
| `str` | Human-readable duration string | `"2 days"`, `"1w"`, `"1h 30m"` |
|
|
149
|
+
| `int` or `float` | Seconds from now | `5` (5 seconds) |
|
|
150
|
+
| `datetime.timedelta` | Duration from now | `timedelta(days=7)` |
|
|
151
|
+
| `datetime.datetime` | Absolute wake-up time (must be timezone-aware) | `datetime(2025, 1, 1, tzinfo=UTC)` |
|
|
152
|
+
|
|
153
|
+
The string form accepts one or more `<value><unit>` pairs. Supported units:
|
|
154
|
+
|
|
155
|
+
| Duration | Unit |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| Milliseconds | `ms` |
|
|
158
|
+
| Seconds | `s`, `second`, `seconds` |
|
|
159
|
+
| Minutes | `m`, `minute`, `minutes` |
|
|
160
|
+
| Hours | `h`, `hour`, `hours` |
|
|
161
|
+
| Days | `d`, `day`, `days` |
|
|
162
|
+
| Weeks | `w`, `week`, `weeks` |
|
|
163
|
+
|
|
164
|
+
<Callout>
|
|
165
|
+
`sleep()` must be called from the workflow body, not from inside a step. Calling it from a step raises a `RuntimeError`.
|
|
166
|
+
</Callout>
|
|
167
|
+
|
|
168
|
+
The sleep consumes no resources. The workflow resumes automatically when the time expires.
|
|
114
169
|
|
|
115
170
|
## Hooks
|
|
116
171
|
|
|
@@ -118,13 +173,17 @@ A hook lets a workflow wait for external events such as user actions, webhooks,
|
|
|
118
173
|
|
|
119
174
|
Define a hook model with Pydantic and `workflow.BaseHook`:
|
|
120
175
|
|
|
121
|
-
```python filename="app/workflows/approval.py"
|
|
176
|
+
```python filename="app/workflows/approval.py"
|
|
177
|
+
import typing
|
|
178
|
+
|
|
179
|
+
import pydantic
|
|
180
|
+
from app.workflow import wf
|
|
122
181
|
from vercel import workflow
|
|
123
182
|
|
|
124
|
-
class Approval(BaseModel, workflow.BaseHook):
|
|
183
|
+
class Approval(pydantic.BaseModel, workflow.BaseHook): # [!code highlight]
|
|
125
184
|
"""Human approval for AI-generated drafts"""
|
|
126
185
|
|
|
127
|
-
decision: Literal["approved", "changes"]
|
|
186
|
+
decision: typing.Literal["approved", "changes"]
|
|
128
187
|
notes: str | None = None
|
|
129
188
|
|
|
130
189
|
@wf.workflow
|
|
@@ -132,7 +191,7 @@ async def ai_approval_workflow(*, topic: str):
|
|
|
132
191
|
draft = await generate_draft(topic=topic)
|
|
133
192
|
|
|
134
193
|
# Wait for human approval events
|
|
135
|
-
async for event in Approval.wait(token="draft-123"):
|
|
194
|
+
async for event in Approval.wait(token="draft-123"): # [!code highlight]
|
|
136
195
|
if event.decision == "approved":
|
|
137
196
|
await publish_draft(draft)
|
|
138
197
|
break
|
|
@@ -143,22 +202,58 @@ async def ai_approval_workflow(*, topic: str):
|
|
|
143
202
|
|
|
144
203
|
Resume the workflow when data arrives:
|
|
145
204
|
|
|
146
|
-
```python filename="app/api/resume.py"
|
|
205
|
+
```python filename="app/api/resume.py"
|
|
206
|
+
from app.workflows.approval import Approval
|
|
207
|
+
|
|
147
208
|
@app.post("/api/resume")
|
|
148
|
-
async def resume(approval: Approval):
|
|
209
|
+
async def resume(approval: Approval): # [!code highlight]
|
|
149
210
|
"""Resume the workflow when an approval is received"""
|
|
150
211
|
|
|
151
|
-
await approval.resume("draft-123")
|
|
212
|
+
await approval.resume("draft-123") # [!code highlight]
|
|
152
213
|
return {"ok": True}
|
|
153
214
|
```
|
|
154
215
|
|
|
155
216
|
When a hook receives data, the workflow resumes automatically. You don't need polling, message queues, or manual state management.
|
|
156
217
|
|
|
157
|
-
##
|
|
218
|
+
## Streaming
|
|
219
|
+
|
|
220
|
+
Steps can stream progress while a workflow is running. Get the run's writable stream inside a step, write values to it, and close it when no more values will be sent:
|
|
221
|
+
|
|
222
|
+
```python filename="app/workflows/streaming.py"
|
|
223
|
+
from app.workflow import wf
|
|
224
|
+
from vercel import workflow
|
|
225
|
+
|
|
226
|
+
@wf.step
|
|
227
|
+
async def write_progress():
|
|
228
|
+
writable = workflow.get_writable() # [!code highlight]
|
|
229
|
+
|
|
230
|
+
for message in ["Drafting", "Reviewing", "Complete"]:
|
|
231
|
+
await writable.write(message) # [!code highlight]
|
|
232
|
+
|
|
233
|
+
await writable.close()
|
|
234
|
+
|
|
235
|
+
@wf.workflow
|
|
236
|
+
async def streaming_workflow():
|
|
237
|
+
await write_progress()
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Read the values from the returned `Run` as they arrive:
|
|
241
|
+
|
|
242
|
+
```python filename="app/api/stream.py"
|
|
243
|
+
from app.workflows.streaming import streaming_workflow
|
|
244
|
+
from vercel import workflow
|
|
245
|
+
|
|
246
|
+
@app.post("/api/stream")
|
|
247
|
+
async def stream_progress():
|
|
248
|
+
run = await workflow.start(streaming_workflow)
|
|
249
|
+
|
|
250
|
+
async for message in run.readable(): # [!code highlight]
|
|
251
|
+
print(message)
|
|
252
|
+
```
|
|
158
253
|
|
|
159
|
-
|
|
254
|
+
Streams are not closed automatically. Close the writable in the last step that writes to it so readers know when the stream is complete.
|
|
160
255
|
|
|
161
|
-
## Next
|
|
256
|
+
## Next steps
|
|
162
257
|
|
|
163
258
|
- Learn more about the [Foundations](/docs/foundations).
|
|
164
259
|
- 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.
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: React Router v7
|
|
3
|
+
description: Add durable workflows to a React Router v7 framework-mode app using Nitro v3.
|
|
4
|
+
type: guide
|
|
5
|
+
summary: Enable the Vite Environment API and configure React Router v7, Nitro v3, and Workflow SDK.
|
|
6
|
+
prerequisites:
|
|
7
|
+
- /docs/getting-started/react-router
|
|
8
|
+
related:
|
|
9
|
+
- /docs/getting-started/nitro
|
|
10
|
+
- /docs/foundations/workflows-and-steps
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
This guide starts with an existing React Router v7 framework-mode app. It is verified with v7.18.1; if your config does not recognize `v8_viteEnvironmentApi`, update to the latest v7 release.
|
|
14
|
+
|
|
15
|
+
<Steps>
|
|
16
|
+
|
|
17
|
+
<Step>
|
|
18
|
+
|
|
19
|
+
## Install Nitro and Workflow SDK
|
|
20
|
+
|
|
21
|
+
<Tabs items={["npm", "pnpm", "bun", "yarn"]} defaultValue="pnpm">
|
|
22
|
+
|
|
23
|
+
<Tab value="npm">
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install nitro workflow
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
</Tab>
|
|
30
|
+
|
|
31
|
+
<Tab value="pnpm">
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pnpm add nitro workflow
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
</Tab>
|
|
38
|
+
|
|
39
|
+
<Tab value="bun">
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
bun add nitro workflow
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
</Tab>
|
|
46
|
+
|
|
47
|
+
<Tab value="yarn">
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
yarn add nitro workflow
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
</Tab>
|
|
54
|
+
|
|
55
|
+
</Tabs>
|
|
56
|
+
|
|
57
|
+
This integration requires Nitro v3.
|
|
58
|
+
|
|
59
|
+
</Step>
|
|
60
|
+
|
|
61
|
+
<Step>
|
|
62
|
+
|
|
63
|
+
## Enable the Vite Environment API
|
|
64
|
+
|
|
65
|
+
React Router v7 keeps the Vite Environment API behind a future flag. Enable the required flag and set an explicit build directory:
|
|
66
|
+
|
|
67
|
+
```typescript title="react-router.config.ts" lineNumbers
|
|
68
|
+
import type { Config } from "@react-router/dev/config";
|
|
69
|
+
|
|
70
|
+
export default {
|
|
71
|
+
ssr: true,
|
|
72
|
+
buildDirectory: "build", // [!code highlight]
|
|
73
|
+
future: {
|
|
74
|
+
v8_viteEnvironmentApi: true, // [!code highlight]
|
|
75
|
+
},
|
|
76
|
+
} satisfies Config;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
</Step>
|
|
80
|
+
|
|
81
|
+
<Step>
|
|
82
|
+
|
|
83
|
+
## Create the React Router server handler
|
|
84
|
+
|
|
85
|
+
Create `server/ssr.ts`:
|
|
86
|
+
|
|
87
|
+
```typescript title="server/ssr.ts" lineNumbers
|
|
88
|
+
import { createRequestHandler } from "react-router";
|
|
89
|
+
|
|
90
|
+
export default {
|
|
91
|
+
fetch: createRequestHandler(
|
|
92
|
+
() => import("virtual:react-router/server-build"),
|
|
93
|
+
import.meta.env.MODE,
|
|
94
|
+
),
|
|
95
|
+
};
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
This adapts React Router's generated server build to the Fetch API handler Nitro expects.
|
|
99
|
+
|
|
100
|
+
</Step>
|
|
101
|
+
|
|
102
|
+
<Step>
|
|
103
|
+
|
|
104
|
+
## Configure Vite
|
|
105
|
+
|
|
106
|
+
Update `vite.config.ts`:
|
|
107
|
+
|
|
108
|
+
```typescript title="vite.config.ts" lineNumbers
|
|
109
|
+
import { reactRouter } from "@react-router/dev/vite";
|
|
110
|
+
import { nitro } from "nitro/vite";
|
|
111
|
+
import { defineConfig } from "vite";
|
|
112
|
+
import { workflow } from "workflow/vite";
|
|
113
|
+
import reactRouterConfig from "./react-router.config";
|
|
114
|
+
|
|
115
|
+
export default defineConfig({
|
|
116
|
+
plugins: [
|
|
117
|
+
reactRouter(),
|
|
118
|
+
nitro({
|
|
119
|
+
serverDir: "./server",
|
|
120
|
+
output: {
|
|
121
|
+
dir: reactRouterConfig.buildDirectory,
|
|
122
|
+
serverDir: `${reactRouterConfig.buildDirectory}/server`,
|
|
123
|
+
publicDir: `${reactRouterConfig.buildDirectory}/client`,
|
|
124
|
+
},
|
|
125
|
+
}),
|
|
126
|
+
workflow({ dirs: ["workflows"] }),
|
|
127
|
+
],
|
|
128
|
+
environments: {
|
|
129
|
+
ssr: {
|
|
130
|
+
build: {
|
|
131
|
+
rollupOptions: {
|
|
132
|
+
input: "./server/ssr.ts",
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Keep `dirs: ["workflows"]` so subsequent builds do not scan generated files under `build`. Place `reactRouter()` before `nitro()` in the plugin array.
|
|
141
|
+
|
|
142
|
+
</Step>
|
|
143
|
+
|
|
144
|
+
<Step>
|
|
145
|
+
|
|
146
|
+
## Create a workflow
|
|
147
|
+
|
|
148
|
+
Create `workflows/greeting.ts`:
|
|
149
|
+
|
|
150
|
+
```typescript title="workflows/greeting.ts" lineNumbers
|
|
151
|
+
export async function greetingWorkflow(name: string) {
|
|
152
|
+
"use workflow";
|
|
153
|
+
|
|
154
|
+
return greet(name);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function greet(name: string) {
|
|
158
|
+
"use step";
|
|
159
|
+
|
|
160
|
+
return `Hello, ${name}!`;
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
</Step>
|
|
165
|
+
|
|
166
|
+
<Step>
|
|
167
|
+
|
|
168
|
+
## Start the workflow from a Nitro route
|
|
169
|
+
|
|
170
|
+
Create `server/routes/api/greeting.post.ts`:
|
|
171
|
+
|
|
172
|
+
```typescript title="server/routes/api/greeting.post.ts" lineNumbers
|
|
173
|
+
import { defineHandler } from "nitro";
|
|
174
|
+
import { start } from "workflow/api";
|
|
175
|
+
import { greetingWorkflow } from "../../../workflows/greeting";
|
|
176
|
+
|
|
177
|
+
export default defineHandler(async (event) => {
|
|
178
|
+
const { name } = (await event.req.json()) as { name: string };
|
|
179
|
+
const run = await start(greetingWorkflow, [name]);
|
|
180
|
+
|
|
181
|
+
return { runId: run.runId };
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
React Router continues to handle your application routes. Nitro handles this server route at `POST /api/greeting`, as well as Workflow SDK's internal routes.
|
|
186
|
+
|
|
187
|
+
</Step>
|
|
188
|
+
|
|
189
|
+
<Step>
|
|
190
|
+
|
|
191
|
+
## Run the app
|
|
192
|
+
|
|
193
|
+
Start the development server:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
pnpm vite dev
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Then start a workflow:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
curl -X POST \
|
|
203
|
+
-H "content-type: application/json" \
|
|
204
|
+
-d '{"name":"Workflow"}' \
|
|
205
|
+
http://localhost:3000/api/greeting
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Build and start the production server:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
pnpm vite build
|
|
212
|
+
node ./build/server/index.mjs
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
You can inspect local runs with `pnpm workflow web`.
|
|
216
|
+
|
|
217
|
+
</Step>
|
|
218
|
+
|
|
219
|
+
</Steps>
|
|
220
|
+
|
|
221
|
+
## Troubleshooting
|
|
222
|
+
|
|
223
|
+
### Vite reports an invalid SSR input or `path.replace is not a function`
|
|
224
|
+
|
|
225
|
+
Set `future.v8_viteEnvironmentApi` to `true` in `react-router.config.ts`.
|
|
226
|
+
|
|
227
|
+
### React Router pages return 404
|
|
228
|
+
|
|
229
|
+
Check that the `ssr` environment input points to `./server/ssr.ts`.
|
|
230
|
+
|
|
231
|
+
### A second build tries to compile files under `build/server`
|
|
232
|
+
|
|
233
|
+
Use `workflow({ dirs: ["workflows"] })`, remove the existing `build` directory once, and rebuild.
|
|
234
|
+
|
|
235
|
+
### `vite build` finishes output but does not exit
|
|
236
|
+
|
|
237
|
+
Use `workflow@5.0.0` or later with Nitro v3.
|