workflow 5.0.0-beta.2 → 5.0.0-beta.21
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/api-workflow.d.ts +1 -1
- package/dist/api-workflow.d.ts.map +1 -1
- package/dist/api-workflow.js +2 -2
- package/dist/api.d.ts +5 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +14 -2
- package/dist/internal/builtins.d.ts +17 -0
- package/dist/internal/builtins.d.ts.map +1 -1
- package/dist/internal/builtins.js +65 -1
- package/dist/observability.d.ts +1 -1
- package/dist/observability.js +2 -2
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +2 -2
- package/docs/ai/index.mdx +27 -23
- package/docs/api-reference/index.mdx +24 -0
- package/docs/api-reference/meta.json +8 -0
- package/docs/api-reference/vitest/index.mdx +28 -7
- package/docs/api-reference/workflow/create-hook.mdx +38 -0
- package/docs/api-reference/workflow/create-webhook.mdx +1 -0
- package/docs/api-reference/workflow/experimental-set-attributes.mdx +65 -0
- package/docs/api-reference/workflow/fetch.mdx +5 -0
- package/docs/api-reference/workflow/index.mdx +3 -0
- package/docs/api-reference/workflow-ai/durable-agent.mdx +7 -45
- package/docs/api-reference/workflow-api/get-hook-by-token.mdx +7 -0
- package/docs/api-reference/workflow-api/get-run.mdx +6 -0
- package/docs/api-reference/workflow-api/index.mdx +6 -8
- package/docs/api-reference/workflow-api/resume-hook.mdx +57 -0
- package/docs/api-reference/workflow-api/start.mdx +13 -5
- package/docs/api-reference/workflow-astro/index.mdx +18 -0
- package/docs/api-reference/workflow-astro/meta.json +4 -0
- package/docs/api-reference/workflow-astro/workflow.mdx +45 -0
- package/docs/api-reference/workflow-errors/hook-conflict-error.mdx +60 -0
- package/docs/api-reference/workflow-errors/index.mdx +85 -0
- package/docs/api-reference/workflow-errors/meta.json +5 -0
- package/docs/api-reference/workflow-errors/run-not-supported-error.mdx +58 -0
- package/docs/api-reference/workflow-errors/workflow-error.mdx +52 -0
- package/docs/api-reference/workflow-errors/workflow-run-failed-error.mdx +16 -6
- package/docs/api-reference/workflow-errors/workflow-run-not-completed-error.mdx +58 -0
- package/docs/api-reference/workflow-errors/workflow-runtime-error.mdx +58 -0
- package/docs/api-reference/workflow-nest/configure-workflow-controller.mdx +33 -0
- package/docs/api-reference/workflow-nest/index.mdx +31 -0
- package/docs/api-reference/workflow-nest/meta.json +9 -0
- package/docs/api-reference/workflow-nest/nest-local-builder.mdx +64 -0
- package/docs/api-reference/workflow-nest/workflow-controller.mdx +40 -0
- package/docs/api-reference/workflow-nest/workflow-module.mdx +74 -0
- package/docs/api-reference/workflow-next/with-workflow.mdx +56 -2
- package/docs/api-reference/workflow-nitro/index.mdx +59 -0
- package/docs/api-reference/workflow-nuxt/index.mdx +47 -0
- package/docs/api-reference/workflow-observability/hydrate-data.mdx +35 -0
- package/docs/api-reference/workflow-observability/hydrate-resource-io.mdx +62 -0
- package/docs/api-reference/workflow-observability/index.mdx +64 -0
- package/docs/api-reference/workflow-observability/meta.json +11 -0
- package/docs/api-reference/workflow-observability/observability-revivers.mdx +50 -0
- package/docs/api-reference/workflow-observability/parse-class-name.mdx +41 -0
- package/docs/api-reference/workflow-observability/parse-step-name.mdx +40 -0
- package/docs/api-reference/workflow-observability/parse-workflow-name.mdx +55 -0
- package/docs/api-reference/workflow-runtime/create-world.mdx +39 -0
- package/docs/api-reference/workflow-runtime/get-world-handlers.mdx +44 -0
- package/docs/api-reference/{workflow-api → workflow-runtime}/get-world.mdx +7 -10
- package/docs/api-reference/workflow-runtime/health-check.mdx +50 -0
- package/docs/api-reference/workflow-runtime/index.mdx +43 -0
- package/docs/api-reference/workflow-runtime/meta.json +12 -0
- package/docs/api-reference/workflow-runtime/set-world.mdx +49 -0
- package/docs/api-reference/workflow-runtime/workflow-entrypoint.mdx +42 -0
- package/docs/api-reference/{workflow-api → workflow-runtime}/world/index.mdx +5 -8
- package/docs/api-reference/workflow-runtime/world/meta.json +4 -0
- package/docs/api-reference/{workflow-api → workflow-runtime}/world/queue.mdx +2 -2
- package/docs/api-reference/{workflow-api → workflow-runtime}/world/storage.mdx +11 -4
- package/docs/api-reference/{workflow-api → workflow-runtime}/world/streams.mdx +2 -2
- package/docs/api-reference/workflow-serde/index.mdx +0 -1
- package/docs/api-reference/workflow-serde/workflow-deserialize.mdx +1 -2
- package/docs/api-reference/workflow-serde/workflow-serialize.mdx +1 -2
- package/docs/api-reference/workflow-sveltekit/index.mdx +18 -0
- package/docs/api-reference/workflow-sveltekit/meta.json +4 -0
- package/docs/api-reference/workflow-sveltekit/workflow-plugin.mdx +42 -0
- package/docs/api-reference/workflow-vite/index.mdx +18 -0
- package/docs/api-reference/workflow-vite/meta.json +4 -0
- package/docs/api-reference/workflow-vite/workflow.mdx +48 -0
- package/docs/changelog/attributes-mvp.mdx +380 -0
- package/docs/changelog/eager-processing.mdx +269 -0
- package/docs/changelog/index.mdx +2 -1
- package/docs/changelog/lazy-event-creation.md +127 -0
- package/docs/changelog/meta.json +7 -1
- package/docs/changelog/resilient-start.mdx +31 -283
- package/docs/changelog/turbo-mode.md +87 -0
- package/docs/cookbook/advanced/child-workflows.mdx +315 -0
- package/docs/cookbook/advanced/meta.json +2 -3
- package/docs/cookbook/advanced/publishing-libraries.mdx +87 -29
- package/docs/cookbook/advanced/serializable-steps.mdx +17 -5
- package/docs/cookbook/advanced/upgrading-workflows.mdx +195 -0
- package/docs/cookbook/agent-patterns/agent-cancellation.mdx +156 -0
- package/docs/cookbook/agent-patterns/durable-agent.mdx +11 -184
- package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +150 -173
- package/docs/cookbook/agent-patterns/meta.json +1 -7
- package/docs/cookbook/common-patterns/batching.mdx +44 -118
- package/docs/cookbook/common-patterns/idempotency.mdx +36 -52
- package/docs/cookbook/common-patterns/meta.json +4 -4
- package/docs/cookbook/common-patterns/rate-limiting.mdx +1 -1
- package/docs/cookbook/common-patterns/saga.mdx +128 -33
- package/docs/cookbook/common-patterns/scheduling.mdx +77 -193
- package/docs/cookbook/common-patterns/sequential-and-parallel.mdx +155 -0
- package/docs/cookbook/common-patterns/timeouts.mdx +100 -0
- package/docs/cookbook/common-patterns/workflow-composition.mdx +117 -0
- package/docs/cookbook/index.mdx +14 -17
- package/docs/cookbook/integrations/ai-sdk.mdx +330 -142
- package/docs/cookbook/integrations/chat-sdk.mdx +264 -151
- package/docs/cookbook/integrations/sandbox.mdx +482 -81
- package/docs/cookbook/meta.json +1 -1
- package/docs/deploying/building-a-world.mdx +1 -1
- package/docs/deploying/world/postgres-world.mdx +5 -3
- package/docs/deploying/world/vercel-world.mdx +2 -0
- package/docs/errors/abort-signal-timeout-in-workflow.mdx +80 -0
- package/docs/errors/corrupted-event-log.mdx +5 -5
- package/docs/errors/hook-conflict.mdx +56 -4
- package/docs/errors/index.mdx +9 -0
- package/docs/errors/replay-divergence.mdx +27 -0
- package/docs/errors/runtime-decryption-failed.mdx +77 -0
- package/docs/errors/step-executed-multiple-times.mdx +23 -0
- package/docs/errors/step-not-registered.mdx +1 -1
- package/docs/foundations/cancellation.mdx +459 -0
- package/docs/foundations/errors-and-retries.mdx +7 -3
- package/docs/foundations/hooks.mdx +29 -0
- package/docs/foundations/idempotency.mdx +236 -11
- package/docs/foundations/index.mdx +3 -3
- package/docs/foundations/meta.json +3 -2
- package/docs/foundations/serialization.mdx +78 -42
- package/docs/foundations/starting-workflows.mdx +6 -2
- package/docs/foundations/streaming.mdx +14 -23
- package/docs/foundations/versioning.mdx +263 -0
- package/docs/getting-started/astro.mdx +6 -0
- package/docs/getting-started/index.mdx +6 -7
- package/docs/getting-started/meta.json +1 -0
- package/docs/getting-started/nestjs.mdx +9 -0
- package/docs/getting-started/next.mdx +5 -3
- package/docs/getting-started/nitro.mdx +22 -0
- package/docs/getting-started/sveltekit.mdx +6 -0
- package/docs/getting-started/tanstack-start.mdx +241 -0
- package/docs/how-it-works/cancellation.mdx +287 -0
- package/docs/how-it-works/code-transform.mdx +2 -2
- package/docs/how-it-works/encryption.mdx +2 -2
- package/docs/how-it-works/event-sourcing.mdx +2 -2
- package/docs/how-it-works/meta.json +2 -1
- package/docs/internal/index.mdx +21 -0
- package/docs/internal/meta.json +10 -0
- package/docs/internal/nitro-native-build.mdx +38 -0
- package/docs/internal/nitro-web-ui.mdx +24 -0
- package/docs/internal/serializable-abort-controller.mdx +148 -0
- package/docs/migration-guides/migrating-from-aws-step-functions.mdx +63 -16
- package/docs/migration-guides/migrating-from-inngest.mdx +44 -22
- package/docs/migration-guides/migrating-from-temporal.mdx +43 -14
- package/docs/migration-guides/migrating-from-trigger-dev.mdx +59 -27
- package/docs/observability/attributes.mdx +87 -0
- package/docs/observability/index.mdx +25 -1
- package/docs/observability/meta.json +1 -1
- package/docs/observability/tracing.mdx +106 -0
- package/docs/testing/index.mdx +2 -2
- package/package.json +14 -13
- package/docs/api-reference/workflow-api/world/meta.json +0 -4
- package/docs/api-reference/workflow-api/world/observability.mdx +0 -164
- package/docs/cookbook/advanced/custom-serialization.mdx +0 -168
- package/docs/cookbook/advanced/durable-objects.mdx +0 -148
- package/docs/cookbook/advanced/isomorphic-packages.mdx +0 -145
- package/docs/cookbook/agent-patterns/stop-workflow.mdx +0 -216
- package/docs/cookbook/agent-patterns/tool-orchestration.mdx +0 -255
- package/docs/cookbook/agent-patterns/tool-streaming.mdx +0 -181
- package/docs/cookbook/common-patterns/child-workflows.mdx +0 -372
- package/docs/cookbook/common-patterns/content-router.mdx +0 -207
- package/docs/cookbook/common-patterns/fan-out.mdx +0 -208
- package/docs/foundations/common-patterns.mdx +0 -265
|
@@ -30,7 +30,12 @@ npx skills add https://github.com/vercel/workflow --skill migrating-to-workflow-
|
|
|
30
30
|
- Streaming is built in. Write durable progress from steps with `getWritable()` and named streams. No DynamoDB or SNS glue to surface status to clients.
|
|
31
31
|
- Infrastructure lives in one deployment. No separate state machine, per-task Lambda, IAM role wiring, or callback SQS queues.
|
|
32
32
|
- Error handling is TypeScript-native: step-level retries, `RetryableError`, and `FatalError` replace per-state Retry/Catch blocks.
|
|
33
|
-
-
|
|
33
|
+
- The `npx workflow` CLI and `npx workflow web` observability UI ship out of the box.
|
|
34
|
+
- AI/agent helpers — `@workflow/ai` for AI-SDK integration and the Claude migration skill — are available as separate installs.
|
|
35
|
+
|
|
36
|
+
## Before you migrate
|
|
37
|
+
|
|
38
|
+
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.
|
|
34
39
|
|
|
35
40
|
## What changes when you leave Step Functions?
|
|
36
41
|
|
|
@@ -49,10 +54,11 @@ The migration replaces declarative configuration with idiomatic TypeScript and c
|
|
|
49
54
|
| Choice state | `if` / `else` / `switch` | Native TypeScript control flow. |
|
|
50
55
|
| Wait state | `sleep()` | Import `sleep` from `workflow`. |
|
|
51
56
|
| Parallel state | `Promise.all()` | Standard concurrency primitives. |
|
|
52
|
-
| Map state |
|
|
57
|
+
| 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. |
|
|
53
58
|
| Retry / Catch | Step retries, `RetryableError`, `FatalError` | Retry logic moves to step boundaries. |
|
|
59
|
+
| `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. |
|
|
54
60
|
| `.waitForTaskToken` | `createHook()` or `createWebhook()` | Hooks for typed signals; webhooks for HTTP. |
|
|
55
|
-
| Child state machine (`StartExecution`) | `"use step"` around `
|
|
61
|
+
| Child state machine (`StartExecution`) | `start()` plus a `"use step"` wrapper around `getRun()` | Return the `Run` object, await its result from another step. |
|
|
56
62
|
| Execution event history | Workflow event log | Same durable replay model. |
|
|
57
63
|
| Progress via DynamoDB / SNS for client polling | `getWritable()` + named streams | Stream durable updates; clients read from the stream. |
|
|
58
64
|
|
|
@@ -73,6 +79,10 @@ Start with a single Task state. In ASL, even "call one Lambda" requires a state
|
|
|
73
79
|
}
|
|
74
80
|
```
|
|
75
81
|
|
|
82
|
+
<Callout type="info">
|
|
83
|
+
Examples use JSONPath mode. If your state machine sets `QueryLanguage: 'JSONata'`, the shape of `Arguments`/`Output` fields differs but the TypeScript translation is identical.
|
|
84
|
+
</Callout>
|
|
85
|
+
|
|
76
86
|
```typescript title="workflow/workflows/order.ts (Workflow SDK)"
|
|
77
87
|
export async function processOrder(orderId: string) {
|
|
78
88
|
'use workflow'; // [!code highlight]
|
|
@@ -101,7 +111,7 @@ export async function processOrder(orderId: string) {
|
|
|
101
111
|
}
|
|
102
112
|
```
|
|
103
113
|
|
|
104
|
-
`await` replaces `"Next"`. Each new step is a new function with `"use step"`; no additional deployment.
|
|
114
|
+
`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.
|
|
105
115
|
|
|
106
116
|
### Starting from an API route
|
|
107
117
|
|
|
@@ -118,6 +128,20 @@ export async function POST(request: Request) {
|
|
|
118
128
|
}
|
|
119
129
|
```
|
|
120
130
|
|
|
131
|
+
### Waiting for a fixed duration
|
|
132
|
+
|
|
133
|
+
A `Wait` state becomes `sleep()`:
|
|
134
|
+
|
|
135
|
+
```json title="stateMachine.asl.json (Step Functions)"
|
|
136
|
+
{ "Type": "Wait", "Seconds": 60, "Next": "Next" }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
{/* @skip-typecheck: one-line snippet fragment */}
|
|
140
|
+
|
|
141
|
+
```typescript title="workflow/workflows/order.ts (Workflow SDK)"
|
|
142
|
+
await sleep('1m');
|
|
143
|
+
```
|
|
144
|
+
|
|
121
145
|
## Wait for an external signal
|
|
122
146
|
|
|
123
147
|
The minimal ASL for a callback is a Task with `.waitForTaskToken`:
|
|
@@ -189,21 +213,16 @@ return { refundId, status: 'rejected' };
|
|
|
189
213
|
|
|
190
214
|
## Spawn a child workflow
|
|
191
215
|
|
|
192
|
-
In ASL, a parent machine calls `StartExecution` (usually via `.sync` or `.waitForTaskToken`) to launch a child. In
|
|
216
|
+
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.
|
|
193
217
|
|
|
194
218
|
### Parent starts a child
|
|
195
219
|
|
|
196
220
|
```typescript title="workflow/workflows/parent.ts"
|
|
197
221
|
import { start } from 'workflow/api';
|
|
198
222
|
|
|
199
|
-
async function spawnChild(item: string) {
|
|
200
|
-
'use step'; // [!code highlight]
|
|
201
|
-
return start(childWorkflow, [item]);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
223
|
export async function parentWorkflow(item: string) {
|
|
205
224
|
'use workflow';
|
|
206
|
-
const run = await
|
|
225
|
+
const run = await start(childWorkflow, [item]); // [!code highlight]
|
|
207
226
|
return { childRunId: run.runId };
|
|
208
227
|
}
|
|
209
228
|
```
|
|
@@ -232,9 +251,13 @@ Moving off Step Functions removes these surfaces from the application:
|
|
|
232
251
|
- Per-task Lambda functions, their IAM roles, and CloudFormation/CDK wiring.
|
|
233
252
|
- Task-token delivery infrastructure (SQS queues, callback Lambdas).
|
|
234
253
|
- Separate progress channels (DynamoDB, SNS) for client-visible updates.
|
|
235
|
-
- CloudWatch and X-Ray
|
|
254
|
+
- 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.
|
|
255
|
+
|
|
256
|
+
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.
|
|
257
|
+
|
|
258
|
+
### What you take on
|
|
236
259
|
|
|
237
|
-
|
|
260
|
+
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.
|
|
238
261
|
|
|
239
262
|
## Step-by-step first migration
|
|
240
263
|
|
|
@@ -276,6 +299,18 @@ async function loadOrder(id: string) {
|
|
|
276
299
|
|
|
277
300
|
Swap the task-token callback Lambda for `createHook()`. Callers `resumeHook(token, payload)` instead of `SendTaskSuccess`.
|
|
278
301
|
|
|
302
|
+
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:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
async function chargePayment(orderId: string) {
|
|
306
|
+
"use step";
|
|
307
|
+
// ...
|
|
308
|
+
}
|
|
309
|
+
chargePayment.maxRetries = 5;
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
See [`/docs/foundations/errors-and-retries`](/docs/foundations/errors-and-retries) for the full retry and SAGA compensation patterns.
|
|
313
|
+
|
|
279
314
|
### Step 5: Start runs from an API route
|
|
280
315
|
|
|
281
316
|
Delete the `StartExecution` call and IAM wiring. Launch runs directly from a route handler:
|
|
@@ -293,7 +328,16 @@ export async function POST(req: Request) {
|
|
|
293
328
|
|
|
294
329
|
### Step 6: Retire the Step Functions infrastructure
|
|
295
330
|
|
|
296
|
-
Delete the ASL JSON, per-task Lambda deployments, IAM roles, and callback queues. Remove CloudWatch and X-Ray wiring
|
|
331
|
+
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.
|
|
332
|
+
|
|
333
|
+
## Features without a 1:1 equivalent
|
|
334
|
+
|
|
335
|
+
- **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.
|
|
336
|
+
- **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.
|
|
337
|
+
- **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.
|
|
338
|
+
- **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.
|
|
339
|
+
- **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.
|
|
340
|
+
- **`JSONata` `QueryLanguage` mode.** Valid at the source; the TS translation is identical regardless of mode.
|
|
297
341
|
|
|
298
342
|
## Quick-start checklist
|
|
299
343
|
|
|
@@ -302,10 +346,13 @@ Delete the ASL JSON, per-task Lambda deployments, IAM roles, and callback queues
|
|
|
302
346
|
- Replace Choice states with `if`/`else`/`switch`.
|
|
303
347
|
- Replace Wait states with `sleep()` from `workflow`.
|
|
304
348
|
- Replace Parallel states with `Promise.all()`.
|
|
305
|
-
- Replace Map states
|
|
306
|
-
- Replace `StartExecution` child machines with `"use step"`
|
|
349
|
+
- 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.
|
|
350
|
+
- Replace `StartExecution` child machines with direct `start()` calls and a `"use step"` wrapper around `getRun()` when collecting results.
|
|
307
351
|
- Replace `.waitForTaskToken` with `createHook()` (internal callers) or `createWebhook()` (HTTP callers).
|
|
308
352
|
- Move Retry/Catch to step boundaries using `maxRetries`, `RetryableError`, and `FatalError`.
|
|
309
353
|
- Use `getStepMetadata().stepId` as the idempotency key for external side effects.
|
|
310
354
|
- Stream progress from steps with `getWritable()` instead of polling DynamoDB or SNS.
|
|
311
355
|
- Deploy and verify runs end-to-end with built-in observability.
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
*Verified against `workflow@5.0.0-beta.1` and the AWS Step Functions Amazon States Language spec on 2026-04-16.*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Migrating from Inngest
|
|
3
|
-
description: Move an Inngest TypeScript app to the Workflow SDK by replacing createFunction, step.run(), step.sleep(), step.waitForEvent(), and step.invoke() with Workflows, Steps, Hooks, and start()/getRun().
|
|
3
|
+
description: Move an Inngest TypeScript SDK v3 or v4 app to the Workflow SDK by replacing createFunction, step.run(), step.sleep(), step.waitForEvent(), and step.invoke() with Workflows, Steps, Hooks, and start()/getRun().
|
|
4
4
|
type: guide
|
|
5
5
|
summary: Translate an Inngest app into the Workflow SDK with side-by-side code examples.
|
|
6
6
|
prerequisites:
|
|
@@ -29,6 +29,8 @@ npx skills add https://github.com/vercel/workflow --skill migrating-to-workflow-
|
|
|
29
29
|
- TypeScript-first DX. Steps are named async functions marked with `"use step"`. No inline closures tied to a framework-specific lifecycle.
|
|
30
30
|
- Agent-first tooling: the `npx workflow` CLI, `@workflow/ai` integration for durable AI agents, and a Claude skill for generating workflows.
|
|
31
31
|
|
|
32
|
+
Inngest code samples in this guide use the v4 two-argument `createFunction({ id, triggers }, handler)` shape. If the app is still on Inngest v3, treat the three-argument form `createFunction({ id }, { event: '...' }, handler)` as equivalent — the `triggers` option in v4 replaces the second argument in v3.
|
|
33
|
+
|
|
32
34
|
## What changes when you leave Inngest
|
|
33
35
|
|
|
34
36
|
Inngest defines functions with `inngest.createFunction()`, registers them through a `serve()` handler, and breaks work into steps with `step.run()`, `step.sleep()`, and `step.waitForEvent()`. The platform routes events, schedules steps, and applies retries.
|
|
@@ -37,18 +39,22 @@ The Workflow SDK replaces that with `"use workflow"` functions that orchestrate
|
|
|
37
39
|
|
|
38
40
|
Migration collapses the SDK abstraction into plain async functions. Business logic stays the same.
|
|
39
41
|
|
|
42
|
+
<Callout type="info">
|
|
43
|
+
Inngest's event-bus model is loosely coupled — publishers don't know consumers. `start()` requires the caller to import the workflow function directly, giving stronger type safety but tighter coupling. For event-bus-like fan-out, wrap `start()` in a shared publisher module.
|
|
44
|
+
</Callout>
|
|
45
|
+
|
|
40
46
|
## Concept mapping
|
|
41
47
|
|
|
42
48
|
| Inngest | Workflow SDK | Migration note |
|
|
43
49
|
| --- | --- | --- |
|
|
44
50
|
| `inngest.createFunction()` | `"use workflow"` function started with `start()` | No wrapper needed. |
|
|
45
51
|
| `step.run()` | `"use step"` function | Standalone async function with Node.js access. |
|
|
46
|
-
| `step.sleep()` / `step.sleepUntil()` | `sleep()` |
|
|
52
|
+
| `step.sleep()` / `step.sleepUntil()` | `sleep()` | `sleep('5m')` for a duration; `sleep(date)` for sleep-until. |
|
|
47
53
|
| `step.waitForEvent()` | `createHook()` or `createWebhook()` | Hooks for typed signals, webhooks for HTTP. |
|
|
48
|
-
| `step.invoke()` | `"use step"`
|
|
54
|
+
| `step.invoke()` | `start()` plus a `"use step"` wrapper around `getRun()` | Spawn a child run, pass `runId` forward, and collect from a step when needed. |
|
|
49
55
|
| `inngest.send()` / event triggers | `start()` from your app boundary | Start workflows directly. |
|
|
50
56
|
| Retry configuration (`retries`) | `RetryableError`, `FatalError`, `maxRetries` | Retry logic lives at the step level. |
|
|
51
|
-
| `step.sendEvent()` | `
|
|
57
|
+
| `step.sendEvent()` | `start()` from the workflow or app boundary | Fan out explicitly, not through an event bus. |
|
|
52
58
|
| Realtime / `step.realtime.publish()` | `getWritable()` / `getWritable({ namespace })` | Named streams are the canonical way for clients to read workflow status. No database or `getRun()` polling required. |
|
|
53
59
|
|
|
54
60
|
## Translate your first workflow
|
|
@@ -60,7 +66,7 @@ Start with the shell of a function. Inngest wraps it in `createFunction`; Workfl
|
|
|
60
66
|
export const processOrder = inngest.createFunction(
|
|
61
67
|
{
|
|
62
68
|
id: 'process-order',
|
|
63
|
-
triggers:
|
|
69
|
+
triggers: { event: 'order/created' },
|
|
64
70
|
},
|
|
65
71
|
async ({ event, step }) => {
|
|
66
72
|
return { orderId: event.data.orderId, status: 'completed' };
|
|
@@ -163,17 +169,9 @@ Event matching disappears. A hook's token encodes the routing (for example, `ref
|
|
|
163
169
|
|
|
164
170
|
## Spawn a child workflow
|
|
165
171
|
|
|
166
|
-
`step.invoke()` splits into
|
|
172
|
+
`step.invoke()` splits into spawn and collect. In v5, call `start()` directly from the workflow to spawn the child. Wrap `getRun()` and `returnValue` access in a `"use step"` function when you need to collect the result. Returning the `Run` object from `start()` lets observability deep-link into the child run.
|
|
167
173
|
|
|
168
|
-
|
|
169
|
-
```typescript title="workflow/workflows/parent.ts"
|
|
170
|
-
async function spawnChild(item: string) {
|
|
171
|
-
'use step';
|
|
172
|
-
return start(childWorkflow, [item]); // [!code highlight]
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
Await the result in a second step, then orchestrate both from the parent:
|
|
174
|
+
You can return either the full `Run` object (enables deep-linking) or just `run.runId` (simpler).
|
|
177
175
|
|
|
178
176
|
{/* @skip-typecheck: snippet without imports */}
|
|
179
177
|
```typescript title="workflow/workflows/parent.ts"
|
|
@@ -185,7 +183,7 @@ async function collectResult(runId: string) {
|
|
|
185
183
|
|
|
186
184
|
export async function parentWorkflow(item: string) {
|
|
187
185
|
'use workflow';
|
|
188
|
-
const child = await
|
|
186
|
+
const child = await start(childWorkflow, [item]); // [!code highlight]
|
|
189
187
|
return await collectResult(child.runId);
|
|
190
188
|
}
|
|
191
189
|
```
|
|
@@ -206,7 +204,7 @@ Pick one Inngest function and migrate it end-to-end before touching the rest. Th
|
|
|
206
204
|
|
|
207
205
|
### Step 1: Install the Workflow SDK
|
|
208
206
|
|
|
209
|
-
|
|
207
|
+
Install the SDK. Framework integrations (`workflow/next`, `workflow/nitro`, `workflow/nuxt`, etc.) are subpath exports of the same `workflow` package — no additional install needed.
|
|
210
208
|
|
|
211
209
|
```bash
|
|
212
210
|
pnpm add workflow
|
|
@@ -219,7 +217,7 @@ Replace the factory call with a plain async export. Move the handler body up. Th
|
|
|
219
217
|
```ts title="workflows/order.ts"
|
|
220
218
|
// Before (Inngest)
|
|
221
219
|
// export const processOrder = inngest.createFunction(
|
|
222
|
-
// { id: "process-order", triggers:
|
|
220
|
+
// { id: "process-order", triggers: { event: "order/created" } },
|
|
223
221
|
// async ({ event, step }) => { ... }
|
|
224
222
|
// );
|
|
225
223
|
|
|
@@ -237,15 +235,28 @@ Each inline callback becomes a named function with `"use step"` on the first lin
|
|
|
237
235
|
```ts
|
|
238
236
|
async function loadOrder(id: string) {
|
|
239
237
|
"use step"; // [!code highlight]
|
|
240
|
-
return fetch(
|
|
238
|
+
return fetch(`https://example.com/api/orders/${id}`).then((r) => r.json());
|
|
241
239
|
}
|
|
242
240
|
```
|
|
243
241
|
|
|
242
|
+
Configure per-step retry counts by assigning `maxRetries` as a function property:
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
async function callApi(endpoint: string) {
|
|
246
|
+
"use step";
|
|
247
|
+
const response = await fetch(endpoint);
|
|
248
|
+
return response.json();
|
|
249
|
+
}
|
|
250
|
+
callApi.maxRetries = 5;
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
See [Errors and retries](/docs/foundations/errors-and-retries) for full retry docs.
|
|
254
|
+
|
|
244
255
|
### Step 4: Replace `waitForEvent`, `sleep`, and `invoke`
|
|
245
256
|
|
|
246
257
|
- `step.waitForEvent(...)` → `createHook({ token })` + `await hook`. Resume it from an API route with `resumeHook(token, payload)`.
|
|
247
258
|
- `step.sleep(...)` → `sleep("5m")` from `workflow`.
|
|
248
|
-
- `step.invoke(child, { data })` →
|
|
259
|
+
- `step.invoke(child, { data })` → call `start(child, [data])` from the workflow, and optionally read its return value from a step with `getRun(run.runId).returnValue`.
|
|
249
260
|
|
|
250
261
|
### Step 5: Start runs from the app
|
|
251
262
|
|
|
@@ -266,6 +277,13 @@ export async function POST(req: Request) {
|
|
|
266
277
|
|
|
267
278
|
Remove the `inngest` client, the `serve()` route, event schemas, and the Inngest Dev Server from the app. Verify the run in `npx workflow web` before shipping.
|
|
268
279
|
|
|
280
|
+
## Features without a 1:1 equivalent
|
|
281
|
+
|
|
282
|
+
- **Cron / scheduled functions (`triggers: { cron: '...' }`).** The SDK has no built-in scheduler. Trigger runs from Vercel Cron or a system cron calling `start()` from an API route.
|
|
283
|
+
- **Concurrency, throttling, rate limiting, debounce, singleton, priority, and `batchEvents`.** These function-level settings have no direct analog. Enforce limits inside steps (semaphores, external rate-limiter service) or debounce at the publisher before calling `start()`.
|
|
284
|
+
- **`EventSchemas` / typed events.** The event-bus indirection goes away; publishers import the workflow function directly, giving the same type safety through a different mechanism.
|
|
285
|
+
- **Event-bus fan-out by name match.** `inngest.send()` that triggered multiple functions by event name must be replaced by explicit `start()` calls for each target workflow.
|
|
286
|
+
|
|
269
287
|
## Quick-start checklist
|
|
270
288
|
|
|
271
289
|
- Replace `inngest.createFunction()` with a `"use workflow"` function; launch it with `start()`.
|
|
@@ -273,10 +291,14 @@ Remove the `inngest` client, the `serve()` route, event schemas, and the Inngest
|
|
|
273
291
|
- Swap `step.sleep()` / `step.sleepUntil()` for `sleep()` from `workflow`.
|
|
274
292
|
- Swap `step.waitForEvent()` for `createHook()` (internal) or `createWebhook()` (HTTP).
|
|
275
293
|
- Model `waitForEvent` timeouts as `Promise.race()` between the hook and `sleep()`.
|
|
276
|
-
- Replace `step.invoke()` with `"use step"`
|
|
277
|
-
- Replace `step.sendEvent()` fan-out with `start()`
|
|
294
|
+
- Replace `step.invoke()` with direct `start()` calls and a `"use step"` wrapper around `getRun()` when collecting results.
|
|
295
|
+
- Replace `step.sendEvent()` fan-out with explicit `start()` calls.
|
|
278
296
|
- Remove the Inngest client, `serve()` handler, and event definitions.
|
|
279
297
|
- Push retry configuration down to step boundaries via `maxRetries`, `RetryableError`, and `FatalError`.
|
|
280
298
|
- Use `getStepMetadata().stepId` as the idempotency key for external side effects.
|
|
281
299
|
- Replace `step.realtime.publish()` with `getWritable()`.
|
|
282
300
|
- Deploy and verify end-to-end with the built-in observability UI.
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
*Verified against `workflow@5.0.0-beta.1` and Inngest TypeScript SDK v4 on 2026-04-16.*
|
|
@@ -27,7 +27,7 @@ npx skills add https://github.com/vercel/workflow --skill migrating-to-workflow-
|
|
|
27
27
|
- Streaming is built in. Durable progress writes go to named streams via `getWritable({ namespace })`, and clients read them directly. No separate WebSocket, SSE, or progress-polling layer to operate.
|
|
28
28
|
- Infrastructure and orchestration live in a single deployment. There is no separate Worker fleet or Temporal Server to run. The runtime, step logic, and orchestration share the app's observability and log aggregation.
|
|
29
29
|
- TypeScript-first developer experience. Workflows and steps live in the same file with plain `await` control flow, `try/catch`, and `Promise.all`.
|
|
30
|
-
- Agent-first tooling. A first-class CLI (`npx workflow`), `@workflow/ai` integration for durable AI agents, and a bundled Claude skill for AI-assisted authoring.
|
|
30
|
+
- Agent-first tooling. A first-class CLI (`npx workflow`), [`@workflow/ai` integration for durable AI agents](/docs/ai), and a bundled Claude skill for AI-assisted authoring.
|
|
31
31
|
- Per-step retry controls. `RetryableError`, `FatalError`, and `maxRetries` live at the step boundary instead of an Activity-level retry policy configured elsewhere.
|
|
32
32
|
|
|
33
33
|
## What changes when you leave Temporal
|
|
@@ -36,6 +36,8 @@ Temporal requires operating a control plane (Temporal Server or Cloud), a Worker
|
|
|
36
36
|
|
|
37
37
|
The Workflow SDK runs on managed infrastructure. Write `"use workflow"` functions that orchestrate `"use step"` functions in the same file, in plain TypeScript. There are no Workers, Task Queues, or separate Activity modules. Durable replay, automatic retries, and event history are handled by the runtime.
|
|
38
38
|
|
|
39
|
+
Workflow functions must still be deterministic — no `Date.now()`, `Math.random()`, direct network I/O, or wall-clock branches inside `"use workflow"`. Move any such logic into a step, as you do today with Activities.
|
|
40
|
+
|
|
39
41
|
Migration removes infrastructure and collapses indirection. Business logic stays as regular async TypeScript.
|
|
40
42
|
|
|
41
43
|
## Concept mapping
|
|
@@ -47,10 +49,10 @@ Migration removes infrastructure and collapses indirection. Business logic stays
|
|
|
47
49
|
| Worker + Task Queue | Managed execution | No worker fleet or polling loop to operate. |
|
|
48
50
|
| Signal | `createHook()` or `createWebhook()` | Use hooks for typed resume signals; webhooks for HTTP callbacks. |
|
|
49
51
|
| Query | `getWritable({ namespace: 'status' })` stream | Durably stream status updates from the workflow. Clients read from the stream instead of polling a database. |
|
|
50
|
-
| Update | `createHook()` + `resumeHook()` |
|
|
51
|
-
| Child Workflow | `"use step"`
|
|
52
|
+
| Update | `createHook()` + `resumeHook()` (one-way) | Temporal Updates return a value to the caller; hooks do not. If the Update returns data, either write the result to a named stream via `getWritable()` and have the caller read from it, or keep an HTTP read route that fetches the workflow's current state. |
|
|
53
|
+
| Child Workflow | `start()` plus a `"use step"` wrapper around `getRun()` | Spawn a child run and return the `Run` object so observability can deep-link into child runs. |
|
|
52
54
|
| Activity retry policy | Step retries, `RetryableError`, `FatalError`, `maxRetries` | Retries live at the step boundary. |
|
|
53
|
-
| Event History | Workflow event log / run timeline | Same durable replay
|
|
55
|
+
| Event History | Workflow event log / run timeline | Same durable replay; built-in observability UI replaces Temporal Web. Search attributes and visibility APIs have no direct equivalent — filter by run status and timestamps instead. |
|
|
54
56
|
|
|
55
57
|
## Translate your first workflow
|
|
56
58
|
|
|
@@ -60,6 +62,9 @@ Start with the directive change. The Temporal definition proxies activities thro
|
|
|
60
62
|
|
|
61
63
|
{/* @skip-typecheck: Temporal SDK types not available */}
|
|
62
64
|
```typescript title="workflows/order.ts (Temporal)"
|
|
65
|
+
import * as wf from '@temporalio/workflow';
|
|
66
|
+
import type * as activities from './activities';
|
|
67
|
+
|
|
63
68
|
const { chargePayment } = wf.proxyActivities<typeof activities>({
|
|
64
69
|
startToCloseTimeout: '5 minutes',
|
|
65
70
|
});
|
|
@@ -143,6 +148,8 @@ export async function refundWorkflow(refundId: string) {
|
|
|
143
148
|
|
|
144
149
|
The workflow suspends durably at `await approval` until resumed. No polling, no handler registration.
|
|
145
150
|
|
|
151
|
+
Requires TypeScript 5.2+ for the `using` keyword. On older TypeScript, assign the hook to a `const` and call `resumeHook()` from the consuming code path.
|
|
152
|
+
|
|
146
153
|
### Resuming from an API route
|
|
147
154
|
|
|
148
155
|
Any HTTP caller can resume by token:
|
|
@@ -166,19 +173,14 @@ Temporal Queries expose in-memory workflow state on demand. In the Workflow SDK,
|
|
|
166
173
|
|
|
167
174
|
### Minimal translation
|
|
168
175
|
|
|
169
|
-
`start()`
|
|
176
|
+
In v5, call `start()` directly from the workflow to spawn a child run. Wrap `getRun()` and `returnValue` access in a `"use step"` function when you need to await the child result. Return the `Run` object (not a plain `runId` string) so workflow observability can deep-link into child runs.
|
|
170
177
|
|
|
171
178
|
```typescript title="workflow/workflows/parent.ts"
|
|
172
179
|
import { start } from 'workflow/api';
|
|
173
180
|
|
|
174
|
-
async function spawnChild(item: string) {
|
|
175
|
-
'use step'; // [!code highlight]
|
|
176
|
-
return start(childWorkflow, [item]); // [!code highlight]
|
|
177
|
-
}
|
|
178
|
-
|
|
179
181
|
export async function parentWorkflow(item: string) {
|
|
180
182
|
'use workflow';
|
|
181
|
-
const child = await
|
|
183
|
+
const child = await start(childWorkflow, [item]); // [!code highlight]
|
|
182
184
|
return { childRunId: child.runId };
|
|
183
185
|
}
|
|
184
186
|
```
|
|
@@ -197,10 +199,26 @@ async function collectResult(runId: string) {
|
|
|
197
199
|
}
|
|
198
200
|
```
|
|
199
201
|
|
|
200
|
-
Call
|
|
202
|
+
Call `start()` and then `collectResult()` from the parent in sequence: `const result = await collectResult(child.runId)`. To fan out, call `start()` inside a loop, then `Promise.all` the `collectResult` calls.
|
|
201
203
|
|
|
202
204
|
<Callout type="warn">
|
|
203
205
|
Activity retry policy moves to the step boundary. Use `maxRetries`, `RetryableError`, and `FatalError` on each step instead of a single workflow-wide retry block.
|
|
206
|
+
|
|
207
|
+
Temporal's per-activity timeouts (`startToCloseTimeout`, `scheduleToCloseTimeout`, `heartbeatTimeout`) have no direct Workflow SDK equivalent. Enforce per-step deadlines inside the step using `AbortSignal.timeout(ms)` (e.g. on `fetch`), or wrap the call from the workflow in `Promise.race(step(), sleep('5m'))` to bail out after a bounded duration.
|
|
208
|
+
|
|
209
|
+
Temporal's retry policy knobs (`initialInterval`, `backoffCoefficient`, `maximumInterval`, `nonRetryableErrorTypes`) don't port 1:1 — only `maxRetries` is configurable at the step boundary. Classify retryability with `RetryableError` (retryable) and `FatalError` (terminal) instead of listing error types, and control the delay between attempts with `new RetryableError(msg, { retryAfter: '5s' })`.
|
|
210
|
+
|
|
211
|
+
Set `maxRetries` as a property assignment on the step function:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
async function chargePayment(orderId: string) {
|
|
215
|
+
"use step";
|
|
216
|
+
// ...
|
|
217
|
+
}
|
|
218
|
+
chargePayment.maxRetries = 5;
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
See [/docs/foundations/errors-and-retries](/docs/foundations/errors-and-retries) for full retry docs.
|
|
204
222
|
</Callout>
|
|
205
223
|
|
|
206
224
|
## What you stop operating
|
|
@@ -250,7 +268,7 @@ export async function processOrder(orderId: string) {
|
|
|
250
268
|
|
|
251
269
|
### Step 4: Replace Signals with hooks
|
|
252
270
|
|
|
253
|
-
Swap `defineSignal` + `setHandler` for `createHook()`. Callers `resumeHook(token, payload)` instead of `client.workflow.
|
|
271
|
+
Swap `defineSignal` + `setHandler` for `createHook()`. Callers `resumeHook(token, payload)` instead of `handle.signal(signalDef, payload)` on a `WorkflowHandle` obtained from `client.workflow.getHandle(workflowId)`.
|
|
254
272
|
|
|
255
273
|
### Step 5: Start runs from an API route or server action
|
|
256
274
|
|
|
@@ -271,14 +289,25 @@ export async function POST(req: Request) {
|
|
|
271
289
|
|
|
272
290
|
Remove the Worker process, `@temporalio/*` dependencies, and the Temporal Server or Cloud connection. Verify the run in the built-in observability UI (`npx workflow web`) before shipping.
|
|
273
291
|
|
|
292
|
+
## Features without a 1:1 equivalent
|
|
293
|
+
|
|
294
|
+
- **Search attributes / visibility queries.** Temporal's search attribute system has no direct analog. Filter runs by status and timestamps via `getRun()` / observability UI.
|
|
295
|
+
- **Event history archival.** Temporal archives histories to S3/GCS for long-term retention. Workflow SDK event logs are durable, but retention depends on the integration you are using. For example, see [Vercel Workflow Storage Retention](https://vercel.com/docs/workflows/pricing#storage-retention) for Vercel.
|
|
296
|
+
- **Per-activity timeouts (`startToCloseTimeout`, `scheduleToCloseTimeout`, `heartbeatTimeout`).** Implement deadlines inside the step with `AbortSignal.timeout(ms)`, or wrap the call in `Promise.race(step(), sleep(...))` from the workflow.
|
|
297
|
+
- **Rich retry policy (`initialInterval`, `backoffCoefficient`, `maximumInterval`, `nonRetryableErrorTypes`).** Only `maxRetries` is configurable. Classify retryability with `RetryableError`/`FatalError`; control delay between attempts via `new RetryableError(msg, { retryAfter: '5s' })`.
|
|
298
|
+
- **Workers + task queues.** Managed deployments replace workers; self-hosted deployments still need a `World` implementation (see [/docs/deploying/building-a-world](/docs/deploying/building-a-world)).
|
|
299
|
+
|
|
274
300
|
## Quick-start checklist
|
|
275
301
|
|
|
276
302
|
- Move orchestration into a `"use workflow"` function.
|
|
277
303
|
- Convert each Activity into a `"use step"` function.
|
|
278
304
|
- Remove Worker and Task Queue code. Start workflows from the app with `start()`.
|
|
279
305
|
- Replace Signals with `createHook()` or `createWebhook()` for HTTP callers.
|
|
280
|
-
-
|
|
306
|
+
- Use `start()` directly for child workflows, and wrap `getRun()` in a `"use step"` function when collecting results. Return the `Run` object from `start()` so observability can deep-link into child runs.
|
|
281
307
|
- Set retry policy per step with `maxRetries`, `RetryableError`, and `FatalError`.
|
|
282
308
|
- Use `getStepMetadata().stepId` as the idempotency key for external side effects.
|
|
283
309
|
- Stream status and progress from steps with `getWritable({ namespace: 'status' })`, and have clients read from the stream instead of polling.
|
|
284
310
|
- Deploy the app and verify runs end-to-end in the built-in observability UI.
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
*Verified against `workflow@5.0.0-beta.1` and `@temporalio/workflow@1.16` on 2026-04-16.*
|