@workflow/core 4.0.1-beta.8 → 4.1.0-beta.51
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/builtins.js +1 -1
- package/dist/class-serialization.d.ts +26 -0
- package/dist/class-serialization.d.ts.map +1 -0
- package/dist/class-serialization.js +66 -0
- package/dist/create-hook.js +1 -1
- package/dist/define-hook.d.ts +40 -25
- package/dist/define-hook.d.ts.map +1 -1
- package/dist/define-hook.js +22 -27
- package/dist/events-consumer.d.ts.map +1 -1
- package/dist/events-consumer.js +5 -1
- package/dist/flushable-stream.d.ts +82 -0
- package/dist/flushable-stream.d.ts.map +1 -0
- package/dist/flushable-stream.js +214 -0
- package/dist/global.d.ts +4 -1
- package/dist/global.d.ts.map +1 -1
- package/dist/global.js +21 -9
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/logger.js +1 -1
- package/dist/observability.d.ts +60 -0
- package/dist/observability.d.ts.map +1 -1
- package/dist/observability.js +265 -32
- package/dist/private.d.ts +10 -1
- package/dist/private.d.ts.map +1 -1
- package/dist/private.js +6 -1
- package/dist/runtime/helpers.d.ts +52 -0
- package/dist/runtime/helpers.d.ts.map +1 -0
- package/dist/runtime/helpers.js +264 -0
- package/dist/runtime/resume-hook.d.ts +17 -12
- package/dist/runtime/resume-hook.d.ts.map +1 -1
- package/dist/runtime/resume-hook.js +79 -64
- package/dist/runtime/start.d.ts +14 -0
- package/dist/runtime/start.d.ts.map +1 -1
- package/dist/runtime/start.js +71 -45
- package/dist/runtime/step-handler.d.ts +7 -0
- package/dist/runtime/step-handler.d.ts.map +1 -0
- package/dist/runtime/step-handler.js +337 -0
- package/dist/runtime/suspension-handler.d.ts +25 -0
- package/dist/runtime/suspension-handler.d.ts.map +1 -0
- package/dist/runtime/suspension-handler.js +182 -0
- package/dist/runtime/world.d.ts.map +1 -1
- package/dist/runtime/world.js +20 -21
- package/dist/runtime.d.ts +3 -7
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +103 -410
- package/dist/schemas.d.ts +1 -15
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +2 -15
- package/dist/serialization.d.ts +112 -21
- package/dist/serialization.d.ts.map +1 -1
- package/dist/serialization.js +469 -85
- package/dist/sleep.d.ts +10 -0
- package/dist/sleep.d.ts.map +1 -1
- package/dist/sleep.js +1 -1
- package/dist/source-map.d.ts +10 -0
- package/dist/source-map.d.ts.map +1 -0
- package/dist/source-map.js +56 -0
- package/dist/step/context-storage.d.ts +2 -0
- package/dist/step/context-storage.d.ts.map +1 -1
- package/dist/step/context-storage.js +1 -1
- package/dist/step/get-closure-vars.d.ts +9 -0
- package/dist/step/get-closure-vars.d.ts.map +1 -0
- package/dist/step/get-closure-vars.js +16 -0
- package/dist/step/get-step-metadata.js +1 -1
- package/dist/step/get-workflow-metadata.js +1 -1
- package/dist/{writable-stream.d.ts → step/writable-stream.d.ts} +5 -5
- package/dist/step/writable-stream.d.ts.map +1 -0
- package/dist/step/writable-stream.js +30 -0
- package/dist/step.d.ts +1 -1
- package/dist/step.d.ts.map +1 -1
- package/dist/step.js +93 -47
- package/dist/symbols.d.ts +6 -0
- package/dist/symbols.d.ts.map +1 -1
- package/dist/symbols.js +7 -1
- package/dist/telemetry/semantic-conventions.d.ts +66 -38
- package/dist/telemetry/semantic-conventions.d.ts.map +1 -1
- package/dist/telemetry/semantic-conventions.js +16 -3
- package/dist/telemetry.d.ts +8 -4
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +39 -6
- package/dist/types.js +1 -1
- package/dist/util.d.ts +5 -24
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +19 -38
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/vm/index.js +2 -2
- package/dist/vm/uuid.js +1 -1
- package/dist/workflow/create-hook.js +1 -1
- package/dist/workflow/define-hook.d.ts +3 -3
- package/dist/workflow/define-hook.d.ts.map +1 -1
- package/dist/workflow/define-hook.js +1 -1
- package/dist/workflow/get-workflow-metadata.js +1 -1
- package/dist/workflow/hook.d.ts.map +1 -1
- package/dist/workflow/hook.js +49 -14
- package/dist/workflow/index.d.ts +1 -1
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +2 -2
- package/dist/workflow/sleep.d.ts +1 -1
- package/dist/workflow/sleep.d.ts.map +1 -1
- package/dist/workflow/sleep.js +26 -39
- package/dist/workflow/writable-stream.d.ts +1 -1
- package/dist/workflow/writable-stream.d.ts.map +1 -1
- package/dist/workflow/writable-stream.js +1 -1
- package/dist/workflow.d.ts +1 -1
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +72 -9
- package/docs/api-reference/create-hook.mdx +133 -0
- package/docs/api-reference/create-webhook.mdx +225 -0
- package/docs/api-reference/define-hook.mdx +206 -0
- package/docs/api-reference/fatal-error.mdx +37 -0
- package/docs/api-reference/fetch.mdx +139 -0
- package/docs/api-reference/get-step-metadata.mdx +76 -0
- package/docs/api-reference/get-workflow-metadata.mdx +44 -0
- package/docs/api-reference/get-writable.mdx +292 -0
- package/docs/api-reference/index.mdx +55 -0
- package/docs/api-reference/meta.json +3 -0
- package/docs/api-reference/retryable-error.mdx +106 -0
- package/docs/api-reference/sleep.mdx +59 -0
- package/docs/foundations/common-patterns.mdx +253 -0
- package/docs/foundations/errors-and-retries.mdx +190 -0
- package/docs/foundations/hooks.mdx +455 -0
- package/docs/foundations/idempotency.mdx +55 -0
- package/docs/foundations/index.mdx +32 -0
- package/docs/foundations/meta.json +14 -0
- package/docs/foundations/serialization.mdx +157 -0
- package/docs/foundations/starting-workflows.mdx +211 -0
- package/docs/foundations/streaming.mdx +569 -0
- package/docs/foundations/workflows-and-steps.mdx +197 -0
- package/docs/how-it-works/code-transform.mdx +334 -0
- package/docs/how-it-works/event-sourcing.mdx +254 -0
- package/docs/how-it-works/framework-integrations.mdx +437 -0
- package/docs/how-it-works/meta.json +10 -0
- package/docs/how-it-works/understanding-directives.mdx +611 -0
- package/package.json +31 -25
- package/dist/builtins.js.map +0 -1
- package/dist/create-hook.js.map +0 -1
- package/dist/define-hook.js.map +0 -1
- package/dist/events-consumer.js.map +0 -1
- package/dist/global.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/logger.js.map +0 -1
- package/dist/observability.js.map +0 -1
- package/dist/parse-name.d.ts +0 -25
- package/dist/parse-name.d.ts.map +0 -1
- package/dist/parse-name.js +0 -40
- package/dist/parse-name.js.map +0 -1
- package/dist/private.js.map +0 -1
- package/dist/runtime/resume-hook.js.map +0 -1
- package/dist/runtime/start.js.map +0 -1
- package/dist/runtime/world.js.map +0 -1
- package/dist/runtime.js.map +0 -1
- package/dist/schemas.js.map +0 -1
- package/dist/serialization.js.map +0 -1
- package/dist/sleep.js.map +0 -1
- package/dist/step/context-storage.js.map +0 -1
- package/dist/step/get-step-metadata.js.map +0 -1
- package/dist/step/get-workflow-metadata.js.map +0 -1
- package/dist/step.js.map +0 -1
- package/dist/symbols.js.map +0 -1
- package/dist/telemetry/semantic-conventions.js.map +0 -1
- package/dist/telemetry.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/util.js.map +0 -1
- package/dist/vm/index.js.map +0 -1
- package/dist/vm/uuid.js.map +0 -1
- package/dist/workflow/create-hook.js.map +0 -1
- package/dist/workflow/define-hook.js.map +0 -1
- package/dist/workflow/get-workflow-metadata.js.map +0 -1
- package/dist/workflow/hook.js.map +0 -1
- package/dist/workflow/index.js.map +0 -1
- package/dist/workflow/sleep.js.map +0 -1
- package/dist/workflow/writable-stream.js.map +0 -1
- package/dist/workflow.js.map +0 -1
- package/dist/writable-stream.d.ts.map +0 -1
- package/dist/writable-stream.js +0 -16
- package/dist/writable-stream.js.map +0 -1
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: createWebhook
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Creates a webhook that can be used to suspend and resume a workflow run upon receiving an HTTP request.
|
|
6
|
+
|
|
7
|
+
Webhooks provide a way for external systems to send HTTP requests directly to your workflow. Unlike hooks which accept arbitrary payloads, webhooks work with standard HTTP `Request` objects and can return HTTP `Response` objects.
|
|
8
|
+
|
|
9
|
+
```ts lineNumbers
|
|
10
|
+
import { createWebhook } from "workflow"
|
|
11
|
+
|
|
12
|
+
export async function webhookWorkflow() {
|
|
13
|
+
"use workflow";
|
|
14
|
+
const webhook = createWebhook(); // [!code highlight]
|
|
15
|
+
console.log("Webhook URL:", webhook.url);
|
|
16
|
+
|
|
17
|
+
const request = await webhook; // Suspends until HTTP request received
|
|
18
|
+
console.log("Received request:", request.method, request.url);
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## API Signature
|
|
23
|
+
|
|
24
|
+
### Parameters
|
|
25
|
+
|
|
26
|
+
<TSDoc
|
|
27
|
+
definition={`
|
|
28
|
+
import { createWebhook } from "workflow";
|
|
29
|
+
export default createWebhook;`}
|
|
30
|
+
showSections={['parameters']}
|
|
31
|
+
/>
|
|
32
|
+
|
|
33
|
+
### Returns
|
|
34
|
+
|
|
35
|
+
<TSDoc
|
|
36
|
+
definition={`
|
|
37
|
+
import { createWebhook } from "workflow";
|
|
38
|
+
export default createWebhook;`}
|
|
39
|
+
showSections={['returns']}
|
|
40
|
+
/>
|
|
41
|
+
|
|
42
|
+
The returned `Webhook` object has:
|
|
43
|
+
|
|
44
|
+
- `url`: The HTTP endpoint URL that external systems can call
|
|
45
|
+
- `token`: The unique token identifying this webhook
|
|
46
|
+
- Implements `AsyncIterable<RequestWithResponse>` for handling multiple requests
|
|
47
|
+
|
|
48
|
+
The `RequestWithResponse` type extends the standard `Request` interface with a `respondWith(response: Response)` method for sending custom responses back to the caller.
|
|
49
|
+
|
|
50
|
+
## Examples
|
|
51
|
+
|
|
52
|
+
### Basic Usage
|
|
53
|
+
|
|
54
|
+
Create a webhook that receives HTTP requests and logs the request details:
|
|
55
|
+
|
|
56
|
+
```typescript lineNumbers
|
|
57
|
+
import { createWebhook } from "workflow"
|
|
58
|
+
|
|
59
|
+
export async function basicWebhookWorkflow() {
|
|
60
|
+
"use workflow";
|
|
61
|
+
|
|
62
|
+
const webhook = createWebhook(); // [!code highlight]
|
|
63
|
+
console.log("Send requests to:", webhook.url);
|
|
64
|
+
|
|
65
|
+
const request = await webhook;
|
|
66
|
+
|
|
67
|
+
console.log("Method:", request.method);
|
|
68
|
+
console.log("Headers:", Object.fromEntries(request.headers));
|
|
69
|
+
|
|
70
|
+
const body = await request.text();
|
|
71
|
+
console.log("Body:", body);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Responding to Webhook Requests
|
|
76
|
+
|
|
77
|
+
Use the `respondWith()` method to send custom HTTP responses. Note that `respondWith()` must be called from within a step function:
|
|
78
|
+
|
|
79
|
+
```typescript lineNumbers
|
|
80
|
+
import { createWebhook, type RequestWithResponse } from "workflow"
|
|
81
|
+
|
|
82
|
+
async function sendResponse(request: RequestWithResponse) { // [!code highlight]
|
|
83
|
+
"use step"; // [!code highlight]
|
|
84
|
+
await request.respondWith( // [!code highlight]
|
|
85
|
+
new Response(JSON.stringify({ success: true, message: "Received!" }), { // [!code highlight]
|
|
86
|
+
status: 200, // [!code highlight]
|
|
87
|
+
headers: { "Content-Type": "application/json" } // [!code highlight]
|
|
88
|
+
}) // [!code highlight]
|
|
89
|
+
); // [!code highlight]
|
|
90
|
+
} // [!code highlight]
|
|
91
|
+
|
|
92
|
+
export async function respondingWebhookWorkflow() {
|
|
93
|
+
"use workflow";
|
|
94
|
+
|
|
95
|
+
const webhook = createWebhook();
|
|
96
|
+
console.log("Webhook URL:", webhook.url);
|
|
97
|
+
|
|
98
|
+
const request = await webhook;
|
|
99
|
+
|
|
100
|
+
// Send a custom response back to the caller
|
|
101
|
+
await sendResponse(request);
|
|
102
|
+
|
|
103
|
+
// Continue workflow processing
|
|
104
|
+
const data = await request.json();
|
|
105
|
+
await processData(data);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function processData(data: any) {
|
|
109
|
+
"use step";
|
|
110
|
+
// Process the webhook data
|
|
111
|
+
console.log("Processing:", data);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Customizing Tokens
|
|
116
|
+
|
|
117
|
+
Tokens are used to identify a specific webhook. You can customize the token to be more specific to a use case.
|
|
118
|
+
|
|
119
|
+
```typescript lineNumbers
|
|
120
|
+
import { type RequestWithResponse } from "workflow"
|
|
121
|
+
|
|
122
|
+
async function sendAck(request: RequestWithResponse) {
|
|
123
|
+
"use step";
|
|
124
|
+
await request.respondWith(
|
|
125
|
+
new Response(JSON.stringify({ received: true }), {
|
|
126
|
+
headers: { "Content-Type": "application/json" }
|
|
127
|
+
})
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function githubWebhookWorkflow(repoName: string) {
|
|
132
|
+
"use workflow";
|
|
133
|
+
|
|
134
|
+
// Use a deterministic token based on the repository
|
|
135
|
+
const webhook = createWebhook({ // [!code highlight]
|
|
136
|
+
token: `github_webhook:${repoName}`, // [!code highlight]
|
|
137
|
+
}); // [!code highlight]
|
|
138
|
+
|
|
139
|
+
console.log("Configure GitHub webhook:", webhook.url);
|
|
140
|
+
|
|
141
|
+
const request = await webhook;
|
|
142
|
+
const event = await request.json();
|
|
143
|
+
|
|
144
|
+
await sendAck(request);
|
|
145
|
+
|
|
146
|
+
await deployCommit(event);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function deployCommit(event: any) {
|
|
150
|
+
"use step";
|
|
151
|
+
// Deploy logic here
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Waiting for Multiple Requests
|
|
156
|
+
|
|
157
|
+
You can also wait for multiple requests by using the `for await...of` syntax.
|
|
158
|
+
|
|
159
|
+
```typescript lineNumbers
|
|
160
|
+
import { createWebhook, type RequestWithResponse } from "workflow"
|
|
161
|
+
|
|
162
|
+
async function sendSlackResponse(request: RequestWithResponse, message: string) {
|
|
163
|
+
"use step";
|
|
164
|
+
await request.respondWith(
|
|
165
|
+
new Response(
|
|
166
|
+
JSON.stringify({
|
|
167
|
+
response_type: "in_channel",
|
|
168
|
+
text: message
|
|
169
|
+
}),
|
|
170
|
+
{ headers: { "Content-Type": "application/json" } }
|
|
171
|
+
)
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function sendStopResponse(request: RequestWithResponse) {
|
|
176
|
+
"use step";
|
|
177
|
+
await request.respondWith(
|
|
178
|
+
new Response("Stopping workflow...")
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export async function slackCommandWorkflow(channelId: string) {
|
|
183
|
+
"use workflow";
|
|
184
|
+
|
|
185
|
+
const webhook = createWebhook({
|
|
186
|
+
token: `slack_command:${channelId}`,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
for await (const request of webhook) { // [!code highlight]
|
|
190
|
+
const formData = await request.formData();
|
|
191
|
+
const command = formData.get("command");
|
|
192
|
+
const text = formData.get("text");
|
|
193
|
+
|
|
194
|
+
if (command === "/status") {
|
|
195
|
+
// Respond immediately to Slack
|
|
196
|
+
await sendSlackResponse(request, "Checking status...");
|
|
197
|
+
|
|
198
|
+
// Process the command
|
|
199
|
+
const status = await checkSystemStatus();
|
|
200
|
+
await postToSlack(channelId, `Status: ${status}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (text === "stop") {
|
|
204
|
+
await sendStopResponse(request);
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function checkSystemStatus() {
|
|
211
|
+
"use step";
|
|
212
|
+
return "All systems operational";
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function postToSlack(channelId: string, message: string) {
|
|
216
|
+
"use step";
|
|
217
|
+
// Post message to Slack
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Related Functions
|
|
222
|
+
|
|
223
|
+
- [`createHook()`](/docs/api-reference/workflow/create-hook) - Lower-level hook primitive for arbitrary payloads
|
|
224
|
+
- [`defineHook()`](/docs/api-reference/workflow/define-hook) - Type-safe hook helper
|
|
225
|
+
- [`resumeWebhook()`](/docs/api-reference/workflow-api/resume-webhook) - Resume a webhook from an API route
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: defineHook
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Creates a type-safe hook helper that ensures the payload type is consistent between hook creation and resumption.
|
|
6
|
+
|
|
7
|
+
This is a lightweight wrapper around [`createHook()`](/docs/api-reference/workflow/create-hook) and [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) to avoid type mismatches. It also supports optional runtime validation and transformation of payloads using any [Standard Schema v1](https://standardschema.dev) compliant validator like Zod or Valibot.
|
|
8
|
+
|
|
9
|
+
<Callout>
|
|
10
|
+
We recommend using `defineHook()` over `createHook()` in production codebases for better type safety and optional runtime validation.
|
|
11
|
+
</Callout>
|
|
12
|
+
|
|
13
|
+
```ts lineNumbers
|
|
14
|
+
import { defineHook } from "workflow";
|
|
15
|
+
|
|
16
|
+
const nameHook = defineHook<{
|
|
17
|
+
name: string;
|
|
18
|
+
}>();
|
|
19
|
+
|
|
20
|
+
export async function nameWorkflow() {
|
|
21
|
+
"use workflow";
|
|
22
|
+
|
|
23
|
+
const hook = nameHook.create(); // [!code highlight]
|
|
24
|
+
const result = await hook; // Fully typed as { name: string }
|
|
25
|
+
console.log("Name:", result.name);
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## API Signature
|
|
30
|
+
|
|
31
|
+
### Parameters
|
|
32
|
+
|
|
33
|
+
<TSDoc
|
|
34
|
+
definition={`
|
|
35
|
+
import { defineHook } from "workflow";
|
|
36
|
+
export default defineHook;`}
|
|
37
|
+
showSections={['parameters']}
|
|
38
|
+
/>
|
|
39
|
+
|
|
40
|
+
### Returns
|
|
41
|
+
|
|
42
|
+
<TSDoc
|
|
43
|
+
definition={`
|
|
44
|
+
interface DefineHook<T> {
|
|
45
|
+
/**
|
|
46
|
+
|
|
47
|
+
* Creates a new hook with the defined payload type.
|
|
48
|
+
*/
|
|
49
|
+
create: (options?: HookOptions) => Hook<T>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
|
|
53
|
+
* Resumes a hook by sending a payload with the defined type.
|
|
54
|
+
*/
|
|
55
|
+
resume: (token: string, payload: T) => Promise<HookEntity | null>;
|
|
56
|
+
}
|
|
57
|
+
export default DefineHook;`}
|
|
58
|
+
/>
|
|
59
|
+
|
|
60
|
+
## Examples
|
|
61
|
+
|
|
62
|
+
### Basic Type-Safe Hook Definition
|
|
63
|
+
|
|
64
|
+
By defining the hook once with a specific payload type, you can reuse it in multiple workflows and API routes with automatic type safety.
|
|
65
|
+
|
|
66
|
+
```typescript lineNumbers
|
|
67
|
+
import { defineHook } from "workflow";
|
|
68
|
+
|
|
69
|
+
// Define once with a specific payload type
|
|
70
|
+
const approvalHook = defineHook<{ // [!code highlight]
|
|
71
|
+
approved: boolean; // [!code highlight]
|
|
72
|
+
comment: string; // [!code highlight]
|
|
73
|
+
}>(); // [!code highlight]
|
|
74
|
+
|
|
75
|
+
// In your workflow
|
|
76
|
+
export async function workflowWithApproval() {
|
|
77
|
+
"use workflow";
|
|
78
|
+
|
|
79
|
+
const hook = approvalHook.create();
|
|
80
|
+
const result = await hook; // Fully typed as { approved: boolean; comment: string }
|
|
81
|
+
|
|
82
|
+
console.log("Approved:", result.approved);
|
|
83
|
+
console.log("Comment:", result.comment);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Resuming with Type Safety
|
|
88
|
+
|
|
89
|
+
Hooks can be resumed using the same defined hook and a token. By using the same hook, you can ensure that the payload matches the defined type when resuming a hook.
|
|
90
|
+
|
|
91
|
+
```typescript lineNumbers
|
|
92
|
+
// Use the same defined hook to resume
|
|
93
|
+
export async function POST(request: Request) {
|
|
94
|
+
const { token, approved, comment } = await request.json();
|
|
95
|
+
|
|
96
|
+
// Type-safe resumption - TypeScript ensures the payload matches
|
|
97
|
+
const result = await approvalHook.resume(token, { // [!code highlight]
|
|
98
|
+
approved, // [!code highlight]
|
|
99
|
+
comment, // [!code highlight]
|
|
100
|
+
}); // [!code highlight]
|
|
101
|
+
|
|
102
|
+
if (!result) {
|
|
103
|
+
return Response.json({ error: "Hook not found" }, { status: 404 });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return Response.json({ success: true, runId: result.runId });
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Validate and Transform with Schema
|
|
111
|
+
|
|
112
|
+
You can provide runtime validation and transformation of hook payloads using the `schema` option. This option accepts any validator that conforms to the [Standard Schema v1](https://standardschema.dev) specification.
|
|
113
|
+
|
|
114
|
+
<Callout type="info">
|
|
115
|
+
Standard Schema is a standardized specification for schema validation libraries. Most popular validation libraries support it, including Zod, Valibot, ArkType, and Effect Schema. You can also write custom validators.
|
|
116
|
+
</Callout>
|
|
117
|
+
|
|
118
|
+
#### Using Zod with defineHook
|
|
119
|
+
|
|
120
|
+
Here's an example using [Zod](https://zod.dev) to validate and transform hook payloads:
|
|
121
|
+
|
|
122
|
+
```typescript lineNumbers
|
|
123
|
+
import { defineHook } from "workflow";
|
|
124
|
+
import { z } from "zod";
|
|
125
|
+
|
|
126
|
+
export const approvalHook = defineHook({
|
|
127
|
+
schema: z.object({ // [!code highlight]
|
|
128
|
+
approved: z.boolean(), // [!code highlight]
|
|
129
|
+
comment: z.string().min(1).transform((value) => value.trim()), // [!code highlight]
|
|
130
|
+
}), // [!code highlight]
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
export async function approvalWorkflow(approvalId: string) {
|
|
134
|
+
"use workflow";
|
|
135
|
+
|
|
136
|
+
const hook = approvalHook.create({
|
|
137
|
+
token: `approval:${approvalId}`,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Payload is automatically typed based on the schema
|
|
141
|
+
const { approved, comment } = await hook;
|
|
142
|
+
console.log("Approved:", approved);
|
|
143
|
+
console.log("Comment (trimmed):", comment);
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
When resuming the hook from an API route, the schema validates and transforms the incoming payload before the workflow resumes:
|
|
148
|
+
|
|
149
|
+
```typescript lineNumbers
|
|
150
|
+
export async function POST(request: Request) {
|
|
151
|
+
// Incoming payload: { token: "...", approved: true, comment: " Ready! " }
|
|
152
|
+
const { token, approved, comment } = await request.json();
|
|
153
|
+
|
|
154
|
+
// The schema validates and transforms the payload:
|
|
155
|
+
// - Checks that `approved` is a boolean
|
|
156
|
+
// - Checks that `comment` is a non-empty string
|
|
157
|
+
// - Trims whitespace from the comment
|
|
158
|
+
// If validation fails, an error is thrown and the hook is not resumed
|
|
159
|
+
await approvalHook.resume(token, { // [!code highlight]
|
|
160
|
+
approved, // [!code highlight]
|
|
161
|
+
comment, // Automatically trimmed to "Ready!" // [!code highlight]
|
|
162
|
+
}); // [!code highlight]
|
|
163
|
+
|
|
164
|
+
return Response.json({ success: true });
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### Using Other Standard Schema Libraries
|
|
169
|
+
|
|
170
|
+
The same pattern works with any Standard Schema v1 compliant library. Here's an example with [Valibot](https://valibot.dev):
|
|
171
|
+
|
|
172
|
+
```typescript lineNumbers
|
|
173
|
+
import { defineHook } from "workflow";
|
|
174
|
+
import * as v from "valibot";
|
|
175
|
+
|
|
176
|
+
export const approvalHook = defineHook({
|
|
177
|
+
schema: v.object({ // [!code highlight]
|
|
178
|
+
approved: v.boolean(), // [!code highlight]
|
|
179
|
+
comment: v.pipe(v.string(), v.minLength(1), v.trim()), // [!code highlight]
|
|
180
|
+
}), // [!code highlight]
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Customizing Tokens
|
|
185
|
+
|
|
186
|
+
Tokens are used to identify a specific hook and for resuming a hook. You can customize the token to be more specific to a use case.
|
|
187
|
+
|
|
188
|
+
```typescript lineNumbers
|
|
189
|
+
const slackHook = defineHook<{ text: string; userId: string }>();
|
|
190
|
+
|
|
191
|
+
export async function slackBotWorkflow(channelId: string) {
|
|
192
|
+
"use workflow";
|
|
193
|
+
|
|
194
|
+
const hook = slackHook.create({
|
|
195
|
+
token: `slack:${channelId}`, // [!code highlight]
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const message = await hook;
|
|
199
|
+
console.log(`Message from ${message.userId}: ${message.text}`);
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Related Functions
|
|
204
|
+
|
|
205
|
+
* [`createHook()`](/docs/api-reference/workflow/create-hook) - Create a hook in a workflow.
|
|
206
|
+
* [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) - Resume a hook with a payload.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: FatalError
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
When a `FatalError` is thrown in a step, it indicates that the workflow should not retry a step, marking it as failure.
|
|
6
|
+
|
|
7
|
+
You should use this when you don't want a specific step to retry.
|
|
8
|
+
|
|
9
|
+
```typescript lineNumbers
|
|
10
|
+
import { FatalError } from "workflow"
|
|
11
|
+
|
|
12
|
+
async function fallibleWorkflow() {
|
|
13
|
+
"use workflow"
|
|
14
|
+
await fallibleStep();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function fallibleStep() {
|
|
18
|
+
"use step"
|
|
19
|
+
throw new FatalError("Fallible!") // [!code highlight]
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## API Signature
|
|
24
|
+
|
|
25
|
+
### Parameters
|
|
26
|
+
|
|
27
|
+
<TSDoc
|
|
28
|
+
definition={`
|
|
29
|
+
interface Error {
|
|
30
|
+
/**
|
|
31
|
+
|
|
32
|
+
* The error message.
|
|
33
|
+
*/
|
|
34
|
+
message: string;
|
|
35
|
+
}
|
|
36
|
+
export default Error;`}
|
|
37
|
+
/>
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: fetch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Makes HTTP requests from within a workflow. This is a special step function that wraps the standard `fetch` API, automatically handling serialization and providing retry semantics.
|
|
6
|
+
|
|
7
|
+
This is useful when you need to call external APIs or services from within your workflow.
|
|
8
|
+
|
|
9
|
+
<Callout>
|
|
10
|
+
`fetch` is a *special* type of step function provided and should be called directly inside workflow functions.
|
|
11
|
+
</Callout>
|
|
12
|
+
|
|
13
|
+
```typescript lineNumbers
|
|
14
|
+
import { fetch } from "workflow"
|
|
15
|
+
|
|
16
|
+
async function apiWorkflow() {
|
|
17
|
+
"use workflow"
|
|
18
|
+
|
|
19
|
+
// Fetch data from an API
|
|
20
|
+
const response = await fetch("https://api.example.com/data") // [!code highlight]
|
|
21
|
+
return await response.json()
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## API Signature
|
|
26
|
+
|
|
27
|
+
### Parameters
|
|
28
|
+
|
|
29
|
+
Accepts the same arguments as web [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch)
|
|
30
|
+
|
|
31
|
+
<TSDoc
|
|
32
|
+
definition={`
|
|
33
|
+
import { fetch } from "workflow";
|
|
34
|
+
export default fetch;`}
|
|
35
|
+
showSections={['parameters']}
|
|
36
|
+
/>
|
|
37
|
+
|
|
38
|
+
### Returns
|
|
39
|
+
|
|
40
|
+
Returns the same response as web [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch)
|
|
41
|
+
|
|
42
|
+
<TSDoc
|
|
43
|
+
definition={`
|
|
44
|
+
import { fetch } from "workflow";
|
|
45
|
+
export default fetch;`}
|
|
46
|
+
showSections={['returns']}
|
|
47
|
+
/>
|
|
48
|
+
|
|
49
|
+
## Examples
|
|
50
|
+
|
|
51
|
+
### Basic Usage
|
|
52
|
+
|
|
53
|
+
Here's a simple example of how you can use `fetch` inside your workflow.
|
|
54
|
+
|
|
55
|
+
```typescript lineNumbers
|
|
56
|
+
import { fetch } from "workflow"
|
|
57
|
+
|
|
58
|
+
async function apiWorkflow() {
|
|
59
|
+
"use workflow"
|
|
60
|
+
|
|
61
|
+
// Fetch data from an API
|
|
62
|
+
const response = await fetch("https://api.example.com/data") // [!code highlight]
|
|
63
|
+
const data = await response.json()
|
|
64
|
+
|
|
65
|
+
// Make a POST request
|
|
66
|
+
const postResponse = await fetch("https://api.example.com/create", { // [!code highlight]
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: {
|
|
69
|
+
"Content-Type": "application/json"
|
|
70
|
+
},
|
|
71
|
+
body: JSON.stringify({ name: "test" })
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
return data
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
We call `fetch()` with a URL and optional request options, just like the standard fetch API. The workflow runtime automatically handles the response serialization.
|
|
79
|
+
|
|
80
|
+
This API is provided as a convenience to easily use `fetch` in workflow, but often, you might want to extend and implement your own fetch for more powerful error handing and retry logic.
|
|
81
|
+
|
|
82
|
+
### Customizing Fetch Behavior
|
|
83
|
+
|
|
84
|
+
Here's an example of a custom fetch wrapper that provides more sophisticated error handling with custom retry logic:
|
|
85
|
+
|
|
86
|
+
```typescript lineNumbers
|
|
87
|
+
import { FatalError, RetryableError } from "workflow"
|
|
88
|
+
|
|
89
|
+
export async function customFetch(
|
|
90
|
+
url: string,
|
|
91
|
+
init?: RequestInit
|
|
92
|
+
) {
|
|
93
|
+
"use step"
|
|
94
|
+
|
|
95
|
+
const response = await fetch(url, init)
|
|
96
|
+
|
|
97
|
+
// Handle client errors (4xx) - don't retry
|
|
98
|
+
if (response.status >= 400 && response.status < 500) {
|
|
99
|
+
if (response.status === 429) {
|
|
100
|
+
// Rate limited - retry with backoff from Retry-After header
|
|
101
|
+
const retryAfter = response.headers.get("Retry-After")
|
|
102
|
+
|
|
103
|
+
if (retryAfter) {
|
|
104
|
+
// The Retry-After header is either a number (seconds) or an RFC 7231 date string
|
|
105
|
+
const retryAfterValue = /^\d+$/.test(retryAfter)
|
|
106
|
+
? parseInt(retryAfter) * 1000 // Convert seconds to milliseconds
|
|
107
|
+
: new Date(retryAfter); // Parse RFC 7231 date format
|
|
108
|
+
|
|
109
|
+
// Use `RetryableError` to customize the retry
|
|
110
|
+
throw new RetryableError( // [!code highlight]
|
|
111
|
+
`Rate limited by ${url}`, // [!code highlight]
|
|
112
|
+
{ retryAfter: retryAfterValue } // [!code highlight]
|
|
113
|
+
) // [!code highlight]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Other client errors are fatal (400, 401, 403, 404, etc.)
|
|
118
|
+
throw new FatalError( // [!code highlight]
|
|
119
|
+
`Client error ${response.status}: ${response.statusText}` // [!code highlight]
|
|
120
|
+
) // [!code highlight]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Handle server errors (5xx) - will retry automatically
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`Server error ${response.status}: ${response.statusText}`
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return response
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
This example demonstrates:
|
|
135
|
+
|
|
136
|
+
- Setting custom `maxRetries` to 5 retries (6 total attempts including the initial attempt).
|
|
137
|
+
- Throwing [`FatalError`](/docs/api-reference/workflow/fatal-error) for client errors (400-499) to prevent retries.
|
|
138
|
+
- Handling 429 rate limiting by reading the `Retry-After` header and using [`RetryableError`](/docs/api-reference/workflow/retryable-error).
|
|
139
|
+
- Allowing automatic retries for server errors (5xx).
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: getStepMetadata
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Returns metadata available in the current step function.
|
|
6
|
+
|
|
7
|
+
You may want to use this function when you need to:
|
|
8
|
+
|
|
9
|
+
- Track retry attempts in error handling
|
|
10
|
+
- Access timing information of a step and execution metadata
|
|
11
|
+
- Generate idempotency keys for external APIs
|
|
12
|
+
|
|
13
|
+
<Callout type="warn">
|
|
14
|
+
This function can only be called inside a step function.
|
|
15
|
+
</Callout>
|
|
16
|
+
|
|
17
|
+
```typescript lineNumbers
|
|
18
|
+
import { getStepMetadata } from "workflow";
|
|
19
|
+
|
|
20
|
+
async function testWorkflow() {
|
|
21
|
+
"use workflow";
|
|
22
|
+
await logStepId();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function logStepId() {
|
|
26
|
+
"use step";
|
|
27
|
+
const ctx = getStepMetadata(); // [!code highlight]
|
|
28
|
+
console.log(ctx.stepId); // Grab the current step ID
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Example: Use `stepId` as an idempotency key
|
|
33
|
+
|
|
34
|
+
```typescript lineNumbers
|
|
35
|
+
import { getStepMetadata } from "workflow";
|
|
36
|
+
|
|
37
|
+
async function chargeUser(userId: string, amount: number) {
|
|
38
|
+
"use step";
|
|
39
|
+
const { stepId } = getStepMetadata();
|
|
40
|
+
|
|
41
|
+
await stripe.charges.create(
|
|
42
|
+
{
|
|
43
|
+
amount,
|
|
44
|
+
currency: "usd",
|
|
45
|
+
customer: userId,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
idempotencyKey: `charge:${stepId}`, // [!code highlight]
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
<Callout type="info">
|
|
55
|
+
Learn more about patterns and caveats in the{" "}
|
|
56
|
+
<a href="/docs/foundations/idempotency">Idempotency</a> guide.
|
|
57
|
+
</Callout>
|
|
58
|
+
|
|
59
|
+
## API Signature
|
|
60
|
+
|
|
61
|
+
### Parameters
|
|
62
|
+
|
|
63
|
+
<TSDoc
|
|
64
|
+
definition={`
|
|
65
|
+
import { getStepMetadata } from "workflow";
|
|
66
|
+
export default getStepMetadata;`}
|
|
67
|
+
showSections={["parameters"]}
|
|
68
|
+
/>
|
|
69
|
+
|
|
70
|
+
### Returns
|
|
71
|
+
|
|
72
|
+
<TSDoc
|
|
73
|
+
definition={`
|
|
74
|
+
import type { StepMetadata } from "workflow";
|
|
75
|
+
export default StepMetadata;`}
|
|
76
|
+
/>
|