workflow 5.0.0-beta.13 → 5.0.0-beta.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/api-reference/index.mdx +24 -0
- package/docs/api-reference/meta.json +8 -0
- package/docs/api-reference/vitest/index.mdx +0 -6
- package/docs/api-reference/workflow/create-hook.mdx +32 -0
- package/docs/api-reference/workflow/create-webhook.mdx +1 -0
- package/docs/api-reference/workflow/experimental-set-attributes.mdx +3 -1
- package/docs/api-reference/workflow-api/index.mdx +6 -8
- package/docs/api-reference/workflow-api/start.mdx +3 -1
- 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/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-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-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 +5 -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 +15 -3
- package/docs/changelog/eager-processing.mdx +26 -6
- package/docs/errors/index.mdx +3 -0
- package/docs/errors/step-executed-multiple-times.mdx +23 -0
- package/docs/foundations/hooks.mdx +29 -0
- package/docs/foundations/streaming.mdx +1 -1
- package/docs/foundations/versioning.mdx +1 -1
- package/docs/how-it-works/encryption.mdx +2 -2
- package/docs/how-it-works/event-sourcing.mdx +2 -2
- package/docs/observability/attributes.mdx +12 -5
- package/package.json +10 -10
- package/docs/api-reference/workflow-api/world/meta.json +0 -4
- package/docs/api-reference/workflow-api/world/observability.mdx +0 -164
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: workflow
|
|
3
|
+
description: Configure Vite and Nitro to transform workflow directives.
|
|
4
|
+
type: reference
|
|
5
|
+
summary: Add the workflow plugin to your Vite config to enable workflow directive transformation in Vite + Nitro apps.
|
|
6
|
+
prerequisites:
|
|
7
|
+
- /docs/getting-started/vite
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
Returns the Vite plugins that transform workflow code (`"use step"`/`"use workflow"` directives) and configure the [`workflow/nitro`](/docs/api-reference/workflow-nitro) module on the Nitro server. It is designed to be used alongside `nitro()` from `nitro/vite`, which provides the server framework for API routes and deployment.
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
To enable `"use step"` and `"use workflow"` directives while developing locally or deploying to production, add `workflow()` to the `plugins` array of your Vite config, together with `nitro()`.
|
|
15
|
+
|
|
16
|
+
```typescript title="vite.config.ts" lineNumbers
|
|
17
|
+
import { nitro } from "nitro/vite";
|
|
18
|
+
import { defineConfig } from "vite";
|
|
19
|
+
import { workflow } from "workflow/vite"; // [!code highlight]
|
|
20
|
+
|
|
21
|
+
export default defineConfig({
|
|
22
|
+
plugins: [nitro(), workflow()], // [!code highlight]
|
|
23
|
+
nitro: {
|
|
24
|
+
serverDir: "./",
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## API Signature
|
|
30
|
+
|
|
31
|
+
### Parameters
|
|
32
|
+
|
|
33
|
+
| Parameter | Type | Description |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| `options` | `ModuleOptions` | Optional. Forwarded to the `workflow/nitro` module as its module options. |
|
|
36
|
+
|
|
37
|
+
#### ModuleOptions
|
|
38
|
+
|
|
39
|
+
| Option | Type | Default | Description |
|
|
40
|
+
| --- | --- | --- | --- |
|
|
41
|
+
| `dirs` | `string[]` | — | Directories to scan for workflows and steps. By default, the `workflows/` directory is scanned from the project root and all layer source directories. |
|
|
42
|
+
| `typescriptPlugin` | `boolean` | `false` | Adds the `workflow` TypeScript plugin to the generated `tsconfig.json` for IDE IntelliSense. |
|
|
43
|
+
| `runtime` | `string` | — | Node.js runtime version for Vercel Functions (e.g. `'nodejs22.x'`, `'nodejs24.x'`). Only applies when deploying to Vercel. |
|
|
44
|
+
| `sourcemap` | `boolean \| 'inline' \| 'linked' \| 'external' \| 'both'` | `'inline'` | Controls source maps on generated workflow bundles. Accepts the same values as esbuild's `sourcemap` option. Set to `false` for smaller function bundles (useful for staying under the Vercel 250MB function size limit) at the cost of stack traces pointing at generated code. Can also be set via the `WORKFLOW_SOURCEMAP` environment variable. |
|
|
45
|
+
|
|
46
|
+
### Returns
|
|
47
|
+
|
|
48
|
+
Returns an array of Vite `Plugin` objects. Spread or pass the array directly to the `plugins` option of your Vite config — Vite flattens nested plugin arrays automatically.
|
|
@@ -9,6 +9,18 @@ This is a minimal, **experimental** subset of the [planned Workflow Attributes f
|
|
|
9
9
|
|
|
10
10
|
The MVP lets workflow code attach plaintext `string → string` metadata to a run, viewable in any observability surface that reads the `WorkflowRun` entity. It is deliberately narrow: write-only, no reads from inside a run, no list/filter endpoints, no event-log representation. The wire format and SDK surface are chosen so the full 5.0.0 implementation replaces this without source-level breaking changes for end users.
|
|
11
11
|
|
|
12
|
+
## Native v4 follow-up
|
|
13
|
+
|
|
14
|
+
The v4 implementation now completes the planned dispatch migration while preserving the experimental SDK name:
|
|
15
|
+
|
|
16
|
+
- `experimental_setAttributes()` appends a native `attr_set` event from workflow and step bodies instead of mutating through the internal step bridge.
|
|
17
|
+
- Native events carry writer attribution and are materialized immediately onto `run.attributes`.
|
|
18
|
+
- `start(workflow, args, { attributes })` seeds attributes on the initial `run_created` event, including the resilient-start creation path.
|
|
19
|
+
- The World spec is bumped to v4; attributes are supported for v4 runs rather than being backported into older run specifications.
|
|
20
|
+
- The direct server attributes endpoint remains available only as a deprecated compatibility path for clients that shipped with the MVP.
|
|
21
|
+
|
|
22
|
+
The remainder of this page documents the original MVP motivation and implementation.
|
|
23
|
+
|
|
12
24
|
## What MVP supports
|
|
13
25
|
|
|
14
26
|
- `experimental_setAttributes(record)` callable from a **workflow body** (`"use workflow"` function), dispatched via an internal `__builtin_set_attributes` step bridge so the mutation gets a `step_created → step_completed` event pair
|
|
@@ -296,13 +308,13 @@ End-to-end in `workbench/nextjs-turbopack` (exercises the full SWC plugin + work
|
|
|
296
308
|
- `Promise.all` of disjoint-key writes — every key persists
|
|
297
309
|
- Workflow throws after an awaited `experimental_setAttributes` — the attribute persists on the now-`failed` run (the per-run file lock on `run_failed` re-reads inside the critical section so the attribute snapshot survives the lifecycle write)
|
|
298
310
|
|
|
299
|
-
## Migration to
|
|
311
|
+
## Migration to native v4
|
|
300
312
|
|
|
301
|
-
|
|
313
|
+
The native attributes follow-up applies this migration:
|
|
302
314
|
|
|
303
315
|
- `experimental_setAttributes` (SDK) — unchanged signature, new dispatch path
|
|
304
316
|
- `runs.experimentalSetAttributes` (world interface) — deprecated, then removed; replaced by `events.create(runId, { eventType: 'attr_set', eventData: { changes, writer } })`
|
|
305
|
-
- Wire endpoint — `POST /v2/runs/:runId/attributes`
|
|
317
|
+
- Wire endpoint — `POST /v2/runs/:runId/attributes` is deprecated but temporarily retained for older clients; new calls post the same `changes` shape to events as `attr_set`
|
|
306
318
|
- Pre-existing attribute values on MVP-era runs remain on the run entity but are not represented in the event log
|
|
307
319
|
|
|
308
320
|
Skew protection means workflows started under the MVP will continue to run with the MVP dispatch path on their original deployment. New deployments use the new path. No in-place data migration is needed.
|
|
@@ -157,15 +157,35 @@ To prevent deadlock when polling is executed inline by the step executor, `Run#p
|
|
|
157
157
|
|
|
158
158
|
### Mixed Suspensions
|
|
159
159
|
|
|
160
|
-
A suspension may contain steps, hooks, and waits simultaneously. The handler creates events for all, then
|
|
160
|
+
A suspension may contain steps, hooks, and waits simultaneously. The handler creates events for all, then dispatches everything we are not running inline as a single parallel batch of queue messages:
|
|
161
161
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
```
|
|
163
|
+
ownedPendingSteps = pendingSteps.filter(owned by this handler)
|
|
164
|
+
inlineStep = ownedPendingSteps[0] // optional
|
|
165
|
+
|
|
166
|
+
dispatches = [
|
|
167
|
+
...for each non-inline pendingStep: queue stepId message (idempotency=correlationId),
|
|
168
|
+
...if soonest pending wait: queue delayed continuation
|
|
169
|
+
(delaySeconds=min(remaining, maxDelay),
|
|
170
|
+
idempotency=waitCorrelationId[:hop|:secondBucket]),
|
|
171
|
+
]
|
|
172
|
+
await Promise.all(dispatches)
|
|
173
|
+
|
|
174
|
+
if (!inlineStep) return
|
|
175
|
+
await executeStep(inlineStep)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The wait timer is queued as its own continuation rather than encoded in the handler's return value (`{ timeoutSeconds }`). This is what makes `Promise.race(step, sleep)` behave correctly: even when the inline step blocks the handler for the full step duration, the wait continuation fires in a separate function invocation. If the sleep wins, that parallel invocation observes `wait_completed` via the "complete elapsed waits" pass and finishes the run; if the step wins, the wait continuation fires later and no-ops on the terminal run via the existing terminal-event check.
|
|
179
|
+
|
|
180
|
+
Step queueing remains unconditional (covers crash recovery: if a prior handler wrote `step_created` but crashed before queueing, a later handler will queue it; idempotency keys dedupe redundant queues across concurrent handlers).
|
|
181
|
+
|
|
182
|
+
Wait continuations are likewise deduplicated, keyed on the wait's correlation ID: while a wait is pending, every replay pass over the run re-observes it and would otherwise enqueue another delayed continuation. A key is attached in all cases, since some worlds serialize key-less workflow messages per run, which would park the continuation behind the handler's own inline step execution.
|
|
183
|
+
|
|
184
|
+
Two situations deliver a continuation while its wait is still pending, and each varies the key so the re-enqueue isn't dropped by a world's dedupe window (which outlives the first delivery): waits longer than the maximum queue delay (23h, bounded by VQS's 24h message retention) are clamped and chained across hops, with the hop index suffixed to the key so each hop dedupes within its window but the chain always advances; and near-elapsed waits (≤2s remaining) use a second-bucketed key suffix so a continuation delivered marginally early (clock skew) can enqueue a fresh short-delay retry. See `runtime/wait-continuation.ts` for the full selection logic.
|
|
165
185
|
|
|
166
|
-
The
|
|
186
|
+
The retry/throttle and hook-conflict paths still return `{ timeoutSeconds }` since their semantics are "redeliver THIS message after a delay" rather than "schedule a fresh wait timer." Those can be unified in a follow-up.
|
|
167
187
|
|
|
168
|
-
|
|
188
|
+
The unified dispatch requires `world-local` to honor `delaySeconds` on the queue (added in the same PR series). Without it, the wait continuation would fire instantly in dev and trigger a spurious replay before the wait elapsed (recoverable via redelivery, but inefficient and observable as duplicate `step_started` events under contention).
|
|
169
189
|
|
|
170
190
|
### VM Sandboxing
|
|
171
191
|
|
package/docs/errors/index.mdx
CHANGED
|
@@ -43,6 +43,9 @@ Fix common mistakes when creating and executing workflows in the **Workflow SDK*
|
|
|
43
43
|
<Card href="/docs/errors/step-not-registered" title="step-not-registered">
|
|
44
44
|
Resolve step not registered errors caused by deployment mismatches.
|
|
45
45
|
</Card>
|
|
46
|
+
<Card href="/docs/errors/step-executed-multiple-times" title="Step executed multiple times">
|
|
47
|
+
Diagnose duplicate step_started events from function crashes, timeouts, or OOMs.
|
|
48
|
+
</Card>
|
|
46
49
|
<Card href="/docs/errors/workflow-not-registered" title="workflow-not-registered">
|
|
47
50
|
Resolve workflow not registered errors caused by deployment mismatches.
|
|
48
51
|
</Card>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Step executed multiple times
|
|
3
|
+
description: A step ran more than once because its function invocation crashed before it could report a result.
|
|
4
|
+
type: troubleshooting
|
|
5
|
+
summary: Diagnose duplicate step_started events caused by function timeouts, OOMs, or network issues.
|
|
6
|
+
prerequisites:
|
|
7
|
+
- /docs/foundations/workflows-and-steps
|
|
8
|
+
related:
|
|
9
|
+
- /docs/observability
|
|
10
|
+
- /docs/foundations/errors-and-retries
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
There may be cases where you see multiple `step_started` events for the same step in a workflow run. This happens if the function invocation executing the step crashes unexpectedly, and the step can not report the error. The step will be re-tried according to your retry policy in this case, but no error will be visible in the [Observability UI](/docs/observability).
|
|
14
|
+
|
|
15
|
+
## Common Causes
|
|
16
|
+
|
|
17
|
+
- **Function timeouts**: if your step code runs longer than the configured maximum function duration, it will be killed. Compare the gap between the `step_started` events to your configured function duration to be sure.
|
|
18
|
+
- **Out of memory (OOM)**: if your step code loads enough data into memory, especially if the step is invoked concurrently, the function invocation might run out of memory. You can see your function's peak memory use by going to the [Observability Query page](https://vercel.com/docs/observability) and showing the **Function Invocation Peak Memory** metric, then filtering down the **Route** to `/.well-known/workflow` endpoints.
|
|
19
|
+
- **Network issues**: persistent firewall, network stability, and related issues might prevent your function from reporting results or errors. This should be temporary.
|
|
20
|
+
|
|
21
|
+
## Getting Help
|
|
22
|
+
|
|
23
|
+
If you consistently see multiple `step_started` events and have ruled out function timeouts, OOMs, and firewall issues, please [contact support](https://vercel.com/help).
|
|
@@ -85,6 +85,35 @@ The key points:
|
|
|
85
85
|
- You need the hook's `token` to resume it
|
|
86
86
|
- The workflow will resume execution right where it left off
|
|
87
87
|
|
|
88
|
+
### Checking for Token Conflicts
|
|
89
|
+
|
|
90
|
+
Sometimes you need to know that a hook token has been claimed, but you do not want to wait for external data yet. Await `hook.getConflict()` for that:
|
|
91
|
+
|
|
92
|
+
```typescript lineNumbers
|
|
93
|
+
import { createHook } from "workflow";
|
|
94
|
+
|
|
95
|
+
declare function processOrder(orderId: string): Promise<void>; // @setup
|
|
96
|
+
|
|
97
|
+
export async function orderWorkflow(orderId: string) {
|
|
98
|
+
"use workflow";
|
|
99
|
+
|
|
100
|
+
using hook = createHook({
|
|
101
|
+
token: `order:${orderId}`
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const conflict = await hook.getConflict(); // [!code highlight]
|
|
105
|
+
if (conflict) { // [!code highlight]
|
|
106
|
+
// Another active run already owns this token.
|
|
107
|
+
return { dedupedTo: conflict.runId };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// The hook token is registered and reserved here.
|
|
111
|
+
await processOrder(orderId);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Calling `createHook()` on its own does not register the hook — registration is only committed when the workflow suspends. Awaiting `hook.getConflict()` suspends the workflow to commit the hook registration, then resolves with `null` once the hook is registered and ready to receive payloads, or with a `Run` handle for the run that owns the token if another active hook already claimed it (see [`HookConflictError`](/docs/errors/hook-conflict)). For `hook_conflict` events persisted by older worlds that did not record the owning run's ID, `getConflict()` rejects with `HookConflictError` instead of resolving with an incomplete handle. The conflicting run's accessors are durable steps, so the workflow can inspect `await conflict.status`, wait on `await conflict.returnValue`, or cancel the owner with `await conflict.cancel()` — see [Idempotency](/docs/foundations/idempotency) for these strategies.
|
|
116
|
+
|
|
88
117
|
### Custom Tokens for Deterministic Hooks
|
|
89
118
|
|
|
90
119
|
By default, hooks generate a random token. However, you often want to use a **custom token** that external systems can reconstruct. This is especially useful for long-running workflows where the same workflow instance should handle multiple events.
|
|
@@ -584,7 +584,7 @@ Stream errors don't trigger automatic retries for the producer step. Design your
|
|
|
584
584
|
- [`sleep()` API Reference](/docs/api-reference/workflow/sleep) - Pause workflow execution for a duration
|
|
585
585
|
- [`start()` API Reference](/docs/api-reference/workflow-api/start) - Start workflows and access the `Run` object
|
|
586
586
|
- [`getRun()` API Reference](/docs/api-reference/workflow-api/get-run) - Retrieve runs and their streams later
|
|
587
|
-
- [world.streams](/docs/api-reference/workflow-
|
|
587
|
+
- [world.streams](/docs/api-reference/workflow-runtime/world/streams) - Low-level stream read/write/close via World SDK
|
|
588
588
|
- [WorkflowAgent](https://ai-sdk.dev/v7/docs/agents/workflow-agent#workflowagent) - AI agents with durable, resumable streaming support
|
|
589
589
|
- [Errors and Retries](/docs/foundations/errors-and-retries) - Understanding error handling and retry behavior
|
|
590
590
|
- [Serialization](/docs/foundations/serialization) - Understanding what data types can be passed in workflows
|
|
@@ -94,7 +94,7 @@ npx workflow cancel \
|
|
|
94
94
|
--backend vercel
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
The `--workflowName` filter expects the generated workflow ID, not only the exported function's short name. Use the `workflowName` value from `workflow inspect runs`, and use [`parseWorkflowName()`](/docs/api-reference/workflow-
|
|
97
|
+
The `--workflowName` filter expects the generated workflow ID, not only the exported function's short name. Use the `workflowName` value from `workflow inspect runs`, and use [`parseWorkflowName()`](/docs/api-reference/workflow-observability/parse-workflow-name) when you need display-friendly names.
|
|
98
98
|
|
|
99
99
|
In the [observability UI](/docs/observability), use **Rerun on latest** to enqueue the workflow again with the same inputs against the latest deployment.
|
|
100
100
|
|
|
@@ -105,8 +105,8 @@ To add encryption support to a custom `World`:
|
|
|
105
105
|
import type { WorkflowRun, World } from "@workflow/world";
|
|
106
106
|
|
|
107
107
|
export const getEncryptionKeyForRun: World["getEncryptionKeyForRun"] = async (
|
|
108
|
-
run,
|
|
109
|
-
context
|
|
108
|
+
run: WorkflowRun | string,
|
|
109
|
+
context?: Record<string, unknown>
|
|
110
110
|
) => {
|
|
111
111
|
const runId = typeof run === "string" ? run : run.runId;
|
|
112
112
|
const deploymentId =
|
|
@@ -127,7 +127,7 @@ flowchart TD
|
|
|
127
127
|
|
|
128
128
|
Unlike other entities, hooks don't have a `status` field—the states above are conceptual. An "active" hook is one that exists in storage, while "disposed" means the hook has been deleted. When a `hook_disposed` event is created, the hook record is removed rather than updated.
|
|
129
129
|
|
|
130
|
-
While a hook is active, its token is reserved and cannot be used by other workflows. If a workflow attempts to create a hook with a token that is already in use by another active hook, a `hook_conflict` event is recorded instead of `hook_created`. Current worlds include the token and the run ID that currently owns it, though older persisted events or world implementations may only include the token. This causes the hook's promise to reject with a `HookConflictError`, which you can detect with `HookConflictError.is(error)`. See the [hook-conflict error](/docs/errors/hook-conflict) documentation for more details.
|
|
130
|
+
While a hook is active, its token is reserved and cannot be used by other workflows. If a workflow attempts to create a hook with a token that is already in use by another active hook, a `hook_conflict` event is recorded instead of `hook_created`. Current worlds include the token and the run ID that currently owns it, though older persisted events or world implementations may only include the token. This causes `hook.getConflict()` to resolve with the conflicting run and the hook's payload promise to reject with a `HookConflictError`, which you can detect with `HookConflictError.is(error)`. See the [hook-conflict error](/docs/errors/hook-conflict) documentation for more details.
|
|
131
131
|
|
|
132
132
|
When a hook is disposed (either explicitly or when its workflow completes), the token is released and can be claimed by future workflows. Hooks are automatically disposed when a workflow reaches a terminal state (`completed`, `failed`, or `cancelled`). The `hook_disposed` event is only needed for explicit disposal before workflow completion.
|
|
133
133
|
|
|
@@ -188,7 +188,7 @@ Events are categorized by the entity type they affect. Each event contains metad
|
|
|
188
188
|
| Event | Description |
|
|
189
189
|
|-------|-------------|
|
|
190
190
|
| `hook_created` | Creates a new hook in `active` state. Contains the hook token and optional metadata. |
|
|
191
|
-
| `hook_conflict` | Records that hook creation failed because the token is already in use by another active hook. Contains the token and, for current worlds, the active hook owner's run ID. The hook is not created, and awaiting the hook
|
|
191
|
+
| `hook_conflict` | Records that hook creation failed because the token is already in use by another active hook. Contains the token and, for current worlds, the active hook owner's run ID. The hook is not created: `hook.getConflict()` resolves with the conflicting run, and awaiting the hook payload rejects with a `HookConflictError`. |
|
|
192
192
|
| `hook_received` | Records that a payload was delivered to the hook. The hook remains `active` and can receive more payloads. |
|
|
193
193
|
| `hook_disposed` | Deletes the hook from storage (conceptually transitioning to `disposed` state). The token is released for reuse by future workflows. |
|
|
194
194
|
|
|
@@ -18,6 +18,15 @@ This feature is experimental and may change before the stable attributes API is
|
|
|
18
18
|
[`experimental_setAttributes`](/docs/api-reference/workflow/experimental-set-attributes) attaches plaintext string metadata to the current workflow run. These attributes are displayed in observability CLI/UI.
|
|
19
19
|
In the future, you'll be able to search and filter runs by attributes.
|
|
20
20
|
|
|
21
|
+
You can also seed any attributes directly when starting a run:
|
|
22
|
+
|
|
23
|
+
{/* @skip-typecheck: abbreviated usage; orderWorkflow is defined below */}
|
|
24
|
+
```typescript lineNumbers
|
|
25
|
+
const run = await start(orderWorkflow, ["ord_123"], {
|
|
26
|
+
attributes: { source: "checkout" }, // [!code highlight]
|
|
27
|
+
})
|
|
28
|
+
```
|
|
29
|
+
|
|
21
30
|
```typescript lineNumbers
|
|
22
31
|
import { experimental_setAttributes } from "workflow"
|
|
23
32
|
|
|
@@ -57,10 +66,8 @@ Attribute keys must be 1-256 characters, values must be strings up to 256 bytes,
|
|
|
57
66
|
|
|
58
67
|
While attributes are experimental:
|
|
59
68
|
|
|
60
|
-
-
|
|
61
|
-
-
|
|
69
|
+
- Attributes require a World implementing spec version 4 or later.
|
|
70
|
+
- Writes from workflow and step bodies append native `attr_set` events and immediately materialize `run.attributes`.
|
|
71
|
+
- Storage errors surface rather than being silently ignored: transient errors on workflow-body writes are retried, and a write the World rejects as invalid (for example, exceeding the per-run attribute cap across multiple calls) fails the run with the validation error.
|
|
62
72
|
- Step-body storage errors throw from `experimental_setAttributes` like any other step-side network write. Catch the error inside the step if the attribute is best-effort.
|
|
63
|
-
- Setting attributes from a workflow body is currently slower than the final API will be, because each write goes through an internal workflow step. Step-body calls post directly to the World. Prefer batching related attributes in one call.
|
|
64
73
|
- Reading and querying attributes is not available yet. A query API is planned.
|
|
65
|
-
|
|
66
|
-
In a future release, using attributes with a World that does not support them, or when the World fails to store them, will fail with a [world error](/docs/api-reference/workflow-errors/workflow-world-error). This can be caught and handled to prevent failing a run.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "workflow",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.15",
|
|
4
4
|
"description": "Workflow SDK - Build durable, resilient, and observable workflows",
|
|
5
5
|
"main": "dist/typescript-plugin.cjs",
|
|
6
6
|
"type": "module",
|
|
@@ -57,18 +57,18 @@
|
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"ms": "2.1.3",
|
|
60
|
-
"@workflow/astro": "5.0.0-beta.
|
|
61
|
-
"@workflow/cli": "5.0.0-beta.
|
|
62
|
-
"@workflow/core": "5.0.0-beta.
|
|
60
|
+
"@workflow/astro": "5.0.0-beta.15",
|
|
61
|
+
"@workflow/cli": "5.0.0-beta.15",
|
|
62
|
+
"@workflow/core": "5.0.0-beta.15",
|
|
63
63
|
"@workflow/errors": "5.0.0-beta.7",
|
|
64
64
|
"@workflow/typescript-plugin": "5.0.0-beta.4",
|
|
65
65
|
"@workflow/utils": "5.0.0-beta.3",
|
|
66
|
-
"@workflow/next": "5.0.0-beta.
|
|
67
|
-
"@workflow/nest": "5.0.0-beta.
|
|
68
|
-
"@workflow/nitro": "5.0.0-beta.
|
|
69
|
-
"@workflow/nuxt": "5.0.0-beta.
|
|
70
|
-
"@workflow/sveltekit": "5.0.0-beta.
|
|
71
|
-
"@workflow/rollup": "5.0.0-beta.
|
|
66
|
+
"@workflow/next": "5.0.0-beta.15",
|
|
67
|
+
"@workflow/nest": "5.0.0-beta.15",
|
|
68
|
+
"@workflow/nitro": "5.0.0-beta.15",
|
|
69
|
+
"@workflow/nuxt": "5.0.0-beta.15",
|
|
70
|
+
"@workflow/sveltekit": "5.0.0-beta.15",
|
|
71
|
+
"@workflow/rollup": "5.0.0-beta.15"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@types/ms": "2.1.0",
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Observability Utilities
|
|
3
|
-
description: Hydrate step I/O, parse display names, and decrypt workflow data using workflow/observability.
|
|
4
|
-
type: reference
|
|
5
|
-
summary: "Functions: hydrateResourceIO(), parseStepName(), parseWorkflowName(), parseClassName(), getEncryptionKeyForRun(), hydrateResourceIOWithKey()."
|
|
6
|
-
prerequisites:
|
|
7
|
-
- /docs/api-reference/workflow-api/get-world
|
|
8
|
-
related:
|
|
9
|
-
- /docs/api-reference/workflow-api/world/storage
|
|
10
|
-
keywords:
|
|
11
|
-
- workflow/observability
|
|
12
|
-
- hydrateResourceIO
|
|
13
|
-
- observabilityRevivers
|
|
14
|
-
- parseStepName
|
|
15
|
-
- parseWorkflowName
|
|
16
|
-
- parseClassName
|
|
17
|
-
- getEncryptionKeyForRun
|
|
18
|
-
- hydrateResourceIOWithKey
|
|
19
|
-
- data hydration
|
|
20
|
-
- devalue deserialization
|
|
21
|
-
- encryption decryption
|
|
22
|
-
- display name parsing
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
The `workflow/observability` module provides utilities for working with workflow data in observability and debugging tools. It includes functions to hydrate serialized step I/O, parse machine-readable names into display-friendly formats, and decrypt encrypted workflow data.
|
|
26
|
-
|
|
27
|
-
## Import
|
|
28
|
-
|
|
29
|
-
```typescript lineNumbers
|
|
30
|
-
import { // [!code highlight]
|
|
31
|
-
hydrateResourceIO, // [!code highlight]
|
|
32
|
-
observabilityRevivers, // [!code highlight]
|
|
33
|
-
parseStepName, // [!code highlight]
|
|
34
|
-
parseWorkflowName, // [!code highlight]
|
|
35
|
-
parseClassName, // [!code highlight]
|
|
36
|
-
} from "workflow/observability"; // [!code highlight]
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## Data Hydration
|
|
40
|
-
|
|
41
|
-
### hydrateResourceIO()
|
|
42
|
-
|
|
43
|
-
Deserialize step or run data that was serialized using the [devalue](https://github.com/Rich-Harris/devalue) format. Required to display step input/output in your UI.
|
|
44
|
-
|
|
45
|
-
```typescript lineNumbers
|
|
46
|
-
import { hydrateResourceIO, observabilityRevivers } from "workflow/observability"; // [!code highlight]
|
|
47
|
-
|
|
48
|
-
const step = await world.steps.get(runId, stepId);
|
|
49
|
-
const hydrated = hydrateResourceIO(step, observabilityRevivers); // [!code highlight]
|
|
50
|
-
console.log(hydrated.input, hydrated.output);
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
**Parameters:**
|
|
54
|
-
|
|
55
|
-
| Parameter | Type | Description |
|
|
56
|
-
|-----------|------|-------------|
|
|
57
|
-
| `resource` | `Step \| WorkflowRun` | The step or run with serialized data |
|
|
58
|
-
| `revivers` | `Revivers` | Reviver functions for deserialization. Use `observabilityRevivers` for standard use. |
|
|
59
|
-
|
|
60
|
-
**Returns:** The resource with hydrated `input` and `output` fields.
|
|
61
|
-
|
|
62
|
-
### observabilityRevivers
|
|
63
|
-
|
|
64
|
-
A set of reviver functions that handle standard workflow serialization types (Date, Map, Set, Error, etc.).
|
|
65
|
-
|
|
66
|
-
## Name Parsing
|
|
67
|
-
|
|
68
|
-
Workflow and step names are stored as machine-readable identifiers. These utilities extract display-friendly names. All return `{ shortName: string, moduleSpecifier: string } | null`.
|
|
69
|
-
|
|
70
|
-
### parseStepName()
|
|
71
|
-
|
|
72
|
-
```typescript lineNumbers
|
|
73
|
-
import { parseStepName } from "workflow/observability"; // [!code highlight]
|
|
74
|
-
|
|
75
|
-
const parsed = parseStepName("step//./src/workflows/order//processPayment"); // [!code highlight]
|
|
76
|
-
// parsed?.shortName → "processPayment"
|
|
77
|
-
// parsed?.moduleSpecifier → "./src/workflows/order"
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### parseWorkflowName()
|
|
81
|
-
|
|
82
|
-
```typescript lineNumbers
|
|
83
|
-
import { parseWorkflowName } from "workflow/observability"; // [!code highlight]
|
|
84
|
-
|
|
85
|
-
const parsed = parseWorkflowName("workflow//./src/workflows/order//processOrder"); // [!code highlight]
|
|
86
|
-
// parsed?.shortName → "processOrder"
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### parseClassName()
|
|
90
|
-
|
|
91
|
-
```typescript lineNumbers
|
|
92
|
-
import { parseClassName } from "workflow/observability"; // [!code highlight]
|
|
93
|
-
|
|
94
|
-
const parsed = parseClassName("class//./src/models//User"); // [!code highlight]
|
|
95
|
-
// parsed?.shortName → "User"
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
## Encryption
|
|
99
|
-
|
|
100
|
-
For workflows with encrypted step data, decrypt before hydrating.
|
|
101
|
-
|
|
102
|
-
### getEncryptionKeyForRun()
|
|
103
|
-
|
|
104
|
-
Retrieve the encryption key used for a specific workflow run.
|
|
105
|
-
|
|
106
|
-
{/* @expect-error:2305 */}
|
|
107
|
-
```typescript lineNumbers
|
|
108
|
-
import { getEncryptionKeyForRun } from "workflow/observability"; // [!code highlight]
|
|
109
|
-
|
|
110
|
-
const key = await getEncryptionKeyForRun(runId); // [!code highlight]
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
**Parameters:**
|
|
114
|
-
|
|
115
|
-
| Parameter | Type | Description |
|
|
116
|
-
|-----------|------|-------------|
|
|
117
|
-
| `runId` | `string` | The workflow run ID |
|
|
118
|
-
|
|
119
|
-
**Returns:** Encryption key for the run
|
|
120
|
-
|
|
121
|
-
### hydrateResourceIOWithKey()
|
|
122
|
-
|
|
123
|
-
Hydrate step or run data using a decryption key. Use this instead of `hydrateResourceIO()` when data is encrypted.
|
|
124
|
-
|
|
125
|
-
{/* @expect-error:2305,2724 */}
|
|
126
|
-
```typescript lineNumbers
|
|
127
|
-
import { getEncryptionKeyForRun, hydrateResourceIOWithKey } from "workflow/observability"; // [!code highlight]
|
|
128
|
-
|
|
129
|
-
const key = await getEncryptionKeyForRun(runId); // [!code highlight]
|
|
130
|
-
const hydrated = hydrateResourceIOWithKey(step, key); // [!code highlight]
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
**Parameters:**
|
|
134
|
-
|
|
135
|
-
| Parameter | Type | Description |
|
|
136
|
-
|-----------|------|-------------|
|
|
137
|
-
| `resource` | `Step \| WorkflowRun` | The step or run with encrypted serialized data |
|
|
138
|
-
| `key` | `EncryptionKey` | The encryption key from `getEncryptionKeyForRun()` |
|
|
139
|
-
|
|
140
|
-
**Returns:** The resource with decrypted and hydrated `input` and `output` fields.
|
|
141
|
-
|
|
142
|
-
## Examples
|
|
143
|
-
|
|
144
|
-
### Parse Display Names for a Run's Steps
|
|
145
|
-
|
|
146
|
-
```typescript lineNumbers
|
|
147
|
-
import { getWorld } from "workflow/runtime";
|
|
148
|
-
import { parseStepName, parseWorkflowName } from "workflow/observability"; // [!code highlight]
|
|
149
|
-
|
|
150
|
-
const world = await getWorld();
|
|
151
|
-
const run = await world.runs.get(runId, { resolveData: "none" });
|
|
152
|
-
console.log("Workflow:", parseWorkflowName(run.workflowName)?.shortName); // [!code highlight]
|
|
153
|
-
|
|
154
|
-
const steps = await world.steps.list({ runId, resolveData: "none" });
|
|
155
|
-
for (const step of steps.data) {
|
|
156
|
-
const parsed = parseStepName(step.stepName); // [!code highlight]
|
|
157
|
-
console.log(` ${parsed?.shortName}: ${step.status}`); // [!code highlight]
|
|
158
|
-
}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
## Related
|
|
162
|
-
|
|
163
|
-
- [Storage](/docs/api-reference/workflow-api/world/storage) — Query runs, steps, hooks, and events
|
|
164
|
-
- [Serialization](/docs/foundations/serialization) — How workflow data is serialized
|