@workflow/core 5.0.0-beta.5 → 5.0.0-beta.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/attribute-changes.d.ts +5 -0
- package/dist/attribute-changes.d.ts.map +1 -0
- package/dist/attribute-changes.js +25 -0
- package/dist/capabilities.d.ts +35 -2
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +74 -5
- package/dist/capture-stack.d.ts +1 -1
- package/dist/capture-stack.js +2 -2
- package/dist/class-serialization.d.ts +32 -0
- package/dist/class-serialization.d.ts.map +1 -1
- package/dist/class-serialization.js +37 -1
- package/dist/classify-error.d.ts +19 -3
- package/dist/classify-error.d.ts.map +1 -1
- package/dist/classify-error.js +98 -6
- package/dist/context-violation-error.d.ts +5 -5
- package/dist/context-violation-error.d.ts.map +1 -1
- package/dist/context-violation-error.js +8 -6
- package/dist/create-hook.d.ts +72 -7
- package/dist/create-hook.d.ts.map +1 -1
- package/dist/create-hook.js +1 -1
- package/dist/define-hook.js +2 -2
- package/dist/describe-error.d.ts +7 -7
- package/dist/describe-error.d.ts.map +1 -1
- package/dist/describe-error.js +97 -12
- package/dist/encryption.d.ts +38 -4
- package/dist/encryption.d.ts.map +1 -1
- package/dist/encryption.js +96 -14
- package/dist/events-consumer.d.ts +271 -0
- package/dist/events-consumer.d.ts.map +1 -1
- package/dist/events-consumer.js +646 -42
- package/dist/flushable-stream.d.ts +56 -1
- package/dist/flushable-stream.d.ts.map +1 -1
- package/dist/flushable-stream.js +123 -11
- package/dist/global.d.ts +36 -2
- package/dist/global.d.ts.map +1 -1
- package/dist/global.js +21 -6
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/log-format.d.ts +2 -2
- package/dist/log-format.js +7 -7
- package/dist/logger.d.ts +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +10 -6
- package/dist/private.d.ts +226 -9
- package/dist/private.d.ts.map +1 -1
- package/dist/private.js +626 -7
- package/dist/replay-payload-cache.d.ts +64 -0
- package/dist/replay-payload-cache.d.ts.map +1 -0
- package/dist/replay-payload-cache.js +158 -0
- package/dist/runtime/compute-instance.d.ts +13 -0
- package/dist/runtime/compute-instance.d.ts.map +1 -0
- package/dist/runtime/compute-instance.js +14 -0
- package/dist/runtime/constants.d.ts +315 -0
- package/dist/runtime/constants.d.ts.map +1 -1
- package/dist/runtime/constants.js +502 -16
- package/dist/runtime/count-step-started-events.d.ts +52 -0
- package/dist/runtime/count-step-started-events.d.ts.map +1 -0
- package/dist/runtime/count-step-started-events.js +72 -0
- package/dist/runtime/deployment-guard.d.ts +99 -0
- package/dist/runtime/deployment-guard.d.ts.map +1 -0
- package/dist/runtime/deployment-guard.js +154 -0
- package/dist/runtime/get-port-lazy.d.ts +15 -0
- package/dist/runtime/get-port-lazy.d.ts.map +1 -1
- package/dist/runtime/get-port-lazy.js +75 -6
- package/dist/runtime/get-world-lazy.d.ts +5 -14
- package/dist/runtime/get-world-lazy.d.ts.map +1 -1
- package/dist/runtime/get-world-lazy.js +19 -29
- package/dist/runtime/helpers.d.ts +341 -29
- package/dist/runtime/helpers.d.ts.map +1 -1
- package/dist/runtime/helpers.js +559 -57
- package/dist/runtime/quickjs-assets.generated.d.ts +14 -0
- package/dist/runtime/quickjs-assets.generated.d.ts.map +1 -0
- package/dist/runtime/quickjs-assets.generated.js +30 -0
- package/dist/runtime/quickjs-entrypoint.d.ts +136 -0
- package/dist/runtime/quickjs-entrypoint.d.ts.map +1 -0
- package/dist/runtime/quickjs-entrypoint.js +1686 -0
- package/dist/runtime/quickjs-runtime.d.ts +244 -0
- package/dist/runtime/quickjs-runtime.d.ts.map +1 -0
- package/dist/runtime/quickjs-runtime.js +2427 -0
- package/dist/runtime/quickjs-serde.d.ts +107 -0
- package/dist/runtime/quickjs-serde.d.ts.map +1 -0
- package/dist/runtime/quickjs-serde.js +2159 -0
- package/dist/runtime/replay-budget.d.ts +97 -0
- package/dist/runtime/replay-budget.d.ts.map +1 -0
- package/dist/runtime/replay-budget.js +139 -0
- package/dist/runtime/replay-recovery-reporter.d.ts +36 -0
- package/dist/runtime/replay-recovery-reporter.d.ts.map +1 -0
- package/dist/runtime/replay-recovery-reporter.js +64 -0
- package/dist/runtime/resume-hook.d.ts +91 -8
- package/dist/runtime/resume-hook.d.ts.map +1 -1
- package/dist/runtime/resume-hook.js +512 -89
- package/dist/runtime/resume-latency.d.ts +150 -0
- package/dist/runtime/resume-latency.d.ts.map +1 -0
- package/dist/runtime/resume-latency.js +155 -0
- package/dist/runtime/run-id-time.d.ts +19 -0
- package/dist/runtime/run-id-time.d.ts.map +1 -0
- package/dist/runtime/run-id-time.js +42 -0
- package/dist/runtime/run.d.ts +72 -7
- package/dist/runtime/run.d.ts.map +1 -1
- package/dist/runtime/run.js +280 -53
- package/dist/runtime/runs.d.ts +54 -3
- package/dist/runtime/runs.d.ts.map +1 -1
- package/dist/runtime/runs.js +126 -16
- package/dist/runtime/start.d.ts +101 -2
- package/dist/runtime/start.d.ts.map +1 -1
- package/dist/runtime/start.js +352 -61
- package/dist/runtime/step-executor.d.ts +205 -5
- package/dist/runtime/step-executor.d.ts.map +1 -1
- package/dist/runtime/step-executor.js +760 -127
- package/dist/runtime/step-latency.d.ts +220 -0
- package/dist/runtime/step-latency.d.ts.map +1 -0
- package/dist/runtime/step-latency.js +215 -0
- package/dist/runtime/step-ownership.d.ts +72 -0
- package/dist/runtime/step-ownership.d.ts.map +1 -0
- package/dist/runtime/step-ownership.js +114 -0
- package/dist/runtime/step-single-flight.d.ts +12 -0
- package/dist/runtime/step-single-flight.d.ts.map +1 -0
- package/dist/runtime/step-single-flight.js +75 -0
- package/dist/runtime/suspension-handler.d.ts +269 -7
- package/dist/runtime/suspension-handler.d.ts.map +1 -1
- package/dist/runtime/suspension-handler.js +1253 -125
- package/dist/runtime/unserializable-step.d.ts +34 -0
- package/dist/runtime/unserializable-step.d.ts.map +1 -0
- package/dist/runtime/unserializable-step.js +52 -0
- package/dist/runtime/vm-mode.d.ts +44 -0
- package/dist/runtime/vm-mode.d.ts.map +1 -0
- package/dist/runtime/vm-mode.js +62 -0
- package/dist/runtime/vm-serde-bundle.generated.d.ts +14 -0
- package/dist/runtime/vm-serde-bundle.generated.d.ts.map +1 -0
- package/dist/runtime/vm-serde-bundle.generated.js +16 -0
- package/dist/runtime/wait-continuation.d.ts +106 -0
- package/dist/runtime/wait-continuation.d.ts.map +1 -0
- package/dist/runtime/wait-continuation.js +136 -0
- package/dist/runtime/wait-until.d.ts +18 -0
- package/dist/runtime/wait-until.d.ts.map +1 -0
- package/dist/runtime/wait-until.js +42 -0
- package/dist/runtime/world-compatibility.d.ts +32 -0
- package/dist/runtime/world-compatibility.d.ts.map +1 -0
- package/dist/runtime/world-compatibility.js +44 -0
- package/dist/runtime/world-init.d.ts +17 -42
- package/dist/runtime/world-init.d.ts.map +1 -1
- package/dist/runtime/world-init.js +18 -43
- package/dist/runtime/world.d.ts +14 -2
- package/dist/runtime/world.d.ts.map +1 -1
- package/dist/runtime/world.js +75 -19
- package/dist/runtime.d.ts +10 -6
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +3741 -490
- package/dist/sealed-box.d.ts +167 -0
- package/dist/sealed-box.d.ts.map +1 -0
- package/dist/sealed-box.js +571 -0
- package/dist/serialization/client.d.ts +3 -3
- package/dist/serialization/client.d.ts.map +1 -1
- package/dist/serialization/client.js +8 -4
- package/dist/serialization/codec-devalue-vm.d.ts +16 -0
- package/dist/serialization/codec-devalue-vm.d.ts.map +1 -0
- package/dist/serialization/codec-devalue-vm.js +148 -0
- package/dist/serialization/codec-devalue.d.ts +1 -1
- package/dist/serialization/codec-devalue.d.ts.map +1 -1
- package/dist/serialization/codec-devalue.js +24 -6
- package/dist/serialization/codec.d.ts +37 -2
- package/dist/serialization/codec.d.ts.map +1 -1
- package/dist/serialization/codec.js +2 -2
- package/dist/serialization/compression.d.ts +105 -0
- package/dist/serialization/compression.d.ts.map +1 -0
- package/dist/serialization/compression.js +261 -0
- package/dist/serialization/encryption.d.ts +119 -17
- package/dist/serialization/encryption.d.ts.map +1 -1
- package/dist/serialization/encryption.js +152 -17
- package/dist/serialization/errors.d.ts +13 -0
- package/dist/serialization/errors.d.ts.map +1 -1
- package/dist/serialization/errors.js +20 -2
- package/dist/serialization/format.d.ts +6 -6
- package/dist/serialization/format.js +7 -7
- package/dist/serialization/hardened.d.ts +156 -0
- package/dist/serialization/hardened.d.ts.map +1 -0
- package/dist/serialization/hardened.js +538 -0
- package/dist/serialization/index.d.ts +10 -8
- package/dist/serialization/index.d.ts.map +1 -1
- package/dist/serialization/index.js +10 -8
- package/dist/serialization/reducers/class-vm.d.ts +20 -0
- package/dist/serialization/reducers/class-vm.d.ts.map +1 -0
- package/dist/serialization/reducers/class-vm.js +77 -0
- package/dist/serialization/reducers/class.d.ts.map +1 -1
- package/dist/serialization/reducers/class.js +11 -6
- package/dist/serialization/reducers/common-vm.d.ts +15 -0
- package/dist/serialization/reducers/common-vm.d.ts.map +1 -0
- package/dist/serialization/reducers/common-vm.js +616 -0
- package/dist/serialization/reducers/common.d.ts +1 -1
- package/dist/serialization/reducers/common.d.ts.map +1 -1
- package/dist/serialization/reducers/common.js +202 -62
- package/dist/serialization/reducers/step-function-vm.d.ts +44 -0
- package/dist/serialization/reducers/step-function-vm.d.ts.map +1 -0
- package/dist/serialization/reducers/step-function-vm.js +97 -0
- package/dist/serialization/reducers/step-function.d.ts.map +1 -1
- package/dist/serialization/reducers/step-function.js +25 -9
- package/dist/serialization/step.d.ts +4 -4
- package/dist/serialization/step.d.ts.map +1 -1
- package/dist/serialization/step.js +9 -5
- package/dist/serialization/types.d.ts +80 -1
- package/dist/serialization/types.d.ts.map +1 -1
- package/dist/serialization/types.js +15 -1
- package/dist/serialization/workflow-vm.d.ts +29 -0
- package/dist/serialization/workflow-vm.d.ts.map +1 -0
- package/dist/serialization/workflow-vm.js +74 -0
- package/dist/serialization-format.d.ts +64 -8
- package/dist/serialization-format.d.ts.map +1 -1
- package/dist/serialization-format.js +225 -73
- package/dist/serialization.d.ts +262 -34
- package/dist/serialization.d.ts.map +1 -1
- package/dist/serialization.js +1879 -274
- package/dist/set-attributes.d.ts +13 -0
- package/dist/set-attributes.d.ts.map +1 -0
- package/dist/set-attributes.js +60 -0
- package/dist/source-map.d.ts +25 -0
- package/dist/source-map.d.ts.map +1 -1
- package/dist/source-map.js +154 -10
- package/dist/step/context-storage.d.ts +61 -2
- package/dist/step/context-storage.d.ts.map +1 -1
- package/dist/step/context-storage.js +7 -5
- package/dist/step/writable-stream.d.ts +3 -3
- package/dist/step/writable-stream.d.ts.map +1 -1
- package/dist/step/writable-stream.js +72 -9
- package/dist/step.d.ts.map +1 -1
- package/dist/step.js +148 -27
- package/dist/symbols.d.ts +50 -0
- package/dist/symbols.d.ts.map +1 -1
- package/dist/symbols.js +51 -1
- package/dist/telemetry/semantic-conventions.d.ts +397 -2
- package/dist/telemetry/semantic-conventions.d.ts.map +1 -1
- package/dist/telemetry/semantic-conventions.js +297 -3
- package/dist/telemetry.d.ts +91 -0
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +244 -17
- package/dist/test-support/orchestrator-context.d.ts +25 -0
- package/dist/test-support/orchestrator-context.d.ts.map +1 -0
- package/dist/test-support/orchestrator-context.js +101 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +23 -1
- package/dist/util.d.ts +0 -5
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +1 -15
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +2 -2
- package/dist/vm/index.d.ts.map +1 -1
- package/dist/vm/index.js +84 -14
- package/dist/vm/script-cache.d.ts +26 -0
- package/dist/vm/script-cache.d.ts.map +1 -0
- package/dist/vm/script-cache.js +131 -0
- package/dist/vm/uint8array-base64.js +1 -1
- package/dist/workflow/abort-controller.d.ts +2 -2
- package/dist/workflow/abort-controller.d.ts.map +1 -1
- package/dist/workflow/abort-controller.js +88 -21
- package/dist/workflow/attribute-dispatcher.d.ts +6 -0
- package/dist/workflow/attribute-dispatcher.d.ts.map +1 -0
- package/dist/workflow/attribute-dispatcher.js +45 -0
- package/dist/workflow/create-hook.d.ts.map +1 -1
- package/dist/workflow/create-hook.js +24 -3
- package/dist/workflow/get-workflow-metadata.js +2 -2
- package/dist/workflow/hook.d.ts.map +1 -1
- package/dist/workflow/hook.js +304 -47
- package/dist/workflow/index.d.ts +1 -0
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +2 -1
- package/dist/workflow/set-attributes.d.ts +68 -0
- package/dist/workflow/set-attributes.d.ts.map +1 -0
- package/dist/workflow/set-attributes.js +60 -0
- package/dist/workflow/sleep.d.ts.map +1 -1
- package/dist/workflow/sleep.js +58 -12
- package/dist/workflow/world-init-stub.d.ts +3 -3
- package/dist/workflow/world-init-stub.js +4 -4
- package/dist/workflow.d.ts +95 -3
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +895 -561
- package/docs/api-reference/create-hook.mdx +89 -10
- package/docs/api-reference/create-webhook.mdx +16 -15
- package/docs/api-reference/define-hook.mdx +35 -33
- package/docs/api-reference/fatal-error.mdx +30 -8
- package/docs/api-reference/fetch.mdx +14 -10
- package/docs/api-reference/get-step-metadata.mdx +2 -2
- package/docs/api-reference/get-workflow-metadata.mdx +3 -3
- package/docs/api-reference/get-writable.mdx +7 -7
- package/docs/api-reference/index.mdx +4 -1
- package/docs/api-reference/retryable-error.mdx +1 -1
- package/docs/api-reference/set-attributes.mdx +61 -0
- package/docs/api-reference/sleep.mdx +4 -4
- package/docs/foundations/cancellation.mdx +31 -32
- package/docs/foundations/errors-and-retries.mdx +42 -11
- package/docs/foundations/hooks.mdx +64 -35
- package/docs/foundations/idempotency.mdx +244 -12
- package/docs/foundations/index.mdx +1 -23
- package/docs/foundations/meta.json +2 -1
- package/docs/foundations/serialization.mdx +21 -22
- package/docs/foundations/starting-workflows.mdx +106 -30
- package/docs/foundations/streaming.mdx +107 -59
- package/docs/foundations/versioning.mdx +263 -0
- package/docs/foundations/workflows-and-steps.mdx +9 -9
- package/docs/how-it-works/cancellation.mdx +63 -63
- package/docs/how-it-works/code-transform.mdx +83 -67
- package/docs/how-it-works/encryption.mdx +30 -26
- package/docs/how-it-works/event-sourcing.mdx +98 -34
- package/docs/how-it-works/framework-integrations.mdx +96 -337
- package/docs/how-it-works/understanding-directives.mdx +22 -22
- package/package.json +24 -15
- package/dist/runtime/step-handler.d.ts +0 -2
- package/dist/runtime/step-handler.d.ts.map +0 -1
- package/dist/runtime/step-handler.js +0 -776
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: setAttributes
|
|
3
|
+
description: Attach string metadata to workflow run for observability.
|
|
4
|
+
type: reference
|
|
5
|
+
summary: Use setAttributes inside a workflow or step function to set run attributes.
|
|
6
|
+
prerequisites:
|
|
7
|
+
- /docs/foundations/workflows-and-steps
|
|
8
|
+
related:
|
|
9
|
+
- /docs/observability/attributes
|
|
10
|
+
- /docs/api-reference/workflow/fatal-error
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
Attaches string metadata to the current workflow run.
|
|
14
|
+
|
|
15
|
+
```typescript lineNumbers
|
|
16
|
+
import { setAttributes } from "workflow"
|
|
17
|
+
|
|
18
|
+
export async function orderWorkflow(orderId: string) {
|
|
19
|
+
"use workflow"
|
|
20
|
+
|
|
21
|
+
await setAttributes({
|
|
22
|
+
phase: "received",
|
|
23
|
+
orderId,
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## API signature
|
|
29
|
+
|
|
30
|
+
### Parameters
|
|
31
|
+
|
|
32
|
+
<TSDoc
|
|
33
|
+
definition={`
|
|
34
|
+
import { setAttributes } from "workflow";
|
|
35
|
+
export default setAttributes;`}
|
|
36
|
+
showSections={['parameters']}
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
Call `setAttributes` from a `"use workflow"` function or a `"use step"` function. Calling it from plain application code is not supported because there is no active workflow run.
|
|
42
|
+
|
|
43
|
+
Attribute values must be strings. Pass `undefined` to remove an attribute:
|
|
44
|
+
|
|
45
|
+
```typescript lineNumbers
|
|
46
|
+
import { setAttributes } from "workflow"
|
|
47
|
+
|
|
48
|
+
export async function cleanupAttributes() {
|
|
49
|
+
"use workflow"
|
|
50
|
+
|
|
51
|
+
await setAttributes({ staleKey: undefined })
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Attribute keys must be 1-256 characters, values must be strings up to 256 bytes, and each run can have up to 64 attributes. Keys that start with `$` are reserved for framework and library code.
|
|
56
|
+
|
|
57
|
+
Validation errors throw [`FatalError`](/docs/api-reference/workflow/fatal-error) and fail the run before an attribute write is attempted.
|
|
58
|
+
|
|
59
|
+
Calls from both workflow and step bodies append a native `attr_set` event, which the World materializes onto `run.attributes`. Workflow-originated events record a workflow writer; step-originated events record the originating step ID and attempt.
|
|
60
|
+
|
|
61
|
+
Native attributes require spec version 4 or later. Step-body storage errors throw from `setAttributes`; catch them inside the step if the write should be best-effort. Workflow-body writes are committed when the workflow suspends: transient storage errors are retried with the suspension, while a write the World rejects as invalid (such as exceeding the per-run attribute cap across multiple calls) fails the run with the validation error.
|
|
@@ -14,7 +14,7 @@ Suspends a workflow for a specified duration or until an end date without consum
|
|
|
14
14
|
This is useful when you want to resume a workflow after some duration or date.
|
|
15
15
|
|
|
16
16
|
<Callout>
|
|
17
|
-
`sleep` is a
|
|
17
|
+
`sleep` is a built-in workflow runtime function (backed by a timer event in the event log, not a step) and should be called directly inside workflow functions.
|
|
18
18
|
</Callout>
|
|
19
19
|
|
|
20
20
|
```typescript lineNumbers
|
|
@@ -26,7 +26,7 @@ async function testWorkflow() {
|
|
|
26
26
|
}
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
## API
|
|
29
|
+
## API signature
|
|
30
30
|
|
|
31
31
|
### Parameters
|
|
32
32
|
|
|
@@ -39,7 +39,7 @@ showSections={['parameters']}
|
|
|
39
39
|
|
|
40
40
|
## Examples
|
|
41
41
|
|
|
42
|
-
### Sleeping
|
|
42
|
+
### Sleeping with a duration
|
|
43
43
|
|
|
44
44
|
You can specify a duration for `sleep` to suspend the workflow for a fixed amount of time.
|
|
45
45
|
|
|
@@ -52,7 +52,7 @@ async function testWorkflow() {
|
|
|
52
52
|
}
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
### Sleeping
|
|
55
|
+
### Sleeping until an end date
|
|
56
56
|
|
|
57
57
|
You can specify a future `Date` object for `sleep` to suspend the workflow until a specific date.
|
|
58
58
|
|
|
@@ -6,7 +6,6 @@ summary: Cancel in-flight work with AbortSignal or stop entire workflow runs.
|
|
|
6
6
|
prerequisites:
|
|
7
7
|
- /docs/foundations/workflows-and-steps
|
|
8
8
|
related:
|
|
9
|
-
- /docs/foundations/common-patterns
|
|
10
9
|
- /docs/foundations/hooks
|
|
11
10
|
- /docs/how-it-works/cancellation
|
|
12
11
|
---
|
|
@@ -15,7 +14,7 @@ Workflow DevKit supports two cancellation mechanisms: **AbortSignal** for fine-g
|
|
|
15
14
|
|
|
16
15
|
## AbortSignal
|
|
17
16
|
|
|
18
|
-
`AbortController` and `AbortSignal` work across workflow and step boundaries. Create an `AbortController` with `new AbortController()` in a workflow function, pass its signal to steps, and call `abort()`
|
|
17
|
+
`AbortController` and `AbortSignal` work across workflow and step boundaries. Create an `AbortController` with `new AbortController()` in a workflow function, pass its signal to steps, and call `abort()` using the standard [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) API.
|
|
19
18
|
|
|
20
19
|
```typescript lineNumbers
|
|
21
20
|
import { sleep } from "workflow";
|
|
@@ -49,17 +48,17 @@ async function longRunningStep(signal: AbortSignal) {
|
|
|
49
48
|
}
|
|
50
49
|
```
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
Use the standard `AbortController` API without special imports or wrapper functions.
|
|
53
52
|
|
|
54
53
|
<Callout type="info">
|
|
55
|
-
Cancellation is **cooperative**. Aborting a signal doesn't forcefully kill a step
|
|
54
|
+
Cancellation is **cooperative**. Aborting a signal doesn't forcefully kill a step. The step's code must check `signal.aborted` or pass the signal to APIs like `fetch` that respect it. If a step ignores the signal, it runs to completion.
|
|
56
55
|
</Callout>
|
|
57
56
|
|
|
58
57
|
<Callout type="info">
|
|
59
58
|
To learn how `AbortController` works durably across workflow suspensions, replays, and step boundaries, see [How Cancellation Works](/docs/how-it-works/cancellation).
|
|
60
59
|
</Callout>
|
|
61
60
|
|
|
62
|
-
### Timeout with
|
|
61
|
+
### Timeout with cancellation
|
|
63
62
|
|
|
64
63
|
Race a step against a timeout, and cancel the step if the timeout wins:
|
|
65
64
|
|
|
@@ -91,7 +90,7 @@ async function fetchUrl(url: string, signal: AbortSignal) {
|
|
|
91
90
|
}
|
|
92
91
|
```
|
|
93
92
|
|
|
94
|
-
### Cancelling
|
|
93
|
+
### Cancelling parallel work
|
|
95
94
|
|
|
96
95
|
When racing multiple steps, cancel the losers:
|
|
97
96
|
|
|
@@ -117,7 +116,7 @@ async function fetchUrl(url: string, signal: AbortSignal) {
|
|
|
117
116
|
}
|
|
118
117
|
```
|
|
119
118
|
|
|
120
|
-
### Passing
|
|
119
|
+
### Passing signal through a pipeline
|
|
121
120
|
|
|
122
121
|
Pass the same signal to a chain of steps. Aborting cancels whichever step is currently running:
|
|
123
122
|
|
|
@@ -176,7 +175,7 @@ async function uploadData(data: ArrayBuffer, signal: AbortSignal) {
|
|
|
176
175
|
}
|
|
177
176
|
```
|
|
178
177
|
|
|
179
|
-
### Step-
|
|
178
|
+
### Step-initiated abort
|
|
180
179
|
|
|
181
180
|
A step can receive the full `AbortController` and call `abort()` to cancel parallel work. This is useful for watchdog/monitor patterns where one step observes an external condition and cancels other in-flight steps:
|
|
182
181
|
|
|
@@ -221,7 +220,7 @@ async function monitorQuota(userId: string, controller: AbortController) {
|
|
|
221
220
|
}
|
|
222
221
|
```
|
|
223
222
|
|
|
224
|
-
### User-
|
|
223
|
+
### User-triggered cancellation with hooks
|
|
225
224
|
|
|
226
225
|
Combine hooks with abort controllers to let users cancel in-flight work from an external API:
|
|
227
226
|
|
|
@@ -267,23 +266,23 @@ export async function POST(request: Request) {
|
|
|
267
266
|
}
|
|
268
267
|
```
|
|
269
268
|
|
|
270
|
-
### How
|
|
269
|
+
### How steps handle abort
|
|
271
270
|
|
|
272
271
|
When an `AbortSignal` is aborted, the behavior depends on how the step uses it:
|
|
273
272
|
|
|
274
|
-
| Usage | Behavior on
|
|
273
|
+
| Usage | Behavior on abort |
|
|
275
274
|
|-------|-------------------|
|
|
276
|
-
| `fetch(url, { signal })` | Request is
|
|
275
|
+
| `fetch(url, { signal })` | Request is canceled, throws `AbortError` |
|
|
277
276
|
| `signal.throwIfAborted()` | Throws the abort reason |
|
|
278
277
|
| `signal.aborted` check | Returns `true`, step can exit gracefully |
|
|
279
278
|
| `signal.addEventListener('abort', fn)` | Callback fires, step can clean up |
|
|
280
279
|
| Ignored | Step runs to completion (abort is cooperative) |
|
|
281
280
|
|
|
282
|
-
### Abort
|
|
281
|
+
### Abort errors skip retries
|
|
283
282
|
|
|
284
|
-
When a step throws due to an abort (
|
|
283
|
+
When a step throws due to an abort (for example, `fetch` throws `AbortError`, or `signal.throwIfAborted()` throws), the runtime wraps the error in a `FatalError`. The step **skips retries**, and the error immediately bubbles up to the workflow.
|
|
285
284
|
|
|
286
|
-
|
|
285
|
+
An abort is an intentional cancellation, so retrying the step would result in another abort. You don't need to manually wrap abort errors in `FatalError`.
|
|
287
286
|
|
|
288
287
|
```typescript lineNumbers
|
|
289
288
|
import { sleep } from "workflow";
|
|
@@ -300,7 +299,7 @@ export async function workflow() {
|
|
|
300
299
|
if (result === null) controller.abort();
|
|
301
300
|
return result;
|
|
302
301
|
} catch (err) {
|
|
303
|
-
// AbortError arrives as FatalError
|
|
302
|
+
// AbortError arrives as FatalError, with no retries attempted // [!code highlight]
|
|
304
303
|
return { status: "cancelled" };
|
|
305
304
|
}
|
|
306
305
|
}
|
|
@@ -313,7 +312,7 @@ async function cancellableStep(signal: AbortSignal) {
|
|
|
313
312
|
}
|
|
314
313
|
```
|
|
315
314
|
|
|
316
|
-
### Passing AbortSignal as
|
|
315
|
+
### Passing AbortSignal as workflow input
|
|
317
316
|
|
|
318
317
|
You can pass an `AbortSignal` from external code into a workflow via `start()`:
|
|
319
318
|
|
|
@@ -330,15 +329,15 @@ export async function POST(request: Request) {
|
|
|
330
329
|
}
|
|
331
330
|
```
|
|
332
331
|
|
|
333
|
-
When the signal is serialized at the `start()` boundary, an event listener
|
|
332
|
+
When the signal is serialized at the `start()` boundary, an event listener attaches to the external signal and writes the cancellation packet to the backing stream. The external `abort()` propagates into the workflow only while the originating process is still alive (the same constraint as passing a `ReadableStream` as input).
|
|
334
333
|
|
|
335
334
|
<Callout type="info">
|
|
336
335
|
For reliable external cancellation that works regardless of process lifetime, prefer the [User-Triggered Cancellation with Hooks](#user-triggered-cancellation-with-hooks) pattern. Hooks are durable and don't depend on the caller's process staying alive.
|
|
337
336
|
</Callout>
|
|
338
337
|
|
|
339
|
-
## Run
|
|
338
|
+
## Run cancellation
|
|
340
339
|
|
|
341
|
-
Run cancellation stops an entire workflow at the next suspension point. Unlike `AbortSignal`, it is not cooperative
|
|
340
|
+
Run cancellation stops an entire workflow at the next suspension point. Unlike `AbortSignal`, it is not cooperative. The workflow does not continue executing after cancellation.
|
|
342
341
|
|
|
343
342
|
```typescript title="app/api/cancel-run/route.ts" lineNumbers
|
|
344
343
|
import { getRun } from "workflow/api";
|
|
@@ -354,10 +353,10 @@ export async function POST(request: Request) {
|
|
|
354
353
|
```
|
|
355
354
|
|
|
356
355
|
<Callout type="info">
|
|
357
|
-
Calling `run.cancel()` is the same action as clicking the **Cancel** button on a run in the observability UI
|
|
356
|
+
Calling `run.cancel()` is the same action as clicking the **Cancel** button on a run in the observability UI. Both produce identical `run_cancelled` events in the event log.
|
|
358
357
|
</Callout>
|
|
359
358
|
|
|
360
|
-
When a run is
|
|
359
|
+
When a run is canceled:
|
|
361
360
|
- The workflow stops at its next suspension point (step call, hook await, or sleep)
|
|
362
361
|
- A `run_cancelled` event is recorded in the [event log](/docs/how-it-works/event-sourcing)
|
|
363
362
|
- All associated hooks are disposed and their tokens released
|
|
@@ -367,19 +366,19 @@ When a run is cancelled:
|
|
|
367
366
|
Run cancellation does **not** automatically abort any outstanding `AbortSignal`s. Steps that are currently executing will run to completion. If you need in-flight cancellation of specific operations, use `AbortSignal`.
|
|
368
367
|
</Callout>
|
|
369
368
|
|
|
370
|
-
## AbortSignal vs.
|
|
369
|
+
## AbortSignal vs. run cancellation
|
|
371
370
|
|
|
372
371
|
| | AbortSignal | Run Cancellation |
|
|
373
372
|
|---|---|---|
|
|
374
373
|
| **Scope** | Individual operations within a step | Entire workflow run |
|
|
375
374
|
| **Triggered by** | Your code (`controller.abort()`) | External API (`run.cancel()`) |
|
|
376
|
-
| **Cooperative** | Yes
|
|
375
|
+
| **Cooperative** | Yes. Steps must check the signal | No. The workflow stops at the next suspension point |
|
|
377
376
|
| **Granularity** | Can target specific steps or operations | All-or-nothing |
|
|
378
377
|
| **In-flight steps** | Aborted immediately if using the signal | Run to completion |
|
|
379
378
|
|
|
380
379
|
Use `AbortSignal` when you need fine-grained, in-flight cancellation of specific operations. Use run cancellation when you want to stop the entire workflow.
|
|
381
380
|
|
|
382
|
-
## Best
|
|
381
|
+
## Best practices
|
|
383
382
|
|
|
384
383
|
**Use `throwIfAborted()` before expensive work.** This throws the signal's abort reason if the signal is already aborted, preventing wasted compute:
|
|
385
384
|
|
|
@@ -449,12 +448,12 @@ export async function workflow() {
|
|
|
449
448
|
}
|
|
450
449
|
```
|
|
451
450
|
|
|
452
|
-
This is safe even if both steps have already completed
|
|
451
|
+
This is safe even if both steps have already completed because aborting a finished operation is a no-op.
|
|
453
452
|
|
|
454
|
-
## Related
|
|
453
|
+
## Related documentation
|
|
455
454
|
|
|
456
|
-
- [How Cancellation Works](/docs/how-it-works/cancellation)
|
|
457
|
-
- [Serialization](/docs/foundations/serialization)
|
|
458
|
-
- [
|
|
459
|
-
- [Hooks](/docs/foundations/hooks)
|
|
460
|
-
- [Errors and Retries](/docs/foundations/errors-and-retries)
|
|
455
|
+
- [How Cancellation Works](/docs/how-it-works/cancellation): Hook and stream backing, serialization internals
|
|
456
|
+
- [Serialization](/docs/foundations/serialization): Understanding serializable types
|
|
457
|
+
- [Cookbook](/cookbook): Timeout, race, and other reliability patterns
|
|
458
|
+
- [Hooks](/docs/foundations/hooks): Pausing workflows for external events
|
|
459
|
+
- [Errors and Retries](/docs/foundations/errors-and-retries): Handling step failures
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Errors & Retrying
|
|
3
|
-
description: Customize retry behavior with FatalError and RetryableError
|
|
3
|
+
description: Customize retry behavior with FatalError and RetryableError.
|
|
4
4
|
type: conceptual
|
|
5
5
|
summary: Control how steps handle failures and customize retry behavior.
|
|
6
6
|
prerequisites:
|
|
@@ -12,7 +12,7 @@ related:
|
|
|
12
12
|
|
|
13
13
|
By default, errors thrown inside steps are retried. Additionally, Workflow SDK provides two new types of errors you can use to customize retries.
|
|
14
14
|
|
|
15
|
-
## Default
|
|
15
|
+
## Default retrying
|
|
16
16
|
|
|
17
17
|
By default, steps retry up to 3 times on arbitrary errors. You can customize the number of retries by adding a `maxRetries` property to the step function.
|
|
18
18
|
|
|
@@ -42,9 +42,9 @@ Steps get enqueued immediately after a failure. Read on to see how this can be c
|
|
|
42
42
|
more information.
|
|
43
43
|
</Callout>
|
|
44
44
|
|
|
45
|
-
## Intentional
|
|
45
|
+
## Intentional errors
|
|
46
46
|
|
|
47
|
-
When your step needs to intentionally throw an error and skip retrying,
|
|
47
|
+
When your step needs to intentionally throw an error and skip retrying, throw a [`FatalError`](/docs/api-reference/workflow/fatal-error).
|
|
48
48
|
|
|
49
49
|
```typescript lineNumbers
|
|
50
50
|
import { FatalError } from "workflow";
|
|
@@ -67,7 +67,7 @@ async function callApi(endpoint: string) {
|
|
|
67
67
|
}
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
## Customize
|
|
70
|
+
## Customize retry behavior
|
|
71
71
|
|
|
72
72
|
When you need to customize the delay on a retry, use [`RetryableError`](/docs/api-reference/workflow/retryable-error) and set the `retryAfter` property.
|
|
73
73
|
|
|
@@ -97,7 +97,7 @@ async function callApi(endpoint: string) {
|
|
|
97
97
|
}
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
## Advanced
|
|
100
|
+
## Advanced example
|
|
101
101
|
|
|
102
102
|
This final example combines everything we've learned, along with [`getStepMetadata`](/docs/api-reference/workflow/get-step-metadata).
|
|
103
103
|
|
|
@@ -139,7 +139,32 @@ callApi.maxRetries = 5; // Retry up to 5 times on failure (6 total attempts)
|
|
|
139
139
|
step can run up to 4 times total (1 initial attempt + 3 retries).
|
|
140
140
|
</Callout>
|
|
141
141
|
|
|
142
|
-
##
|
|
142
|
+
## Serialization failures
|
|
143
|
+
|
|
144
|
+
A step whose arguments or return value cannot be [serialized](/docs/foundations/serialization) fails like a step whose body threw a `FatalError`: the failure is deterministic, so it skips the retry loop, and a `try/catch` around the step call observes the `SerializationError`:
|
|
145
|
+
|
|
146
|
+
```typescript lineNumbers
|
|
147
|
+
async function someStep(input: unknown) {
|
|
148
|
+
"use step";
|
|
149
|
+
return input;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function myWorkflow(input: unknown) {
|
|
153
|
+
"use workflow";
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
await someStep(input);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
if ((err as Error).name === "SerializationError") {
|
|
159
|
+
// e.g. `Failed to serialize step arguments at path "..."`
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Uncaught, the run fails immediately with the `USER_ERROR` code, without retrying. See [serialization-failed](/docs/errors/serialization-failed) for common causes and fixes.
|
|
166
|
+
|
|
167
|
+
## Error codes
|
|
143
168
|
|
|
144
169
|
When a workflow run fails, the error includes an `errorCode` that classifies the failure, alongside the original thrown value (preserved as `cause`):
|
|
145
170
|
|
|
@@ -153,7 +178,7 @@ try {
|
|
|
153
178
|
const result = await run.returnValue;
|
|
154
179
|
} catch (err) {
|
|
155
180
|
if (WorkflowRunFailedError.is(err)) {
|
|
156
|
-
console.log(err.errorCode); // "USER_ERROR", "
|
|
181
|
+
console.log(err.errorCode); // e.g. "USER_ERROR", "MAX_EVENTS_EXCEEDED", or undefined
|
|
157
182
|
// `cause` is the original thrown value, hydrated through the workflow
|
|
158
183
|
// serialization pipeline. It can be any thrown value, so check shape.
|
|
159
184
|
if (err.cause instanceof Error) {
|
|
@@ -166,13 +191,19 @@ try {
|
|
|
166
191
|
| Code | Meaning |
|
|
167
192
|
| --- | --- |
|
|
168
193
|
| `USER_ERROR` | An error thrown in your workflow or step code (including propagated step failures like `FatalError`) |
|
|
169
|
-
| `
|
|
194
|
+
| `MAX_EVENTS_EXCEEDED` | The run reached the World's per-run event ceiling (25,000 on the Local and Vercel Worlds). Split unbounded loops into [child workflows](/cookbook/advanced/child-workflows); see [Limits](/docs/configuration/runtime-tuning#limits) |
|
|
195
|
+
| `MAX_DELIVERIES_EXCEEDED` | The run exceeded the maximum number of queue deliveries |
|
|
196
|
+
| `REPLAY_TIMEOUT` | A workflow replay exceeded the maximum allowed duration |
|
|
197
|
+
| `REPLAY_DIVERGENCE` | A replay could not consume the event log deterministically, usually because of non-deterministic workflow code. |
|
|
198
|
+
| `CORRUPTED_EVENT_LOG` | The event log cannot be replayed: it contains orphaned or mismatched events, or one of its stored payloads is no longer readable from the World's storage. If you see this, please [file an issue](https://github.com/vercel/workflow/issues) |
|
|
199
|
+
| `WORLD_CONTRACT_ERROR` | A World response violated the SDK contract; points at a World implementation bug |
|
|
200
|
+
| `RUNTIME_ERROR` | An internal runtime error. If you see this, please [file an issue](https://github.com/vercel/workflow/issues) |
|
|
170
201
|
|
|
171
202
|
<Callout type="info">
|
|
172
|
-
The error code is also available on the run entity
|
|
203
|
+
The error code is also available on the run entity through the Workflow CLI (`npx workflow inspect runs <runId>`) in the `error.code` field, and as an OpenTelemetry span attribute (`workflow.error.code`) for observability.
|
|
173
204
|
</Callout>
|
|
174
205
|
|
|
175
|
-
## Rolling
|
|
206
|
+
## Rolling back failed steps
|
|
176
207
|
|
|
177
208
|
When a workflow fails partway through, it can leave the system in an inconsistent state.
|
|
178
209
|
A common pattern to address this is "rollbacks": for each successful step, record a corresponding rollback action that can undo it.
|
|
@@ -11,9 +11,9 @@ related:
|
|
|
11
11
|
- /docs/ai/human-in-the-loop
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
Hooks
|
|
14
|
+
Hooks pause workflow execution and resume it later with external data. Workflows can wait for external events, user interactions (also known as "human in the loop"), or HTTP requests.
|
|
15
15
|
|
|
16
|
-
## Understanding
|
|
16
|
+
## Understanding hooks
|
|
17
17
|
|
|
18
18
|
At their core, **Hooks** are a low-level primitive that allows you to pause a workflow and resume it later with arbitrary [serializable data](/docs/foundations/serialization). Think of them as suspension points in your workflow where you're waiting for external input.
|
|
19
19
|
|
|
@@ -23,9 +23,9 @@ When you create a hook, it generates a unique token that external systems can us
|
|
|
23
23
|
- Receiving data from an external system or service
|
|
24
24
|
- Implementing event-driven workflows that react to multiple events over time
|
|
25
25
|
|
|
26
|
-
### Creating
|
|
26
|
+
### Creating your first hook
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
This workflow creates a hook and waits for external data:
|
|
29
29
|
|
|
30
30
|
```typescript lineNumbers
|
|
31
31
|
import { createHook } from "workflow";
|
|
@@ -59,7 +59,7 @@ We recommend using the `using` keyword which implements the [TC39 Explicit Resou
|
|
|
59
59
|
See the full API reference for [`createHook()`](/docs/api-reference/workflow/create-hook) for all available options.
|
|
60
60
|
</Callout>
|
|
61
61
|
|
|
62
|
-
### Resuming a
|
|
62
|
+
### Resuming a hook
|
|
63
63
|
|
|
64
64
|
To send data to a waiting workflow, use [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) from an API route, server action, or any other external context:
|
|
65
65
|
|
|
@@ -85,9 +85,38 @@ 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
|
-
###
|
|
88
|
+
### Checking for token conflicts
|
|
89
89
|
|
|
90
|
-
|
|
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 (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 [Run idempotency](/docs/foundations/idempotency#run-idempotency) for these strategies.
|
|
116
|
+
|
|
117
|
+
### Custom tokens for deterministic hooks
|
|
118
|
+
|
|
119
|
+
By default, hooks generate their own 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.
|
|
91
120
|
|
|
92
121
|
For example, imagine a Slack bot where each channel should have its own workflow instance:
|
|
93
122
|
|
|
@@ -139,9 +168,9 @@ export async function POST(request: Request) {
|
|
|
139
168
|
}
|
|
140
169
|
```
|
|
141
170
|
|
|
142
|
-
### Receiving
|
|
171
|
+
### Receiving multiple events
|
|
143
172
|
|
|
144
|
-
Hooks are _reusable_
|
|
173
|
+
Hooks are _reusable_. They implement `AsyncIterable`, which means you can use `for await...of` to receive multiple events over time:
|
|
145
174
|
|
|
146
175
|
```typescript lineNumbers
|
|
147
176
|
import { createHook } from "workflow";
|
|
@@ -169,7 +198,7 @@ export async function dataCollectionWorkflow() {
|
|
|
169
198
|
|
|
170
199
|
Each time you call `resumeHook()` with the same token, the loop receives another value.
|
|
171
200
|
|
|
172
|
-
### Disposing
|
|
201
|
+
### Disposing hooks early
|
|
173
202
|
|
|
174
203
|
When a workflow ends, hooks are automatically disposed. However, you may want to release a hook token early so another workflow can use it while your workflow continues running. Use a block scope with `using` to control when disposal happens:
|
|
175
204
|
|
|
@@ -211,9 +240,9 @@ hook.dispose(); // Manually release the token
|
|
|
211
240
|
After disposal, the hook will no longer receive events and the async iterator will stop yielding values.
|
|
212
241
|
</Callout>
|
|
213
242
|
|
|
214
|
-
## Understanding
|
|
243
|
+
## Understanding webhooks
|
|
215
244
|
|
|
216
|
-
|
|
245
|
+
Hooks require you to manually handle HTTP requests and route them to workflows. **Webhooks** provide a higher-level abstraction built on top of hooks that:
|
|
217
246
|
|
|
218
247
|
1. Automatically serializes the entire HTTP [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object
|
|
219
248
|
2. Provides an automatically addressable `url` property pointing to the generated webhook endpoint
|
|
@@ -222,16 +251,16 @@ While hooks are powerful, they require you to manually handle HTTP requests and
|
|
|
222
251
|
When using Workflow SDK, webhooks are automatically wired up at `/.well-known/workflow/v1/webhook/:token` without any additional setup.
|
|
223
252
|
|
|
224
253
|
<Callout type="warn">
|
|
225
|
-
`createWebhook()` exposes a public route at `/.well-known/workflow/v1/webhook/:token`, and the token in that URL is the only authorization performed for incoming requests. This is convenient for prototypes
|
|
254
|
+
`createWebhook()` exposes a public route at `/.well-known/workflow/v1/webhook/:token`, and the token in that URL is the only authorization performed for incoming requests. This is convenient for prototypes because you can share the webhook URL (endpoint) without creating another route, but if you need stronger security, prefer [`createHook()`](/docs/api-reference/workflow/create-hook) behind your own route and authorize the request before calling [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) to avoid unauthenticated workflow resumptions.
|
|
226
255
|
</Callout>
|
|
227
256
|
|
|
228
257
|
<Callout type="info">
|
|
229
258
|
See the full API reference for [`createWebhook()`](/docs/api-reference/workflow/create-webhook) for all available options.
|
|
230
259
|
</Callout>
|
|
231
260
|
|
|
232
|
-
### Creating
|
|
261
|
+
### Creating your first webhook
|
|
233
262
|
|
|
234
|
-
Here's a
|
|
263
|
+
Here's a webhook that receives HTTP requests. Like hooks, webhooks support the `using` keyword for automatic cleanup:
|
|
235
264
|
|
|
236
265
|
```typescript lineNumbers
|
|
237
266
|
import { createWebhook } from "workflow";
|
|
@@ -255,13 +284,13 @@ export async function webhookWorkflow() {
|
|
|
255
284
|
}
|
|
256
285
|
```
|
|
257
286
|
|
|
258
|
-
The webhook will automatically respond with a `202 Accepted` status by default. External systems can
|
|
287
|
+
The webhook will automatically respond with a `202 Accepted` status by default. External systems can make an HTTP request to the `webhook.url` to resume your workflow.
|
|
259
288
|
|
|
260
|
-
### Sending
|
|
289
|
+
### Sending custom responses
|
|
261
290
|
|
|
262
291
|
Webhooks provide two ways to send custom HTTP responses: **static responses** and **dynamic responses**.
|
|
263
292
|
|
|
264
|
-
#### Static
|
|
293
|
+
#### Static responses
|
|
265
294
|
|
|
266
295
|
Use the `respondWith` option to provide a static response that will be sent automatically for every request:
|
|
267
296
|
|
|
@@ -290,7 +319,7 @@ async function processData(data: any) {
|
|
|
290
319
|
}
|
|
291
320
|
```
|
|
292
321
|
|
|
293
|
-
#### Dynamic
|
|
322
|
+
#### Dynamic responses (manual mode)
|
|
294
323
|
|
|
295
324
|
For dynamic responses based on the request content, set `respondWith: "manual"` and call the `respondWith()` method on the request:
|
|
296
325
|
|
|
@@ -336,7 +365,7 @@ export async function webhookWithDynamicResponse() {
|
|
|
336
365
|
When using `respondWith: "manual"`, the `respondWith()` method **must** be called from within a step function due to serialization requirements. This requirement may be removed in the future.
|
|
337
366
|
</Callout>
|
|
338
367
|
|
|
339
|
-
### Handling
|
|
368
|
+
### Handling multiple webhook requests
|
|
340
369
|
|
|
341
370
|
Like hooks, webhooks support iteration:
|
|
342
371
|
|
|
@@ -376,7 +405,7 @@ export async function eventCollectorWorkflow() {
|
|
|
376
405
|
}
|
|
377
406
|
```
|
|
378
407
|
|
|
379
|
-
## Hooks vs.
|
|
408
|
+
## Hooks vs. webhooks: when to use each
|
|
380
409
|
|
|
381
410
|
| Feature | Hooks | Webhooks |
|
|
382
411
|
|---------|-------|----------|
|
|
@@ -386,19 +415,19 @@ export async function eventCollectorWorkflow() {
|
|
|
386
415
|
| **Use Case** | Custom integrations, type-safe payloads | HTTP webhooks, standard REST APIs |
|
|
387
416
|
| **Resuming** | [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) | Automatic via HTTP, or [`resumeWebhook()`](/docs/api-reference/workflow-api/resume-webhook) |
|
|
388
417
|
|
|
389
|
-
**Use
|
|
418
|
+
**Use hooks when:**
|
|
390
419
|
- You need full control over the payload structure
|
|
391
420
|
- You're integrating with custom event sources
|
|
392
421
|
- You want strong TypeScript typing with [`defineHook()`](/docs/api-reference/workflow/define-hook)
|
|
393
422
|
|
|
394
|
-
**Use
|
|
423
|
+
**Use webhooks when:**
|
|
395
424
|
- You're receiving HTTP requests from external services
|
|
396
425
|
- You need to send HTTP responses back to the caller
|
|
397
426
|
- You want automatic URL routing without writing API handlers
|
|
398
427
|
|
|
399
|
-
## Advanced
|
|
428
|
+
## Advanced patterns
|
|
400
429
|
|
|
401
|
-
### Type-
|
|
430
|
+
### Type-safe hooks with `defineHook()`
|
|
402
431
|
|
|
403
432
|
The [`defineHook()`](/docs/api-reference/workflow/define-hook) helper provides type safety and runtime validation between creating and resuming hooks using [Standard Schema v1](https://standardschema.dev). Use any compliant validator like Zod or Valibot:
|
|
404
433
|
|
|
@@ -447,25 +476,25 @@ export async function POST(request: Request) {
|
|
|
447
476
|
|
|
448
477
|
This pattern is especially valuable in larger applications where the workflow and API code are in separate files, providing both compile-time type safety and runtime validation.
|
|
449
478
|
|
|
450
|
-
## Best
|
|
479
|
+
## Best practices
|
|
451
480
|
|
|
452
|
-
### Token
|
|
481
|
+
### Token design
|
|
453
482
|
|
|
454
|
-
Custom tokens are available for `createHook()` with server-side `resumeHook()` only. Webhooks (`createWebhook()`) always
|
|
483
|
+
Custom tokens are available for `createHook()` with server-side `resumeHook()` only. Webhooks (`createWebhook()`) always generate their own unique tokens. A generated token is not trivial to guess, but it is not a strong security contract either, so anyone who obtains the URL can invoke an unintended webhook resumption. To prevent unauthenticated run resumptions entirely, prefer a **hook** over the **webhook** convenience and implement your own authentication on the route that calls `resumeHook()`.
|
|
455
484
|
|
|
456
485
|
When using custom tokens with `createHook()`:
|
|
457
486
|
|
|
458
|
-
- **Make them deterministic**: Base them on data the external system can reconstruct
|
|
459
|
-
- **Use namespacing**: Prefix tokens to avoid conflicts
|
|
460
|
-
- **Include routing information**: Ensure the token contains enough information to identify the correct workflow instance
|
|
487
|
+
- **Make them deterministic**: Base them on data the external system can reconstruct, such as channel IDs or user IDs.
|
|
488
|
+
- **Use namespacing**: Prefix tokens to avoid conflicts, such as `slack:${channelId}` or `github:${repoId}`.
|
|
489
|
+
- **Include routing information**: Ensure the token contains enough information to identify the correct workflow instance.
|
|
461
490
|
|
|
462
|
-
### Response
|
|
491
|
+
### Response handling in webhooks
|
|
463
492
|
|
|
464
|
-
- Use **static responses** (`respondWith: Response`) for
|
|
493
|
+
- Use **static responses** (`respondWith: Response`) for acknowledgments
|
|
465
494
|
- Use **manual mode** (`respondWith: "manual"`) when responses depend on request processing
|
|
466
495
|
- Remember that `respondWith()` must be called from within a step function
|
|
467
496
|
|
|
468
|
-
### Iterating
|
|
497
|
+
### Iterating over events
|
|
469
498
|
|
|
470
499
|
Both hooks and webhooks support iteration, making them perfect for long-running event loops:
|
|
471
500
|
|
|
@@ -484,7 +513,7 @@ for await (const event of hook) {
|
|
|
484
513
|
|
|
485
514
|
This pattern allows a single workflow instance to handle multiple events over time, maintaining state between events.
|
|
486
515
|
|
|
487
|
-
## Related
|
|
516
|
+
## Related documentation
|
|
488
517
|
|
|
489
518
|
- [Serialization](/docs/foundations/serialization) - Understanding what data can be passed through hooks
|
|
490
519
|
- [`createHook()` API Reference](/docs/api-reference/workflow/create-hook)
|