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.
@@ -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"}
@@ -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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3JzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2ludGVybmFsL2Vycm9ycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsbUJBQW1CLEVBQ25CLGlCQUFpQixFQUNqQixpQkFBaUIsRUFDakIsZUFBZSxFQUNmLG9CQUFvQixFQUNwQixzQkFBc0IsRUFDdEIsYUFBYSxFQUNiLGFBQWEsRUFDYixhQUFhLEVBQ2IsMEJBQTBCLEVBQzFCLHlCQUF5QixFQUN6QixzQkFBc0IsRUFDdEIsNEJBQTRCLEVBQzVCLHdCQUF3QixFQUN4QixvQkFBb0IsRUFDcEIsa0JBQWtCLEdBQ25CLE1BQU0sa0JBQWtCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQge1xuICBFbnRpdHlDb25mbGljdEVycm9yLFxuICBIb29rQ29uZmxpY3RFcnJvcixcbiAgSG9va05vdEZvdW5kRXJyb3IsXG4gIFJ1bkV4cGlyZWRFcnJvcixcbiAgUnVuTm90U3VwcG9ydGVkRXJyb3IsXG4gIFN0ZXBOb3RSZWdpc3RlcmVkRXJyb3IsXG4gIFRocm90dGxlRXJyb3IsXG4gIFRvb0Vhcmx5RXJyb3IsXG4gIFdvcmtmbG93RXJyb3IsXG4gIFdvcmtmbG93Tm90UmVnaXN0ZXJlZEVycm9yLFxuICBXb3JrZmxvd1J1bkNhbmNlbGxlZEVycm9yLFxuICBXb3JrZmxvd1J1bkZhaWxlZEVycm9yLFxuICBXb3JrZmxvd1J1bk5vdENvbXBsZXRlZEVycm9yLFxuICBXb3JrZmxvd1J1bk5vdEZvdW5kRXJyb3IsXG4gIFdvcmtmbG93UnVudGltZUVycm9yLFxuICBXb3JrZmxvd1dvcmxkRXJyb3IsXG59IGZyb20gJ0B3b3JrZmxvdy9lcnJvcnMnO1xuIl19
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/api-reference/overview) page.
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>
@@ -14,6 +14,7 @@
14
14
  "workflow-world-error",
15
15
  "throttle-error",
16
16
  "entity-conflict-error",
17
+ "precondition-failed-error",
17
18
  "run-expired-error",
18
19
  "run-not-supported-error",
19
20
  "too-early-error"
@@ -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 three ways:
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 hit a ClickHouse-backed analytics store instead of the runtime database, so large listings do not compete with workflow execution. Data is ingested asynchronously and may trail the live state by a few seconds.
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, // more history exists behind the plan gate
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: none
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
- - Also enabled by `WORKFLOW_SAFE_MODE=1` when not set explicitly.
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/workflow)** — Pricing details, concepts, and observability for Workflow on Vercel
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. It is also enabled by `WORKFLOW_SAFE_MODE=1` when `WORKFLOW_SEQUENTIAL_REPLAYS` is not set explicitly.
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/workflow).
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" />
@@ -2,6 +2,7 @@
2
2
  "title": "Getting Started",
3
3
  "pages": [
4
4
  "next",
5
+ "react-router",
5
6
  "astro",
6
7
  "express",
7
8
  "fastify",
@@ -11,37 +11,30 @@ related:
11
11
  ---
12
12
 
13
13
  <CopyPrompt
14
- text="In this Python project, run `pip install vercel`. Add `vercel.json` with `experimentalServices.ai_content_workflow.type = &quot;worker&quot;`, `entrypoint = &quot;app/workflows/ai_content_workflow.py&quot;`, and `topics = [&quot;__wkf_*&quot;]`. 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`. Mark step functions with `@wf.step`, use `await workflow.sleep(&quot;7 days&quot;)` for durable delays where needed, and use a `workflow.BaseHook` Pydantic model plus `.wait(token=...)` and `.resume(token)` for external approval events. Verify the worker entrypoint and route names match the project."
14
+ text="In this Python project, add `requires-python = &quot;&gt;=3.12&quot;` and `dependencies = [&quot;vercel&quot;]` under `[project]` in `pyproject.toml`. Add `[[tool.vercel.workflows]]` with `entrypoint = &quot;app.workflows:wf&quot;`. 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(&quot;7 days&quot;)` 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/workflow/python?language=py).
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
- Install the `vercel` package:
25
+ Add the `vercel` package and workflow entrypoint to `pyproject.toml`:
26
26
 
27
- ```bash filename="Terminal"
28
- pip install vercel
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
- ```json filename="vercel.json"
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&apos;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/workflow/python).
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,5 @@
1
+ {
2
+ "title": "React Router",
3
+ "pages": ["v7", "v8"],
4
+ "defaultOpen": true
5
+ }
@@ -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`, or `WORKFLOW_SAFE_MODE=1` when the specific variable is unset — 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.
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/observability/otel-overview) in `instrumentation.ts`:
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.33",
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.33",
61
- "@workflow/cli": "5.0.0-beta.33",
62
- "@workflow/core": "5.0.0-beta.33",
63
- "@workflow/errors": "5.0.0-beta.10",
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.33",
67
- "@workflow/nest": "5.0.0-beta.33",
68
- "@workflow/nitro": "5.0.0-beta.33",
69
- "@workflow/nuxt": "5.0.0-beta.33",
70
- "@workflow/sveltekit": "5.0.0-beta.33",
71
- "@workflow/rollup": "5.0.0-beta.33"
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",