workflow 5.0.0-beta.35 → 5.0.0-beta.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/nest-builder.d.ts +2 -0
- package/dist/nest-builder.d.ts.map +1 -0
- package/dist/nest-builder.js +2 -0
- package/dist/nest-vercel-builder.d.ts +2 -0
- package/dist/nest-vercel-builder.d.ts.map +1 -0
- package/dist/nest-vercel-builder.js +2 -0
- package/docs/api-reference/workflow/create-hook.mdx +43 -2
- package/docs/api-reference/workflow/define-hook.mdx +26 -24
- package/docs/api-reference/workflow/fatal-error.mdx +29 -7
- package/docs/api-reference/workflow/fetch.mdx +3 -4
- package/docs/api-reference/workflow/sleep.mdx +1 -1
- package/docs/api-reference/workflow-api/get-hook-by-token.mdx +2 -0
- package/docs/api-reference/workflow-api/resume-hook.mdx +2 -0
- package/docs/api-reference/workflow-api/resume-webhook.mdx +6 -4
- package/docs/api-reference/workflow-api/start.mdx +1 -1
- package/docs/api-reference/workflow-nest/nest-local-builder.mdx +1 -1
- package/docs/comparisons/index.mdx +66 -0
- package/docs/comparisons/meta.json +11 -0
- package/docs/comparisons/workflow-sdk-vs-aws-agentcore.mdx +55 -0
- package/docs/comparisons/workflow-sdk-vs-aws-step-functions.mdx +111 -0
- package/docs/comparisons/workflow-sdk-vs-cloudflare-workflows.mdx +71 -0
- package/docs/comparisons/workflow-sdk-vs-inngest.mdx +102 -0
- package/docs/comparisons/workflow-sdk-vs-temporal.mdx +123 -0
- package/docs/comparisons/workflow-sdk-vs-trigger-dev.mdx +103 -0
- package/docs/configuration/runtime-tuning.mdx +22 -3
- package/docs/configuration/worlds.mdx +4 -4
- package/docs/cookbook/common-patterns/idempotency.mdx +1 -1
- package/docs/{deploying/index.mdx → deploying.mdx} +6 -8
- package/docs/foundations/hooks.mdx +1 -1
- package/docs/foundations/idempotency.mdx +16 -9
- package/docs/getting-started/astro.mdx +1 -1
- package/docs/getting-started/express.mdx +1 -1
- package/docs/getting-started/fastify.mdx +1 -1
- package/docs/getting-started/hono.mdx +1 -1
- package/docs/getting-started/index.mdx +3 -3
- package/docs/getting-started/meta.json +2 -1
- package/docs/getting-started/nestjs.mdx +63 -2
- package/docs/getting-started/next.mdx +2 -2
- package/docs/getting-started/nitro.mdx +1 -1
- package/docs/getting-started/nuxt.mdx +1 -1
- package/docs/getting-started/sveltekit.mdx +1 -1
- package/docs/getting-started/vite.mdx +1 -1
- package/docs/how-it-works/encryption.mdx +3 -3
- package/docs/how-it-works/event-sourcing.mdx +6 -6
- package/docs/how-it-works/framework-integrations.mdx +2 -2
- package/docs/meta.json +1 -1
- package/package.json +13 -11
- package/docs/deploying/building-a-world.mdx +0 -251
- package/docs/deploying/meta.json +0 -4
- package/docs/deploying/world/local-world.mdx +0 -105
- package/docs/deploying/world/meta.json +0 -4
- package/docs/deploying/world/postgres-world.mdx +0 -288
- package/docs/deploying/world/vercel-world.mdx +0 -270
- package/docs/migration-guides/index.mdx +0 -34
- package/docs/migration-guides/meta.json +0 -9
- package/docs/migration-guides/migrating-from-aws-step-functions.mdx +0 -362
- package/docs/migration-guides/migrating-from-inngest.mdx +0 -308
- package/docs/migration-guides/migrating-from-temporal.mdx +0 -317
- package/docs/migration-guides/migrating-from-trigger-dev.mdx +0 -332
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Vercel World
|
|
3
|
-
description: Fully-managed world for Vercel deployments with automatic storage, queuing, and authentication.
|
|
4
|
-
type: integration
|
|
5
|
-
summary: Deploy workflows to Vercel with fully-managed storage, queuing, and authentication.
|
|
6
|
-
prerequisites:
|
|
7
|
-
- /docs/deploying
|
|
8
|
-
related:
|
|
9
|
-
- /docs/how-it-works/encryption
|
|
10
|
-
- /docs/deploying/world/local-world
|
|
11
|
-
- /docs/deploying/world/postgres-world
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
The Vercel World is a fully-managed workflow backend for applications deployed on Vercel. It provides scalable storage, distributed queuing, and automatic authentication with zero configuration.
|
|
15
|
-
|
|
16
|
-
When you deploy to Vercel, workflows automatically use the Vercel World - no setup required.
|
|
17
|
-
|
|
18
|
-
## Usage
|
|
19
|
-
|
|
20
|
-
Deploy your application to Vercel:
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
vercel deploy
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
That's it. Vercel automatically:
|
|
27
|
-
|
|
28
|
-
- Selects the Vercel World backend
|
|
29
|
-
- Configures authentication using OIDC tokens
|
|
30
|
-
- Provisions storage and queuing infrastructure
|
|
31
|
-
- Isolates data per environment (production, preview, development)
|
|
32
|
-
|
|
33
|
-
<FluidComputeCallout />
|
|
34
|
-
|
|
35
|
-
## Vercel platform documentation
|
|
36
|
-
|
|
37
|
-
For complete details on pricing, usage limits, and included allotments on Vercel, see the official Vercel documentation:
|
|
38
|
-
|
|
39
|
-
- **[Vercel Workflow](https://vercel.com/docs/workflows)** — Pricing details, concepts, and observability for Workflow on Vercel
|
|
40
|
-
- **[Vercel limits](https://vercel.com/docs/limits)** — Platform-wide limits including Workflow-specific constraints
|
|
41
|
-
- **[Vercel Hobby plan](https://vercel.com/docs/plans/hobby)** — Free tier included usage for Workflow and other resources
|
|
42
|
-
|
|
43
|
-
For self-hosted deployments, use the [Postgres World](/worlds/postgres). For local development, use the [Local World](/worlds/local).
|
|
44
|
-
|
|
45
|
-
## Multi-region
|
|
46
|
-
|
|
47
|
-
The Vercel World runs in every [Vercel Function region](https://vercel.com/docs/regions). Each workflow run is pinned to a single region at creation time: its stored state, queue dispatch, and streams are all served from that region — no cross-region round trips on the hot path. When your application is deployed in the run's region (the automatic case below), step execution is region-local too.
|
|
48
|
-
|
|
49
|
-
<Callout type="info">
|
|
50
|
-
Multi-region requires `workflow` version **5.0.0-beta.33** or later.
|
|
51
|
-
The 4.x release line does not support region pinning — runs created by
|
|
52
|
-
4.x always live in `iad1`.
|
|
53
|
-
</Callout>
|
|
54
|
-
|
|
55
|
-
### Automatic region pinning
|
|
56
|
-
|
|
57
|
-
No configuration is needed. A run is pinned to the region of the function that creates it:
|
|
58
|
-
|
|
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
|
-
- 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
|
-
|
|
62
|
-
### Explicit region selection
|
|
63
|
-
|
|
64
|
-
To pin a specific run somewhere else, pass the `region` option to [`start()`](/docs/api-reference/workflow-api/start):
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
import { start } from "workflow/api";
|
|
68
|
-
import { myWorkflow } from "@/workflows/my-workflow";
|
|
69
|
-
|
|
70
|
-
const run = await start(myWorkflow, [input], { region: "sfo1" });
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
<Callout type="warn">
|
|
74
|
-
The `region` option controls where the run's **data is stored** and where
|
|
75
|
-
its **queue messages are dispatched from** — it does not deploy your code
|
|
76
|
-
there. Your workflow and step functions execute in the regions your
|
|
77
|
-
application is deployed to. For execution to actually happen in the
|
|
78
|
-
specified region, your app must be deployed there — via
|
|
79
|
-
[`regions`](https://vercel.com/docs/project-configuration/vercel-json#regions) in
|
|
80
|
-
`vercel.json` or the Function Regions setting in your project settings.
|
|
81
|
-
If it isn't, the run's data lives in the requested region but its steps
|
|
82
|
-
execute in the nearest region your app is deployed to.
|
|
83
|
-
</Callout>
|
|
84
|
-
|
|
85
|
-
### Good to know
|
|
86
|
-
|
|
87
|
-
- Reads, hook resumes, and stream consumers can come from anywhere — the platform routes them to the run's region automatically.
|
|
88
|
-
- Runs created by 4.x SDKs (and any runs that existed before you upgraded) live in `iad1` and are unaffected by an upgrade; there is no migration.
|
|
89
|
-
- **Hook tokens are currently stored in `iad1`** for every run, regardless of the run's region: the token-to-run mapping that powers [`getHookByToken()`](/docs/api-reference/workflow-api/get-hook-by-token) and [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) lives there so tokens — which carry no region information — can always be resolved. Hook *payloads* are not affected: a received payload is recorded on the run's event log, which lives in the run's region like all other run data. This token placement may become a project-level setting in the future.
|
|
90
|
-
|
|
91
|
-
## Limitations
|
|
92
|
-
|
|
93
|
-
- **No run migration** - A run's region is fixed at creation. Existing runs cannot be moved to a different region.
|
|
94
|
-
|
|
95
|
-
## Observability
|
|
96
|
-
|
|
97
|
-
Workflow observability is built into the Vercel dashboard on your project page. It respects your existing authentication and project permission settings.
|
|
98
|
-
|
|
99
|
-
The `workflow` CLI commands open a browser window deeplinked to the Vercel dashboard:
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
# List workflow runs (opens Vercel dashboard)
|
|
103
|
-
npx workflow inspect runs --backend vercel
|
|
104
|
-
|
|
105
|
-
# Launch the web UI (opens Vercel dashboard)
|
|
106
|
-
npx workflow web --backend vercel
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
The CLI automatically retrieves authentication from the Vercel CLI (`vercel login`) and infers project/team IDs from your local Vercel project linking.
|
|
110
|
-
|
|
111
|
-
To use the local observability UI instead of the Vercel dashboard:
|
|
112
|
-
|
|
113
|
-
```bash
|
|
114
|
-
npx workflow web --backend vercel --localUi
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
To override the automatic configuration:
|
|
118
|
-
|
|
119
|
-
```bash
|
|
120
|
-
npx workflow inspect runs \
|
|
121
|
-
--backend vercel \
|
|
122
|
-
--env production \
|
|
123
|
-
--project my-project \
|
|
124
|
-
--team my-team \
|
|
125
|
-
--authToken <your-token>
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
Learn more in the [Observability](/docs/observability) documentation.
|
|
129
|
-
|
|
130
|
-
## Testing & Compatibility
|
|
131
|
-
|
|
132
|
-
<WorldTestingPerformance worldId="vercel" />
|
|
133
|
-
|
|
134
|
-
## Configuration
|
|
135
|
-
|
|
136
|
-
In a Vercel deployment, you do not configure the Vercel World yourself. The platform injects everything the runtime needs, including `VERCEL_DEPLOYMENT_ID`, `VERCEL_PROJECT_ID`, per-request OIDC tokens, and `VERCEL_DEPLOYMENT_KEY` for encryption.
|
|
137
|
-
|
|
138
|
-
Do not set those platform-provided values yourself.
|
|
139
|
-
|
|
140
|
-
Most users never need to set the `WORKFLOW_VERCEL_*` variables below. They are only overrides for tools running outside Vercel, such as your laptop or CI, when those tools need to inspect or test a remote Vercel Workflow project and cannot infer the project, team, token, or target environment automatically.
|
|
141
|
-
|
|
142
|
-
For example, you might set them when running `workflow inspect runs --backend vercel` from CI without an interactive `vercel login`, or when running tests against a specific preview deployment. In normal local development, the CLI infers these values from your `.vercel` directory and Vercel CLI login. In a deployed Vercel function, these variables have no effect on runtime configuration, and the runtime warns if they are set there.
|
|
143
|
-
|
|
144
|
-
### `WORKFLOW_VERCEL_ENV`
|
|
145
|
-
|
|
146
|
-
The Vercel environment to target. Options: `production`, `preview`. Default: `production`.
|
|
147
|
-
|
|
148
|
-
### `WORKFLOW_VERCEL_AUTH_TOKEN`
|
|
149
|
-
|
|
150
|
-
Vercel API authentication token (secret — keep it in your environment, not in code). Falls back to `VERCEL_TOKEN`, then to your Vercel CLI login.
|
|
151
|
-
|
|
152
|
-
### `WORKFLOW_VERCEL_PROJECT`
|
|
153
|
-
|
|
154
|
-
Vercel project ID (`prj_...`).
|
|
155
|
-
|
|
156
|
-
### `WORKFLOW_VERCEL_PROJECT_NAME`
|
|
157
|
-
|
|
158
|
-
Vercel project name/slug, used for dashboard links.
|
|
159
|
-
|
|
160
|
-
### `WORKFLOW_VERCEL_TEAM`
|
|
161
|
-
|
|
162
|
-
Vercel team ID.
|
|
163
|
-
|
|
164
|
-
### `WORKFLOW_VERCEL_BACKEND_URL`
|
|
165
|
-
|
|
166
|
-
Custom base URL for the Vercel workflow API proxy. Default: `https://api.vercel.com/v1/workflow`.
|
|
167
|
-
|
|
168
|
-
### `VERCEL_WORKFLOW_SERVER_URL`
|
|
169
|
-
|
|
170
|
-
Custom workflow-server URL for direct runtime requests, or for the proxy to forward to via `WORKFLOW_VERCEL_BACKEND_URL`. Default: unset; normal deployments should not need this.
|
|
171
|
-
|
|
172
|
-
### `WORKFLOW_SEQUENTIAL_REPLAYS`
|
|
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.
|
|
175
|
-
|
|
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
|
-
|
|
178
|
-
<Callout type="warn">
|
|
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
|
-
</Callout>
|
|
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
|
-
|
|
184
|
-
### `VERCEL_QUEUE_MAX_DELAY_SECONDS`
|
|
185
|
-
|
|
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).
|
|
187
|
-
|
|
188
|
-
Vercel Queues can delay messages for up to 7 days, capped by the message TTL. Because the default TTL is 24 hours, Workflow uses a 23-hour continuation hop to stay safely inside that default.
|
|
189
|
-
|
|
190
|
-
### `WORKFLOW_REQUEST_TIMEOUT_MS`
|
|
191
|
-
|
|
192
|
-
Per-request timeout, in milliseconds, for Vercel World HTTP calls to workflow-server. Default: `60000`. Minimum: `1`.
|
|
193
|
-
|
|
194
|
-
### `WORKFLOW_MAX_CHUNKS_PER_REQUEST`
|
|
195
|
-
|
|
196
|
-
Maximum stream chunks written in one Vercel World request. Larger batches are split across multiple requests. Default: `1000`. Minimum: `1`.
|
|
197
|
-
|
|
198
|
-
### Programmatic configuration
|
|
199
|
-
|
|
200
|
-
`createWorld()` accepts explicit API configuration. It does not read `WORKFLOW_VERCEL_*` automatically, so pass the environment values yourself when you want a configured World module:
|
|
201
|
-
|
|
202
|
-
{/*@skip-typecheck: incomplete code sample*/}
|
|
203
|
-
|
|
204
|
-
```typescript title="my-world.ts" lineNumbers
|
|
205
|
-
import { createWorld } from "@workflow/world-vercel";
|
|
206
|
-
|
|
207
|
-
export default createWorld({
|
|
208
|
-
token: process.env.WORKFLOW_VERCEL_AUTH_TOKEN,
|
|
209
|
-
projectConfig: {
|
|
210
|
-
projectId: "prj_...",
|
|
211
|
-
teamId: "team_...",
|
|
212
|
-
environment: "production",
|
|
213
|
-
},
|
|
214
|
-
});
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
```bash title=".env"
|
|
218
|
-
WORKFLOW_TARGET_WORLD="./my-world.ts"
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
## Versioning
|
|
222
|
-
|
|
223
|
-
On Vercel, workflow runs are pegged to the deployment that started them. This means:
|
|
224
|
-
|
|
225
|
-
- Existing workflow runs continue executing on their original deployment, even as new code is deployed
|
|
226
|
-
- New workflow runs start on the latest deployment
|
|
227
|
-
- Code changes won't break in-flight workflows
|
|
228
|
-
|
|
229
|
-
This ensures long-running workflows complete reliably without being affected by subsequent deployments.
|
|
230
|
-
|
|
231
|
-
For the full model, including rerunning on latest and explicit upgrade boundaries, see [Versioning](/docs/foundations/versioning).
|
|
232
|
-
|
|
233
|
-
## Security
|
|
234
|
-
|
|
235
|
-
### Consumer function security
|
|
236
|
-
|
|
237
|
-
Workflow handler functions on Vercel are not accessible through public endpoints. During the build step, the Workflow SDK registers each handler as only reachable by [Vercel Queue](https://vercel.com/docs/queues), by using the `experimentalTriggers` configuration in `.vc-config.json`:
|
|
238
|
-
|
|
239
|
-
```json title=".vc-config.json (step handler)"
|
|
240
|
-
{
|
|
241
|
-
"experimentalTriggers": [
|
|
242
|
-
{
|
|
243
|
-
"type": "queue/v2beta",
|
|
244
|
-
"topic": "__wkf_step_*",
|
|
245
|
-
"consumer": "default",
|
|
246
|
-
}
|
|
247
|
-
]
|
|
248
|
-
}
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
Practically, this means:
|
|
252
|
-
|
|
253
|
-
- You don't need to add authentication or authorization logic to workflow handlers
|
|
254
|
-
- Unauthorized requests can never reach the step or workflow functions
|
|
255
|
-
- Only messages delivered through Vercel Queues can trigger execution
|
|
256
|
-
- Handlers receive only a message ID that must be retrieved from Vercel's backend, making it impossible to craft custom payloads
|
|
257
|
-
|
|
258
|
-
<Callout>
|
|
259
|
-
This configuration is managed entirely by the Workflow SDK build step. You should not need to write this yourself. If you are writing a custom integration, see [Framework Integrations — Security](/docs/how-it-works/framework-integrations#security) for more details.
|
|
260
|
-
</Callout>
|
|
261
|
-
|
|
262
|
-
## How It Works
|
|
263
|
-
|
|
264
|
-
The Vercel World uses Vercel's infrastructure for workflow execution:
|
|
265
|
-
|
|
266
|
-
- **Storage** - Workflow data is stored in Vercel's cloud with automatic replication and [end-to-end encryption](/docs/how-it-works/encryption)
|
|
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)
|
|
268
|
-
- **Authentication** - OIDC tokens provide secure, automatic authentication
|
|
269
|
-
|
|
270
|
-
For more details, see the [Vercel Workflow documentation](https://vercel.com/docs/workflows).
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Migration Guides
|
|
3
|
-
description: Move your existing durable workflow system to the Workflow SDK with side-by-side code comparisons and a realistic migration example.
|
|
4
|
-
type: overview
|
|
5
|
-
summary: Migrate from Temporal, Inngest, AWS Step Functions, or trigger.dev to the Workflow SDK.
|
|
6
|
-
related:
|
|
7
|
-
- /docs/foundations/workflows-and-steps
|
|
8
|
-
- /docs/getting-started
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
<Callout type="info">
|
|
12
|
-
Install the Workflow SDK migration skill:
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
npx skills add https://github.com/vercel/workflow --skill migrating-to-workflow-sdk
|
|
16
|
-
```
|
|
17
|
-
</Callout>
|
|
18
|
-
|
|
19
|
-
Move an existing orchestration system to the Workflow SDK. Each guide pairs a concept-mapping table with side-by-side code, so you can translate one piece of your codebase at a time.
|
|
20
|
-
|
|
21
|
-
<Cards>
|
|
22
|
-
<Card href="/docs/migration-guides/migrating-from-temporal" title="Migrating from Temporal">
|
|
23
|
-
Map Activities, Workers, Signals, and Child Workflows onto workflows, steps, hooks, and `start()` / `getRun()`.
|
|
24
|
-
</Card>
|
|
25
|
-
<Card href="/docs/migration-guides/migrating-from-inngest" title="Migrating from Inngest">
|
|
26
|
-
Map `createFunction`, `step.run`, `step.sleep`, `step.waitForEvent`, and `step.invoke` onto workflows, steps, and hooks.
|
|
27
|
-
</Card>
|
|
28
|
-
<Card href="/docs/migration-guides/migrating-from-aws-step-functions" title="Migrating from AWS Step Functions">
|
|
29
|
-
Replace ASL JSON states, Task / Choice / Wait / Parallel states, and `.waitForTaskToken` callbacks with TypeScript.
|
|
30
|
-
</Card>
|
|
31
|
-
<Card href="/docs/migration-guides/migrating-from-trigger-dev" title="Migrating from trigger.dev">
|
|
32
|
-
Map `task()`, `schemaTask()`, `wait.for` / `wait.forToken`, `triggerAndWait`, and `metadata.stream` onto workflows, steps, hooks, and `start()` / `getRun()`.
|
|
33
|
-
</Card>
|
|
34
|
-
</Cards>
|
|
@@ -1,362 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Migrating from AWS Step Functions
|
|
3
|
-
description: Move an AWS Step Functions state machine to the Workflow SDK by replacing JSON state definitions, Task states, Choice/Wait/Parallel states, Retry/Catch blocks, and .waitForTaskToken callbacks with Workflows, Steps, Hooks, and idiomatic TypeScript control flow.
|
|
4
|
-
type: guide
|
|
5
|
-
summary: Translate an AWS Step Functions state machine into the Workflow SDK with side-by-side code examples.
|
|
6
|
-
prerequisites:
|
|
7
|
-
- /docs/getting-started/next
|
|
8
|
-
- /docs/foundations/workflows-and-steps
|
|
9
|
-
related:
|
|
10
|
-
- /docs/foundations/starting-workflows
|
|
11
|
-
- /docs/foundations/errors-and-retries
|
|
12
|
-
- /docs/foundations/hooks
|
|
13
|
-
- /docs/foundations/streaming
|
|
14
|
-
- /docs/deploying/world/vercel-world
|
|
15
|
-
---
|
|
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
|
-
|
|
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.
|
|
22
|
-
|
|
23
|
-
<Callout type="info">
|
|
24
|
-
Install the Workflow SDK migration skill:
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
npx skills add https://github.com/vercel/workflow --skill migrating-to-workflow-sdk
|
|
28
|
-
```
|
|
29
|
-
</Callout>
|
|
30
|
-
|
|
31
|
-
## Why migrate to the Workflow SDK
|
|
32
|
-
|
|
33
|
-
- Orchestration code is TypeScript, not JSON ASL. Transitions are `await`, branches are `if`/`switch`, and parallelism is `Promise.all`.
|
|
34
|
-
- Streaming is built in. Write durable progress from steps with `getWritable()` and named streams. No DynamoDB or SNS glue to surface status to clients.
|
|
35
|
-
- Infrastructure lives in one deployment. No separate state machine, per-task Lambda, IAM role wiring, or callback SQS queues.
|
|
36
|
-
- Error handling is TypeScript-native: step-level retries, `RetryableError`, and `FatalError` replace per-state Retry/Catch blocks.
|
|
37
|
-
- The `npx workflow` CLI and `npx workflow web` observability UI ship out of the box.
|
|
38
|
-
- AI/agent helpers — `@workflow/ai` for AI-SDK integration and the Claude migration skill — are available as separate installs.
|
|
39
|
-
|
|
40
|
-
## Before you migrate
|
|
41
|
-
|
|
42
|
-
This guide assumes **Standard** workflows. Express workflows have different semantics (at-least-once, 5-minute max duration, no execution history) and may need a different target — consider keeping them on Step Functions, moving them to a queue consumer, or ensuring your steps are idempotent before replaying the pattern here.
|
|
43
|
-
|
|
44
|
-
## What changes when you leave Step Functions?
|
|
45
|
-
|
|
46
|
-
AWS Step Functions defines workflows as JSON state machines using Amazon States Language (ASL). Each state (Task, Choice, Wait, Parallel, Map) is a node in a declarative graph. Lambda functions handle tasks, Retry/Catch blocks configure per-state error handling, and `.waitForTaskToken` manages callbacks.
|
|
47
|
-
|
|
48
|
-
The Workflow SDK replaces that JSON DSL with TypeScript. `"use workflow"` functions orchestrate `"use step"` functions in the same file. Branching is `if`/`else`. Waiting is `sleep()`. Parallelism is `Promise.all()`. Retries move down to the step level.
|
|
49
|
-
|
|
50
|
-
The migration replaces declarative configuration with idiomatic TypeScript and collapses the orchestrator and compute split. Business logic stays the same.
|
|
51
|
-
|
|
52
|
-
## Concept mapping
|
|
53
|
-
|
|
54
|
-
| AWS Step Functions | Workflow SDK | Migration note |
|
|
55
|
-
| --- | --- | --- |
|
|
56
|
-
| State machine (ASL JSON) | `"use workflow"` function | The workflow function is the state machine. |
|
|
57
|
-
| Task state / Lambda | `"use step"` function | Side effects go in steps. No separate Lambda. |
|
|
58
|
-
| Choice state | `if` / `else` / `switch` | Native TypeScript control flow. |
|
|
59
|
-
| Wait state | `sleep()` | Import `sleep` from `workflow`. |
|
|
60
|
-
| Parallel state | `Promise.all()` | Standard concurrency primitives. |
|
|
61
|
-
| Map state | Inline sequential → `for` loop; bounded parallel (`MaxConcurrency: N`) → batched `Promise.all` or a concurrency limiter like `p-limit`; Distributed Map / large fan-out → `start()` per item, then step-wrapped `getRun()` to collect. | Match the concurrency mode of the original Map. |
|
|
62
|
-
| Retry / Catch | Step retries, `RetryableError`, `FatalError` | Retry logic moves to step boundaries. |
|
|
63
|
-
| `Catch` to a compensation state | `try`/`catch` in the workflow function, calling compensation steps in reverse order (push/pop a rollback stack) | See [`/docs/foundations/errors-and-retries`](/docs/foundations/errors-and-retries) for the SAGA pattern. |
|
|
64
|
-
| `.waitForTaskToken` | `createHook()` or `createWebhook()` | Hooks for typed signals; webhooks for HTTP. |
|
|
65
|
-
| Child state machine (`StartExecution`) | `start()` plus a `"use step"` wrapper around `getRun()` | Return the `Run` object, await its result from another step. |
|
|
66
|
-
| Execution event history | Workflow event log | Same durable replay model. |
|
|
67
|
-
| Progress via DynamoDB / SNS for client polling | `getWritable()` + named streams | Stream durable updates; clients read from the stream. |
|
|
68
|
-
|
|
69
|
-
<Callout type="info">
|
|
70
|
-
`.waitForTaskToken` becomes `createHook()` or `createWebhook()`. Choice states become `if`/`else`. Map states become `Promise.all()`. Retry policies move from per-state configuration to step-level defaults.
|
|
71
|
-
</Callout>
|
|
72
|
-
|
|
73
|
-
## Translate your first workflow
|
|
74
|
-
|
|
75
|
-
Start with a single Task state. In ASL, even "call one Lambda" requires a state machine shell:
|
|
76
|
-
|
|
77
|
-
```json title="stateMachine.asl.json (Step Functions)"
|
|
78
|
-
"LoadOrder": {
|
|
79
|
-
"Type": "Task",
|
|
80
|
-
"Resource": "arn:aws:states:::lambda:invoke",
|
|
81
|
-
"Parameters": { "FunctionName": "loadOrder", "Payload.$": "$" },
|
|
82
|
-
"End": true
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
<Callout type="info">
|
|
87
|
-
Examples use JSONPath mode. If your state machine sets `QueryLanguage: 'JSONata'`, the shape of `Arguments`/`Output` fields differs but the TypeScript translation is identical.
|
|
88
|
-
</Callout>
|
|
89
|
-
|
|
90
|
-
```typescript title="workflow/workflows/order.ts (Workflow SDK)"
|
|
91
|
-
export async function processOrder(orderId: string) {
|
|
92
|
-
'use workflow'; // [!code highlight]
|
|
93
|
-
return await loadOrder(orderId);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async function loadOrder(orderId: string) {
|
|
97
|
-
'use step'; // [!code highlight]
|
|
98
|
-
const res = await fetch(`https://example.com/api/orders/${orderId}`);
|
|
99
|
-
return res.json() as Promise<{ id: string }>;
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
What changed: the ASL state machine and its Lambda collapse into two directive-tagged functions in one file.
|
|
104
|
-
|
|
105
|
-
### Adding a second step
|
|
106
|
-
|
|
107
|
-
In ASL, a second Task means a new state and a `"Next"` transition. In the Workflow SDK, it's another `await`:
|
|
108
|
-
|
|
109
|
-
```typescript
|
|
110
|
-
export async function processOrder(orderId: string) {
|
|
111
|
-
'use workflow';
|
|
112
|
-
const order = await loadOrder(orderId);
|
|
113
|
-
await reserveInventory(order.id); // [!code highlight]
|
|
114
|
-
return { orderId: order.id, status: 'reserved' };
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
`await` replaces `"Next"`. Each new step is a new function with `"use step"`; no additional deployment. The second version also reshapes the return value; the workflow return type can be anything serializable.
|
|
119
|
-
|
|
120
|
-
### Starting from an API route
|
|
121
|
-
|
|
122
|
-
Step Functions starts a run via `StartExecution` (AWS SDK or API Gateway integration). The Workflow SDK starts a run with `start()` from a route handler:
|
|
123
|
-
|
|
124
|
-
```typescript title="app/api/orders/route.ts"
|
|
125
|
-
import { start } from 'workflow/api';
|
|
126
|
-
import { processOrder } from '@/workflows/order';
|
|
127
|
-
|
|
128
|
-
export async function POST(request: Request) {
|
|
129
|
-
const { orderId } = (await request.json()) as { orderId: string };
|
|
130
|
-
const run = await start(processOrder, [orderId]); // [!code highlight]
|
|
131
|
-
return Response.json({ runId: run.runId });
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### Waiting for a fixed duration
|
|
136
|
-
|
|
137
|
-
A `Wait` state becomes `sleep()`:
|
|
138
|
-
|
|
139
|
-
```json title="stateMachine.asl.json (Step Functions)"
|
|
140
|
-
{ "Type": "Wait", "Seconds": 60, "Next": "Next" }
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
{/* @skip-typecheck: one-line snippet fragment */}
|
|
144
|
-
|
|
145
|
-
```typescript title="workflow/workflows/order.ts (Workflow SDK)"
|
|
146
|
-
await sleep('1m');
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
## Wait for an external signal
|
|
150
|
-
|
|
151
|
-
The minimal ASL for a callback is a Task with `.waitForTaskToken`:
|
|
152
|
-
|
|
153
|
-
```json title="approval.asl.json (Step Functions)"
|
|
154
|
-
"WaitForApproval": {
|
|
155
|
-
"Type": "Task",
|
|
156
|
-
"Resource": "arn:aws:states:::sqs:sendMessage.waitForTaskToken",
|
|
157
|
-
"Parameters": {
|
|
158
|
-
"QueueUrl": "https://sqs.us-east-1.amazonaws.com/123456789012/approvals",
|
|
159
|
-
"MessageBody": {
|
|
160
|
-
"refundId.$": "$.refundId",
|
|
161
|
-
"TaskToken.$": "$$.Task.Token"
|
|
162
|
-
}
|
|
163
|
-
},
|
|
164
|
-
"End": true
|
|
165
|
-
}
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
```typescript title="workflow/workflows/refund.ts (Workflow SDK)"
|
|
169
|
-
import { createHook } from 'workflow';
|
|
170
|
-
|
|
171
|
-
export async function refundWorkflow(refundId: string) {
|
|
172
|
-
'use workflow';
|
|
173
|
-
using approval = createHook<{ approved: boolean }>({ // [!code highlight]
|
|
174
|
-
token: `refund:${refundId}:approval`,
|
|
175
|
-
});
|
|
176
|
-
return await approval;
|
|
177
|
-
}
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
What changed: no SQS queue, no task token, no callback Lambda. The hook suspends the workflow durably until it is resumed.
|
|
181
|
-
|
|
182
|
-
### Resuming the hook
|
|
183
|
-
|
|
184
|
-
Step Functions resumes by calling `SendTaskSuccess` with the task token. The Workflow SDK resumes by calling `resumeHook` with the hook's token:
|
|
185
|
-
|
|
186
|
-
```typescript title="app/api/refunds/[refundId]/approve/route.ts"
|
|
187
|
-
import { resumeHook } from 'workflow/api';
|
|
188
|
-
|
|
189
|
-
export async function POST(req: Request, { params }: { params: Promise<{ refundId: string }> }) {
|
|
190
|
-
const { refundId } = await params;
|
|
191
|
-
const { approved } = (await req.json()) as { approved: boolean };
|
|
192
|
-
await resumeHook(`refund:${refundId}:approval`, { approved }); // [!code highlight]
|
|
193
|
-
return Response.json({ ok: true });
|
|
194
|
-
}
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Branching on the result
|
|
198
|
-
|
|
199
|
-
In ASL, branching after the wait requires a Choice state. In TypeScript, it's just `if`/`else`:
|
|
200
|
-
|
|
201
|
-
```json title="approval.asl.json (Step Functions)"
|
|
202
|
-
"CheckApproval": {
|
|
203
|
-
"Type": "Choice",
|
|
204
|
-
"Choices": [
|
|
205
|
-
{ "Variable": "$.approved", "BooleanEquals": true, "Next": "Approved" }
|
|
206
|
-
],
|
|
207
|
-
"Default": "Rejected"
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
{/* @skip-typecheck: continuation snippet */}
|
|
212
|
-
```typescript title="workflow/workflows/refund.ts (Workflow SDK)"
|
|
213
|
-
const { approved } = await approval;
|
|
214
|
-
if (approved) return { refundId, status: 'approved' }; // [!code highlight]
|
|
215
|
-
return { refundId, status: 'rejected' };
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
## Spawn a child workflow
|
|
219
|
-
|
|
220
|
-
In ASL, a parent machine calls `StartExecution` (usually via `.sync` or `.waitForTaskToken`) to launch a child. In v5, call `start()` directly from the workflow to launch a child. Wrap `getRun()` and `returnValue` access in a `"use step"` function when you need to await the child result.
|
|
221
|
-
|
|
222
|
-
### Parent starts a child
|
|
223
|
-
|
|
224
|
-
```typescript title="workflow/workflows/parent.ts"
|
|
225
|
-
import { start } from 'workflow/api';
|
|
226
|
-
|
|
227
|
-
export async function parentWorkflow(item: string) {
|
|
228
|
-
'use workflow';
|
|
229
|
-
const run = await start(childWorkflow, [item]); // [!code highlight]
|
|
230
|
-
return { childRunId: run.runId };
|
|
231
|
-
}
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
### Awaiting the child's result
|
|
235
|
-
|
|
236
|
-
Add a second step that wraps `getRun()` and awaits `returnValue`:
|
|
237
|
-
|
|
238
|
-
```typescript
|
|
239
|
-
import { getRun } from 'workflow/api';
|
|
240
|
-
|
|
241
|
-
async function collectResult(runId: string) {
|
|
242
|
-
'use step'; // [!code highlight]
|
|
243
|
-
const run = getRun(runId);
|
|
244
|
-
return (await run.returnValue) as { item: string; result: string };
|
|
245
|
-
}
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
Then in the workflow: `const result = await collectResult(run.runId);`. The child workflow itself (`childWorkflow`) is defined elsewhere with `"use workflow"`.
|
|
249
|
-
|
|
250
|
-
## What you stop operating
|
|
251
|
-
|
|
252
|
-
Moving off Step Functions removes these surfaces from the application:
|
|
253
|
-
|
|
254
|
-
- ASL state machine JSON and its reference syntax.
|
|
255
|
-
- Per-task Lambda functions, their IAM roles, and CloudFormation/CDK wiring.
|
|
256
|
-
- Task-token delivery infrastructure (SQS queues, callback Lambdas).
|
|
257
|
-
- Separate progress channels (DynamoDB, SNS) for client-visible updates.
|
|
258
|
-
- Remove CloudWatch and X-Ray wiring that was specific to orchestrator state transitions. Keep (or re-wire) any application-level CloudWatch alarms, log retention policies, or X-Ray propagation that the rest of your AWS footprint still depends on. Workflow SDK exports OTEL traces, so existing OTEL-compatible backends can continue to ingest them.
|
|
259
|
-
|
|
260
|
-
Workflow and step functions live in the same deployment as the application. State transitions are ordinary control flow (`await`, `if`, `Promise.all`, `for`). Progress streaming, retries, and observability are built in.
|
|
261
|
-
|
|
262
|
-
### What you take on
|
|
263
|
-
|
|
264
|
-
Steps that previously invoked AWS services via optimized integrations (EventBridge, DynamoDB, Bedrock, ECS.RunTask.sync, etc.) become ordinary SDK calls inside `'use step'` functions. Credentials and retries move into the step, and `.sync`-style waits for long-running jobs become explicit polling loops or hook-based callbacks.
|
|
265
|
-
|
|
266
|
-
## Step-by-step first migration
|
|
267
|
-
|
|
268
|
-
Pick one state machine and migrate it end-to-end before touching the rest. The steps below describe the smallest viable path.
|
|
269
|
-
|
|
270
|
-
### Step 1: Install the Workflow SDK
|
|
271
|
-
|
|
272
|
-
Add the `workflow` runtime package.
|
|
273
|
-
|
|
274
|
-
```bash
|
|
275
|
-
pnpm add workflow
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
### Step 2: Rewrite the state machine as a `"use workflow"` function
|
|
279
|
-
|
|
280
|
-
Transitions become `await` calls. Control flow (`Choice`, `Wait`, `Parallel`, `Map`) becomes `if`/`switch`, `sleep`, `Promise.all`, and loops.
|
|
281
|
-
|
|
282
|
-
```ts title="workflows/order.ts"
|
|
283
|
-
export async function processOrder(orderId: string) {
|
|
284
|
-
"use workflow"; // [!code highlight]
|
|
285
|
-
const order = await loadOrder(orderId);
|
|
286
|
-
if (order.total > 1000) await reviewManually(order);
|
|
287
|
-
await chargePayment(order);
|
|
288
|
-
}
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
### Step 3: Move each Lambda into a step function
|
|
292
|
-
|
|
293
|
-
Inline the Lambda body into a function with `"use step"` on the first line. Step functions keep full Node.js access, so existing SDK calls work unchanged.
|
|
294
|
-
|
|
295
|
-
```ts
|
|
296
|
-
async function loadOrder(id: string) {
|
|
297
|
-
"use step"; // [!code highlight]
|
|
298
|
-
return fetch(`/api/orders/${id}`).then((r) => r.json());
|
|
299
|
-
}
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
### Step 4: Replace `.waitForTaskToken` with a hook
|
|
303
|
-
|
|
304
|
-
Swap the task-token callback Lambda for `createHook()`. Callers `resumeHook(token, payload)` instead of `SendTaskSuccess`.
|
|
305
|
-
|
|
306
|
-
Move Retry/Catch off per-state configuration and onto step boundaries. Set `maxRetries` as a function property; throw `RetryableError` or `FatalError` to control retry behavior:
|
|
307
|
-
|
|
308
|
-
```typescript
|
|
309
|
-
async function chargePayment(orderId: string) {
|
|
310
|
-
"use step";
|
|
311
|
-
// ...
|
|
312
|
-
}
|
|
313
|
-
chargePayment.maxRetries = 5;
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
See [`/docs/foundations/errors-and-retries`](/docs/foundations/errors-and-retries) for the full retry and SAGA compensation patterns.
|
|
317
|
-
|
|
318
|
-
### Step 5: Start runs from an API route
|
|
319
|
-
|
|
320
|
-
Delete the `StartExecution` call and IAM wiring. Launch runs directly from a route handler:
|
|
321
|
-
|
|
322
|
-
```ts title="app/api/orders/route.ts"
|
|
323
|
-
import { start } from "workflow/api";
|
|
324
|
-
import { processOrder } from "@/workflows/order";
|
|
325
|
-
|
|
326
|
-
export async function POST(req: Request) {
|
|
327
|
-
const { orderId } = await req.json();
|
|
328
|
-
const run = await start(processOrder, [orderId]);
|
|
329
|
-
return Response.json({ runId: run.runId });
|
|
330
|
-
}
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
### Step 6: Retire the Step Functions infrastructure
|
|
334
|
-
|
|
335
|
-
Delete the ASL JSON, per-task Lambda deployments, IAM roles, and callback queues. Remove CloudWatch and X-Ray wiring that was specific to orchestrator state transitions — keep alarms, log retention, and traces for resources you still depend on. Verify the run in `npx workflow web` before shipping.
|
|
336
|
-
|
|
337
|
-
## Features without a 1:1 equivalent
|
|
338
|
-
|
|
339
|
-
- **Express workflows.** At-least-once semantics and 5-minute duration make them a poor fit for the SDK's durable replay model. Consider keeping them on Step Functions or migrating to a queue consumer.
|
|
340
|
-
- **Distributed Map state.** Up to 10,000 concurrent child executions with S3 item sources has no 1:1 analog; fan out with `start()` per item, then `Promise.all` with `p-limit` to bound concurrency.
|
|
341
|
-
- **Optimized AWS service integrations (`arn:aws:states:::dynamodb:*`, `eventbridge:*`, `bedrock:*`, `ecs:runTask.sync`, etc.).** These become regular SDK calls inside `'use step'` functions — credentials, retries, and polling move into the step.
|
|
342
|
-
- **Per-state IAM roles.** ASL lets each state run under its own IAM role. In the SDK, all steps share the deployment's credentials; scope secrets and roles at deployment time.
|
|
343
|
-
- **CloudWatch alarms / X-Ray cross-service traces / CloudWatch Logs retention.** The SDK event log + observability UI replaces orchestrator state transitions, not AWS-wide observability. Keep alarms and traces for other resources.
|
|
344
|
-
- **`JSONata` `QueryLanguage` mode.** Valid at the source; the TS translation is identical regardless of mode.
|
|
345
|
-
|
|
346
|
-
## Quick-start checklist
|
|
347
|
-
|
|
348
|
-
- Replace the ASL state machine with a single `"use workflow"` function. Transitions become `await` calls.
|
|
349
|
-
- Convert each Task / Lambda into a `"use step"` function in the same file.
|
|
350
|
-
- Replace Choice states with `if`/`else`/`switch`.
|
|
351
|
-
- Replace Wait states with `sleep()` from `workflow`.
|
|
352
|
-
- Replace Parallel states with `Promise.all()`.
|
|
353
|
-
- Replace Map states based on their concurrency mode: inline sequential → `for` loop; bounded parallel (`MaxConcurrency: N`) → batched `Promise.all` or a concurrency limiter like `p-limit`; Distributed Map / large fan-out → `start()` per item, then step-wrapped `getRun()` to collect.
|
|
354
|
-
- Replace `StartExecution` child machines with direct `start()` calls and a `"use step"` wrapper around `getRun()` when collecting results.
|
|
355
|
-
- Replace `.waitForTaskToken` with `createHook()` (internal callers) or `createWebhook()` (HTTP callers).
|
|
356
|
-
- Move Retry/Catch to step boundaries using `maxRetries`, `RetryableError`, and `FatalError`.
|
|
357
|
-
- Use `getStepMetadata().stepId` as the idempotency key for external side effects.
|
|
358
|
-
- Stream progress from steps with `getWritable()` instead of polling DynamoDB or SNS.
|
|
359
|
-
- Deploy and verify runs end-to-end with built-in observability.
|
|
360
|
-
|
|
361
|
-
---
|
|
362
|
-
*Verified against `workflow@5.0.0-beta.1` and the AWS Step Functions Amazon States Language spec on 2026-04-16.*
|