workflow 5.0.0-beta.34 → 5.0.0-beta.36
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/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/docs/api-reference/workflow/create-hook.mdx +43 -2
- package/docs/api-reference/workflow/define-hook.mdx +26 -24
- package/docs/api-reference/workflow/fatal-error.mdx +29 -7
- package/docs/api-reference/workflow/fetch.mdx +3 -4
- package/docs/api-reference/workflow/sleep.mdx +1 -1
- package/docs/api-reference/workflow-api/get-hook-by-token.mdx +2 -0
- package/docs/api-reference/workflow-api/resume-hook.mdx +2 -0
- package/docs/api-reference/workflow-api/resume-webhook.mdx +6 -4
- package/docs/api-reference/workflow-api/start.mdx +1 -1
- package/docs/api-reference/workflow-errors/index.mdx +3 -0
- package/docs/api-reference/workflow-errors/meta.json +1 -0
- package/docs/api-reference/workflow-errors/precondition-failed-error.mdx +62 -0
- package/docs/api-reference/workflow-nest/nest-local-builder.mdx +1 -1
- 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 +103 -0
- package/docs/configuration/runtime-tuning.mdx +26 -0
- package/docs/configuration/worlds.mdx +6 -7
- package/docs/cookbook/common-patterns/idempotency.mdx +1 -1
- package/docs/{deploying/index.mdx → deploying.mdx} +6 -8
- package/docs/foundations/hooks.mdx +1 -1
- package/docs/foundations/idempotency.mdx +16 -9
- package/docs/getting-started/astro.mdx +1 -1
- package/docs/getting-started/express.mdx +1 -1
- package/docs/getting-started/fastify.mdx +1 -1
- package/docs/getting-started/hono.mdx +1 -1
- package/docs/getting-started/index.mdx +10 -3
- package/docs/getting-started/meta.json +3 -1
- package/docs/getting-started/nestjs.mdx +63 -2
- package/docs/getting-started/next.mdx +2 -2
- package/docs/getting-started/nitro.mdx +1 -1
- package/docs/getting-started/nuxt.mdx +1 -1
- package/docs/getting-started/python.mdx +19 -17
- 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 +1 -1
- package/docs/getting-started/vite.mdx +1 -1
- package/docs/how-it-works/encryption.mdx +3 -3
- package/docs/how-it-works/event-sourcing.mdx +6 -6
- package/docs/how-it-works/framework-integrations.mdx +3 -3
- package/docs/meta.json +1 -1
- package/docs/observability/attributes.mdx +9 -0
- package/package.json +13 -11
- package/docs/deploying/building-a-world.mdx +0 -251
- package/docs/deploying/meta.json +0 -4
- package/docs/deploying/world/local-world.mdx +0 -101
- package/docs/deploying/world/meta.json +0 -4
- package/docs/deploying/world/postgres-world.mdx +0 -288
- package/docs/deploying/world/vercel-world.mdx +0 -275
- 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 -362
- package/docs/migration-guides/migrating-from-inngest.mdx +0 -308
- package/docs/migration-guides/migrating-from-temporal.mdx +0 -317
- package/docs/migration-guides/migrating-from-trigger-dev.mdx +0 -332
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Deploying
|
|
3
|
-
icon: Rocket
|
|
4
3
|
description: Deploy workflows locally, on Vercel, or anywhere using pluggable World adapters.
|
|
5
4
|
type: overview
|
|
6
5
|
summary: Learn how to deploy workflows to different environments using World adapters.
|
|
7
|
-
manualCards: true
|
|
8
6
|
related:
|
|
9
|
-
- /
|
|
10
|
-
- /
|
|
11
|
-
- /
|
|
12
|
-
- /
|
|
7
|
+
- /worlds/local
|
|
8
|
+
- /worlds/postgres
|
|
9
|
+
- /worlds/vercel
|
|
10
|
+
- /worlds/building-a-world
|
|
13
11
|
---
|
|
14
12
|
|
|
15
13
|
Workflows are designed to be highly portable. The same workflow code can run locally during development, on Vercel with zero configuration, or on any infrastructure using **Worlds** — pluggable adapters that handle storage, queuing, and communication.
|
|
@@ -54,7 +52,7 @@ vercel deploy
|
|
|
54
52
|
<FluidComputeCallout />
|
|
55
53
|
|
|
56
54
|
<Callout>
|
|
57
|
-
Learn more about the [Vercel World](/
|
|
55
|
+
Learn more about the [Vercel World](/worlds/vercel) and its capabilities, including [multi-region](/worlds/vercel#multi-region).
|
|
58
56
|
</Callout>
|
|
59
57
|
|
|
60
58
|
## Self-Hosting & Other Providers
|
|
@@ -65,7 +63,7 @@ For self-hosting or deploying to other cloud providers, you can use community-ma
|
|
|
65
63
|
<Card title="Explore Worlds" href="/worlds">
|
|
66
64
|
Browse official and community World implementations with compatibility status and performance benchmarks.
|
|
67
65
|
</Card>
|
|
68
|
-
<Card title="Build Your Own" href="/
|
|
66
|
+
<Card title="Build Your Own" href="/worlds/building-a-world">
|
|
69
67
|
Learn how to implement a custom World for your infrastructure.
|
|
70
68
|
</Card>
|
|
71
69
|
</Cards>
|
|
@@ -112,7 +112,7 @@ export async function orderWorkflow(orderId: string) {
|
|
|
112
112
|
}
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
Calling `createHook()` on its own does not register the hook — registration is only committed when the workflow suspends. Awaiting `hook.getConflict()` suspends the workflow to commit the hook registration, then resolves with `null` once the hook is registered and ready to receive payloads, or with a `Run` handle for the run that owns the token
|
|
115
|
+
Calling `createHook()` on its own does not register the hook — registration is only committed when the workflow suspends. Awaiting `hook.getConflict()` suspends the workflow to commit the hook registration, then resolves with `null` once the hook is registered and ready to receive payloads, or with a `Run` handle for the run that owns the token (see [`HookConflictError`](/docs/errors/hook-conflict)). For `hook_conflict` events persisted by older worlds that did not record the owning run's ID, `getConflict()` rejects with `HookConflictError` instead of resolving with an incomplete handle. The conflicting run's accessors are durable steps, so the workflow can inspect `await conflict.status`, wait on `await conflict.returnValue`, or cancel the owner with `await conflict.cancel()` — see [Run idempotency](/docs/foundations/idempotency#run-idempotency) for these strategies.
|
|
116
116
|
|
|
117
117
|
### Custom Tokens for Deterministic Hooks
|
|
118
118
|
|
|
@@ -96,7 +96,7 @@ export async function processOrder(orderId: string): Promise<OrderResult> {
|
|
|
96
96
|
}
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
The runtime creates the hook atomically. At most one
|
|
99
|
+
The runtime creates the hook atomically. At most one hook can own `order:${orderId}`, so duplicate workflow runs converge on one owner. A duplicate run observes `getConflict()` resolving with the owner's `Run` and returns before it reaches `chargeOrder()`. The conflicting run's accessors (`status`, `returnValue`, `cancel()`, …) are durable steps, so the duplicate run can do more than report the owner — see [conflict-handling strategies](#conflict-handling-strategies) below.
|
|
100
100
|
|
|
101
101
|
Outside the workflow, try to resume the hook first. If the hook is not registered yet, start the workflow and retry the resume until the new run creates the hook:
|
|
102
102
|
|
|
@@ -149,7 +149,7 @@ export async function POST(request: Request) {
|
|
|
149
149
|
This avoids creating a new run only after the first run has registered its hook. Because `start()` returns before the run body executes and calls `createHook()`, two concurrent requests can both observe "no hook yet" and each call `start()`. The race is resolved inside the workflow body, where the losing run observes `getConflict()` resolving with the active owner and returns without doing duplicate-sensitive work — and the route detects it by comparing the resumed hook's `runId` against the run it just started, without waiting for either run to finish. A native API for atomically starting a run and registering a hook is in the works. Until then, model recovery inside the workflow by checking `hook.getConflict()`.
|
|
150
150
|
</Callout>
|
|
151
151
|
|
|
152
|
-
This
|
|
152
|
+
This coordinates active runs by default: the token becomes available when its workflow ends. Set `experimental_minRetention` to keep it unavailable to late duplicates. After the workflow ends, the Hook can still be found with `getHookByToken()` until retention ends, but it cannot be resumed. See [`createHook()` minimum retention](/docs/api-reference/workflow/create-hook#keep-a-token-unavailable-after-the-run-ends) for examples and supported values.
|
|
153
153
|
|
|
154
154
|
### Conflict-handling strategies
|
|
155
155
|
|
|
@@ -182,7 +182,7 @@ export async function processOrder(orderId: string) {
|
|
|
182
182
|
}
|
|
183
183
|
```
|
|
184
184
|
|
|
185
|
-
**Inspect the owner before deciding.**
|
|
185
|
+
**Inspect the owner before deciding.** Reuse a completed owner's result, but reject other duplicates:
|
|
186
186
|
|
|
187
187
|
```typescript lineNumbers
|
|
188
188
|
import { createHook } from "workflow";
|
|
@@ -200,17 +200,17 @@ export async function processOrder(orderId: string) {
|
|
|
200
200
|
const conflict = await request.getConflict();
|
|
201
201
|
if (conflict) {
|
|
202
202
|
const status = await conflict.status; // [!code highlight]
|
|
203
|
-
if (status === "
|
|
204
|
-
return
|
|
203
|
+
if (status === "completed") {
|
|
204
|
+
return await conflict.returnValue;
|
|
205
205
|
}
|
|
206
|
-
|
|
206
|
+
return { status: "duplicate" as const, runId: conflict.runId };
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
return await processOwnedOrder(orderId);
|
|
210
210
|
}
|
|
211
211
|
```
|
|
212
212
|
|
|
213
|
-
**Signal the owner instead of doing the work.**
|
|
213
|
+
**Signal the owner instead of doing the work.** A conflict can refer to a finished run when `experimental_minRetention` is set, so check its status before sending data to its Hook:
|
|
214
214
|
|
|
215
215
|
```typescript lineNumbers
|
|
216
216
|
import { createHook } from "workflow";
|
|
@@ -230,16 +230,19 @@ export async function processOrder(orderId: string, confirmed: boolean) {
|
|
|
230
230
|
using request = createHook<OrderRequest>({ token });
|
|
231
231
|
|
|
232
232
|
const conflict = await request.getConflict();
|
|
233
|
-
if (conflict) {
|
|
233
|
+
if (conflict && ["pending", "running"].includes(await conflict.status)) {
|
|
234
234
|
await forwardToOwner(token, { confirmed }); // [!code highlight]
|
|
235
235
|
return { status: "forwarded" as const, runId: conflict.runId };
|
|
236
236
|
}
|
|
237
|
+
if (conflict) {
|
|
238
|
+
return { status: "duplicate" as const, runId: conflict.runId };
|
|
239
|
+
}
|
|
237
240
|
|
|
238
241
|
// ... own the token and do the work
|
|
239
242
|
}
|
|
240
243
|
```
|
|
241
244
|
|
|
242
|
-
**Supersede the owner.**
|
|
245
|
+
**Supersede the owner.** Without minimum retention, cancel the active run, then claim the released token. The retry loop covers the window where cancellation cleanup has not propagated yet:
|
|
243
246
|
|
|
244
247
|
```typescript lineNumbers
|
|
245
248
|
import { createHook } from "workflow";
|
|
@@ -272,6 +275,10 @@ export async function processOrderNewestWins(orderId: string) {
|
|
|
272
275
|
}
|
|
273
276
|
```
|
|
274
277
|
|
|
278
|
+
<Callout type="warn">
|
|
279
|
+
This pattern does not work with `experimental_minRetention`: cancelling the old run does not make its token available early.
|
|
280
|
+
</Callout>
|
|
281
|
+
|
|
275
282
|
If duplicate requests should only reuse the active run without sending data, use [`getHookByToken()`](/docs/api-reference/workflow-api/get-hook-by-token) as an advisory pre-check before calling `start()`. The workflow should still check `hook.getConflict()`, because the lookup and `start()` are not atomic.
|
|
276
283
|
|
|
277
284
|
Because this pattern uses hooks for idempotency, duplicate requests can also inject additional data and steer the existing run. The route example above uses `resumeHook()` for that: if the hook already exists, the duplicate request resumes the active workflow; if the hook is not registered yet, the route starts the workflow and retries `resumeHook()` so the payload is not dropped.
|
|
@@ -252,7 +252,7 @@ Additionally, check the [Deploying](/docs/deploying) section to learn how your w
|
|
|
252
252
|
If you see this error:
|
|
253
253
|
|
|
254
254
|
```
|
|
255
|
-
'start' received an invalid workflow function. Ensure the Workflow
|
|
255
|
+
'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive.
|
|
256
256
|
```
|
|
257
257
|
|
|
258
258
|
Check both of these first:
|
|
@@ -273,7 +273,7 @@ Check the [Deploying](/docs/deploying) section to learn how your workflows can b
|
|
|
273
273
|
If you see this error:
|
|
274
274
|
|
|
275
275
|
```
|
|
276
|
-
'start' received an invalid workflow function. Ensure the Workflow
|
|
276
|
+
'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive.
|
|
277
277
|
```
|
|
278
278
|
|
|
279
279
|
Check both of these first:
|
|
@@ -260,7 +260,7 @@ Check the [Deploying](/docs/deploying) section to learn how your workflows can b
|
|
|
260
260
|
If you see this error:
|
|
261
261
|
|
|
262
262
|
```
|
|
263
|
-
'start' received an invalid workflow function. Ensure the Workflow
|
|
263
|
+
'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive.
|
|
264
264
|
```
|
|
265
265
|
|
|
266
266
|
Check both of these first:
|
|
@@ -255,7 +255,7 @@ Check the [Deploying](/docs/deploying) section to learn how your workflows can b
|
|
|
255
255
|
If you see this error:
|
|
256
256
|
|
|
257
257
|
```
|
|
258
|
-
'start' received an invalid workflow function. Ensure the Workflow
|
|
258
|
+
'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive.
|
|
259
259
|
```
|
|
260
260
|
|
|
261
261
|
Check both of these first:
|
|
@@ -9,6 +9,7 @@ related:
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
import { Next, Nitro, SvelteKit, Nuxt, Hono, Bun, AstroDark, AstroLight, TanStack, Vite, Express, Nest, Fastify, Python } from "@/app/[lang]/(home)/components/frameworks";
|
|
12
|
+
import { SiReactrouter } from "@icons-pack/react-simple-icons";
|
|
12
13
|
|
|
13
14
|
<Cards>
|
|
14
15
|
<Card href="/docs/getting-started/next">
|
|
@@ -20,6 +21,12 @@ import { Next, Nitro, SvelteKit, Nuxt, Hono, Bun, AstroDark, AstroLight, TanStac
|
|
|
20
21
|
<span className="font-medium">Vite</span>
|
|
21
22
|
</div>
|
|
22
23
|
</Card>
|
|
24
|
+
<Card href="/docs/getting-started/react-router">
|
|
25
|
+
<div className="flex flex-col items-center justify-center gap-2">
|
|
26
|
+
<SiReactrouter className="size-16" />
|
|
27
|
+
<span className="font-medium">React Router</span>
|
|
28
|
+
</div>
|
|
29
|
+
</Card>
|
|
23
30
|
<Card href="/docs/getting-started/astro">
|
|
24
31
|
<div className="flex flex-col items-center justify-center gap-2">
|
|
25
32
|
<AstroLight className="size-16 dark:hidden" />
|
|
@@ -76,11 +83,11 @@ import { Next, Nitro, SvelteKit, Nuxt, Hono, Bun, AstroDark, AstroLight, TanStac
|
|
|
76
83
|
<Badge variant="secondary">Beta</Badge>
|
|
77
84
|
</div>
|
|
78
85
|
</Card>
|
|
79
|
-
<Card
|
|
86
|
+
<Card href="/docs/getting-started/nestjs">
|
|
80
87
|
<div className="flex flex-col items-center justify-center gap-2">
|
|
81
|
-
<Nest className="size-16 dark:invert
|
|
88
|
+
<Nest className="size-16 dark:invert" />
|
|
82
89
|
<span className="font-medium">NestJS</span>
|
|
83
|
-
<Badge variant="secondary">
|
|
90
|
+
<Badge variant="secondary">Experimental</Badge>
|
|
84
91
|
</div>
|
|
85
92
|
</Card>
|
|
86
93
|
</Cards>
|
|
@@ -16,7 +16,8 @@ related:
|
|
|
16
16
|
This guide will walk through setting up your first workflow in a NestJS app. Along the way, you'll learn more about the concepts that are fundamental to using the Workflow SDK in your own projects.
|
|
17
17
|
|
|
18
18
|
<Callout>
|
|
19
|
-
NestJS integration is experimental
|
|
19
|
+
NestJS integration is experimental. Deployment to Vercel is supported via the
|
|
20
|
+
`workflow-nest build --vercel` command — see [Deploy to Vercel](#deploy-to-vercel) below.
|
|
20
21
|
</Callout>
|
|
21
22
|
|
|
22
23
|
---
|
|
@@ -361,6 +362,66 @@ npx workflow inspect runs
|
|
|
361
362
|
|
|
362
363
|
</Step>
|
|
363
364
|
|
|
365
|
+
<Step>
|
|
366
|
+
|
|
367
|
+
## Deploy to Vercel
|
|
368
|
+
|
|
369
|
+
Because NestJS is not a Vercel-native framework, the Workflow SDK produces a
|
|
370
|
+
[Build Output API](https://vercel.com/docs/build-output-api) directory for you. This emits the
|
|
371
|
+
workflow queue-consumer function (registered with `experimentalTriggers` so Vercel's queue can
|
|
372
|
+
discover it) alongside your NestJS app as a catch-all function. Without it, workflow runs stay
|
|
373
|
+
`pending` because nothing consumes the queue.
|
|
374
|
+
|
|
375
|
+
Add a Vercel-specific entry module that default-exports your Nest app's underlying Node handler:
|
|
376
|
+
|
|
377
|
+
```typescript title="_vercel/entry.ts" lineNumbers
|
|
378
|
+
import 'reflect-metadata';
|
|
379
|
+
import { NestFactory } from '@nestjs/core';
|
|
380
|
+
import type { NestExpressApplication } from '@nestjs/platform-express';
|
|
381
|
+
import express from 'express';
|
|
382
|
+
import { AppModule } from '../dist/app.module.js';
|
|
383
|
+
|
|
384
|
+
let ready: Promise<express.Express> | undefined;
|
|
385
|
+
|
|
386
|
+
async function createHandler() {
|
|
387
|
+
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
|
|
388
|
+
bodyParser: false,
|
|
389
|
+
});
|
|
390
|
+
app.use(express.json());
|
|
391
|
+
await app.init();
|
|
392
|
+
return app.getHttpAdapter().getInstance();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export default async function handler(req: express.Request, res: express.Response) {
|
|
396
|
+
ready ??= createHandler();
|
|
397
|
+
return (await ready)(req, res);
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Skip the in-process build on Vercel (the bundles are pre-built) by passing `skipBuild` when
|
|
402
|
+
`VERCEL` is set:
|
|
403
|
+
|
|
404
|
+
{/* @skip-typecheck - config snippet, WorkflowModule imported above */}
|
|
405
|
+
```typescript title="src/app.module.ts"
|
|
406
|
+
WorkflowModule.forRoot({ skipBuild: Boolean(process.env.VERCEL) });
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Then set your build command so the Build Output is produced after `nest build`:
|
|
410
|
+
|
|
411
|
+
```json title="package.json" lineNumbers
|
|
412
|
+
{
|
|
413
|
+
"scripts": {
|
|
414
|
+
"vercel-build": "workflow-nest init --force && nest build && workflow-nest build --vercel --dirs src/workflows --entry _vercel/entry.ts"
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Deploy as usual. `workflow-nest build --vercel` runs automatically on Vercel (it also detects the
|
|
420
|
+
`VERCEL` environment variable), writing `.vercel/output` with the consumer function so your runs
|
|
421
|
+
execute instead of staying `pending`.
|
|
422
|
+
|
|
423
|
+
</Step>
|
|
424
|
+
|
|
364
425
|
</Steps>
|
|
365
426
|
|
|
366
427
|
---
|
|
@@ -409,7 +470,7 @@ WorkflowModule.forRoot({
|
|
|
409
470
|
If you see this error:
|
|
410
471
|
|
|
411
472
|
```
|
|
412
|
-
'start' received an invalid workflow function. Ensure the Workflow
|
|
473
|
+
'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive.
|
|
413
474
|
```
|
|
414
475
|
|
|
415
476
|
Check both of these first:
|
|
@@ -7,7 +7,7 @@ prerequisites:
|
|
|
7
7
|
- /docs/getting-started
|
|
8
8
|
related:
|
|
9
9
|
- /docs/api-reference/workflow-next
|
|
10
|
-
- /
|
|
10
|
+
- /worlds/vercel
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
<CopyPrompt
|
|
@@ -316,7 +316,7 @@ Without this configuration, you may experience intermittent issues where workflo
|
|
|
316
316
|
If you see this error:
|
|
317
317
|
|
|
318
318
|
```
|
|
319
|
-
'start' received an invalid workflow function. Ensure the Workflow
|
|
319
|
+
'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive.
|
|
320
320
|
```
|
|
321
321
|
|
|
322
322
|
Check both of these first:
|
|
@@ -261,7 +261,7 @@ Check the [Deploying](/docs/deploying) section to learn how your workflows can b
|
|
|
261
261
|
If you see this error:
|
|
262
262
|
|
|
263
263
|
```
|
|
264
|
-
'start' received an invalid workflow function. Ensure the Workflow
|
|
264
|
+
'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive.
|
|
265
265
|
```
|
|
266
266
|
|
|
267
267
|
Check both of these first:
|
|
@@ -240,7 +240,7 @@ Check the [Deploying](/docs/deploying) section to learn how your workflows can b
|
|
|
240
240
|
If you see this error:
|
|
241
241
|
|
|
242
242
|
```
|
|
243
|
-
'start' received an invalid workflow function. Ensure the Workflow
|
|
243
|
+
'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive.
|
|
244
244
|
```
|
|
245
245
|
|
|
246
246
|
Check both of these first:
|
|
@@ -11,7 +11,7 @@ related:
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
<CopyPrompt
|
|
14
|
-
text="In this Python project,
|
|
14
|
+
text="In this Python project, add `requires-python = ">=3.12"` and `dependencies = ["vercel"]` under `[project]` in `pyproject.toml`. Add `[[tool.vercel.workflows]]` with `entrypoint = "app.workflows:wf"`. Create `app/workflow.py` with `from vercel import workflow` and `wf = workflow.Workflows()`. Create `app/workflows/ai_content_workflow.py` importing `wf`, define `@wf.workflow async def ai_content_workflow(*, topic: str)`, and call step functions such as `generate_draft` and `summarize_draft`. Export `wf` from `app/workflows/__init__.py` and import the workflow module so its definitions are registered. Mark step functions with `@wf.step`, use `await workflow.sleep("7 days")` for durable delays where needed, and use a `workflow.BaseHook` Pydantic model plus `.wait(token=...)` and `.resume(token)` for external approval events. Verify the workflow entrypoint uses the `module:object` format and points to the exported `Workflows` registry."
|
|
15
15
|
/>
|
|
16
16
|
|
|
17
17
|
<Callout type="warn">
|
|
@@ -22,26 +22,19 @@ You can build durable workflows in Python using the [`vercel` Python SDK](https:
|
|
|
22
22
|
|
|
23
23
|
## Getting Started
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Add the `vercel` package and workflow entrypoint to `pyproject.toml`:
|
|
26
26
|
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
Configure `experimentalServices` in your `vercel.json`:
|
|
27
|
+
```toml filename="pyproject.toml"
|
|
28
|
+
[project]
|
|
29
|
+
requires-python = ">=3.12"
|
|
30
|
+
dependencies = ["vercel"]
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"experimentalServices": {
|
|
36
|
-
"ai_content_workflow": {
|
|
37
|
-
"type": "worker",
|
|
38
|
-
"entrypoint": "app/workflows/ai_content_workflow.py",
|
|
39
|
-
"topics": ["__wkf_*"]
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
32
|
+
[[tool.vercel.workflows]]
|
|
33
|
+
entrypoint = "app.workflows:wf"
|
|
43
34
|
```
|
|
44
35
|
|
|
36
|
+
The workflow `entrypoint` uses the `module:object` format and points to the exported `Workflows` registry.
|
|
37
|
+
|
|
45
38
|
## Workflows
|
|
46
39
|
|
|
47
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:
|
|
@@ -66,6 +59,15 @@ async def ai_content_workflow(*, topic: str):
|
|
|
66
59
|
}
|
|
67
60
|
```
|
|
68
61
|
|
|
62
|
+
Export the registry from the workflow package and import the module containing your workflow so its definitions are registered:
|
|
63
|
+
|
|
64
|
+
```python filename="app/workflows/__init__.py"
|
|
65
|
+
from app.workflow import wf
|
|
66
|
+
from app.workflows import ai_content_workflow
|
|
67
|
+
|
|
68
|
+
__all__ = ["ai_content_workflow", "wf"]
|
|
69
|
+
```
|
|
70
|
+
|
|
69
71
|
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.
|
|
70
72
|
|
|
71
73
|
## Steps
|
|
@@ -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.
|