workflow 5.0.0-beta.8 → 5.0.0
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/README.md +68 -23
- package/dist/api-workflow.d.ts +3 -1
- package/dist/api-workflow.d.ts.map +1 -1
- package/dist/api-workflow.js +2 -1
- package/dist/api.d.ts +5 -4
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +6 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/internal/builtins.d.ts +4 -4
- package/dist/internal/builtins.js +6 -6
- package/dist/internal/errors.d.ts +1 -1
- package/dist/internal/errors.d.ts.map +1 -1
- package/dist/internal/errors.js +2 -2
- package/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/dist/runtime.d.ts +2 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +4 -1
- package/docs/advanced/dynamic-workflows.mdx +224 -0
- package/docs/ai/chat-session-modeling.mdx +176 -422
- package/docs/ai/defining-tools.mdx +6 -7
- package/docs/ai/human-in-the-loop.mdx +11 -11
- package/docs/ai/index.mdx +67 -72
- package/docs/ai/message-queueing.mdx +71 -110
- package/docs/ai/meta.json +1 -0
- package/docs/ai/resumable-streams.mdx +40 -28
- package/docs/ai/sleep-and-delays.mdx +10 -10
- package/docs/ai/streaming-updates-from-tools.mdx +6 -6
- package/docs/api-reference/index.mdx +25 -1
- package/docs/api-reference/meta.json +8 -0
- package/docs/api-reference/vitest/index.mdx +68 -15
- package/docs/api-reference/workflow/create-hook.mdx +166 -10
- package/docs/api-reference/workflow/create-webhook.mdx +16 -15
- package/docs/api-reference/workflow/define-hook.mdx +37 -33
- package/docs/api-reference/workflow/fatal-error.mdx +30 -8
- package/docs/api-reference/workflow/fetch.mdx +14 -10
- package/docs/api-reference/workflow/get-step-metadata.mdx +2 -2
- package/docs/api-reference/workflow/get-workflow-metadata.mdx +3 -3
- package/docs/api-reference/workflow/get-writable.mdx +7 -7
- package/docs/api-reference/workflow/index.mdx +4 -1
- package/docs/api-reference/workflow/retryable-error.mdx +1 -1
- package/docs/api-reference/workflow/set-attributes.mdx +63 -0
- package/docs/api-reference/workflow/sleep.mdx +4 -4
- package/docs/api-reference/workflow-ai/durable-agent.mdx +63 -101
- package/docs/api-reference/workflow-ai/index.mdx +5 -5
- package/docs/api-reference/workflow-ai/workflow-chat-transport.mdx +67 -24
- package/docs/api-reference/workflow-api/get-hook-by-token.mdx +28 -12
- package/docs/api-reference/workflow-api/get-run.mdx +43 -8
- package/docs/api-reference/workflow-api/index.mdx +8 -9
- package/docs/api-reference/workflow-api/register-lifecycle-hooks.mdx +87 -0
- package/docs/api-reference/workflow-api/resume-hook.mdx +73 -12
- package/docs/api-reference/workflow-api/resume-webhook.mdx +11 -9
- package/docs/api-reference/workflow-api/start.mdx +107 -12
- 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/entity-conflict-error.mdx +4 -4
- package/docs/api-reference/workflow-errors/hook-conflict-error.mdx +60 -0
- package/docs/api-reference/workflow-errors/hook-force-claimed-error.mdx +70 -0
- package/docs/api-reference/workflow-errors/hook-not-found-error.mdx +8 -8
- package/docs/api-reference/workflow-errors/index.mdx +91 -0
- package/docs/api-reference/workflow-errors/meta.json +7 -0
- package/docs/api-reference/workflow-errors/precondition-failed-error.mdx +68 -0
- package/docs/api-reference/workflow-errors/run-expired-error.mdx +2 -2
- package/docs/api-reference/workflow-errors/run-not-supported-error.mdx +58 -0
- package/docs/api-reference/workflow-errors/step-not-registered-error.mdx +5 -5
- package/docs/api-reference/workflow-errors/throttle-error.mdx +2 -2
- package/docs/api-reference/workflow-errors/too-early-error.mdx +2 -2
- package/docs/api-reference/workflow-errors/workflow-error.mdx +52 -0
- package/docs/api-reference/workflow-errors/workflow-not-registered-error.mdx +5 -6
- package/docs/api-reference/workflow-errors/workflow-run-cancelled-error.mdx +13 -6
- package/docs/api-reference/workflow-errors/workflow-run-failed-error.mdx +12 -5
- package/docs/api-reference/workflow-errors/workflow-run-not-completed-error.mdx +58 -0
- package/docs/api-reference/workflow-errors/workflow-run-not-found-error.mdx +4 -4
- package/docs/api-reference/workflow-errors/workflow-runtime-error.mdx +58 -0
- package/docs/api-reference/workflow-errors/workflow-world-error.mdx +8 -8
- package/docs/api-reference/workflow-globals.mdx +15 -11
- package/docs/api-reference/workflow-nest/configure-workflow-controller.mdx +37 -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 +46 -0
- package/docs/api-reference/workflow-nest/workflow-module.mdx +134 -0
- package/docs/api-reference/workflow-next/with-workflow.mdx +39 -17
- package/docs/api-reference/workflow-nitro/index.mdx +60 -0
- package/docs/api-reference/workflow-nuxt/index.mdx +48 -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 +62 -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 +11 -14
- package/docs/api-reference/workflow-runtime/health-check.mdx +51 -0
- package/docs/api-reference/workflow-runtime/index.mdx +41 -0
- package/docs/api-reference/workflow-runtime/meta.json +12 -0
- package/docs/api-reference/workflow-runtime/set-world.mdx +51 -0
- package/docs/api-reference/workflow-runtime/workflow-entrypoint.mdx +43 -0
- package/docs/api-reference/workflow-runtime/world/analytics.mdx +315 -0
- package/docs/api-reference/workflow-runtime/world/index.mdx +60 -0
- package/docs/api-reference/workflow-runtime/world/meta.json +4 -0
- package/docs/api-reference/workflow-runtime/world/queue.mdx +88 -0
- package/docs/api-reference/{workflow-api → workflow-runtime}/world/storage.mdx +104 -35
- package/docs/api-reference/{workflow-api → workflow-runtime}/world/streams.mdx +8 -8
- package/docs/api-reference/workflow-serde/index.mdx +1 -2
- package/docs/api-reference/workflow-serde/workflow-deserialize.mdx +3 -4
- package/docs/api-reference/workflow-serde/workflow-serialize.mdx +8 -8
- 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 +61 -46
- package/docs/changelog/batched-event-writes.mdx +79 -0
- package/docs/changelog/eager-processing.mdx +110 -436
- package/docs/changelog/index.mdx +4 -2
- package/docs/changelog/lazy-event-creation.md +127 -0
- package/docs/changelog/lazy-hook-resume.mdx +78 -0
- package/docs/changelog/meta.json +11 -1
- package/docs/changelog/resilient-resume.mdx +32 -0
- package/docs/changelog/resilient-start.mdx +33 -285
- package/docs/changelog/step-message-ownership.mdx +360 -0
- package/docs/changelog/turbo-mode.md +87 -0
- 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 +104 -0
- package/docs/configuration/build-and-diagnostics.mdx +79 -0
- package/docs/configuration/cli-and-web-ui.mdx +241 -0
- package/docs/configuration/framework-options.mdx +165 -0
- package/docs/configuration/index.mdx +32 -0
- package/docs/configuration/meta.json +12 -0
- package/docs/configuration/runtime-tuning.mdx +399 -0
- package/docs/configuration/worlds.mdx +315 -0
- package/docs/cookbook/advanced/child-workflows.mdx +33 -25
- package/docs/cookbook/advanced/publishing-libraries.mdx +65 -56
- package/docs/cookbook/advanced/serializable-steps.mdx +48 -68
- package/docs/cookbook/advanced/upgrading-workflows.mdx +35 -31
- package/docs/cookbook/agent-patterns/agent-cancellation.mdx +78 -60
- package/docs/cookbook/agent-patterns/durable-agent.mdx +23 -135
- package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +180 -195
- package/docs/cookbook/common-patterns/batching.mdx +20 -14
- package/docs/cookbook/common-patterns/idempotency.mdx +41 -53
- package/docs/cookbook/common-patterns/rate-limiting.mdx +8 -4
- package/docs/cookbook/common-patterns/saga.mdx +23 -19
- package/docs/cookbook/common-patterns/scheduling.mdx +30 -22
- package/docs/cookbook/common-patterns/sequential-and-parallel.mdx +29 -25
- package/docs/cookbook/common-patterns/timeouts.mdx +26 -21
- package/docs/cookbook/common-patterns/webhooks.mdx +10 -6
- package/docs/cookbook/common-patterns/workflow-composition.mdx +27 -17
- package/docs/cookbook/index.mdx +22 -22
- package/docs/cookbook/integrations/ai-sdk.mdx +63 -48
- package/docs/cookbook/integrations/chat-sdk.mdx +46 -33
- package/docs/cookbook/integrations/sandbox.mdx +58 -45
- package/docs/deploying.mdx +106 -0
- package/docs/errors/abort-signal-timeout-in-workflow.mdx +16 -12
- package/docs/errors/corrupted-event-log.mdx +39 -18
- package/docs/errors/deployment-mismatch.mdx +71 -0
- package/docs/errors/fetch-in-workflow.mdx +15 -14
- package/docs/errors/hook-conflict.mdx +38 -11
- package/docs/errors/hook-force-claimed.mdx +96 -0
- package/docs/errors/index.mdx +24 -37
- package/docs/errors/node-js-module-in-workflow.mdx +9 -5
- package/docs/errors/replay-divergence.mdx +27 -0
- package/docs/errors/run-expired.mdx +85 -0
- package/docs/errors/runtime-decryption-failed.mdx +77 -0
- package/docs/errors/serialization-failed.mdx +44 -12
- package/docs/errors/start-invalid-workflow-function.mdx +9 -5
- package/docs/errors/step-executed-multiple-times.mdx +23 -0
- package/docs/errors/step-not-registered.mdx +6 -6
- package/docs/errors/timeout-in-workflow.mdx +12 -8
- package/docs/errors/webhook-invalid-respond-with-value.mdx +18 -18
- package/docs/errors/webhook-response-not-sent.mdx +20 -16
- package/docs/errors/workflow-not-registered.mdx +5 -5
- package/docs/foundations/cancellation.mdx +31 -32
- package/docs/foundations/errors-and-retries.mdx +54 -11
- package/docs/foundations/hooks.mdx +98 -35
- package/docs/foundations/idempotency.mdx +267 -12
- package/docs/foundations/index.mdx +1 -26
- package/docs/foundations/serialization.mdx +22 -22
- package/docs/foundations/starting-workflows.mdx +104 -30
- package/docs/foundations/streaming.mdx +108 -60
- package/docs/foundations/versioning.mdx +4 -4
- package/docs/foundations/workflows-and-steps.mdx +9 -9
- package/docs/getting-started/astro.mdx +22 -18
- package/docs/getting-started/express.mdx +15 -11
- package/docs/getting-started/fastify.mdx +15 -11
- package/docs/getting-started/hono.mdx +15 -11
- package/docs/getting-started/index.mdx +10 -3
- package/docs/getting-started/meta.json +3 -1
- package/docs/getting-started/nestjs.mdx +264 -21
- package/docs/getting-started/next.mdx +18 -14
- package/docs/getting-started/nitro.mdx +22 -18
- package/docs/getting-started/nuxt.mdx +15 -11
- package/docs/getting-started/python.mdx +190 -41
- package/docs/getting-started/react-router/index.mdx +33 -0
- package/docs/getting-started/react-router/meta.json +5 -0
- package/docs/getting-started/react-router/v7.mdx +237 -0
- package/docs/getting-started/react-router/v8.mdx +232 -0
- package/docs/getting-started/sveltekit.mdx +20 -16
- package/docs/getting-started/tanstack-start.mdx +17 -13
- package/docs/getting-started/vite.mdx +15 -11
- package/docs/how-it-works/cancellation.mdx +63 -63
- package/docs/how-it-works/code-transform.mdx +82 -66
- package/docs/how-it-works/encryption.mdx +30 -26
- package/docs/how-it-works/event-sourcing.mdx +132 -35
- package/docs/how-it-works/framework-integrations.mdx +96 -337
- package/docs/how-it-works/understanding-directives.mdx +22 -22
- package/docs/internal/index.mdx +6 -4
- package/docs/internal/meta.json +6 -1
- 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 +7 -7
- package/docs/meta.json +4 -2
- package/docs/observability/attributes.mdx +136 -0
- package/docs/observability/index.mdx +32 -10
- package/docs/observability/lifecycle-hooks.mdx +95 -0
- package/docs/observability/meta.json +1 -1
- package/docs/observability/retention.mdx +95 -0
- package/docs/observability/tracing.mdx +124 -0
- package/docs/testing/index.mdx +120 -38
- package/docs/testing/server-based.mdx +10 -10
- package/docs/whats-new.mdx +190 -0
- package/docs/worlds/building-a-world.mdx +538 -0
- package/docs/worlds/local.mdx +129 -0
- package/docs/worlds/meta.json +10 -0
- package/docs/worlds/postgres.mdx +424 -0
- package/docs/worlds/upgrading-to-v5.mdx +162 -0
- package/docs/worlds/vercel.mdx +345 -0
- package/package.json +17 -14
- package/docs/api-reference/workflow-api/world/index.mdx +0 -58
- package/docs/api-reference/workflow-api/world/meta.json +0 -4
- package/docs/api-reference/workflow-api/world/observability.mdx +0 -164
- package/docs/api-reference/workflow-api/world/queue.mdx +0 -86
- package/docs/deploying/building-a-world.mdx +0 -251
- package/docs/deploying/index.mdx +0 -95
- package/docs/deploying/meta.json +0 -4
- package/docs/deploying/world/local-world.mdx +0 -84
- package/docs/deploying/world/meta.json +0 -4
- package/docs/deploying/world/postgres-world.mdx +0 -224
- package/docs/deploying/world/vercel-world.mdx +0 -181
- 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 -358
- package/docs/migration-guides/migrating-from-inngest.mdx +0 -304
- package/docs/migration-guides/migrating-from-temporal.mdx +0 -313
- package/docs/migration-guides/migrating-from-trigger-dev.mdx +0 -328
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Data retention
|
|
3
|
+
description: Control how long a run's data is kept after it finishes.
|
|
4
|
+
type: reference
|
|
5
|
+
summary: Control how long a run's data is kept after the run ends.
|
|
6
|
+
prerequisites:
|
|
7
|
+
- /docs/foundations/workflows-and-steps
|
|
8
|
+
related:
|
|
9
|
+
- /docs/observability
|
|
10
|
+
- /docs/api-reference/workflow-api/start
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
A finished run leaves data behind: the inputs and outputs of the workflow and
|
|
14
|
+
each of its steps, the payloads on its event log, and anything written to its
|
|
15
|
+
streams. How long that data is kept is decided by the World you are running
|
|
16
|
+
on, not by the SDK.
|
|
17
|
+
|
|
18
|
+
`experimental_retention` on [`start()`](/docs/api-reference/workflow-api/start)
|
|
19
|
+
lets a run ask for a specific retention period, rather than the World's default.
|
|
20
|
+
|
|
21
|
+
## Deleting a run's data as soon as it ends
|
|
22
|
+
|
|
23
|
+
{/* @skip-typecheck: abbreviated usage; processDocumentWorkflow is the reader's own workflow */}
|
|
24
|
+
```typescript lineNumbers
|
|
25
|
+
const run = await start(processDocumentWorkflow, [documentId], {
|
|
26
|
+
experimental_retention: 0, // [!code highlight]
|
|
27
|
+
})
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`0` asks the World to delete the run's **user data** the moment the run
|
|
31
|
+
completes or fails, rather than keeping it for the World's default window.
|
|
32
|
+
|
|
33
|
+
Two values are accepted today:
|
|
34
|
+
|
|
35
|
+
| Value | Meaning |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| `0` | Delete user data as soon as the run reaches a terminal state. |
|
|
38
|
+
| `'default'` | Use the World's default. Identical to omitting the option. |
|
|
39
|
+
|
|
40
|
+
<Callout type="warn">
|
|
41
|
+
The option is prefixed `experimental_` because both its name and the set of
|
|
42
|
+
values it accepts are expected to change.
|
|
43
|
+
</Callout>
|
|
44
|
+
|
|
45
|
+
## What is deleted, and what is not
|
|
46
|
+
|
|
47
|
+
**Deleted:** the run's input, output and error; every step's input, output and
|
|
48
|
+
error; the payloads on the event log; and stream contents.
|
|
49
|
+
|
|
50
|
+
**Kept:** the run, step and event records themselves — their ids, timestamps,
|
|
51
|
+
status, step names, and any [attributes](/docs/observability/attributes) you
|
|
52
|
+
set. They are kept for the World's default period so the run stays visible in
|
|
53
|
+
the CLI and web UI. A purged run is still listed and still traceable; its
|
|
54
|
+
payloads simply read back as expired.
|
|
55
|
+
|
|
56
|
+
Inspecting a purged run shows it as expired rather than failing. The Workflow
|
|
57
|
+
CLI renders the run's own input, output and error as `<data expired>`:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
workflow inspect runs wrun_...
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Step, hook and event payloads read back empty. On the Vercel World they also
|
|
64
|
+
render as `<data expired>`; on Worlds that clear the stored value outright
|
|
65
|
+
they simply show as empty. Either way the data is gone — the difference is
|
|
66
|
+
only in how the absence is labelled.
|
|
67
|
+
|
|
68
|
+
<Callout type="warn">
|
|
69
|
+
**You cannot read the return value of a run started with
|
|
70
|
+
`experimental_retention: 0`.** The deletion races your own read of the
|
|
71
|
+
result and generally wins, so `await run.returnValue` throws
|
|
72
|
+
[`RunExpiredError`](/docs/errors/run-expired) instead of resolving.
|
|
73
|
+
|
|
74
|
+
This is a known limitation. If you need the result, send it somewhere you
|
|
75
|
+
control, e.g. a step that writes it to your own store, rather than reading
|
|
76
|
+
it back off the run.
|
|
77
|
+
</Callout>
|
|
78
|
+
|
|
79
|
+
`RunExpiredError` is not specific to `experimental_retention: 0`. Any run read
|
|
80
|
+
after its retention window has passed throws it, and the error carries
|
|
81
|
+
`runId`, `runStatus` and `expiredAt` when the World still has them — so a
|
|
82
|
+
caller can tell a successful run whose result is gone from a failed one whose
|
|
83
|
+
error is gone. If the run's metadata is gone too, the World reports the run as
|
|
84
|
+
missing and you get `WorkflowRunNotFoundError` instead.
|
|
85
|
+
|
|
86
|
+
## Retention is implemented by the World
|
|
87
|
+
|
|
88
|
+
The SDK records your preference; it does not enforce it. `start()` writes the
|
|
89
|
+
value onto the run as the reserved `$retention` attribute, and the World
|
|
90
|
+
decides what to do when the run ends. Attributes are a spec version 4 feature,
|
|
91
|
+
so `experimental_retention: 0` throws against an older World rather than being
|
|
92
|
+
recorded and ignored. A World that does implement spec version 4 but not
|
|
93
|
+
retention **keeps the data**. If you need certainty that a specific World
|
|
94
|
+
deletes your data, confirm it against that World's own documentation rather
|
|
95
|
+
than the presence of this option.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Tracing
|
|
3
|
+
description: Distributed tracing with OpenTelemetry for workflow runs, steps, and queue deliveries.
|
|
4
|
+
type: guide
|
|
5
|
+
summary: Trace workflow execution end to end with OpenTelemetry.
|
|
6
|
+
prerequisites:
|
|
7
|
+
- /docs/foundations/workflows-and-steps
|
|
8
|
+
related:
|
|
9
|
+
- /docs/observability
|
|
10
|
+
- /docs/observability/attributes
|
|
11
|
+
- /docs/how-it-works/event-sourcing
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
The Workflow SDK includes [OpenTelemetry](https://opentelemetry.io) instrumentation. It emits spans for workflow starts, every workflow and step invocation, and the HTTP calls it makes to the workflow backend, and it propagates trace context across queue deliveries so a run remains traceable end to end.
|
|
15
|
+
|
|
16
|
+
The SDK only depends on the OpenTelemetry **API**, never on an SDK or exporter. If your application does not register an OpenTelemetry SDK, all tracing code is a silent no-op with no overhead and no behavior change.
|
|
17
|
+
|
|
18
|
+
## Enabling tracing
|
|
19
|
+
|
|
20
|
+
Register any OpenTelemetry Node SDK in your application. On Vercel with Next.js, the simplest setup is [`@vercel/otel`](https://vercel.com/docs/tracing/instrumentation) in `instrumentation.ts`:
|
|
21
|
+
|
|
22
|
+
```typescript title="instrumentation.ts" lineNumbers
|
|
23
|
+
import { registerOTel } from "@vercel/otel"
|
|
24
|
+
|
|
25
|
+
export function register() {
|
|
26
|
+
registerOTel({ serviceName: "my-app" })
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
No workflow-specific configuration is required. As soon as a tracer provider and propagator are registered, the SDK's spans, context propagation, and span links activate automatically.
|
|
31
|
+
|
|
32
|
+
<Callout>
|
|
33
|
+
`@opentelemetry/api` is an **optional peer dependency**. An OpenTelemetry SDK such as `@vercel/otel` normally pulls it in transitively, but installing it directly (`npm i @opentelemetry/api`) guarantees it is present in your build, particularly for bundled or serverless targets where the SDK's tracing is inlined at build time. If it can't be resolved, tracing is a silent no-op.
|
|
34
|
+
</Callout>
|
|
35
|
+
|
|
36
|
+
## Spans
|
|
37
|
+
|
|
38
|
+
| Span name | Kind | Emitted when |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `workflow.start <name>` | internal | `start()` is called in your application code |
|
|
41
|
+
| `workflow.execute <name>` | consumer (root) | a queue delivery invokes the workflow; replay, orchestration, and inline steps run under it |
|
|
42
|
+
| `step.execute <name>` | internal (inline) / consumer + root (queue-delivered) | a step function executes |
|
|
43
|
+
| `http <method>` | client | the SDK calls the workflow backend (event reads/writes) |
|
|
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) |
|
|
46
|
+
| `workflow.stream.close` | client | the stream-close RPC; its duration is the close round trip |
|
|
47
|
+
| `workflow.stream.read.complete` | client | a stream read drains; back-dated to the read dispatch, so its duration is the total read (`workflow.stream.read.chunks` / `.bytes` carry throughput counts) |
|
|
48
|
+
| `workflow.stream.read.connect` | client | a live stream read opens; the span covers dispatch → response headers (network connect) |
|
|
49
|
+
| `workflow.stream.read` | client | a live stream read receives its first chunk; the span's duration is the end-to-end time-to-first-chunk (see `workflow.stream.read.ttfc_ms`) |
|
|
50
|
+
|
|
51
|
+
`<name>` is the short function name (for example `processOrder`); the full machine name, including the source module, is available in the `workflow.name` / `step.name` attributes.
|
|
52
|
+
|
|
53
|
+
Stream spans are emitted by the SDK's world backend on the client that writes or reads the stream, and (like all SDK spans) are no-ops when no OpenTelemetry SDK is registered. The `workflow.stream.read` span only appears once the first non-empty chunk arrives.
|
|
54
|
+
|
|
55
|
+
## Key attributes
|
|
56
|
+
|
|
57
|
+
| Attribute | Description |
|
|
58
|
+
| --- | --- |
|
|
59
|
+
| `workflow.run.id` | The run ID (`wrun_...`). Present on every workflow and step span; the primary key for finding all spans of a run. |
|
|
60
|
+
| `workflow.name` | The workflow function name. |
|
|
61
|
+
| `workflow.trace.mode` | The active trace mode (`linked` or `continuous`). |
|
|
62
|
+
| `workflow.trace.propagated` | Whether the invocation received trace context from the queue message. |
|
|
63
|
+
| `workflow.queue.overhead_ms` | Time between the message being enqueued and the handler starting: queue dwell plus any cold start. |
|
|
64
|
+
| `workflow.stream.name` | The stream name, on stream write/read spans. |
|
|
65
|
+
| `workflow.stream.operation` | The stream operation: `write`, `write_multi`, `close`, `read`, or `flush`. |
|
|
66
|
+
| `workflow.stream.write.chunk_rtt` | Time between emission of a chunk to the wire, and receiving the `ack` message for that chunk. Also stamped on `workflow.stream.flush` (the batch's write RPC duration, network included). |
|
|
67
|
+
| `workflow.stream.flush.buffer_dwell_ms` | On `workflow.stream.flush`: time the batch's first chunk waited in the client-side write buffer (flush timer, run-ready barrier) before the request was dispatched. `workflow.stream.flush.chunks` / `.bytes` carry the batch shape. |
|
|
68
|
+
| `workflow.stream.read.ttfc_ms` | Time between opening a read connection and observing and receiving the first chunk back. |
|
|
69
|
+
| `workflow.stream.read.connect_ms` | On `workflow.stream.read`: the connect portion (read dispatch → stream handle/response headers), network included. |
|
|
70
|
+
|
|
71
|
+
## Trace shape: one trace per invocation
|
|
72
|
+
|
|
73
|
+
A single workflow run can span hours or days across many separate function invocations: every `sleep()` wake-up, hook resume, retry, and queued step continuation is a new queue delivery. Stitching all of that into one trace produces giant, slow-loading traces that most tracing backends truncate.
|
|
74
|
+
|
|
75
|
+
Instead, the SDK creates **one bounded trace per invocation**. Each `workflow.execute` (or background `step.execute`) span starts a new trace root and attaches two **span links**:
|
|
76
|
+
|
|
77
|
+
- a link to the **enqueue site**, the span that queued the message which triggered this invocation, and
|
|
78
|
+
- a link to the **run origin**, the trace in which `start()` was originally called.
|
|
79
|
+
|
|
80
|
+
A span link is OpenTelemetry's relationship for "causally related, but in a different trace." It is the standard pattern for asynchronous messaging, where producing and consuming a message can be separated by arbitrary time.
|
|
81
|
+
|
|
82
|
+
```mermaid
|
|
83
|
+
flowchart LR
|
|
84
|
+
O["start() request trace"]
|
|
85
|
+
A["invocation 1"]
|
|
86
|
+
B["invocation 2"]
|
|
87
|
+
C["invocation 3 ..."]
|
|
88
|
+
A -. "link" .-> O
|
|
89
|
+
B -. "link" .-> O
|
|
90
|
+
C -. "link" .-> O
|
|
91
|
+
B -. "link" .-> A
|
|
92
|
+
C -. "link" .-> B
|
|
93
|
+
|
|
94
|
+
style O fill:#a78bfa,stroke:#8b5cf6,color:#000
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Each invocation links back to the trace that enqueued it and to the run origin.
|
|
98
|
+
|
|
99
|
+
To see a whole run, query by attribute rather than by trace ID (for example `workflow.run.id = wrun_...` in your tracing backend), or follow the span links between invocation traces.
|
|
100
|
+
|
|
101
|
+
## Trace modes
|
|
102
|
+
|
|
103
|
+
The `WORKFLOW_TRACE_MODE` environment variable controls the shape:
|
|
104
|
+
|
|
105
|
+
| Mode | Behavior |
|
|
106
|
+
| --- | --- |
|
|
107
|
+
| `linked` (default) | Each invocation is its own trace root with span links to the enqueue site and the run origin. Traces stay small; sampling is decided per invocation. |
|
|
108
|
+
| `continuous` | The run-origin context becomes the **parent** of every invocation, so the entire run shares one trace ID. |
|
|
109
|
+
|
|
110
|
+
<Callout type="warn">
|
|
111
|
+
This is a behavior change from v4, which always used `continuous`-style tracing. If you have dashboards or queries that assume one trace ID per run, either update them to use `workflow.run.id` and span links, or set `WORKFLOW_TRACE_MODE=continuous` to restore the previous shape. In `linked` mode, each invocation root makes its own sampling decision, and the number of root spans increases to one per invocation.
|
|
112
|
+
</Callout>
|
|
113
|
+
|
|
114
|
+
## Context propagation
|
|
115
|
+
|
|
116
|
+
When tracing is enabled, the SDK propagates [W3C Trace Context](https://www.w3.org/TR/trace-context/) on its outbound calls:
|
|
117
|
+
|
|
118
|
+
- **Backend requests** carry `traceparent`, `tracestate`, and `baggage` headers, so backend spans can join your trace.
|
|
119
|
+
- **Queue messages** carry the run-origin trace context in the message payload, and the queue re-delivers the producer's context to the workflow handler, where it becomes the enqueue-site span link.
|
|
120
|
+
- **Baggage** carries `workflow.run_id` and `workflow.name` entries during workflow execution, allowing downstream services you call from steps to tag their own telemetry with the run ID.
|
|
121
|
+
|
|
122
|
+
<Callout>
|
|
123
|
+
Baggage entries set by your application are propagated as a `baggage` HTTP header on the SDK's backend requests, like any other OpenTelemetry-instrumented HTTP call. Avoid placing sensitive values in baggage.
|
|
124
|
+
</Callout>
|
package/docs/testing/index.mdx
CHANGED
|
@@ -3,18 +3,18 @@ title: Testing
|
|
|
3
3
|
description: Unit test individual steps and integration test entire workflows using Vitest.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Test steps like any other JavaScript function, or use the Workflow SDK Vitest plugin to run complete workflows in-process without a server.
|
|
7
7
|
|
|
8
8
|
This guide covers two approaches:
|
|
9
9
|
|
|
10
|
-
1. **Unit testing
|
|
11
|
-
2. **Integration testing
|
|
10
|
+
1. **Unit testing**: Test individual steps as plain functions without the workflow runtime.
|
|
11
|
+
2. **Integration testing**: Test entire workflows in-process using the `workflow()` Vitest plugin. Use integration tests for workflow-specific code paths that use [hooks](/docs/foundations/hooks), webhooks, [`sleep()`](/docs/api-reference/workflow/sleep), or retries.
|
|
12
12
|
|
|
13
|
-
## Unit
|
|
13
|
+
## Unit testing steps
|
|
14
14
|
|
|
15
|
-
Without the workflow compiler, the `"use step"` directive is a no-op. Your step functions run as regular JavaScript functions,
|
|
15
|
+
Without the workflow compiler, the `"use step"` directive is a no-op. Your step functions run as regular JavaScript functions, so you can unit test them without special configuration.
|
|
16
16
|
|
|
17
|
-
### Example
|
|
17
|
+
### Example steps
|
|
18
18
|
|
|
19
19
|
Given a workflow file with step functions like this:
|
|
20
20
|
|
|
@@ -49,7 +49,7 @@ export async function sendOnboardingEmail(user: { id: string; email: string }) {
|
|
|
49
49
|
}
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
### Writing
|
|
52
|
+
### Writing unit tests for steps
|
|
53
53
|
|
|
54
54
|
You can import and test step functions directly with Vitest. No special configuration or workflow plugin is needed:
|
|
55
55
|
|
|
@@ -77,18 +77,30 @@ describe("sendWelcomeEmail step", () => {
|
|
|
77
77
|
This approach is ideal for verifying the business logic inside individual steps in isolation.
|
|
78
78
|
|
|
79
79
|
<Callout type="info">
|
|
80
|
-
Unit testing works well for individual steps. A
|
|
80
|
+
Unit testing works well for individual steps. A workflow that only calls steps can also be unit tested this way, since `"use workflow"` is similarly a no-op without the compiler. However, any workflow that uses runtime features like [`sleep()`](/docs/api-reference/workflow/sleep), [hooks](/docs/foundations/hooks), or [webhooks](/docs/foundations/hooks#understanding-webhooks) cannot be unit tested directly because those APIs require the workflow runtime. Use [integration testing](#integration-testing-with-the-vitest-plugin) for testing entire workflows, especially those that depend on workflow-only features.
|
|
81
81
|
</Callout>
|
|
82
82
|
|
|
83
|
-
## Integration
|
|
83
|
+
## Integration testing with the Vitest plugin
|
|
84
84
|
|
|
85
|
-
For workflows that rely on runtime features like [hooks](/docs/foundations/hooks), [webhooks](/docs/foundations/hooks#understanding-webhooks), [`sleep()`](/docs/api-reference/workflow/sleep), or error retries, you need to test against a real workflow setup. The `@workflow/vitest` plugin handles everything automatically
|
|
85
|
+
For workflows that rely on runtime features like [hooks](/docs/foundations/hooks), [webhooks](/docs/foundations/hooks#understanding-webhooks), [`sleep()`](/docs/api-reference/workflow/sleep), or error retries, you need to test against a real workflow setup. The `@workflow/vitest` plugin handles everything automatically: it compiles your workflow directives, builds the runtime bundles, and executes workflows entirely in-process. No server required.
|
|
86
86
|
|
|
87
87
|
<Callout type="warn">
|
|
88
|
-
`vi.mock()`
|
|
88
|
+
`vi.mock()` cannot reach code inside workflow functions at all, and reaches step code only under specific conditions. Read [Mocking](#mocking) before reaching for it.
|
|
89
89
|
</Callout>
|
|
90
90
|
|
|
91
|
-
###
|
|
91
|
+
### Installation
|
|
92
|
+
|
|
93
|
+
```package-install
|
|
94
|
+
npm i -D @workflow/vitest@beta
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
<Callout type="warn">
|
|
98
|
+
**Workflow 5 needs the `beta` tag.** `@workflow/vitest@latest` is still the 4.x line, so a plain `npm i -D @workflow/vitest` installs a v4 plugin next to a v5 app. Install `@workflow/vitest@beta`, or pin the beta your app is on (for example `@workflow/vitest@5.0.0-beta.53`), and upgrade it together with `workflow`.
|
|
99
|
+
|
|
100
|
+
The plugin builds and runs your workflows against the copy of `@workflow/core` it was installed with, so it checks this for you. At the start of each run it compares that copy with the one your app resolves: a different major fails the run with the install command that fixes it, and any other difference logs a warning. Set [`WORKFLOW_VITEST_VERSION_CHECK=off`](/docs/configuration/build-and-diagnostics#workflow_vitest_version_check) to skip the check.
|
|
101
|
+
</Callout>
|
|
102
|
+
|
|
103
|
+
### Vitest configuration
|
|
92
104
|
|
|
93
105
|
Create a separate Vitest config for integration tests that includes the `workflow()` plugin:
|
|
94
106
|
|
|
@@ -109,15 +121,15 @@ That's it. The plugin automatically:
|
|
|
109
121
|
|
|
110
122
|
1. Transforms `"use workflow"` and `"use step"` directives via SWC
|
|
111
123
|
2. Builds workflow and step bundles before tests run
|
|
112
|
-
3. Sets up an in-process workflow runtime using a fresh [Local World](/
|
|
124
|
+
3. Sets up an in-process workflow runtime using a fresh [Local World](/worlds/local) instance in each test worker; all workflow data is cleared automatically between test files for full isolation
|
|
113
125
|
|
|
114
126
|
<Callout type="info">
|
|
115
127
|
Use a separate Vitest configuration and a distinct file naming convention (e.g. `*.integration.test.ts`) to keep unit tests and integration tests separate. Unit tests run with a standard Vitest config without the workflow plugin, while integration tests use the config above.
|
|
116
128
|
</Callout>
|
|
117
129
|
|
|
118
|
-
### Writing
|
|
130
|
+
### Writing integration tests
|
|
119
131
|
|
|
120
|
-
Use [`start()`](/docs/api-reference/workflow-api/start) to trigger a workflow and [`run.returnValue`](/docs/api-reference/workflow-api/start#
|
|
132
|
+
Use [`start()`](/docs/api-reference/workflow-api/start) to trigger a workflow and [`run.returnValue`](/docs/api-reference/workflow-api/start#returns) to get the result. `returnValue` is a promise that blocks until the workflow completes (or throws if it fails):
|
|
121
133
|
|
|
122
134
|
```typescript title="workflows/calculate.integration.test.ts" lineNumbers
|
|
123
135
|
import { describe, it, expect } from "vitest";
|
|
@@ -144,9 +156,9 @@ describe("calculateWorkflow", () => {
|
|
|
144
156
|
});
|
|
145
157
|
```
|
|
146
158
|
|
|
147
|
-
### Testing
|
|
159
|
+
### Testing hooks and waits
|
|
148
160
|
|
|
149
|
-
|
|
161
|
+
Integration testing is most useful for workflow-only features. You can resume hooks and waits programmatically using the [`workflow/api`](/docs/api-reference/workflow-api) functions to simulate external events in your tests.
|
|
150
162
|
|
|
151
163
|
Given a workflow that waits for approval via a hook, then sleeps before publishing:
|
|
152
164
|
|
|
@@ -228,7 +240,7 @@ describe("approvalWorkflow", () => {
|
|
|
228
240
|
reviewer: "bob",
|
|
229
241
|
});
|
|
230
242
|
|
|
231
|
-
// No wakeUp() needed here
|
|
243
|
+
// No wakeUp() needed here; the rejected path has no sleep
|
|
232
244
|
const result = await run.returnValue;
|
|
233
245
|
expect(result).toEqual({
|
|
234
246
|
status: "rejected",
|
|
@@ -243,12 +255,12 @@ describe("approvalWorkflow", () => {
|
|
|
243
255
|
</Callout>
|
|
244
256
|
|
|
245
257
|
<Callout type="info">
|
|
246
|
-
`waitForSleep()` returns the first **pending** sleep
|
|
258
|
+
`waitForSleep()` returns the first **pending** sleep, one that has a `wait_created` event but no corresponding `wait_completed` event. If your workflow has multiple parallel sleeps, `waitForSleep()` returns whichever is found first. After waking one, call `waitForSleep()` again to get the next pending one. For sequential sleeps, `waitForSleep()` naturally returns each one as the workflow reaches it.
|
|
247
259
|
</Callout>
|
|
248
260
|
|
|
249
|
-
### Testing
|
|
261
|
+
### Testing webhooks
|
|
250
262
|
|
|
251
|
-
Webhooks are hooks that receive HTTP `Request` objects. In tests, resume them using [`resumeWebhook()`](/docs/api-reference/workflow-api/resume-webhook) with a `Request` payload
|
|
263
|
+
Webhooks are hooks that receive HTTP `Request` objects. In tests, resume them using [`resumeWebhook()`](/docs/api-reference/workflow-api/resume-webhook) with a `Request` payload, with no HTTP server needed:
|
|
252
264
|
|
|
253
265
|
```typescript title="workflows/ingest.ts" lineNumbers
|
|
254
266
|
import { createWebhook } from "workflow";
|
|
@@ -256,7 +268,7 @@ import { createWebhook } from "workflow";
|
|
|
256
268
|
export async function ingestWorkflow(endpointId: string) {
|
|
257
269
|
"use workflow";
|
|
258
270
|
|
|
259
|
-
// Webhook tokens are always
|
|
271
|
+
// Webhook tokens are always generated for you
|
|
260
272
|
using webhook = createWebhook(); // [!code highlight]
|
|
261
273
|
|
|
262
274
|
const request = await webhook; // [!code highlight]
|
|
@@ -282,7 +294,7 @@ describe("ingestWorkflow", () => {
|
|
|
282
294
|
it("should process webhook data", async () => {
|
|
283
295
|
const run = await start(ingestWorkflow, ["ep-1"]);
|
|
284
296
|
|
|
285
|
-
// Discover the
|
|
297
|
+
// Discover the generated webhook token
|
|
286
298
|
const hook = await waitForHook(run); // [!code highlight]
|
|
287
299
|
|
|
288
300
|
// Resume the webhook with a Request object
|
|
@@ -303,7 +315,73 @@ describe("ingestWorkflow", () => {
|
|
|
303
315
|
});
|
|
304
316
|
```
|
|
305
317
|
|
|
306
|
-
###
|
|
318
|
+
### Referencing workflows by name
|
|
319
|
+
|
|
320
|
+
Importing the workflow function is the best way to start it: [`start()`](/docs/api-reference/workflow-api/start) keeps its argument and return types. When a test cannot import it, because the workflow lives in a module the test does not pull in or because the test drives runs by name, look it up with [`getWorkflowRef()`](/docs/api-reference/vitest#getworkflowref) instead of hand-writing the generated `workflow//...` id:
|
|
321
|
+
|
|
322
|
+
```typescript title="workflows/approval.integration.test.ts" lineNumbers
|
|
323
|
+
import { describe, it, expect } from "vitest";
|
|
324
|
+
import { start } from "workflow/api";
|
|
325
|
+
import { getWorkflowRef, listWorkflowRefs } from "@workflow/vitest"; // [!code highlight]
|
|
326
|
+
|
|
327
|
+
describe("approvalWorkflow", () => {
|
|
328
|
+
it("is part of the test build", () => {
|
|
329
|
+
expect(listWorkflowRefs().map((ref) => ref.name)).toContain( // [!code highlight]
|
|
330
|
+
"approvalWorkflow"
|
|
331
|
+
);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("can be started by name", async () => {
|
|
335
|
+
const run = await start(getWorkflowRef("approvalWorkflow"), ["doc-1"]); // [!code highlight]
|
|
336
|
+
expect(run.runId).toMatch(/^wrun_/);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
The names come from the manifest the test build writes next to its bundles, so they follow your code when files move. When one exported name appears in several files, qualify it with the file, `getWorkflowRef("workflows/approval.ts#approvalWorkflow")`, which also matches by path suffix. An unknown or ambiguous name throws with the workflows the build does contain.
|
|
342
|
+
|
|
343
|
+
<Callout type="info">
|
|
344
|
+
A reference is matched by name, so the run is typed as `Run<unknown>`. Import the workflow function when you want the argument and return types checked.
|
|
345
|
+
</Callout>
|
|
346
|
+
|
|
347
|
+
### Mocking
|
|
348
|
+
|
|
349
|
+
Steps do not run from your test file's module graph. They run from the bundles the plugin builds, and workflow bodies run from a code string inside the sandboxed VM, so `vi.mock()` reaches less here than in an ordinary Vitest test:
|
|
350
|
+
|
|
351
|
+
| What you mock | Step code sees it | Workflow body sees it |
|
|
352
|
+
| --- | --- | --- |
|
|
353
|
+
| An npm package a step file imports | Only when the generated bundles load through Vitest's module runner (see below) | No |
|
|
354
|
+
| An npm package imported by a local module that a step file imports | Same as above | No |
|
|
355
|
+
| A project-local module a step file imports | No: it is bundled into the step bundle, so there is no module left to replace | No |
|
|
356
|
+
| A step function imported and called directly, with no `workflow()` plugin | Yes, ordinary Vitest rules | n/a |
|
|
357
|
+
|
|
358
|
+
Workflow bodies execute in a VM that has no module registry, so nothing can intercept their imports. That is the same reason side effects belong in steps: if a dependency needs mocking, it belongs on the step side.
|
|
359
|
+
|
|
360
|
+
Whether step code sees an npm mock depends on how Vitest loaded the plugin. In a normal install `@workflow/vitest` is external to Vitest, Node loads the generated bundle directly, and steps get the real package. Ask for the other behavior explicitly:
|
|
361
|
+
|
|
362
|
+
```typescript title="vitest.integration.config.ts" lineNumbers
|
|
363
|
+
import { defineConfig } from "vitest/config";
|
|
364
|
+
import { workflow } from "@workflow/vitest";
|
|
365
|
+
|
|
366
|
+
export default defineConfig({
|
|
367
|
+
plugins: [workflow()],
|
|
368
|
+
test: {
|
|
369
|
+
// Route the generated bundles through Vitest's module runner, so vi.mock()
|
|
370
|
+
// applies to the npm packages your steps import.
|
|
371
|
+
server: { deps: { inline: [/@workflow\/vitest/] } }, // [!code highlight]
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
The cost is that the generated bundle goes through Vite's transform pipeline in every test worker.
|
|
377
|
+
|
|
378
|
+
Three approaches work regardless of how the plugin was loaded:
|
|
379
|
+
|
|
380
|
+
- **Unit test the step directly.** Without the plugin, `"use step"` is a no-op and the function is an ordinary async function, so `vi.mock()` behaves normally.
|
|
381
|
+
- **Pass the dependency in.** A step that takes its collaborator as an argument is controlled by the caller, with no module interception involved.
|
|
382
|
+
- **Feed values through hooks.** Resume a [hook](/docs/foundations/hooks) with the data you want instead of mocking whatever would have produced it.
|
|
383
|
+
|
|
384
|
+
### Manual setup
|
|
307
385
|
|
|
308
386
|
If you need more control over the test lifecycle, the plugin also exports the individual setup functions:
|
|
309
387
|
|
|
@@ -354,11 +432,11 @@ afterAll(async () => {
|
|
|
354
432
|
For advanced setups that require a running server (e.g. testing against your actual framework's HTTP layer), see [Server-based integration testing](/docs/testing/server-based).
|
|
355
433
|
</Callout>
|
|
356
434
|
|
|
357
|
-
## Debugging
|
|
435
|
+
## Debugging test runs
|
|
358
436
|
|
|
359
|
-
When integration tests fail, the [Workflow SDK CLI and
|
|
437
|
+
When integration tests fail, the [Workflow SDK CLI and web UI](/docs/observability) can help you inspect what happened. Because integration tests persist workflow state locally, you can use the same observability tools you use in development.
|
|
360
438
|
|
|
361
|
-
Launch the
|
|
439
|
+
Launch the web UI to explore your test workflow runs:
|
|
362
440
|
|
|
363
441
|
```bash
|
|
364
442
|
npx workflow web
|
|
@@ -374,7 +452,7 @@ npx workflow inspect runs
|
|
|
374
452
|
npx workflow inspect run <run-id>
|
|
375
453
|
```
|
|
376
454
|
|
|
377
|
-
The
|
|
455
|
+
The web UI shows each step, its inputs and outputs, retry attempts, hook state, and timing. Use it to diagnose hooks that were not resumed, steps that failed unexpectedly, or workflows that timed out.
|
|
378
456
|
|
|
379
457
|

|
|
380
458
|
|
|
@@ -382,35 +460,39 @@ The Web UI shows each step, its inputs and outputs, retry attempts, hook state,
|
|
|
382
460
|
See the [Observability](/docs/observability) docs for the full set of CLI commands and Web UI features.
|
|
383
461
|
</Callout>
|
|
384
462
|
|
|
385
|
-
## Best
|
|
463
|
+
## Best practices
|
|
386
464
|
|
|
387
|
-
### Separate
|
|
465
|
+
### Separate unit and integration tests
|
|
388
466
|
|
|
389
467
|
Keep two test configurations:
|
|
390
468
|
|
|
391
|
-
- **Unit tests
|
|
392
|
-
- **Integration tests
|
|
469
|
+
- **Unit tests**: Standard Vitest config with no workflow plugin. These tests require no infrastructure.
|
|
470
|
+
- **Integration tests**: Vitest config with the `workflow()` plugin. These tests cover the full workflow lifecycle, including hooks, sleeps, and retries.
|
|
393
471
|
|
|
394
|
-
### Use
|
|
472
|
+
### Use custom hook tokens for deterministic testing
|
|
395
473
|
|
|
396
|
-
When testing workflows with hooks, use [custom tokens](/docs/foundations/hooks#custom-tokens-for-deterministic-hooks) based on predictable values (like document IDs or test identifiers). This
|
|
474
|
+
When testing workflows with hooks, use [custom tokens](/docs/foundations/hooks#custom-tokens-for-deterministic-hooks) based on predictable values (like document IDs or test identifiers). This lets you resume the correct hook in your test code.
|
|
397
475
|
|
|
398
|
-
### Set
|
|
476
|
+
### Set appropriate timeouts
|
|
399
477
|
|
|
400
478
|
Workflows may take longer to execute than typical unit tests, especially when they involve multiple steps or retries. Set a generous `testTimeout` in your integration test config.
|
|
401
479
|
|
|
402
|
-
### Test
|
|
480
|
+
### Test error and retry scenarios
|
|
403
481
|
|
|
404
482
|
Integration tests are the right place to verify that your workflows handle errors correctly, including retryable errors, fatal errors, and timeout scenarios.
|
|
405
483
|
|
|
406
|
-
|
|
484
|
+
### Upgrade `@workflow/vitest` with the SDK
|
|
485
|
+
|
|
486
|
+
`@workflow/vitest` carries its own copy of the Workflow runtime, so treat it as part of the same upgrade as `workflow`. While Workflow 5 is in beta, that means the `beta` tag on both. See [Installation](#installation).
|
|
487
|
+
|
|
488
|
+
## Further reading
|
|
407
489
|
|
|
408
490
|
- [Hooks & Webhooks](/docs/foundations/hooks) - Pausing and resuming workflows with external data
|
|
409
491
|
- [`start()` API Reference](/docs/api-reference/workflow-api/start) - Start workflows programmatically
|
|
410
492
|
- [`resumeHook()` API Reference](/docs/api-reference/workflow-api/resume-hook) - Resume hooks with data
|
|
411
493
|
- [`resumeWebhook()` API Reference](/docs/api-reference/workflow-api/resume-webhook) - Resume webhooks with Request objects
|
|
412
494
|
- [`getRun()` API Reference](/docs/api-reference/workflow-api/get-run) - Check workflow run status and wake up sleeping runs
|
|
413
|
-
- [`@workflow/vitest` API Reference](/docs/api-reference/vitest) - Test helpers: `waitForSleep()`, `waitForHook()`, and plugin setup
|
|
495
|
+
- [`@workflow/vitest` API Reference](/docs/api-reference/vitest) - Test helpers: `waitForSleep()`, `waitForHook()`, `getWorkflowRef()`, and plugin setup
|
|
414
496
|
- [Vite Integration](/docs/getting-started/vite) - Set up the Vite plugin
|
|
415
497
|
- [Observability](/docs/observability) - Inspect and debug workflow runs with the CLI and Web UI
|
|
416
498
|
- [Server-based testing](/docs/testing/server-based) - Integration testing with a running server
|
|
@@ -9,9 +9,9 @@ The [Vitest plugin](/docs/testing#integration-testing-with-the-vitest-plugin) ru
|
|
|
9
9
|
- Reproducing behavior that only occurs in a specific framework's runtime (e.g. Next.js, Nitro)
|
|
10
10
|
- Testing webhook endpoints that receive real HTTP requests
|
|
11
11
|
|
|
12
|
-
This guide shows how to set up integration tests that spawn a dev server as a sidecar process. The example below uses [Nitro](https://v3.nitro.build), but the same pattern works with any supported server framework. It is meant as a starting point
|
|
12
|
+
This guide shows how to set up integration tests that spawn a dev server as a sidecar process. The example below uses [Nitro](https://v3.nitro.build), but the same pattern works with any supported server framework. It is meant as a starting point; customize the server setup to match your own deployment environment.
|
|
13
13
|
|
|
14
|
-
## Vitest
|
|
14
|
+
## Vitest configuration
|
|
15
15
|
|
|
16
16
|
Create a Vitest config with the `workflow()` Vite plugin for code transforms and a `globalSetup` script that manages the server lifecycle:
|
|
17
17
|
|
|
@@ -36,7 +36,7 @@ export default defineConfig({
|
|
|
36
36
|
Note the import path: `workflow/vite` (not `@workflow/vitest`). The Vite plugin handles code transforms but does not set up in-process execution. The server handles workflow execution instead.
|
|
37
37
|
</Callout>
|
|
38
38
|
|
|
39
|
-
## Global
|
|
39
|
+
## Global setup script
|
|
40
40
|
|
|
41
41
|
The `globalSetup` script starts a dev server before tests run and tears it down afterwards. This example uses [Nitro](https://v3.nitro.build), but you can use any server framework that supports the workflow runtime.
|
|
42
42
|
|
|
@@ -148,15 +148,15 @@ export async function teardown() { // [!code highlight]
|
|
|
148
148
|
|
|
149
149
|
These JSON log lines are intentional. They give CI jobs, local tooling, and agents stable events to watch for (`server_starting`, `server_stdout`, `server_stderr`, `server_ready`, `server_exit`), and the thrown timeout error includes the command, expected `WORKFLOW_LOCAL_BASE_URL`, and buffered stdout/stderr so a failed setup is actionable without interactive debugging.
|
|
150
150
|
|
|
151
|
-
The setup script sets `WORKFLOW_LOCAL_BASE_URL` so the workflow runtime sends step execution
|
|
151
|
+
The setup script sets `WORKFLOW_LOCAL_BASE_URL` so the workflow runtime sends flow-route requests (workflow orchestration and step execution) to the running server.
|
|
152
152
|
|
|
153
153
|
<Callout type="info">
|
|
154
154
|
You can use any server framework that supports the workflow runtime. The example above uses [Nitro](https://v3.nitro.build), but you could also use [Next.js](https://nextjs.org), [Hono](https://hono.dev), or any other supported server.
|
|
155
155
|
</Callout>
|
|
156
156
|
|
|
157
|
-
## Writing
|
|
157
|
+
## Writing tests
|
|
158
158
|
|
|
159
|
-
Tests are written the same way as [in-process integration tests](/docs/testing#writing-integration-tests). You can use the same programmatic APIs
|
|
159
|
+
Tests are written the same way as [in-process integration tests](/docs/testing#writing-integration-tests). You can use the same programmatic APIs ([`start()`](/docs/api-reference/workflow-api/start), [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook), [`resumeWebhook()`](/docs/api-reference/workflow-api/resume-webhook), and [`getRun().wakeUp()`](/docs/api-reference/workflow-api/get-run)) to control workflow execution:
|
|
160
160
|
|
|
161
161
|
```typescript title="workflows/calculate.server.test.ts" lineNumbers
|
|
162
162
|
import { describe, it, expect } from "vitest";
|
|
@@ -199,10 +199,10 @@ describe("approvalWorkflow", () => {
|
|
|
199
199
|
```
|
|
200
200
|
|
|
201
201
|
<Callout type="info">
|
|
202
|
-
In server-based tests, the `waitForSleep()` and `waitForHook()` helpers from `@workflow/vitest` are not available since there is no in-process world. Instead, use the programmatic APIs directly
|
|
202
|
+
In server-based tests, the `waitForSleep()` and `waitForHook()` helpers from `@workflow/vitest` are not available since there is no in-process world. Instead, use the programmatic APIs directly. You may need to add short delays or polling to ensure the workflow has reached the desired state before resuming.
|
|
203
203
|
</Callout>
|
|
204
204
|
|
|
205
|
-
## Running
|
|
205
|
+
## Running tests
|
|
206
206
|
|
|
207
207
|
Add a script to your `package.json`:
|
|
208
208
|
|
|
@@ -215,12 +215,12 @@ Add a script to your `package.json`:
|
|
|
215
215
|
}
|
|
216
216
|
```
|
|
217
217
|
|
|
218
|
-
## When to
|
|
218
|
+
## When to use this approach
|
|
219
219
|
|
|
220
220
|
| Scenario | Recommended approach |
|
|
221
221
|
| --- | --- |
|
|
222
222
|
| Testing workflow logic, steps, hooks, retries | [In-process plugin](/docs/testing) |
|
|
223
223
|
| Testing HTTP middleware or authentication | Server-based |
|
|
224
224
|
| Testing webhook endpoints with real HTTP | Server-based |
|
|
225
|
-
| CI/CD pipeline testing | [In-process plugin](/docs/testing) |
|
|
225
|
+
| Continuous integration and continuous delivery (CI/CD) pipeline testing | [In-process plugin](/docs/testing) |
|
|
226
226
|
| Reproducing framework-specific behavior | Server-based |
|