workflow 5.0.0-beta.30 → 5.0.0-beta.32
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/docs/api-reference/workflow/index.mdx +2 -2
- package/docs/api-reference/workflow/{experimental-set-attributes.mdx → set-attributes.mdx} +14 -14
- package/docs/api-reference/workflow-api/start.mdx +1 -1
- package/docs/cookbook/advanced/child-workflows.mdx +4 -0
- package/docs/cookbook/advanced/publishing-libraries.mdx +4 -0
- package/docs/cookbook/advanced/serializable-steps.mdx +4 -0
- package/docs/cookbook/advanced/upgrading-workflows.mdx +4 -0
- package/docs/cookbook/agent-patterns/agent-cancellation.mdx +4 -0
- package/docs/cookbook/agent-patterns/durable-agent.mdx +4 -0
- package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +4 -0
- package/docs/cookbook/common-patterns/batching.mdx +4 -0
- package/docs/cookbook/common-patterns/idempotency.mdx +4 -0
- package/docs/cookbook/common-patterns/rate-limiting.mdx +4 -0
- package/docs/cookbook/common-patterns/saga.mdx +4 -0
- package/docs/cookbook/common-patterns/scheduling.mdx +4 -0
- package/docs/cookbook/common-patterns/sequential-and-parallel.mdx +4 -0
- package/docs/cookbook/common-patterns/timeouts.mdx +4 -0
- package/docs/cookbook/common-patterns/webhooks.mdx +4 -0
- package/docs/cookbook/common-patterns/workflow-composition.mdx +4 -0
- package/docs/cookbook/integrations/ai-sdk.mdx +4 -0
- package/docs/cookbook/integrations/chat-sdk.mdx +4 -0
- package/docs/cookbook/integrations/sandbox.mdx +4 -0
- package/docs/deploying/world/local-world.mdx +1 -1
- package/docs/deploying/world/postgres-world.mdx +1 -1
- package/docs/deploying/world/vercel-world.mdx +1 -1
- package/docs/errors/abort-signal-timeout-in-workflow.mdx +4 -0
- package/docs/errors/fetch-in-workflow.mdx +4 -0
- package/docs/errors/hook-conflict.mdx +4 -0
- package/docs/errors/node-js-module-in-workflow.mdx +4 -0
- package/docs/errors/serialization-failed.mdx +4 -0
- package/docs/errors/start-invalid-workflow-function.mdx +4 -0
- package/docs/errors/timeout-in-workflow.mdx +4 -0
- package/docs/errors/webhook-response-not-sent.mdx +4 -0
- package/docs/getting-started/astro.mdx +4 -0
- package/docs/getting-started/express.mdx +4 -0
- package/docs/getting-started/fastify.mdx +4 -0
- package/docs/getting-started/hono.mdx +4 -0
- package/docs/getting-started/nestjs.mdx +4 -0
- package/docs/getting-started/next.mdx +4 -0
- package/docs/getting-started/nitro.mdx +4 -0
- package/docs/getting-started/nuxt.mdx +4 -0
- package/docs/getting-started/python.mdx +4 -0
- package/docs/getting-started/sveltekit.mdx +4 -0
- package/docs/getting-started/tanstack-start.mdx +4 -0
- package/docs/getting-started/vite.mdx +4 -0
- package/docs/migration-guides/migrating-from-aws-step-functions.mdx +4 -0
- package/docs/migration-guides/migrating-from-inngest.mdx +4 -0
- package/docs/migration-guides/migrating-from-temporal.mdx +4 -0
- package/docs/migration-guides/migrating-from-trigger-dev.mdx +4 -0
- package/docs/observability/attributes.mdx +12 -18
- package/docs/observability/tracing.mdx +3 -1
- package/package.json +10 -10
|
@@ -47,8 +47,8 @@ Workflow SDK contains the following functions you can use inside your workflow f
|
|
|
47
47
|
<Card href="/docs/api-reference/workflow/get-writable" title="getWritable()">
|
|
48
48
|
Access the current workflow run's default stream.
|
|
49
49
|
</Card>
|
|
50
|
-
<Card href="/docs/api-reference/workflow/
|
|
51
|
-
Attach
|
|
50
|
+
<Card href="/docs/api-reference/workflow/set-attributes" title="setAttributes()">
|
|
51
|
+
Attach string metadata to the current workflow run.
|
|
52
52
|
</Card>
|
|
53
53
|
</Cards>
|
|
54
54
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
|
-
title:
|
|
2
|
+
title: setAttributes
|
|
3
3
|
description: Attach string metadata to workflow run for observability.
|
|
4
4
|
type: reference
|
|
5
|
-
summary: Use
|
|
5
|
+
summary: Use setAttributes inside a workflow or step function to set run attributes.
|
|
6
6
|
prerequisites:
|
|
7
7
|
- /docs/foundations/workflows-and-steps
|
|
8
8
|
related:
|
|
@@ -12,17 +12,13 @@ related:
|
|
|
12
12
|
|
|
13
13
|
Attaches string metadata to the current workflow run.
|
|
14
14
|
|
|
15
|
-
<Callout>
|
|
16
|
-
This API is experimental and may change before the stable attributes API is released.
|
|
17
|
-
</Callout>
|
|
18
|
-
|
|
19
15
|
```typescript lineNumbers
|
|
20
|
-
import {
|
|
16
|
+
import { setAttributes } from "workflow"
|
|
21
17
|
|
|
22
18
|
export async function orderWorkflow(orderId: string) {
|
|
23
19
|
"use workflow"
|
|
24
20
|
|
|
25
|
-
await
|
|
21
|
+
await setAttributes({
|
|
26
22
|
phase: "received",
|
|
27
23
|
orderId,
|
|
28
24
|
})
|
|
@@ -35,24 +31,24 @@ export async function orderWorkflow(orderId: string) {
|
|
|
35
31
|
|
|
36
32
|
<TSDoc
|
|
37
33
|
definition={`
|
|
38
|
-
import {
|
|
39
|
-
export default
|
|
34
|
+
import { setAttributes } from "workflow";
|
|
35
|
+
export default setAttributes;`}
|
|
40
36
|
showSections={['parameters']}
|
|
41
37
|
/>
|
|
42
38
|
|
|
43
39
|
## Usage
|
|
44
40
|
|
|
45
|
-
Call `
|
|
41
|
+
Call `setAttributes` from a `"use workflow"` function or a `"use step"` function. Calling it from plain application code is not supported because there is no active workflow run.
|
|
46
42
|
|
|
47
43
|
Attribute values must be strings. Pass `undefined` to remove an attribute:
|
|
48
44
|
|
|
49
45
|
```typescript lineNumbers
|
|
50
|
-
import {
|
|
46
|
+
import { setAttributes } from "workflow"
|
|
51
47
|
|
|
52
48
|
export async function cleanupAttributes() {
|
|
53
49
|
"use workflow"
|
|
54
50
|
|
|
55
|
-
await
|
|
51
|
+
await setAttributes({ staleKey: undefined })
|
|
56
52
|
}
|
|
57
53
|
```
|
|
58
54
|
|
|
@@ -62,4 +58,8 @@ Validation errors throw [`FatalError`](/docs/api-reference/workflow/fatal-error)
|
|
|
62
58
|
|
|
63
59
|
Calls from both workflow and step bodies append a native `attr_set` event, which the World materializes onto `run.attributes`. Workflow-originated events record a workflow writer; step-originated events record the originating step ID and attempt.
|
|
64
60
|
|
|
65
|
-
Native attributes require spec version 4 or later. Step-body storage errors throw from `
|
|
61
|
+
Native attributes require spec version 4 or later. Step-body storage errors throw from `setAttributes`; catch them inside the step if the write should be best-effort. Workflow-body writes are committed when the workflow suspends: transient storage errors are retried with the suspension, while a write the World rejects as invalid — such as exceeding the per-run attribute cap across multiple calls — fails the run with the validation error.
|
|
62
|
+
|
|
63
|
+
<Callout>
|
|
64
|
+
This function was previously exported as `experimental_setAttributes`. The old name still works as a deprecated alias — update imports to `setAttributes`.
|
|
65
|
+
</Callout>
|
|
@@ -59,7 +59,7 @@ Learn more about [`WorkflowReadableStreamOptions`](/docs/api-reference/workflow-
|
|
|
59
59
|
* Each call to `start()` creates a new workflow run. If retried requests must route to one active workflow, have the workflow create a deterministic hook token and use [`getHookByToken()`](/docs/api-reference/workflow-api/get-hook-by-token) to reuse an already-registered active hook. The lookup is not atomic with `start()`, so concurrent callers can still create extra runs before the hook is registered; handle that race inside the workflow by checking `await hook.getConflict()` before duplicate-sensitive work — on a conflict it resolves with the run that owns the token, so the duplicate can return the active owner to the caller. If duplicates must be rejected before a workflow body runs, keep a durable request record until native atomic start-and-hook registration exists. See [Idempotency](/docs/foundations/idempotency#run-idempotency).
|
|
60
60
|
* All arguments must be [serializable](/docs/foundations/serialization).
|
|
61
61
|
* When `deploymentId` is provided, the argument types and return type become `unknown` since there is no guarantee the workflow function's types will be consistent across different deployments.
|
|
62
|
-
* `attributes` seeds plaintext run metadata as part of creation and requires a World implementing spec version 4 or later. Keys that start with `$` are reserved for framework and library code; framework-level callers can pass `allowReservedAttributes: true` to seed reserved keys, with the same semantics as the [`
|
|
62
|
+
* `attributes` seeds plaintext run metadata as part of creation and requires a World implementing spec version 4 or later. Keys that start with `$` are reserved for framework and library code; framework-level callers can pass `allowReservedAttributes: true` to seed reserved keys, with the same semantics as the [`setAttributes`](/docs/api-reference/workflow/set-attributes) option of the same name.
|
|
63
63
|
|
|
64
64
|
<Callout type="info">
|
|
65
65
|
If `start()` throws `'start' received an invalid workflow function. Ensure the Workflow Development Kit is configured correctly and the function includes a 'use workflow' directive.`, the passed function was not transformed as a workflow. The two most common causes are a missing `"use workflow"` directive or missing framework integration. See [start-invalid-workflow-function](/docs/errors/start-invalid-workflow-function).
|
|
@@ -7,6 +7,10 @@ related:
|
|
|
7
7
|
- /docs/api-reference/workflow-api/start
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
<CopyPrompt
|
|
11
|
+
text="Refactor this workflow to use child workflows. Keep the parent as an exported `"use workflow"` function. Move independent units of durable work into separate exported child workflow functions. From the parent, call `start(childWorkflow, [args])` from `workflow/api` or the documented `startAndWait`/hook pattern where completion must resume the parent. Pass only serializable state to children. For fan-out, start children in parallel with `Promise.all` or bounded batches, collect run IDs, handle partial failures with `Promise.allSettled`, and use `getRun(runId)` when status, cancellation, streams, or return values are needed. Verify child start, completion, failure, and parent resume behavior."
|
|
12
|
+
/>
|
|
13
|
+
|
|
10
14
|
Use child workflows when a single workflow needs to orchestrate many independent units of work. Each child runs as its own workflow with a separate event log, retry boundary, and failure scope -- if one child fails, it doesn't take down the parent or siblings.
|
|
11
15
|
|
|
12
16
|
## When to use child workflows
|
|
@@ -5,6 +5,10 @@ type: guide
|
|
|
5
5
|
summary: Learn how to build, export, and test npm packages that ship workflow and step functions — including package.json exports, re-exporting for stable workflow IDs, keeping step I/O clean, and integration testing.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
<CopyPrompt
|
|
9
|
+
text="Package these workflow functions as a publishable npm library. Give the package a dedicated workflows entry point (for example `exports["./workflows"]`) that ships the workflow and step source for the consumer's compiler to process. Keep every workflow and step input and output serializable, and read credentials from environment variables inside steps instead of accepting client instances. Document the consumer re-export requirement: consumers create a file in their `workflows/` directory containing `export * from "<pkg>/workflows"` so their build assigns stable workflow IDs and replay can resolve functions after cold starts. Add an integration test that runs a library workflow end to end from a consumer-style setup. Verify the build, stable IDs across deployments, and replay safety."
|
|
10
|
+
/>
|
|
11
|
+
|
|
8
12
|
import { File, Folder, Files } from "fumadocs-ui/components/files";
|
|
9
13
|
|
|
10
14
|
<Callout>
|
|
@@ -9,6 +9,10 @@ related:
|
|
|
9
9
|
- /docs/foundations/workflows-and-steps#step-functions
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
<CopyPrompt
|
|
13
|
+
text="Make this non-serializable dependency usable inside a durable workflow with the step-as-factory pattern. Instead of passing the object (AI SDK model, cloud SDK client) into the workflow, export a factory that returns an async callback marked with "use step" which constructs and returns the object at execution time, for example `export function openai(...args) { return async () => { "use step"; return openaiProvider(...args); }; }`. Pass the factory across the workflow boundary — the compiler serializes the function reference, not the instance — and invoke it inside steps where full Node.js access is available. Keep the factory's constructor arguments serializable. Verify the workflow builds, replays deterministically, and the dependency is only instantiated during step execution."
|
|
14
|
+
/>
|
|
15
|
+
|
|
12
16
|
<Callout>
|
|
13
17
|
This is an advanced guide. It dives into workflow internals and is not required reading to use workflow.
|
|
14
18
|
</Callout>
|
|
@@ -10,6 +10,10 @@ related:
|
|
|
10
10
|
- /docs/foundations/hooks
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
<CopyPrompt
|
|
14
|
+
text="Add a safe self-upgrade point to this long-running workflow. Identify the loop boundary where no step is mid-side-effect. Define a serializable state object that contains all progress needed to continue. At the boundary, call `start(self, [state], { deploymentId: "latest" })` or the documented replacement workflow with the carried state, then return from the old run. If upgrades should be manual, add a `defineHook()` upgrade signal and resume it from an API route with `resumeHook()` from `workflow/api`. Make the handoff idempotent so retries do not start duplicate successor runs, and verify old-to-new handoff plus duplicate prevention."
|
|
15
|
+
/>
|
|
16
|
+
|
|
13
17
|
Workflows that block on external events for days, weeks, or months can outlive many deployments. **The key is to identify a clean upgrade point in the workflow** — a moment where it's safe to checkpoint state and start fresh — and then call [`start()`](/docs/api-reference/workflow-api/start) with `deploymentId: "latest"` to spawn a new run carrying that state forward. The current run ends; the next run begins on whatever deployment is live at that moment, so shipped fixes apply immediately without ever migrating an in-flight run.
|
|
14
18
|
|
|
15
19
|
<Callout type="info">
|
|
@@ -5,6 +5,10 @@ type: guide
|
|
|
5
5
|
summary: Cancel a running agent cooperatively with AbortController. A stop hook fires controller.abort(), the signal propagates into the agent step to cancel the model stream, and a data-stopped part is emitted to streaming clients before the workflow returns.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
<CopyPrompt
|
|
9
|
+
text="Add cancellation to this durable AI agent. For hard cancellation, expose a server route that receives `runId` and calls `getRun(runId).cancel()` from `workflow/api`. For graceful stop, define `stopHook` with `defineHook()` from `workflow`, create it with a stable token such as the workflow run ID, and race the agent loop against the stop hook using `Promise.race`. Use `getWritable<UIMessageChunk>()` to emit a final stopped/cancelled message before returning. Wire the UI Stop button to the route that resumes the hook or falls back to `getRun(runId).cancel()`. Verify active model/tool work stops, cleanup runs for graceful stop, and stale run IDs are handled."
|
|
10
|
+
/>
|
|
11
|
+
|
|
8
12
|
Cancel a running agent from the outside — for example, a "Stop" button in a chat UI, an admin cancellation endpoint, or a timeout fallback.
|
|
9
13
|
|
|
10
14
|
<Callout type="warn">
|
|
@@ -5,6 +5,10 @@ type: guide
|
|
|
5
5
|
summary: Build durable, resumable AI agents with AI SDK v7's WorkflowAgent.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
<CopyPrompt
|
|
9
|
+
text="Convert this stateless AI SDK agent to a durable agent with AI SDK v7's `WorkflowAgent`. Install/use `@ai-sdk/workflow` and `workflow`. Replace the manual `streamText` loop with `new WorkflowAgent(...)` from `@ai-sdk/workflow`, run inside an exported function with `"use workflow"`. Mark side-effectful tool `execute` functions with `"use step"` so they run as durable workflow steps with retries and observability. Stream `ModelCallStreamPart` chunks with `getWritable()` from `workflow`, convert the run stream to UI message chunks with `createModelCallToUIChunkTransform()` in the API route, and expose a route that starts or reconnects to the run. If migrating from the deprecated `DurableAgent`, follow the migration guide at https://ai-sdk.dev/v7/docs/agents/workflow-agent#migrating-from-durableagent. Verify tool retries, stream output, and resume/reconnect behavior."
|
|
10
|
+
/>
|
|
11
|
+
|
|
8
12
|
## WorkflowAgent from AI SDK v7
|
|
9
13
|
|
|
10
14
|
Use AI SDK v7's [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) for new durable agent work. It replaces `DurableAgent` and keeps the current agent pattern in the AI SDK package.
|
|
@@ -5,6 +5,10 @@ type: guide
|
|
|
5
5
|
summary: Use defineHook with the tool call ID to suspend an agent for human approval, with an optional timeout.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
<CopyPrompt
|
|
9
|
+
text="Add a human approval gate to this AI workflow. Define a typed hook with `defineHook()` from `workflow` for approval payloads. At the approval point, create the hook once with a stable token, await it inside the `"use workflow"` function, and branch on approved/rejected input. Add a server route that receives the human decision and calls `resumeHook(token, payload)` from `workflow/api`. If the approval should expire, race the hook against `sleep()` from `workflow`. Update the UI to show the pending approval and call the resume route. Verify approve, reject, timeout, and duplicate resume behavior."
|
|
10
|
+
/>
|
|
11
|
+
|
|
8
12
|
<Callout type="warn">
|
|
9
13
|
This recipe uses the deprecated `DurableAgent` API. For new agents, use AI SDK's [`WorkflowAgent`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) and follow the [migration guide](https://ai-sdk.dev/v7/docs/agents/workflow-agent#migrating-from-durableagent). The human-in-the-loop pattern here (hooks, `Promise.race`, approval gating) applies to either API.
|
|
10
14
|
</Callout>
|
|
@@ -5,6 +5,10 @@ type: guide
|
|
|
5
5
|
summary: Split items into fixed-size batches, process each batch concurrently with Promise.allSettled, and pace batches with sleep to avoid overloading downstream services.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
<CopyPrompt
|
|
9
|
+
text="Implement durable batch processing. Import `sleep` from `workflow`. In an exported "use workflow" function, split the input records into chunks of a fixed `batchSize`. For each batch, call a "use step" helper such as `processRecord(record)` for every record using `Promise.allSettled` so one record failure does not hide the rest. Record successes and failures in a serializable result object. Between batches, `await sleep("1s")` or another configured delay to respect downstream rate limits. Make the step idempotent using record IDs or external idempotency keys. Verify all-success, partial-failure, and rate-paced execution paths."
|
|
10
|
+
/>
|
|
11
|
+
|
|
8
12
|
Use batching when you need to process a large list of items in parallel while controlling concurrency. Items are split into fixed-size batches, each batch runs concurrently, and failures in one batch don't affect others.
|
|
9
13
|
|
|
10
14
|
## When to use this
|
|
@@ -5,6 +5,10 @@ type: guide
|
|
|
5
5
|
summary: Use step IDs for retry-safe external calls, and use deterministic hook tokens when duplicate requests must route to one active workflow.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
<CopyPrompt
|
|
9
|
+
text="Make this workflow's side effects idempotent. For retry-safe external calls, read the deterministic step ID inside the "use step" function with `getStepMetadata()` from `workflow` and pass `stepId` as the idempotency key to the external API (for example Stripe's `Idempotency-Key` header) so step retries deduplicate. For duplicate workflow starts, derive a deterministic hook token from the domain key (for example `order:${orderId}`): in the API route, look up the active hook with `getHookByToken(token)` from `workflow/api` — catching `HookNotFoundError` from `workflow/errors` — and reuse its `runId`, otherwise call `start(...)`; inside the workflow, create the hook with the same token and check `await hook.getConflict()` before duplicate-sensitive work. Verify retried steps deduplicate, duplicate starts reuse the active run, and conflicts are handled."
|
|
10
|
+
/>
|
|
11
|
+
|
|
8
12
|
Use idempotency when a retry or duplicate request should not repeat the underlying work. In Workflow, there are two common patterns: use the step ID for retry-safe external calls, and use hook tokens to coordinate duplicate workflow starts.
|
|
9
13
|
|
|
10
14
|
## When to use this
|
|
@@ -5,6 +5,10 @@ type: guide
|
|
|
5
5
|
summary: When an external API returns 429, throw RetryableError with the Retry-After value so the workflow runtime automatically reschedules the step after the specified delay.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
<CopyPrompt
|
|
9
|
+
text="Add durable rate-limit handling to external API steps. Import `RetryableError`, `FatalError`, `getStepMetadata`, and `sleep` from `workflow` as needed. In each HTTP-calling "use step" helper, if the response status is 429, read the `Retry-After` header and throw `new RetryableError("Rate limited", { retryAfter })`. For transient 5xx/network failures, throw `RetryableError` with an exponential delay based on `getStepMetadata().attempt`. For permanent 4xx failures, throw `FatalError`. If the dependency is completely down, add a workflow-level circuit breaker that `await sleep("30s")` before probing again. Make requests idempotent with stable keys, and verify 429, 5xx, fatal 4xx, and circuit-breaker behavior."
|
|
10
|
+
/>
|
|
11
|
+
|
|
8
12
|
Use this pattern when calling external APIs that enforce rate limits. Instead of writing manual retry loops, throw `RetryableError` with a `retryAfter` value and let the workflow runtime handle rescheduling.
|
|
9
13
|
|
|
10
14
|
## When to use this
|
|
@@ -5,6 +5,10 @@ type: guide
|
|
|
5
5
|
summary: Run a sequence of steps where each registers a compensation. If any step throws a FatalError, compensations execute in reverse order to restore consistency.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
<CopyPrompt
|
|
9
|
+
text="Implement a saga-style workflow. Import `FatalError` from `workflow`. In the exported "use workflow" function, keep a compensation stack such as `const compensations: Array<() => Promise<void>> = []`. For each forward side effect, create a "use step" helper, await it, then push the matching compensation "use step" helper onto the stack. Throw `FatalError` for permanent business failures and normal errors for retryable failures. In the workflow catch block, run compensations in reverse order with `for (const compensate of compensations.reverse()) await compensate()`, then rethrow or return a failed status. Make each compensation idempotent. Verify successful completion, a fatal failure after multiple steps, and a replay/retry during rollback."
|
|
10
|
+
/>
|
|
11
|
+
|
|
8
12
|
Use the saga pattern when a business transaction spans multiple services and you need automatic rollback if any step fails. Each forward step registers a compensation, and on failure the workflow unwinds them in reverse order.
|
|
9
13
|
|
|
10
14
|
## When to use this
|
|
@@ -5,6 +5,10 @@ type: guide
|
|
|
5
5
|
summary: Schedule future actions with durable sleep that survives cold starts, and race sleeps against hooks to let external events cancel the workflow early.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
<CopyPrompt
|
|
9
|
+
text="Create an interruptible scheduled workflow. Import `defineHook` and `sleep` from `workflow`. Define a cancellation hook such as `export const cancelDrip = defineHook<{ reason?: string }>()`. In an exported workflow function with "use workflow", create the hook once, send the first message in a "use step" helper, then `await Promise.race([sleep("2d").then(() => false), cancelHook.then(() => true)])` before each delayed action. If the hook wins, return a cancelled status; if sleep wins, continue to the next step. Add an API route that calls `cancelDrip.resume(runIdOrToken, { reason })` or `resumeHook()` from `workflow/api`. Verify scheduled delivery, cancellation before sleep completes, and resume after server restart."
|
|
10
|
+
/>
|
|
11
|
+
|
|
8
12
|
Workflow's `sleep()` is durable — it survives cold starts, restarts, and deployments. Combined with `defineHook()` and `Promise.race()`, it becomes the foundation for interruptible scheduled workflows like drip campaigns, reminders, and timed sequences.
|
|
9
13
|
|
|
10
14
|
<Callout type="info">
|
|
@@ -9,6 +9,10 @@ related:
|
|
|
9
9
|
- /cookbook/common-patterns/scheduling
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
<CopyPrompt
|
|
13
|
+
text="Compose these workflow steps with standard async/await patterns. In the exported "use workflow" function, chain dependent "use step" calls with sequential `await`; run independent steps concurrently by starting them without `await` and awaiting `Promise.all([...])`; and use `Promise.race([...])` to act on whichever promise settles first. These compose with durable primitives — race a step or a webhook from `createWebhook()` against `sleep()` from `workflow` for deadlines. Keep every step input and output serializable, and remember `Promise.race` does not cancel the losing branch — it keeps running — so side-effectful losers need idempotency keys. Verify sequential ordering, parallel execution, and both race outcomes."
|
|
14
|
+
/>
|
|
15
|
+
|
|
12
16
|
Workflows are written in plain async/await — there's no new control-flow API to learn. Sequential awaits chain steps that depend on each other, `Promise.all` runs independent steps in parallel, and `Promise.race` returns whichever finishes first. These compose with workflow primitives like [`sleep()`](/docs/api-reference/workflow/sleep) and [`createWebhook()`](/docs/api-reference/workflow/create-webhook) since those are also just promises.
|
|
13
17
|
|
|
14
18
|
## When to use this
|
|
@@ -10,6 +10,10 @@ related:
|
|
|
10
10
|
- /cookbook/common-patterns/webhooks
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
<CopyPrompt
|
|
14
|
+
text="Add a deadline to this slow workflow operation. Import `sleep` from `workflow`. In the exported "use workflow" function, race the operation against a durable sleep: `await Promise.race([slowStep(...), deadline("10m")])`, where `deadline` is a helper that awaits `sleep(duration)` and returns a sentinel value or throws. Branch on the winner: continue normally on success, run the fallback or compensation path on timeout. Use the same pattern to bound hooks and webhooks. `Promise.race` does not cancel the loser — the underlying step keeps running — so pass an `AbortSignal` into the step for cooperative cancellation and make non-idempotent side effects retry-safe with idempotency keys. Verify the fast path, the timeout path, and side-effect safety when the loser completes late."
|
|
15
|
+
/>
|
|
16
|
+
|
|
13
17
|
A common requirement is bounding how long a workflow waits for something to finish — a slow step, an external webhook, a human approval. Race the operation against a durable `sleep()` with `Promise.race()` — whichever finishes first wins, and the loser keeps running but its result is ignored.
|
|
14
18
|
|
|
15
19
|
## When to use this
|
|
@@ -5,6 +5,10 @@ type: guide
|
|
|
5
5
|
summary: Create webhook endpoints that your workflow can await, process incoming requests in steps, and respond to the caller — all within durable workflow context.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
<CopyPrompt
|
|
9
|
+
text="Implement durable webhook handling for this workflow. Use `createWebhook()` from `workflow` inside the "use workflow" function when the workflow needs a generated callback URL. Use `createWebhook({ respondWith: "manual" })` when the workflow must validate the request before sending an HTTP response. Await the webhook request, pass the `RequestWithResponse` into a "use step" function for validation and side effects, and call `request.respondWith(Response.json(...))` on every code path. Race the webhook against `sleep()` for deadlines and throw `FatalError` for permanent timeout/failure paths. For fixed public callbacks or large payloads, use `defineHook()` plus `resumeHook()` from `workflow/api` and pass only a token or blob reference into the hook payload. Verify success response, invalid response, timeout, duplicate callback, and large-payload behavior."
|
|
10
|
+
/>
|
|
11
|
+
|
|
8
12
|
Use webhooks when external services push events to your application via HTTP callbacks. The workflow creates a webhook URL, suspends with zero compute cost, and resumes when a request arrives.
|
|
9
13
|
|
|
10
14
|
## When to use this
|
|
@@ -10,6 +10,10 @@ related:
|
|
|
10
10
|
- /docs/api-reference/workflow-api/get-run
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
<CopyPrompt
|
|
14
|
+
text="Compose these workflows. For direct composition, `await` the child workflow function from the parent "use workflow" function — the child's steps flatten into the parent's event log and run as a single run sharing the parent's lifecycle. For independent background work, spawn the child with `start()` from `workflow/api` called directly in the workflow (in v5, `start()` is step-backed and records a deterministic step boundary) and return `run.runId` so callers can track it with `getRun()`. Choose flattening when the parent needs the child's result; choose background spawn when the child should have its own run, retries, and lifetime. Keep all inputs and outputs serializable. Verify flattened execution, background spawn with a separate runId, and replay determinism."
|
|
15
|
+
/>
|
|
16
|
+
|
|
13
17
|
Workflows can call other workflows. Choose between two composition modes depending on whether the parent needs the child's result inline (direct await) or wants to fire the child off as an independent run (background spawn). For massive fan-out with hook-based waiting and partial-failure handling, see [Child Workflows](/cookbook/advanced/child-workflows).
|
|
14
18
|
|
|
15
19
|
## When to use this
|
|
@@ -11,6 +11,10 @@ related:
|
|
|
11
11
|
- /docs/api-reference/workflow-ai/durable-agent
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
<CopyPrompt
|
|
15
|
+
text="Implement the durable AI SDK multi-turn pattern. Use `streamText`, `stepCountIs`, and `createUIMessageStreamResponse` from `ai`; `defineHook`, `getWritable`, and `getWorkflowMetadata` from `workflow`; and `start`/`getRun` from `workflow/api`. Put the model call in a `"use step"` function such as `runTurn(messages)` and pipe `result.toUIMessageStream()` to `getWritable<UIMessageChunk>()` with `{ preventClose: true }`. In the workflow, create one hook with `turnHook.create({ token: workflowRunId })`, loop over turns, and await the hook between user messages. Add an API route that starts a run on first message, stores/returns the run ID in `x-workflow-run-id`, resumes the hook for follow-up messages, reads from `run.getReadable({ startIndex })`, and handles stale run IDs by starting fresh. Wire the client transport to send `runId` with each request and verify first turn, follow-up turn, `/done`, and reconnect behavior."
|
|
16
|
+
/>
|
|
17
|
+
|
|
14
18
|
[AI SDK](https://ai-sdk.dev/) is Vercel's framework-agnostic TypeScript toolkit for building AI-powered apps and agents — unified provider access, streaming, tool calling, structured output, and UI hooks. Workflow SDK complements it by making the multi-turn loop durable: the conversation state, hooks, and per-turn responses survive restarts and timeouts. Note that in this pattern the durability boundary is the entire turn — individual tool calls inside a turn are **not** durable on their own (see [Pitfalls](#tools-are-not-individually-durable) below).
|
|
15
19
|
|
|
16
20
|
For the full AI SDK reference (providers, `streamText`, `generateObject`, `useChat`, tool calling, etc.) see the [AI SDK docs](https://ai-sdk.dev/docs). This page covers the Workflow-specific integration points.
|
|
@@ -11,6 +11,10 @@ related:
|
|
|
11
11
|
- /docs/api-reference/workflow-api/get-run
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
<CopyPrompt
|
|
15
|
+
text="Make this Chat SDK bot durable with Workflow SDK. Install/use `workflow`. Create one exported workflow function with "use workflow" per chat thread. Store the Chat SDK thread ID, Workflow run ID, and any serialized conversation state in the project data store. Use `defineHook()` from `workflow` for incoming turns and call `resumeHook()` from `workflow/api` from the Chat SDK webhook or message handler. Put provider calls, database writes, and outbound platform messages in "use step" helper functions. Start a new run with `start(workflowFn, [initialThreadState])` when no run exists, otherwise resume the existing hook. Use `getRun(runId)` for status, cancellation, or stream reads. Verify first message, follow-up message, restart/reconnect, duplicate webhook, and failed-send retry behavior."
|
|
16
|
+
/>
|
|
17
|
+
|
|
14
18
|
[Chat SDK](https://chat-sdk.dev/) is a unified TypeScript SDK for building bots across Slack, Microsoft Teams, Google Chat, Discord, Telegram, GitHub, Linear, and WhatsApp. Write the bot once, deploy to every platform. It handles webhook verification, event normalization, subscriptions, and cross-platform features like cards and modals.
|
|
15
19
|
|
|
16
20
|
Workflow SDK complements it by making bot **sessions** durable. Each conversation thread maps to a long-running workflow run that:
|
|
@@ -10,6 +10,10 @@ related:
|
|
|
10
10
|
- /docs/cookbook/agent-patterns/durable-agent
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
<CopyPrompt
|
|
14
|
+
text="Implement a durable Vercel Sandbox-backed coding-agent workflow. Install the Sandbox package used by this project and `workflow`. Create an exported workflow function with "use workflow" that owns the agent session. Put sandbox creation, command execution, snapshot, refresh, and cleanup into helper functions with "use step". Persist the sandbox ID, snapshot ID, and workflow run ID in the project data store so clients can reconnect. Use `getWritable()` from `workflow` to stream agent progress and command output. Use `sleep()` to hibernate, refresh, or enforce idle timeouts. Add API routes to start a session, reconnect by run ID, and stop/cleanup. Verify first run, reconnect after reload, snapshot restore, timeout, and cleanup behavior."
|
|
15
|
+
/>
|
|
16
|
+
|
|
13
17
|
[Vercel Sandbox](https://vercel.com/docs/sandbox) provides isolated code execution environments. The `@vercel/sandbox` package has first-class support for the Workflow SDK — the `Sandbox` class is serializable, and its methods (`create`, `runCommand`, `stop`, `snapshot`) implicitly run as steps. You can use `Sandbox` directly inside a workflow function without wrapping each call in a separate `"use step"` function.
|
|
14
18
|
|
|
15
19
|
## Why Workflow + Sandbox
|
|
@@ -11,6 +11,10 @@ related:
|
|
|
11
11
|
- /docs/errors/timeout-in-workflow
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
<CopyPrompt
|
|
15
|
+
text="Replace `AbortSignal.timeout()` inside workflow functions. Find the failing `AbortSignal.timeout(ms)` call in a `"use workflow"` function. Import `sleep` from `workflow`. Create `const controller = new AbortController()`, pass `controller.signal` into the async work that supports cancellation, and start a deterministic timeout with `void sleep("10s").then(() => controller.abort())` or a `Promise.race` between the work and `sleep(...)`. Keep actual fetch/SDK side effects inside `"use step"` helpers when they need Node.js APIs. Treat intentional aborts as non-retryable if appropriate by throwing `FatalError`. Verify the operation succeeds before the timeout, aborts after the timeout, and replays without abort-signal-timeout-in-workflow."
|
|
16
|
+
/>
|
|
17
|
+
|
|
14
18
|
## Error
|
|
15
19
|
|
|
16
20
|
```
|
|
@@ -9,6 +9,10 @@ related:
|
|
|
9
9
|
- /docs/api-reference/workflow/fetch
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
<CopyPrompt
|
|
13
|
+
text="Fix `fetch` usage inside workflow functions. Search workflow files for direct global `fetch(...)` calls and libraries such as AI SDK calls that use fetch. For simple HTTP calls inside a `"use workflow"` function, import `{ fetch }` from `workflow` and replace the global call. For SDK/client calls that need normal Node.js or provider behavior, move the call into a helper function with `"use step"` and call that step from the workflow. Keep all step inputs and outputs serializable. Verify the workflow starts and replays without the fetch-in-workflow error."
|
|
14
|
+
/>
|
|
15
|
+
|
|
12
16
|
This error occurs when you try to use `fetch()` directly in a workflow function, or when a library (like the AI SDK) tries to call `fetch()` under the hood.
|
|
13
17
|
|
|
14
18
|
## Error Message
|
|
@@ -10,6 +10,10 @@ related:
|
|
|
10
10
|
- /docs/api-reference/workflow/define-hook
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
<CopyPrompt
|
|
14
|
+
text="Fix hook token conflicts. Find every `createHook({ token })` or typed hook creation site. If multiple waits can exist at the same time, include a unique stable discriminator in the token such as `${workflowRunId}:approval:${itemId}` or `${orderId}:${attempt}` instead of reusing one global token. If duplicate work should join an existing run, catch `HookConflictError` from `@workflow/errors`, read the conflicting run ID from the error/result if available, and use `getRun(runId)` plus `resumeHook()` from `workflow/api` to deliver the payload to the active run. Keep token generation deterministic across retries so replay does not create new hook identities. Verify two concurrent runs and a duplicate request no longer throw hook-conflict unexpectedly."
|
|
15
|
+
/>
|
|
16
|
+
|
|
13
17
|
This error occurs when you try to create a hook with a token that is already in use by another active workflow run. Hook tokens must be unique across all running workflows in your project.
|
|
14
18
|
|
|
15
19
|
## Error Message
|
|
@@ -9,6 +9,10 @@ related:
|
|
|
9
9
|
- /docs/how-it-works/understanding-directives
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
<CopyPrompt
|
|
13
|
+
text="Fix Node.js module usage inside workflow functions. Search workflow files for imports or direct usage of Node-only APIs such as `fs`, `path`, `crypto`, `process`, `http`, or SDK clients. Remove those imports from files/functions that execute under `"use workflow"`. Create helper functions with `"use step"` for filesystem, crypto, environment, network, database, or SDK work, and call those helpers from the workflow. Keep the workflow function limited to deterministic orchestration, serializable values, `sleep`, hooks, and step calls. Verify the workflow starts without node-js-module-in-workflow errors."
|
|
14
|
+
/>
|
|
15
|
+
|
|
12
16
|
This error occurs when you try to import or use Node.js core modules (like `fs`, `http`, `crypto`, `path`, etc.) directly inside a workflow function.
|
|
13
17
|
|
|
14
18
|
## Error Message
|
|
@@ -9,6 +9,10 @@ related:
|
|
|
9
9
|
- /docs/foundations/workflows-and-steps
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
<CopyPrompt
|
|
13
|
+
text="Fix Workflow serialization errors. Find the failing `start()` call, workflow argument, step return value, hook payload, or stream chunk. Replace non-serializable values such as class instances, functions, SDK clients, Response/Request objects, streams, database connections, Dates that need custom handling, Maps/Sets, or circular objects with plain JSON-compatible data, IDs, strings, numbers, booleans, arrays, and objects. Recreate runtime-only clients or objects inside `"use step"` helpers instead of passing them through the workflow log. For external resources, pass a stable ID or URL and load the resource inside the step. Add a test or local route call that serializes the same input/output path successfully."
|
|
14
|
+
/>
|
|
15
|
+
|
|
12
16
|
This error occurs when you try to pass non-serializable data between execution boundaries in your workflow. All data passed between workflow functions, step functions, and the workflow runtime must be serializable to persist in the event log.
|
|
13
17
|
|
|
14
18
|
## Error Message
|
|
@@ -11,6 +11,10 @@ related:
|
|
|
11
11
|
- /docs/api-reference/workflow-next/with-workflow
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
<CopyPrompt
|
|
15
|
+
text="Fix `start()` receiving an invalid workflow function. Find the function passed to `start()` from `workflow/api`. Ensure the target function is directly imported, exported from its workflow file, and contains the literal `"use workflow"` directive at the top of the function body. Do not pass wrapper callbacks like `start(async () => workflowFn())`; call `start(workflowFn, [args])`. Verify the framework integration is configured (`withWorkflow()` in Next.js, `workflow()`/`workflowPlugin()` in Vite/Astro/SvelteKit, `workflow/nitro`, `workflow/nuxt`, or `@workflow/nest` as appropriate) and that the workflow file is inside a transformed directory. Add a local route/test that calls `start(workflowFn, args)` and confirms a run is created."
|
|
16
|
+
/>
|
|
17
|
+
|
|
14
18
|
This error occurs when `start()` receives a function that does not have Workflow SDK's generated workflow metadata. In practice, that usually means the function is missing `"use workflow"` or the file was never transformed by your framework integration.
|
|
15
19
|
|
|
16
20
|
## Error Message
|
|
@@ -9,6 +9,10 @@ related:
|
|
|
9
9
|
- /docs/api-reference/workflow/sleep
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
<CopyPrompt
|
|
13
|
+
text="Fix timer usage inside workflow functions. Search `"use workflow"` functions for `setTimeout`, `setInterval`, `timers/promises`, polling loops, or `AbortSignal.timeout()`. Replace workflow delays with `await sleep("5s")`, `await sleep("24h")`, or `await sleep(date)` from `workflow`. For polling, use a workflow loop that calls a `"use step"` helper to check external state and then `await sleep(...)` between attempts. If a step itself needs a short in-process delay, keep that timer inside the `"use step"` function only. Verify the workflow can replay and resume after the durable sleep."
|
|
14
|
+
/>
|
|
15
|
+
|
|
12
16
|
This error occurs when you try to use `setTimeout()`, `setInterval()`, or related timing functions directly inside a workflow function.
|
|
13
17
|
|
|
14
18
|
## Error Message
|
|
@@ -9,6 +9,10 @@ related:
|
|
|
9
9
|
- /docs/api-reference/workflow/create-webhook
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
<CopyPrompt
|
|
13
|
+
text="Fix webhook-response-not-sent errors. Find `createWebhook({ respondWith: "manual" })` usage. In the workflow, await the webhook request and pass the `RequestWithResponse` into a `"use step"` helper for validation and side effects. In every success, validation failure, catch, and early-return branch, call `await request.respondWith(new Response(...))` or `await request.respondWith(Response.json(...))` exactly once before the webhook completes. If manual control is not needed, remove `respondWith: "manual"` or use a static `Response` option. Add tests or local webhook calls for success, invalid input, and thrown-error paths."
|
|
14
|
+
/>
|
|
15
|
+
|
|
12
16
|
This error occurs when a webhook is configured with `respondWith: "manual"` but the workflow does not send a response using `request.respondWith()` before the webhook execution completes.
|
|
13
17
|
|
|
14
18
|
## Error Message
|
|
@@ -9,6 +9,10 @@ related:
|
|
|
9
9
|
- /docs/foundations/workflows-and-steps
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
<CopyPrompt
|
|
13
|
+
text="In this Astro app, run `npm i workflow`. In `astro.config.mjs`, import `workflow` from `workflow/astro` and add `integrations: [workflow()]`. Add the TypeScript plugin `{ "name": "workflow" }` to `tsconfig.json` if TypeScript is used. Create `src/workflows/user-signup.ts` exporting `handleUserSignup(email)` with `"use workflow"`, `sleep` from `workflow`, and `"use step"` helpers. Add `src/pages/api/signup.ts` exporting `POST: APIRoute` that reads `{ email }`, calls `start(handleUserSignup, [email])` from `workflow/api`, returns `Response.json`, and sets `prerender = false`. Run `npm run dev`, call `curl -X POST --json '{"email":"hello@example.com"}' http://localhost:4321/api/signup`, and inspect with `npx workflow inspect runs`."
|
|
14
|
+
/>
|
|
15
|
+
|
|
12
16
|
This guide will walk through setting up your first workflow in an Astro app. Along the way, you'll learn more about the concepts that are fundamental to using the Workflow SDK in your own projects.
|
|
13
17
|
|
|
14
18
|
---
|
|
@@ -9,6 +9,10 @@ related:
|
|
|
9
9
|
- /docs/foundations/workflows-and-steps
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
<CopyPrompt
|
|
13
|
+
text="In this Express app, run `npm i workflow express nitro rollup` and, if using TypeScript, `npm i -D @types/express`. Create `nitro.config.ts` with `modules: ["workflow/nitro"]`, `vercel: { entryFormat: "node" }`, and `routes: { "/**": { handler: "./src/index.ts", format: "node" } }`. Add package scripts `dev: "nitro dev"` and `build: "nitro build"`. Create `workflows/user-signup.ts` with `"use workflow"`, `sleep`, and `"use step"` helpers. Add `src/index.ts` with Express JSON middleware, POST `/api/signup`, and `start(handleUserSignup, [email])` from `workflow/api`. Run `npm run dev`, call `curl -X POST --json '{"email":"hello@example.com"}' http://localhost:3000/api/signup`, and inspect with `npx workflow web` or `npx workflow inspect runs`."
|
|
14
|
+
/>
|
|
15
|
+
|
|
12
16
|
This guide will walk through setting up your first workflow in an Express app. Along the way, you'll learn more about the concepts that are fundamental to using the Workflow SDK in your own projects.
|
|
13
17
|
|
|
14
18
|
---
|
|
@@ -9,6 +9,10 @@ related:
|
|
|
9
9
|
- /docs/foundations/workflows-and-steps
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
<CopyPrompt
|
|
13
|
+
text="In this Fastify app, run `npm i workflow fastify nitro rollup` and, if using TypeScript, `npm i -D @types/node typescript`. Create `nitro.config.ts` with `modules: ["workflow/nitro"]`, `vercel: { entryFormat: "node" }`, and `routes: { "/**": { handler: "./src/index.ts", format: "node" } }`. Add package scripts `dev: "nitro dev"` and `build: "nitro build"`. Create `workflows/user-signup.ts` with `"use workflow"`, `sleep`, and `"use step"` helpers. Add `src/index.ts` with a Fastify app, POST `/api/signup`, `start(handleUserSignup, [email])`, `await app.ready()`, and an exported request handler. Run `npm run dev`, call `curl -X POST --json '{"email":"hello@example.com"}' http://localhost:3000/api/signup`, and inspect with `npx workflow inspect runs --web`."
|
|
14
|
+
/>
|
|
15
|
+
|
|
12
16
|
This guide will walk through setting up your first workflow in a Fastify app. Along the way, you'll learn more about the concepts that are fundamental to using the Workflow SDK in your own projects.
|
|
13
17
|
|
|
14
18
|
---
|
|
@@ -9,6 +9,10 @@ related:
|
|
|
9
9
|
- /docs/foundations/workflows-and-steps
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
<CopyPrompt
|
|
13
|
+
text="In this Hono app, run `npm i workflow nitro rollup`. Create `nitro.config.ts` with `modules: ["workflow/nitro"]` and `routes: { "/**": "./src/index.ts" }`. Add package scripts `dev: "nitro dev"` and `build: "nitro build"`. Add the TypeScript plugin `{ "name": "workflow" }` to `tsconfig.json` if TypeScript is used. Create `workflows/user-signup.ts` with `handleUserSignup(email)`, `"use workflow"`, `sleep` from `workflow`, and `"use step"` helpers. Add `src/index.ts` with a Hono app, POST `/api/signup`, `start(handleUserSignup, [email])` from `workflow/api`, and JSON response. Run `npm run dev`, call `curl -X POST --json '{"email":"hello@example.com"}' http://localhost:3000/api/signup`, and inspect with `npx workflow web` or `npx workflow inspect runs`."
|
|
14
|
+
/>
|
|
15
|
+
|
|
12
16
|
<Steps>
|
|
13
17
|
|
|
14
18
|
<Step>
|
|
@@ -9,6 +9,10 @@ related:
|
|
|
9
9
|
- /docs/foundations/workflows-and-steps
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
<CopyPrompt
|
|
13
|
+
text="In this NestJS app, run `npm i workflow @workflow/nest` and `npm i -D @swc/cli @swc/core`. Configure `nest-cli.json` with `compilerOptions.builder: "swc"` and `deleteOutDir: true`. Run `npx @workflow/nest init`, add `.swcrc` to `.gitignore`, and set package scripts `prebuild: "npx @workflow/nest init --force"` and `start:dev: "npx @workflow/nest init --force && nest start --watch"`. Import `WorkflowModule.forRoot()` from `@workflow/nest` in `src/app.module.ts` (use `{ moduleType: "commonjs", distDir: "dist" }` if compiling CommonJS). Create `src/workflows/user-signup.ts` with `"use workflow"`, `sleep`, and `"use step"` helpers. Add a `POST /signup` controller method that reads `email`, calls `start(handleUserSignup, [email])` from `workflow/api`, and returns JSON. Run `npm run start:dev`, call `curl -X POST --json '{"email":"hello@example.com"}' http://localhost:3000/signup`, and inspect with `npx workflow web` or `npx workflow inspect runs`."
|
|
14
|
+
/>
|
|
15
|
+
|
|
12
16
|
This guide will walk through setting up your first workflow in a NestJS app. Along the way, you'll learn more about the concepts that are fundamental to using the Workflow SDK in your own projects.
|
|
13
17
|
|
|
14
18
|
<Callout>
|
|
@@ -10,6 +10,10 @@ related:
|
|
|
10
10
|
- /docs/deploying/world/vercel-world
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
<CopyPrompt
|
|
14
|
+
text="In this Next.js app, run `npm i workflow`. Wrap `next.config.ts` with `withWorkflow` from `workflow/next`. If the app has `proxy.ts` or middleware, exclude `.well-known/workflow/` from its matcher. Add `workflows/user-signup.ts` exporting `handleUserSignup(email)` with `"use workflow"`, `sleep` from `workflow`, and `"use step"` helper functions that create a user, send a welcome email, and send an onboarding email. Add `app/api/signup/route.ts` with a POST handler that reads `{ email }`, calls `start(handleUserSignup, [email])` from `workflow/api`, and returns JSON. Run `npm run dev`, trigger `curl -X POST --json '{"email":"hello@example.com"}' http://localhost:3000/api/signup`, and inspect with `npx workflow web` or `npx workflow inspect runs`."
|
|
15
|
+
/>
|
|
16
|
+
|
|
13
17
|
<Steps>
|
|
14
18
|
|
|
15
19
|
<Step>
|
|
@@ -9,6 +9,10 @@ related:
|
|
|
9
9
|
- /docs/foundations/workflows-and-steps
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
<CopyPrompt
|
|
13
|
+
text="In this Nitro app, run `npm i workflow`. In `nitro.config.ts`, use `defineConfig` from `nitro`, set `serverDir: "./server"`, and add `modules: ["workflow/nitro"]`. Add the TypeScript plugin `{ "name": "workflow" }` to `tsconfig.json` if TypeScript is used. Create `workflows/user-signup.ts` with `handleUserSignup(email)`, `"use workflow"`, `sleep` from `workflow`, and `"use step"` helpers. Add `server/api/signup.post.ts` using `defineEventHandler` from `nitro/h3` and `start` from `workflow/api` to start the workflow from `{ email }`. Run `npm run dev`, call `curl -X POST --json '{"email":"hello@example.com"}' http://localhost:3000/api/signup`, then inspect with `npx workflow web` or `npx workflow inspect runs`."
|
|
14
|
+
/>
|
|
15
|
+
|
|
12
16
|
<Steps>
|
|
13
17
|
|
|
14
18
|
<Step>
|
|
@@ -9,6 +9,10 @@ related:
|
|
|
9
9
|
- /docs/foundations/workflows-and-steps
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
<CopyPrompt
|
|
13
|
+
text="In this Nuxt app, run `npm i workflow`. In `nuxt.config.ts`, add `modules: ["workflow/nuxt"]` and keep `compatibilityDate: "latest"`. Create `workflows/user-signup.ts` exporting `handleUserSignup(email)` with `"use workflow"`, `sleep` from `workflow`, and `"use step"` helpers that create a user and send emails. Add `server/api/signup.post.ts` using `defineEventHandler` from `h3` or `nitro/h3` and `start` from `workflow/api` to read `{ email }` and start the workflow. Run `npm run dev`, call `curl -X POST --json '{"email":"hello@example.com"}' http://localhost:3000/api/signup`, and inspect with `npx workflow web` or `npx workflow inspect runs`."
|
|
14
|
+
/>
|
|
15
|
+
|
|
12
16
|
<Steps>
|
|
13
17
|
|
|
14
18
|
<Step>
|
|
@@ -10,6 +10,10 @@ related:
|
|
|
10
10
|
- /docs/foundations/workflows-and-steps
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
<CopyPrompt
|
|
14
|
+
text="In this Python project, run `pip install vercel`. Add `vercel.json` with `experimentalServices.ai_content_workflow.type = "worker"`, `entrypoint = "app/workflows/ai_content_workflow.py"`, and `topics = ["__wkf_*"]`. 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("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 worker entrypoint and route names match the project."
|
|
15
|
+
/>
|
|
16
|
+
|
|
13
17
|
<Callout type="warn">
|
|
14
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).
|
|
15
19
|
</Callout>
|
|
@@ -9,6 +9,10 @@ related:
|
|
|
9
9
|
- /docs/foundations/workflows-and-steps
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
<CopyPrompt
|
|
13
|
+
text="In this SvelteKit app, run `npm i workflow`. In `vite.config.ts`, import `workflowPlugin` from `workflow/sveltekit` and add it to `plugins` with `sveltekit()`. Add the TypeScript plugin `{ "name": "workflow" }` to `tsconfig.json` if TypeScript is used. Create `workflows/user-signup.ts` exporting `handleUserSignup(email)` with `"use workflow"`, `sleep` from `workflow`, and `"use step"` helpers that create a user and send emails. Add `src/routes/api/signup/+server.ts` with a POST `RequestHandler` that reads `{ email }`, calls `start(handleUserSignup, [email])` from `workflow/api`, and returns `json({ message: "User signup workflow started" })`. Run `npm run dev`, call `curl -X POST --json '{"email":"hello@example.com"}' http://localhost:5173/api/signup`, then inspect with `npx workflow web` or `npx workflow inspect runs`."
|
|
14
|
+
/>
|
|
15
|
+
|
|
12
16
|
<Steps>
|
|
13
17
|
|
|
14
18
|
<Step>
|
|
@@ -9,6 +9,10 @@ related:
|
|
|
9
9
|
- /docs/foundations/workflows-and-steps
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
<CopyPrompt
|
|
13
|
+
text="In this TanStack Start app, run `npm i workflow`. In `vite.config.ts`, import `workflow` from `workflow/vite` and add `workflow()` first in the existing `plugins` array before `tanstackStart()`, `nitro()`, or other plugins. Add `{ "name": "workflow" }` to `compilerOptions.plugins` in `tsconfig.json` if TypeScript is used. Create `src/workflows/user-signup.ts` with `handleUserSignup(email)`, `"use workflow"`, `sleep`, and `"use step"` helpers. Add `src/routes/api/signup.ts` using `createFileRoute("/api/signup")`, a POST server handler, `start` from `workflow/api`, and `json` from `@tanstack/react-start`. Run `npm run dev`, call `curl -X POST --json '{"email":"hello@example.com"}' http://localhost:3000/api/signup`, and inspect with `npx workflow web` or `npx workflow inspect runs`."
|
|
14
|
+
/>
|
|
15
|
+
|
|
12
16
|
This guide will walk through setting up your first workflow in a TanStack Start app. Along the way, you'll learn more about the concepts that are fundamental to using the Workflow SDK in your own projects.
|
|
13
17
|
|
|
14
18
|
---
|
|
@@ -9,6 +9,10 @@ related:
|
|
|
9
9
|
- /docs/foundations/workflows-and-steps
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
<CopyPrompt
|
|
13
|
+
text="In this Vite app, run `npm i workflow nitro`. In `vite.config.ts`, import `nitro` from `nitro/vite`, `workflow` from `workflow/vite`, and configure `plugins: [nitro(), workflow()]` plus `nitro: { serverDir: "./" }`. Add the TypeScript plugin `{ "name": "workflow" }` to `tsconfig.json` if TypeScript is used. Create `workflows/user-signup.ts` with `"use workflow"`, `sleep`, and `"use step"` helpers. Add `api/signup.post.ts` using `defineEventHandler` from `nitro/h3` and `start` from `workflow/api`. Run `npm run dev`, call `curl -X POST --json '{"email":"hello@example.com"}' http://localhost:3000/api/signup`, and inspect with `npx workflow web` or `npx workflow inspect runs`."
|
|
14
|
+
/>
|
|
15
|
+
|
|
12
16
|
This guide will walk through setting up your first workflow in a Vite app. Along the way, you'll learn more about the concepts that are fundamental to using the Workflow SDK in your own projects.
|
|
13
17
|
|
|
14
18
|
---
|
|
@@ -14,6 +14,10 @@ related:
|
|
|
14
14
|
- /docs/deploying/world/vercel-world
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
+
<CopyPrompt
|
|
18
|
+
text="Replace this AWS Step Functions state machine with Workflow SDK. Run `npm i workflow`. Translate the ASL state machine into an exported async TypeScript function with `"use workflow"`. Move each Lambda/Task side effect into a helper function with `"use step"`. Replace Wait states with `sleep()` from `workflow`. Replace callback tokens with `defineHook()` plus `resumeHook()` from `workflow/api`, or `createWebhook()` for HTTP callbacks. Replace Parallel and Map states with `Promise.all`, bounded batching, or child workflow runs started with `start()`. Replace Retry/Catch rules with normal errors, `RetryableError`, and `FatalError`. Add an API route or server function that starts the workflow with `start(workflowFn, args)` and verify it against the previous state machine behavior."
|
|
19
|
+
/>
|
|
20
|
+
|
|
17
21
|
Move an AWS Step Functions state machine to the Workflow SDK by replacing JSON state definitions with TypeScript functions. This guide shows the direct mapping between ASL states and Workflow SDK primitives.
|
|
18
22
|
|
|
19
23
|
<Callout type="info">
|
|
@@ -14,6 +14,10 @@ related:
|
|
|
14
14
|
- /docs/deploying/world/vercel-world
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
+
<CopyPrompt
|
|
18
|
+
text="Migrate this Inngest code to Workflow SDK. Run `npm i workflow`. Replace each `inngest.createFunction` handler with an exported async function that contains `"use workflow"`. Replace each `step.run()` callback with a helper function that contains `"use step"`. Replace `step.sleep()` with `sleep()` from `workflow`. Replace event waits with `defineHook()` or `createWebhook()` from `workflow`, and resume them from server routes with `resumeHook()` or webhook requests. Start runs from API routes or server code with `start(workflowFn, args)` from `workflow/api`, and use `getRun(runId)` where callers need status, streams, cancellation, or results. Preserve retries by throwing normal errors for retryable failures and `FatalError` for permanent failures. Add a local test or route call that starts the migrated workflow and verifies the same observable behavior as the Inngest function."
|
|
19
|
+
/>
|
|
20
|
+
|
|
17
21
|
<Callout type="info">
|
|
18
22
|
Install the Workflow SDK migration skill:
|
|
19
23
|
|
|
@@ -14,6 +14,10 @@ related:
|
|
|
14
14
|
- /docs/deploying/world/vercel-world
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
+
<CopyPrompt
|
|
18
|
+
text="Refactor this Temporal TypeScript code to Workflow SDK. Run `npm i workflow`. Replace Temporal workflow functions with exported async functions containing `"use workflow"`. Replace Activities with helper functions containing `"use step"`. Replace timers with `sleep()` from `workflow`. Replace Signals with `defineHook()` plus `resumeHook()` from `workflow/api`, or `createWebhook()` when an HTTP callback URL is needed. Replace Child Workflows with `start(childWorkflow, args)` and coordinate completion with hooks or `getRun()`. Remove Temporal workers; instead expose API routes or server functions that call `start()` and `getRun()` from `workflow/api`. Preserve idempotency and retries with normal errors, `RetryableError`, and `FatalError`, then add a verification path for each migrated workflow."
|
|
19
|
+
/>
|
|
20
|
+
|
|
17
21
|
<Callout type="info">
|
|
18
22
|
Install the Workflow SDK migration skill:
|
|
19
23
|
|
|
@@ -14,6 +14,10 @@ related:
|
|
|
14
14
|
- /docs/deploying/world/vercel-world
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
+
<CopyPrompt
|
|
18
|
+
text="Convert this trigger.dev setup to Workflow SDK. Run `npm i workflow`. Replace each `task()` or job entrypoint with an exported async workflow function containing `"use workflow"`. Replace trigger.dev task bodies that do side effects with `"use step"` helper functions. Replace waits/delays with `sleep()` from `workflow`. Replace external triggers or resume points with `defineHook()`/`resumeHook()` or `createWebhook()`. Replace run creation/status calls with `start()` and `getRun()` from `workflow/api`. Replace metadata/progress streaming with `getWritable()` from `workflow` when the UI needs live updates. Preserve retry semantics with normal errors, `RetryableError`, and `FatalError`, then add a local verification that starts the migrated workflow and checks the expected result or stream."
|
|
19
|
+
/>
|
|
20
|
+
|
|
17
21
|
<Callout type="info">
|
|
18
22
|
Install the Workflow SDK migration skill:
|
|
19
23
|
|
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Attributes
|
|
3
|
-
description: Attach
|
|
3
|
+
description: Attach metadata to workflow runs for observability.
|
|
4
4
|
type: reference
|
|
5
5
|
summary: Add string attributes to a workflow run.
|
|
6
6
|
prerequisites:
|
|
7
7
|
- /docs/foundations/workflows-and-steps
|
|
8
8
|
related:
|
|
9
9
|
- /docs/observability
|
|
10
|
-
- /docs/api-reference/workflow/
|
|
10
|
+
- /docs/api-reference/workflow/set-attributes
|
|
11
11
|
- /docs/api-reference/workflow-errors/workflow-world-error
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
This feature is experimental and may change before the stable attributes API is released.
|
|
16
|
-
</Callout>
|
|
17
|
-
|
|
18
|
-
[`experimental_setAttributes`](/docs/api-reference/workflow/experimental-set-attributes) attaches plaintext string metadata to the current workflow run. These attributes are displayed in observability CLI/UI.
|
|
14
|
+
[`setAttributes`](/docs/api-reference/workflow/set-attributes) attaches plaintext string metadata to the current workflow run. These attributes are displayed in observability CLI/UI.
|
|
19
15
|
In the future, you'll be able to search and filter runs by attributes.
|
|
20
16
|
|
|
21
17
|
You can also seed any attributes directly when starting a run:
|
|
@@ -28,35 +24,35 @@ const run = await start(orderWorkflow, ["ord_123"], {
|
|
|
28
24
|
```
|
|
29
25
|
|
|
30
26
|
```typescript lineNumbers
|
|
31
|
-
import {
|
|
27
|
+
import { setAttributes } from "workflow"
|
|
32
28
|
|
|
33
29
|
export async function orderWorkflow(orderId: string) {
|
|
34
30
|
"use workflow"
|
|
35
31
|
|
|
36
|
-
await
|
|
32
|
+
await setAttributes({ // [!code highlight]
|
|
37
33
|
phase: "received", // [!code highlight]
|
|
38
34
|
orderId, // [!code highlight]
|
|
39
35
|
}) // [!code highlight]
|
|
40
36
|
|
|
41
37
|
// ...work...
|
|
42
38
|
|
|
43
|
-
await
|
|
39
|
+
await setAttributes({ phase: "complete" }) // [!code highlight]
|
|
44
40
|
}
|
|
45
41
|
```
|
|
46
42
|
|
|
47
43
|
## Usage
|
|
48
44
|
|
|
49
|
-
Call [`
|
|
45
|
+
Call [`setAttributes`](/docs/api-reference/workflow/set-attributes) from a `"use workflow"` function or a `"use step"` function. Plain application code is not supported because there is no active workflow run to attach attributes to.
|
|
50
46
|
|
|
51
47
|
Values must be strings. Pass `undefined` to remove a key:
|
|
52
48
|
|
|
53
49
|
```typescript lineNumbers
|
|
54
|
-
import {
|
|
50
|
+
import { setAttributes } from "workflow"
|
|
55
51
|
|
|
56
52
|
export async function cleanupAttributes() {
|
|
57
53
|
"use workflow"
|
|
58
54
|
|
|
59
|
-
await
|
|
55
|
+
await setAttributes({ staleKey: undefined }) // [!code highlight]
|
|
60
56
|
}
|
|
61
57
|
```
|
|
62
58
|
|
|
@@ -68,7 +64,7 @@ The run details panel in the observability UI shows the run's current attributes
|
|
|
68
64
|
|
|
69
65
|

|
|
70
66
|
|
|
71
|
-
Each `
|
|
67
|
+
Each `setAttributes` call appears on the trace timeline as a diamond marker at the moment the attributes were written:
|
|
72
68
|
|
|
73
69
|

|
|
74
70
|
|
|
@@ -76,12 +72,10 @@ Expanding an `attr_set` event — in the run sidebar or the Events tab — shows
|
|
|
76
72
|
|
|
77
73
|

|
|
78
74
|
|
|
79
|
-
##
|
|
80
|
-
|
|
81
|
-
While attributes are experimental:
|
|
75
|
+
## Behavior
|
|
82
76
|
|
|
83
77
|
- Attributes require a World implementing spec version 4 or later.
|
|
84
78
|
- Writes from workflow and step bodies append native `attr_set` events and immediately materialize `run.attributes`.
|
|
85
79
|
- Storage errors surface rather than being silently ignored: transient errors on workflow-body writes are retried, and a write the World rejects as invalid (for example, exceeding the per-run attribute cap across multiple calls) fails the run with the validation error.
|
|
86
|
-
- Step-body storage errors throw from `
|
|
80
|
+
- Step-body storage errors throw from `setAttributes` like any other step-side network write. Catch the error inside the step if the attribute is best-effort.
|
|
87
81
|
- Reading and querying attributes is not available yet. A query API is planned.
|
|
@@ -42,6 +42,7 @@ No workflow-specific configuration is required. As soon as a tracer provider and
|
|
|
42
42
|
| `step.execute <name>` | internal (inline) / consumer + root (queue-delivered) | a step function executes |
|
|
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
|
+
| `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) |
|
|
45
46
|
| `workflow.stream.read.connect` | client | a live stream read opens; the span covers dispatch → response headers (network connect) |
|
|
46
47
|
| `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`) |
|
|
47
48
|
|
|
@@ -59,8 +60,9 @@ Stream spans are emitted by the SDK's world backend on the client that writes or
|
|
|
59
60
|
| `workflow.trace.propagated` | Whether the invocation received trace context from the queue message. |
|
|
60
61
|
| `workflow.queue.overhead_ms` | Time between the message being enqueued and the handler starting — queue dwell plus any cold start. |
|
|
61
62
|
| `workflow.stream.name` | The stream name, on stream write/read spans. |
|
|
62
|
-
| `workflow.stream.operation` | The stream operation: `write`, `write_multi`, `close`, or `
|
|
63
|
+
| `workflow.stream.operation` | The stream operation: `write`, `write_multi`, `close`, `read`, or `flush`. |
|
|
63
64
|
| `workflow.stream.write.chunk_rtt` | Time between emissions of a chunk to the wire, and receiving the `ack` message for that chunk. |
|
|
65
|
+
| `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. |
|
|
64
66
|
| `workflow.stream.read.ttfc_ms` | Time between opening a read connection and observing and receiving the first chunk back. |
|
|
65
67
|
|
|
66
68
|
## Trace shape: one trace per invocation
|
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.32",
|
|
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.
|
|
60
|
+
"@workflow/astro": "5.0.0-beta.32",
|
|
61
|
+
"@workflow/cli": "5.0.0-beta.32",
|
|
62
|
+
"@workflow/core": "5.0.0-beta.32",
|
|
63
63
|
"@workflow/errors": "5.0.0-beta.10",
|
|
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.32",
|
|
67
|
+
"@workflow/nest": "5.0.0-beta.32",
|
|
68
|
+
"@workflow/nitro": "5.0.0-beta.32",
|
|
69
|
+
"@workflow/nuxt": "5.0.0-beta.32",
|
|
70
|
+
"@workflow/sveltekit": "5.0.0-beta.32",
|
|
71
|
+
"@workflow/rollup": "5.0.0-beta.32"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@types/ms": "2.1.0",
|