workflow 5.0.0-beta.33 → 5.0.0-beta.35
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/docs/ai/index.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-runtime/world/analytics.mdx +4 -5
- package/docs/configuration/runtime-tuning.mdx +7 -0
- package/docs/configuration/worlds.mdx +3 -4
- package/docs/deploying/world/local-world.mdx +5 -1
- package/docs/deploying/world/vercel-world.mdx +7 -12
- package/docs/getting-started/index.mdx +7 -0
- package/docs/getting-started/meta.json +1 -0
- package/docs/getting-started/python.mdx +21 -19
- 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/how-it-works/framework-integrations.mdx +1 -1
- package/docs/observability/attributes.mdx +9 -0
- package/docs/observability/tracing.mdx +5 -2
- package/package.json +11 -11
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { EntityConflictError, HookConflictError, HookNotFoundError, RunExpiredError, RunNotSupportedError, StepNotRegisteredError, ThrottleError, TooEarlyError, WorkflowError, WorkflowNotRegisteredError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRunNotFoundError, WorkflowRuntimeError, WorkflowWorldError, } from '@workflow/errors';
|
|
1
|
+
export { EntityConflictError, HookConflictError, HookNotFoundError, PreconditionFailedError, RunExpiredError, RunNotSupportedError, StepNotRegisteredError, ThrottleError, TooEarlyError, WorkflowError, WorkflowNotRegisteredError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRunNotFoundError, WorkflowRuntimeError, WorkflowWorldError, } from '@workflow/errors';
|
|
2
2
|
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/internal/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,aAAa,EACb,aAAa,EACb,aAAa,EACb,0BAA0B,EAC1B,yBAAyB,EACzB,sBAAsB,EACtB,4BAA4B,EAC5B,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/internal/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,uBAAuB,EACvB,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,aAAa,EACb,aAAa,EACb,aAAa,EACb,0BAA0B,EAC1B,yBAAyB,EACzB,sBAAsB,EACtB,4BAA4B,EAC5B,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC"}
|
package/dist/internal/errors.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { EntityConflictError, HookConflictError, HookNotFoundError, RunExpiredError, RunNotSupportedError, StepNotRegisteredError, ThrottleError, TooEarlyError, WorkflowError, WorkflowNotRegisteredError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRunNotFoundError, WorkflowRuntimeError, WorkflowWorldError, } from '@workflow/errors';
|
|
2
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
1
|
+
export { EntityConflictError, HookConflictError, HookNotFoundError, PreconditionFailedError, RunExpiredError, RunNotSupportedError, StepNotRegisteredError, ThrottleError, TooEarlyError, WorkflowError, WorkflowNotRegisteredError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRunNotFoundError, WorkflowRuntimeError, WorkflowWorldError, } from '@workflow/errors';
|
|
2
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3JzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2ludGVybmFsL2Vycm9ycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsbUJBQW1CLEVBQ25CLGlCQUFpQixFQUNqQixpQkFBaUIsRUFDakIsdUJBQXVCLEVBQ3ZCLGVBQWUsRUFDZixvQkFBb0IsRUFDcEIsc0JBQXNCLEVBQ3RCLGFBQWEsRUFDYixhQUFhLEVBQ2IsYUFBYSxFQUNiLDBCQUEwQixFQUMxQix5QkFBeUIsRUFDekIsc0JBQXNCLEVBQ3RCLDRCQUE0QixFQUM1Qix3QkFBd0IsRUFDeEIsb0JBQW9CLEVBQ3BCLGtCQUFrQixHQUNuQixNQUFNLGtCQUFrQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IHtcbiAgRW50aXR5Q29uZmxpY3RFcnJvcixcbiAgSG9va0NvbmZsaWN0RXJyb3IsXG4gIEhvb2tOb3RGb3VuZEVycm9yLFxuICBQcmVjb25kaXRpb25GYWlsZWRFcnJvcixcbiAgUnVuRXhwaXJlZEVycm9yLFxuICBSdW5Ob3RTdXBwb3J0ZWRFcnJvcixcbiAgU3RlcE5vdFJlZ2lzdGVyZWRFcnJvcixcbiAgVGhyb3R0bGVFcnJvcixcbiAgVG9vRWFybHlFcnJvcixcbiAgV29ya2Zsb3dFcnJvcixcbiAgV29ya2Zsb3dOb3RSZWdpc3RlcmVkRXJyb3IsXG4gIFdvcmtmbG93UnVuQ2FuY2VsbGVkRXJyb3IsXG4gIFdvcmtmbG93UnVuRmFpbGVkRXJyb3IsXG4gIFdvcmtmbG93UnVuTm90Q29tcGxldGVkRXJyb3IsXG4gIFdvcmtmbG93UnVuTm90Rm91bmRFcnJvcixcbiAgV29ya2Zsb3dSdW50aW1lRXJyb3IsXG4gIFdvcmtmbG93V29ybGRFcnJvcixcbn0gZnJvbSAnQHdvcmtmbG93L2Vycm9ycyc7XG4iXX0=
|
package/docs/ai/index.mdx
CHANGED
|
@@ -64,7 +64,7 @@ In order to connect to an LLM, we'll need to set up an API key. The easiest way
|
|
|
64
64
|
|
|
65
65
|
<Tab value="Gateway">
|
|
66
66
|
|
|
67
|
-
Get a Gateway API key from the [Vercel Gateway](https://vercel.com/docs/gateway/
|
|
67
|
+
Get a Gateway API key from the [Vercel Gateway](https://vercel.com/docs/ai-gateway/authentication) page.
|
|
68
68
|
|
|
69
69
|
Then add it to your `.env.local` file:
|
|
70
70
|
|
|
@@ -79,6 +79,9 @@ All errors extend [`WorkflowError`](/docs/api-reference/workflow-errors/workflow
|
|
|
79
79
|
<Card href="/docs/api-reference/workflow-errors/entity-conflict-error" title="EntityConflictError">
|
|
80
80
|
Thrown when a storage operation conflicts with the current entity state.
|
|
81
81
|
</Card>
|
|
82
|
+
<Card href="/docs/api-reference/workflow-errors/precondition-failed-error" title="PreconditionFailedError">
|
|
83
|
+
Thrown when an event creation is rejected because the client's event-log snapshot is stale.
|
|
84
|
+
</Card>
|
|
82
85
|
<Card href="/docs/api-reference/workflow-errors/too-early-error" title="TooEarlyError">
|
|
83
86
|
Thrown when a request is made before the system is ready to process it.
|
|
84
87
|
</Card>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: PreconditionFailedError
|
|
3
|
+
description: Thrown when an event creation is rejected because the client's event-log snapshot is stale.
|
|
4
|
+
type: reference
|
|
5
|
+
summary: Catch PreconditionFailedError when a world rejects an event creation made from a stale event-log snapshot.
|
|
6
|
+
related:
|
|
7
|
+
- /docs/api-reference/workflow-errors/workflow-world-error
|
|
8
|
+
- /docs/api-reference/workflow-errors/entity-conflict-error
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
`PreconditionFailedError` is thrown by world implementations when an event creation is rejected because the client's event-log snapshot is stale — a newer out-of-band event (such as a received hook or a completed step) was recorded after the snapshot the client replayed from. It corresponds to HTTP 412 Precondition Failed semantics.
|
|
12
|
+
|
|
13
|
+
This only occurs when the optimistic-concurrency guard is enabled via `WORKFLOW_PRECONDITION_GUARD=1` (see [Runtime Tuning](/docs/configuration/runtime-tuning)); event creations that carry no snapshot are never rejected with this error.
|
|
14
|
+
|
|
15
|
+
<Callout>
|
|
16
|
+
The Workflow runtime handles this error automatically: it reloads the event log and retries, ultimately re-enqueueing the run for a fresh replay if it cannot catch up. You will only encounter it when interacting with world storage APIs directly.
|
|
17
|
+
</Callout>
|
|
18
|
+
|
|
19
|
+
```typescript lineNumbers
|
|
20
|
+
import { PreconditionFailedError } from "workflow/errors"
|
|
21
|
+
declare const world: { events: { create(...args: any[]): Promise<any> } }; // @setup
|
|
22
|
+
declare const runId: string; // @setup
|
|
23
|
+
declare const event: any; // @setup
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
await world.events.create(runId, event);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
if (PreconditionFailedError.is(error)) { // [!code highlight]
|
|
29
|
+
console.log("Snapshot is stale; reload the event log and retry");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## API Signature
|
|
35
|
+
|
|
36
|
+
### Properties
|
|
37
|
+
|
|
38
|
+
<TSDoc
|
|
39
|
+
definition={`
|
|
40
|
+
interface PreconditionFailedError {
|
|
41
|
+
/** Delay in seconds before the operation should be retried. Present when the server sends a Retry-After header. */
|
|
42
|
+
retryAfter?: number;
|
|
43
|
+
/** The error message. */
|
|
44
|
+
message: string;
|
|
45
|
+
}
|
|
46
|
+
export default PreconditionFailedError;`}
|
|
47
|
+
/>
|
|
48
|
+
|
|
49
|
+
### Static Methods
|
|
50
|
+
|
|
51
|
+
#### `PreconditionFailedError.is(value)`
|
|
52
|
+
|
|
53
|
+
Type-safe check for `PreconditionFailedError` instances. Preferred over `instanceof` because it works across module boundaries and VM contexts.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { PreconditionFailedError } from "workflow/errors"
|
|
57
|
+
declare const error: unknown; // @setup
|
|
58
|
+
|
|
59
|
+
if (PreconditionFailedError.is(error)) {
|
|
60
|
+
// error is typed as PreconditionFailedError
|
|
61
|
+
}
|
|
62
|
+
```
|
|
@@ -22,11 +22,10 @@ keywords:
|
|
|
22
22
|
|
|
23
23
|
`world.analytics` is an optional, read-only namespace for observability surfaces — dashboards, CLIs, and admin tools that list large numbers of runs without touching payload data.
|
|
24
24
|
|
|
25
|
-
It differs from [Storage](/docs/api-reference/workflow-runtime/world/storage) in
|
|
25
|
+
It differs from [Storage](/docs/api-reference/workflow-runtime/world/storage) in two ways:
|
|
26
26
|
|
|
27
27
|
- **Metadata only.** Results never include run input/output, step data, or hook tokens. There is no `resolveData` option.
|
|
28
|
-
- **Served from the observability pipeline.** On Vercel, queries
|
|
29
|
-
- **Plan-bounded lookback.** Listings only cover the observability retention window for your plan (up to 30 days). Every page includes a `pageInfo` block describing the current window, and requesting an older window fails with an `observability-upgrade-required` error.
|
|
28
|
+
- **Served from the observability pipeline.** On Vercel, queries are served from the Vercel observability data pipeline, so large listings do not compete with workflow execution. Data is ingested asynchronously and may trail the live state by a few seconds.
|
|
30
29
|
|
|
31
30
|
The namespace is optional — worlds that don't implement it (such as the local development world) leave it `undefined`, so feature-detect before use:
|
|
32
31
|
|
|
@@ -129,10 +128,10 @@ Every paginated response carries `pageInfo` describing the window the query was
|
|
|
129
128
|
```typescript
|
|
130
129
|
{
|
|
131
130
|
currentLookbackDays: 2, // what your plan allows today
|
|
132
|
-
maxLookbackDays: 30, // ceiling with Observability Plus
|
|
131
|
+
maxLookbackDays: 30, // ceiling with Observability Plus on Vercel
|
|
133
132
|
currentWindowStart: Date,
|
|
134
133
|
maxWindowStart: Date,
|
|
135
|
-
upgradeAvailable: true, //
|
|
134
|
+
upgradeAvailable: true, // for Vercel deployed workflows
|
|
136
135
|
}
|
|
137
136
|
```
|
|
138
137
|
|
|
@@ -38,6 +38,13 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
|
|
|
38
38
|
- Default: `3`
|
|
39
39
|
- Recovery replays before replay divergence is recorded as corruption.
|
|
40
40
|
|
|
41
|
+
### `WORKFLOW_PRECONDITION_GUARD`
|
|
42
|
+
|
|
43
|
+
- Default: disabled
|
|
44
|
+
- Set `1` to enable an optimistic-concurrency guard for event creation: replay-context event creations send a `stateUpdatedAt` snapshot timestamp, and a backend that supports the guard rejects a creation with 412 ([`PreconditionFailedError`](/docs/api-reference/workflow-errors/precondition-failed-error)) when a newer out-of-band event (a received hook or a completed step) was recorded after that snapshot.
|
|
45
|
+
- On rejection the runtime reloads the event log and retries, falling back to a queue re-invocation with a fresh replay if it cannot catch up.
|
|
46
|
+
- Backends that do not support the guard ignore the snapshot.
|
|
47
|
+
|
|
41
48
|
## Inline execution
|
|
42
49
|
|
|
43
50
|
### `WORKFLOW_V2_TIMEOUT_MS`
|
|
@@ -82,9 +82,9 @@ The Local World is the default outside Vercel and is intended for development.
|
|
|
82
82
|
|
|
83
83
|
### `recoverActiveRuns`
|
|
84
84
|
|
|
85
|
-
- Environment variable:
|
|
85
|
+
- Environment variable: `WORKFLOW_LOCAL_RECOVER_ACTIVE_RUNS`
|
|
86
86
|
- Default: `true`
|
|
87
|
-
- Re-enqueues pending and running local runs when the World starts.
|
|
87
|
+
- Re-enqueues pending and running local runs when the World starts. Set the environment variable to `0` or `false` to skip recovery; the factory option wins when both are set.
|
|
88
88
|
|
|
89
89
|
### `tag`
|
|
90
90
|
|
|
@@ -201,8 +201,7 @@ Platform-provided values such as `VERCEL_DEPLOYMENT_ID`, `VERCEL_PROJECT_ID`, an
|
|
|
201
201
|
- Default: disabled
|
|
202
202
|
- Set `1` to serialize orchestrator (flow) invocations per run: each run's replays get their own queue topic and the flow trigger is generated with `maxConcurrency: 1`. Inline step executions get per-step topics and keep full parallelism.
|
|
203
203
|
- Read at **both build time and runtime** — set it as a project-level environment variable so the generated trigger and the runtime queue routing agree.
|
|
204
|
-
-
|
|
205
|
-
- Costs: flow-route push deliveries under `maxConcurrency` are billed at 2x units, queue observability sees one topic per run, and deliveries can queue behind the per-run slot — including hook resumes, aborts, and run-timeout enforcement, which are delayed while a replay is in flight. See [Vercel World](/docs/deploying/world/vercel-world#workflow_sequential_replays) for details.
|
|
204
|
+
- Routing each run through a dedicated `maxConcurrency: 1` topic might lead to higher queue performance overhead. See [Vercel World](/docs/deploying/world/vercel-world#workflow_sequential_replays) for details.
|
|
206
205
|
|
|
207
206
|
### `VERCEL_WORKFLOW_SERVER_URL`
|
|
208
207
|
|
|
@@ -62,6 +62,10 @@ Maximum number of concurrent queue message handlers. Default: `1000`
|
|
|
62
62
|
|
|
63
63
|
Maximum number of seconds a local queue message can stay hidden before the handler rechecks the run. Default: unlimited.
|
|
64
64
|
|
|
65
|
+
### `WORKFLOW_LOCAL_RECOVER_ACTIVE_RUNS`
|
|
66
|
+
|
|
67
|
+
Whether pending and running runs found in the data directory are re-enqueued when the world starts. Set to `0` or `false` to skip recovery and leave stale runs untouched. Default: `true`
|
|
68
|
+
|
|
65
69
|
### `WORKFLOW_STREAM_FLUSH_INTERVAL_MS`
|
|
66
70
|
|
|
67
71
|
Flush interval, in milliseconds, for buffered stream writes. Default: `10`.
|
|
@@ -78,7 +82,7 @@ export default createWorld({
|
|
|
78
82
|
port: 5173,
|
|
79
83
|
// baseUrl overrides port if set
|
|
80
84
|
baseUrl: "https://local.example.com:3000",
|
|
81
|
-
recoverActiveRuns: true,
|
|
85
|
+
recoverActiveRuns: true, // overrides WORKFLOW_LOCAL_RECOVER_ACTIVE_RUNS
|
|
82
86
|
streamFlushIntervalMs: 10, // overrides WORKFLOW_STREAM_FLUSH_INTERVAL_MS
|
|
83
87
|
});
|
|
84
88
|
```
|
|
@@ -36,7 +36,7 @@ That's it. Vercel automatically:
|
|
|
36
36
|
|
|
37
37
|
For complete details on pricing, usage limits, and included allotments on Vercel, see the official Vercel documentation:
|
|
38
38
|
|
|
39
|
-
- **[Vercel Workflow](https://vercel.com/docs/
|
|
39
|
+
- **[Vercel Workflow](https://vercel.com/docs/workflows)** — Pricing details, concepts, and observability for Workflow on Vercel
|
|
40
40
|
- **[Vercel limits](https://vercel.com/docs/limits)** — Platform-wide limits including Workflow-specific constraints
|
|
41
41
|
- **[Vercel Hobby plan](https://vercel.com/docs/plans/hobby)** — Free tier included usage for Workflow and other resources
|
|
42
42
|
|
|
@@ -56,7 +56,7 @@ The Vercel World runs in every [Vercel Function region](https://vercel.com/docs/
|
|
|
56
56
|
|
|
57
57
|
No configuration is needed. A run is pinned to the region of the function that creates it:
|
|
58
58
|
|
|
59
|
-
- Deploy your app to a single region (via [`regions`](https://vercel.com/docs/project-configuration#regions) in `vercel.json` or the project settings), and every run lives there.
|
|
59
|
+
- Deploy your app to a single region (via [`regions`](https://vercel.com/docs/project-configuration/vercel-json#regions) in `vercel.json` or the project settings), and every run lives there.
|
|
60
60
|
- Deploy to multiple regions for a globally distributed audience, and each run is pinned to the region that served the user who triggered it — workflow data and streaming stay close to that user.
|
|
61
61
|
|
|
62
62
|
### Explicit region selection
|
|
@@ -76,7 +76,7 @@ const run = await start(myWorkflow, [input], { region: "sfo1" });
|
|
|
76
76
|
there. Your workflow and step functions execute in the regions your
|
|
77
77
|
application is deployed to. For execution to actually happen in the
|
|
78
78
|
specified region, your app must be deployed there — via
|
|
79
|
-
[`regions`](https://vercel.com/docs/project-configuration#regions) in
|
|
79
|
+
[`regions`](https://vercel.com/docs/project-configuration/vercel-json#regions) in
|
|
80
80
|
`vercel.json` or the Function Regions setting in your project settings.
|
|
81
81
|
If it isn't, the run's data lives in the requested region but its steps
|
|
82
82
|
execute in the nearest region your app is deployed to.
|
|
@@ -171,21 +171,16 @@ Custom workflow-server URL for direct runtime requests, or for the proxy to forw
|
|
|
171
171
|
|
|
172
172
|
### `WORKFLOW_SEQUENTIAL_REPLAYS`
|
|
173
173
|
|
|
174
|
-
Set `WORKFLOW_SEQUENTIAL_REPLAYS=1` to guarantee that **at most one orchestrator (flow) invocation runs at a time per workflow run**. This behavior is off by default; without it, the runtime relies on idempotency and the event log to tolerate concurrent flow invocations of the same run.
|
|
174
|
+
Set `WORKFLOW_SEQUENTIAL_REPLAYS=1` to guarantee that **at most one orchestrator (flow) invocation runs at a time per workflow run**. This behavior is off by default; without it, the runtime relies on idempotency and the event log to tolerate concurrent flow invocations of the same run.
|
|
175
175
|
|
|
176
176
|
When enabled, each run's orchestrator messages are given their own queue topic and the flow trigger is configured with `maxConcurrency: 1`, so [Vercel Queues](https://vercel.com/docs/queues) processes replays for a given run strictly one at a time. Step executions (which ride the flow topic in the combined handler model) get a per-step topic, so steps keep full parallelism.
|
|
177
177
|
|
|
178
178
|
<Callout type="warn">
|
|
179
179
|
This variable is read at **both build time and runtime**, so it must be set as a project-level environment variable that applies to your build and your deployed functions. Setting it for only one will produce an inconsistent configuration. The same applies to framework integrations that write their own queue trigger configuration instead of using `getWorkflowQueueTrigger()` from `@workflow/builders`: they only get the runtime half (per-run topics) unless they also emit `maxConcurrency: 1` on their flow trigger.
|
|
180
|
-
|
|
181
|
-
Enabling sequential replays has a cost. Per [Vercel Queues pricing](https://vercel.com/docs/queues/pricing), push deliveries under `maxConcurrency` are billed at **2x units** for that operation, so every flow-route delivery costs double while this is enabled. It also creates one queue topic per run, which increases the number of distinct queues surfaced in queue observability, and each flow invocation waits for a per-run concurrency slot before delivery, which can add queueing latency. Leave it off unless you specifically need the per-run serialization guarantee.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
While a replay holds a run's slot — including time spent executing steps inline — other wake messages for that run (hook resumes, aborts and cancellations, and run-timeout enforcement) wait for the slot. Expect aborts and timeouts to be delayed by up to the duration of the longest single invocation.
|
|
185
|
-
|
|
186
|
-
The guarantee covers messages sent by the Workflow SDK itself. External producers that compute a flow topic name directly (rather than enqueueing through the SDK) still deliver, but bypass the per-run serialization slot.
|
|
187
180
|
</Callout>
|
|
188
181
|
|
|
182
|
+
Because it routes each run's flow invocations through a dedicated `maxConcurrency: 1` queue topic, enabling this might lead to higher queue performance overhead.
|
|
183
|
+
|
|
189
184
|
### `VERCEL_QUEUE_MAX_DELAY_SECONDS`
|
|
190
185
|
|
|
191
186
|
Maximum delay, in seconds, that Workflow uses for one Vercel Queues continuation message when implementing `sleep()`. If a workflow sleeps longer than this, the runtime schedules another continuation message when the first one fires, repeating until the sleep's target time is reached. Default: `82800` (23 hours).
|
|
@@ -272,4 +267,4 @@ The Vercel World uses Vercel's infrastructure for workflow execution:
|
|
|
272
267
|
- **Queuing** - Steps are distributed across serverless functions via [Vercel Queues](https://vercel.com/docs/queues) with automatic retries and [consumer function security](#consumer-function-security)
|
|
273
268
|
- **Authentication** - OIDC tokens provide secure, automatic authentication
|
|
274
269
|
|
|
275
|
-
For more details, see the [Vercel Workflow documentation](https://vercel.com/docs/
|
|
270
|
+
For more details, see the [Vercel Workflow documentation](https://vercel.com/docs/workflows).
|
|
@@ -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" />
|
|
@@ -11,37 +11,30 @@ 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">
|
|
18
|
-
The Python SDK is currently in **beta**. APIs and behavior may change. For the latest documentation and updates, see the [official Vercel Workflow Python documentation](https://vercel.com/docs/
|
|
18
|
+
The Python SDK is currently in **beta**. APIs and behavior may change. For the latest documentation and updates, see the [official Vercel Workflow Python documentation](https://vercel.com/docs/workflows/python).
|
|
19
19
|
</Callout>
|
|
20
20
|
|
|
21
21
|
You can build durable workflows in Python using the [`vercel` Python SDK](https://pypi.org/project/vercel/). Your workflow code can pause, resume, and maintain state, just like the JavaScript and TypeScript Workflow SDK.
|
|
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
|
|
@@ -160,7 +162,7 @@ When a hook receives data, the workflow resumes automatically. You don't ne
|
|
|
160
162
|
|
|
161
163
|
## Learn More
|
|
162
164
|
|
|
163
|
-
For comprehensive documentation, examples, and the latest updates, visit the [official Vercel Workflow Python documentation](https://vercel.com/docs/
|
|
165
|
+
For comprehensive documentation, examples, and the latest updates, visit the [official Vercel Workflow Python documentation](https://vercel.com/docs/workflows/python).
|
|
164
166
|
|
|
165
167
|
## Next Steps
|
|
166
168
|
|
|
@@ -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-beta.33` or later with Nitro v3.
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: React Router v8
|
|
3
|
+
description: Add durable workflows to a React Router v8 framework-mode app using Nitro v3.
|
|
4
|
+
type: guide
|
|
5
|
+
summary: Configure React Router v8, Nitro v3, and Workflow SDK in one Vite build.
|
|
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 v8 framework-mode app.
|
|
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
|
+
## Use a shared build directory
|
|
64
|
+
|
|
65
|
+
Set an explicit build directory in your React Router config:
|
|
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
|
+
} satisfies Config;
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
React Router will place browser assets in `build/client`. Nitro will place the runnable server in `build/server`.
|
|
77
|
+
|
|
78
|
+
</Step>
|
|
79
|
+
|
|
80
|
+
<Step>
|
|
81
|
+
|
|
82
|
+
## Create the React Router server handler
|
|
83
|
+
|
|
84
|
+
Create `server/ssr.ts`:
|
|
85
|
+
|
|
86
|
+
```typescript title="server/ssr.ts" lineNumbers
|
|
87
|
+
import { createRequestHandler } from "react-router";
|
|
88
|
+
|
|
89
|
+
export default {
|
|
90
|
+
fetch: createRequestHandler(
|
|
91
|
+
() => import("virtual:react-router/server-build"),
|
|
92
|
+
import.meta.env.MODE,
|
|
93
|
+
),
|
|
94
|
+
};
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
This adapts React Router's generated server build to the Fetch API handler Nitro expects.
|
|
98
|
+
|
|
99
|
+
</Step>
|
|
100
|
+
|
|
101
|
+
<Step>
|
|
102
|
+
|
|
103
|
+
## Configure Vite
|
|
104
|
+
|
|
105
|
+
Update `vite.config.ts`:
|
|
106
|
+
|
|
107
|
+
```typescript title="vite.config.ts" lineNumbers
|
|
108
|
+
import { reactRouter } from "@react-router/dev/vite";
|
|
109
|
+
import { nitro } from "nitro/vite";
|
|
110
|
+
import { defineConfig } from "vite";
|
|
111
|
+
import { workflow } from "workflow/vite";
|
|
112
|
+
import reactRouterConfig from "./react-router.config";
|
|
113
|
+
|
|
114
|
+
export default defineConfig({
|
|
115
|
+
plugins: [
|
|
116
|
+
reactRouter(),
|
|
117
|
+
nitro({
|
|
118
|
+
serverDir: "./server",
|
|
119
|
+
output: {
|
|
120
|
+
dir: reactRouterConfig.buildDirectory,
|
|
121
|
+
serverDir: `${reactRouterConfig.buildDirectory}/server`,
|
|
122
|
+
publicDir: `${reactRouterConfig.buildDirectory}/client`,
|
|
123
|
+
},
|
|
124
|
+
}),
|
|
125
|
+
workflow({ dirs: ["workflows"] }),
|
|
126
|
+
],
|
|
127
|
+
environments: {
|
|
128
|
+
ssr: {
|
|
129
|
+
build: {
|
|
130
|
+
rollupOptions: {
|
|
131
|
+
input: "./server/ssr.ts",
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Keep `dirs: ["workflows"]` so subsequent builds do not scan generated files under `build`. Place `reactRouter()` before `nitro()` in the plugin array.
|
|
140
|
+
|
|
141
|
+
</Step>
|
|
142
|
+
|
|
143
|
+
<Step>
|
|
144
|
+
|
|
145
|
+
## Create a workflow
|
|
146
|
+
|
|
147
|
+
Create `workflows/greeting.ts`:
|
|
148
|
+
|
|
149
|
+
```typescript title="workflows/greeting.ts" lineNumbers
|
|
150
|
+
export async function greetingWorkflow(name: string) {
|
|
151
|
+
"use workflow";
|
|
152
|
+
|
|
153
|
+
return greet(name);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function greet(name: string) {
|
|
157
|
+
"use step";
|
|
158
|
+
|
|
159
|
+
return `Hello, ${name}!`;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
</Step>
|
|
164
|
+
|
|
165
|
+
<Step>
|
|
166
|
+
|
|
167
|
+
## Start the workflow from a Nitro route
|
|
168
|
+
|
|
169
|
+
Create `server/routes/api/greeting.post.ts`:
|
|
170
|
+
|
|
171
|
+
```typescript title="server/routes/api/greeting.post.ts" lineNumbers
|
|
172
|
+
import { defineHandler } from "nitro";
|
|
173
|
+
import { start } from "workflow/api";
|
|
174
|
+
import { greetingWorkflow } from "../../../workflows/greeting";
|
|
175
|
+
|
|
176
|
+
export default defineHandler(async (event) => {
|
|
177
|
+
const { name } = (await event.req.json()) as { name: string };
|
|
178
|
+
const run = await start(greetingWorkflow, [name]);
|
|
179
|
+
|
|
180
|
+
return { runId: run.runId };
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
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.
|
|
185
|
+
|
|
186
|
+
</Step>
|
|
187
|
+
|
|
188
|
+
<Step>
|
|
189
|
+
|
|
190
|
+
## Run the app
|
|
191
|
+
|
|
192
|
+
Start the development server:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
pnpm vite dev
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Then start a workflow:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
curl -X POST \
|
|
202
|
+
-H "content-type: application/json" \
|
|
203
|
+
-d '{"name":"Workflow"}' \
|
|
204
|
+
http://localhost:3000/api/greeting
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Build and start the production server:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
pnpm vite build
|
|
211
|
+
node ./build/server/index.mjs
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
You can inspect local runs with `pnpm workflow web`.
|
|
215
|
+
|
|
216
|
+
</Step>
|
|
217
|
+
|
|
218
|
+
</Steps>
|
|
219
|
+
|
|
220
|
+
## Troubleshooting
|
|
221
|
+
|
|
222
|
+
### React Router pages return 404
|
|
223
|
+
|
|
224
|
+
Check that the `ssr` environment input points to `./server/ssr.ts`.
|
|
225
|
+
|
|
226
|
+
### A second build tries to compile files under `build/server`
|
|
227
|
+
|
|
228
|
+
Use `workflow({ dirs: ["workflows"] })`, remove the existing `build` directory once, and rebuild.
|
|
229
|
+
|
|
230
|
+
### `vite build` finishes output but does not exit
|
|
231
|
+
|
|
232
|
+
Use `workflow@5.0.0-beta.33` or later with Nitro v3.
|
|
@@ -414,7 +414,7 @@ const flowTriggers = [getWorkflowQueueTrigger()];
|
|
|
414
414
|
const stepTriggers = [STEP_QUEUE_TRIGGER];
|
|
415
415
|
```
|
|
416
416
|
|
|
417
|
-
If your integration constructs the flow trigger object itself instead of calling `getWorkflowQueueTrigger()`, it must add `maxConcurrency: 1` to that trigger when sequential replays are enabled at build time (`WORKFLOW_SEQUENTIAL_REPLAYS=1
|
|
417
|
+
If your integration constructs the flow trigger object itself instead of calling `getWorkflowQueueTrigger()`, it must add `maxConcurrency: 1` to that trigger when sequential replays are enabled at build time (`WORKFLOW_SEQUENTIAL_REPLAYS=1` — the exported `isSequentialReplaysEnabled()` helper implements this check). The runtime half of the feature (per-run queue topics) activates from the environment variable alone — without the trigger half, those per-run topics are not serialized and the setting only adds queue-topic cardinality.
|
|
418
418
|
|
|
419
419
|
|
|
420
420
|
### Custom implementations
|
|
@@ -57,6 +57,15 @@ export async function cleanupAttributes() {
|
|
|
57
57
|
|
|
58
58
|
Attribute keys must be 1-256 characters, values must be strings up to 256 bytes, and each run can have up to 64 attributes. Keys that start with `$` are reserved for framework and library code.
|
|
59
59
|
|
|
60
|
+
## Reserved keys
|
|
61
|
+
|
|
62
|
+
When `start()` is called from inside a running workflow or step, the new run is automatically tagged with two reserved attributes:
|
|
63
|
+
|
|
64
|
+
- `$parentRunId`: the run that started it.
|
|
65
|
+
- `$rootRunId`: the root of the chain. It is inherited, so every run in a daisy chain or fan-out shares one root id.
|
|
66
|
+
|
|
67
|
+
Top-level runs (started outside any workflow or step) are not tagged.
|
|
68
|
+
|
|
60
69
|
## Viewing attributes
|
|
61
70
|
|
|
62
71
|
The run details panel in the observability UI shows the run's current attributes as key-value rows. Reserved `$`-prefixed keys are marked with a badge and sorted after user keys:
|
|
@@ -17,7 +17,7 @@ The SDK only depends on the OpenTelemetry **API**, never on an SDK or exporter.
|
|
|
17
17
|
|
|
18
18
|
## Enabling tracing
|
|
19
19
|
|
|
20
|
-
Register any OpenTelemetry Node SDK in your application. On Vercel with Next.js, the simplest setup is [`@vercel/otel`](https://vercel.com/docs/
|
|
20
|
+
Register any OpenTelemetry Node SDK in your application. On Vercel with Next.js, the simplest setup is [`@vercel/otel`](https://vercel.com/docs/tracing/instrumentation) in `instrumentation.ts`:
|
|
21
21
|
|
|
22
22
|
```typescript title="instrumentation.ts" lineNumbers
|
|
23
23
|
import { registerOTel } from "@vercel/otel"
|
|
@@ -43,6 +43,8 @@ No workflow-specific configuration is required. As soon as a tracer provider and
|
|
|
43
43
|
| `http <method>` | client | the SDK calls the workflow backend (event reads/writes) |
|
|
44
44
|
| `workflow.stream.write` | client | a stream chunk (or the stream close) is flushed to the backend |
|
|
45
45
|
| `workflow.stream.flush` | client | a buffered batch of stream writes settles; back-dated to the batch's first `write()`, so its duration is the app-perceived batch latency (buffer dwell + RPC) |
|
|
46
|
+
| `workflow.stream.close` | client | the stream-close RPC; its duration is the close round trip |
|
|
47
|
+
| `workflow.stream.read.complete` | client | a stream read drains; back-dated to the read dispatch, so its duration is the total read (`workflow.stream.read.chunks` / `.bytes` carry throughput counts) |
|
|
46
48
|
| `workflow.stream.read.connect` | client | a live stream read opens; the span covers dispatch → response headers (network connect) |
|
|
47
49
|
| `workflow.stream.read` | client | a live stream read receives its first chunk; the span's duration is the end-to-end time-to-first-chunk (see `workflow.stream.read.ttfc_ms`) |
|
|
48
50
|
|
|
@@ -61,9 +63,10 @@ Stream spans are emitted by the SDK's world backend on the client that writes or
|
|
|
61
63
|
| `workflow.queue.overhead_ms` | Time between the message being enqueued and the handler starting — queue dwell plus any cold start. |
|
|
62
64
|
| `workflow.stream.name` | The stream name, on stream write/read spans. |
|
|
63
65
|
| `workflow.stream.operation` | The stream operation: `write`, `write_multi`, `close`, `read`, or `flush`. |
|
|
64
|
-
| `workflow.stream.write.chunk_rtt` | Time between emissions of a chunk to the wire, and receiving the `ack` message for that chunk. |
|
|
66
|
+
| `workflow.stream.write.chunk_rtt` | Time between emissions of a chunk to the wire, and receiving the `ack` message for that chunk. Also stamped on `workflow.stream.flush` (the batch's write RPC duration, network included). |
|
|
65
67
|
| `workflow.stream.flush.buffer_dwell_ms` | On `workflow.stream.flush`: time the batch's first chunk waited in the client-side write buffer (flush timer, run-ready barrier) before the request was dispatched. `workflow.stream.flush.chunks` / `.bytes` carry the batch shape. |
|
|
66
68
|
| `workflow.stream.read.ttfc_ms` | Time between opening a read connection and observing and receiving the first chunk back. |
|
|
69
|
+
| `workflow.stream.read.connect_ms` | On `workflow.stream.read`: the connect portion (read dispatch → stream handle/response headers), network included. |
|
|
67
70
|
|
|
68
71
|
## Trace shape: one trace per invocation
|
|
69
72
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "workflow",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.35",
|
|
4
4
|
"description": "Workflow SDK - Build durable, resilient, and observable workflows",
|
|
5
5
|
"main": "dist/typescript-plugin.cjs",
|
|
6
6
|
"type": "module",
|
|
@@ -57,18 +57,18 @@
|
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"ms": "2.1.3",
|
|
60
|
-
"@workflow/astro": "5.0.0-beta.
|
|
61
|
-
"@workflow/cli": "5.0.0-beta.
|
|
62
|
-
"@workflow/core": "5.0.0-beta.
|
|
63
|
-
"@workflow/errors": "5.0.0-beta.
|
|
60
|
+
"@workflow/astro": "5.0.0-beta.35",
|
|
61
|
+
"@workflow/cli": "5.0.0-beta.35",
|
|
62
|
+
"@workflow/core": "5.0.0-beta.35",
|
|
63
|
+
"@workflow/errors": "5.0.0-beta.11",
|
|
64
64
|
"@workflow/typescript-plugin": "5.0.0-beta.5",
|
|
65
65
|
"@workflow/utils": "5.0.0-beta.6",
|
|
66
|
-
"@workflow/next": "5.0.0-beta.
|
|
67
|
-
"@workflow/nest": "5.0.0-beta.
|
|
68
|
-
"@workflow/nitro": "5.0.0-beta.
|
|
69
|
-
"@workflow/nuxt": "5.0.0-beta.
|
|
70
|
-
"@workflow/sveltekit": "5.0.0-beta.
|
|
71
|
-
"@workflow/rollup": "5.0.0-beta.
|
|
66
|
+
"@workflow/next": "5.0.0-beta.35",
|
|
67
|
+
"@workflow/nest": "5.0.0-beta.35",
|
|
68
|
+
"@workflow/nitro": "5.0.0-beta.35",
|
|
69
|
+
"@workflow/nuxt": "5.0.0-beta.35",
|
|
70
|
+
"@workflow/sveltekit": "5.0.0-beta.35",
|
|
71
|
+
"@workflow/rollup": "5.0.0-beta.35"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@types/ms": "2.1.0",
|