Skip to content

Commit 6beb891

Browse files
Merge branch 'main' into peter/web-animation-fast
2 parents b33972a + 6373ab5 commit 6beb891

File tree

7 files changed

+116
-98
lines changed

7 files changed

+116
-98
lines changed

.changeset/light-planes-grin.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@workflow/core": patch
3+
---
4+
5+
BREAKING: `resumeHook()` now throws errors (including when a Hook is not found for a given "token") instead of returning `null`

docs/content/docs/api-reference/workflow-api/resume-hook.mdx

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ import { resumeHook } from "workflow/api";
1919

2020
export async function POST(request: Request) {
2121
const { token, data } = await request.json();
22-
const result = await resumeHook(token, data); // [!code highlight]
2322

24-
if (!result) {
23+
try {
24+
const result = await resumeHook(token, data); // [!code highlight]
25+
return Response.json({
26+
runId: result.runId
27+
});
28+
} catch (error) {
2529
return new Response('Hook not found', { status: 404 });
2630
}
27-
28-
return Response.json({
29-
runId: result.runId
30-
});
3131
}
3232
```
3333

@@ -46,7 +46,7 @@ showSections={['parameters']}
4646

4747
### Returns
4848

49-
Returns a `Promise<Hook | null>` that resolves to:
49+
Returns a `Promise<Hook>` that resolves to:
5050

5151
<TSDoc
5252
definition={generateDefinition({
@@ -69,16 +69,16 @@ import { resumeHook } from "workflow/api";
6969
export async function POST(request: Request) {
7070
const { token, data } = await request.json();
7171

72-
const result = await resumeHook(token, data); // [!code highlight]
72+
try {
73+
const result = await resumeHook(token, data); // [!code highlight]
7374

74-
if (!result) {
75+
return Response.json({
76+
success: true,
77+
runId: result.runId
78+
});
79+
} catch (error) {
7580
return new Response('Hook not found', { status: 404 });
7681
}
77-
78-
return Response.json({
79-
success: true,
80-
runId: result.runId
81-
});
8282
}
8383
```
8484

@@ -97,16 +97,16 @@ type ApprovalPayload = {
9797
export async function POST(request: Request) {
9898
const { token, approved, comment } = await request.json();
9999

100-
const result = await resumeHook<ApprovalPayload>(token, { // [!code highlight]
101-
approved, // [!code highlight]
102-
comment, // [!code highlight]
103-
}); // [!code highlight]
100+
try {
101+
const result = await resumeHook<ApprovalPayload>(token, { // [!code highlight]
102+
approved, // [!code highlight]
103+
comment, // [!code highlight]
104+
}); // [!code highlight]
104105

105-
if (!result) {
106+
return Response.json({ runId: result.runId });
107+
} catch (error) {
106108
return Response.json({ error: 'Invalid token' }, { status: 404 });
107109
}
108-
109-
return Response.json({ runId: result.runId });
110110
}
111111
```
112112

@@ -120,13 +120,12 @@ Using `resumeHook` in Next.js server actions to resume a hook:
120120
import { resumeHook } from "workflow/api";
121121

122122
export async function approveRequest(token: string, approved: boolean) {
123-
const result = await resumeHook(token, { approved });
124-
125-
if (!result) {
123+
try {
124+
const result = await resumeHook(token, { approved });
125+
return result.runId;
126+
} catch (error) {
126127
throw new Error('Invalid approval token');
127128
}
128-
129-
return result.runId;
130129
}
131130
```
132131

@@ -146,14 +145,14 @@ export async function POST(request: Request) {
146145
return Response.json({ error: 'Missing token' }, { status: 400 });
147146
}
148147

149-
const body = await request.json();
150-
const result = await resumeHook(token, body);
148+
try {
149+
const body = await request.json();
150+
const result = await resumeHook(token, body);
151151

152-
if (!result) {
152+
return Response.json({ success: true, runId: result.runId });
153+
} catch (error) {
153154
return Response.json({ error: 'Hook not found' }, { status: 404 });
154155
}
155-
156-
return Response.json({ success: true, runId: result.runId });
157156
}
158157
```
159158

docs/content/docs/api-reference/workflow-api/resume-webhook.mdx

Lines changed: 33 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,12 @@ export async function POST(request: Request) {
2525
return new Response('Missing token', { status: 400 });
2626
}
2727

28-
const response = await resumeWebhook(token, request); // [!code highlight]
29-
30-
if (!response) {
28+
try {
29+
const response = await resumeWebhook(token, request); // [!code highlight]
30+
return response;
31+
} catch (error) {
3132
return new Response('Webhook not found', { status: 404 });
3233
}
33-
34-
return response;
3534
}
3635
```
3736

@@ -50,10 +49,11 @@ showSections={['parameters']}
5049

5150
### Returns
5251

53-
Returns a `Promise<Response | null>` that resolves to:
52+
Returns a `Promise<Response>` that resolves to:
5453

5554
- `Response`: The HTTP response from the workflow's `respondWith()` call
56-
- `null`: If the webhook token is not found or invalid
55+
56+
Throws an error if the webhook token is not found or invalid.
5757

5858
## Examples
5959

@@ -72,13 +72,12 @@ export async function POST(request: Request) {
7272
return new Response('Token required', { status: 400 });
7373
}
7474

75-
const response = await resumeWebhook(token, request); // [!code highlight]
76-
77-
if (!response) {
75+
try {
76+
const response = await resumeWebhook(token, request); // [!code highlight]
77+
return response; // Returns the workflow's custom response
78+
} catch (error) {
7879
return new Response('Webhook not found', { status: 404 });
7980
}
80-
81-
return response; // Returns the workflow's custom response
8281
}
8382
```
8483

@@ -106,13 +105,12 @@ export async function POST(request: Request) {
106105
// Construct deterministic token
107106
const token = `github_webhook:${repo}`;
108107

109-
const response = await resumeWebhook(token, request); // [!code highlight]
110-
111-
if (!response) {
108+
try {
109+
const response = await resumeWebhook(token, request); // [!code highlight]
110+
return response;
111+
} catch (error) {
112112
return new Response('Workflow not found', { status: 404 });
113113
}
114-
115-
return response;
116114
}
117115
```
118116

@@ -137,9 +135,10 @@ export async function POST(request: Request) {
137135
// Construct token from channel ID
138136
const token = `slack_command:${channelId}`;
139137

140-
const response = await resumeWebhook(token, request); // [!code highlight]
141-
142-
if (!response) {
138+
try {
139+
const response = await resumeWebhook(token, request); // [!code highlight]
140+
return response;
141+
} catch (error) {
143142
// If no workflow is listening, return a default response
144143
return new Response(
145144
JSON.stringify({
@@ -151,8 +150,6 @@ export async function POST(request: Request) {
151150
}
152151
);
153152
}
154-
155-
return response;
156153
}
157154
```
158155

@@ -185,13 +182,12 @@ export async function POST(request: Request) {
185182
// Construct namespaced token
186183
const token = `tenant:${tenantId}:webhook:${webhookId}`;
187184

188-
const response = await resumeWebhook(token, request); // [!code highlight]
189-
190-
if (!response) {
185+
try {
186+
const response = await resumeWebhook(token, request); // [!code highlight]
187+
return response;
188+
} catch (error) {
191189
return new Response('Webhook not found or expired', { status: 404 });
192190
}
193-
194-
return response;
195191
}
196192

197193
async function verifyTenantApiKey(tenantId: string, apiKey: string | null) {
@@ -222,19 +218,19 @@ export async function triggerWebhook(
222218
body: JSON.stringify(payload),
223219
});
224220

225-
const response = await resumeWebhook(token, request);
221+
try {
222+
const response = await resumeWebhook(token, request);
226223

227-
if (!response) {
228-
throw new Error('Webhook not found');
229-
}
224+
// Parse and return the response
225+
const contentType = response.headers.get('content-type');
226+
if (contentType?.includes('application/json')) {
227+
return await response.json();
228+
}
230229

231-
// Parse and return the response
232-
const contentType = response.headers.get('content-type');
233-
if (contentType?.includes('application/json')) {
234-
return await response.json();
230+
return await response.text();
231+
} catch (error) {
232+
throw new Error('Webhook not found');
235233
}
236-
237-
return await response.text();
238234
}
239235
```
240236

docs/content/docs/foundations/hooks.mdx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,11 @@ import { resumeHook } from "workflow/api";
6262
export async function POST(request: Request) {
6363
const { token, approved, comment } = await request.json();
6464

65-
// Resume the workflow with the approval data
66-
const result = await resumeHook(token, { approved, comment });
67-
68-
if (result) {
65+
try {
66+
// Resume the workflow with the approval data
67+
const result = await resumeHook(token, { approved, comment });
6968
return Response.json({ success: true, runId: result.runId });
70-
} else {
69+
} catch (error) {
7170
return Response.json({ error: "Invalid token" }, { status: 404 });
7271
}
7372
}
@@ -121,10 +120,14 @@ export async function POST(request: Request) {
121120
const slackEvent = await request.json();
122121
const channelId = slackEvent.channel;
123122

124-
// Reconstruct the token using the channel ID
125-
await resumeHook(`slack_messages:${channelId}`, slackEvent);
123+
try {
124+
// Reconstruct the token using the channel ID
125+
await resumeHook(`slack_messages:${channelId}`, slackEvent);
126126

127-
return new Response("OK");
127+
return new Response("OK");
128+
} catch (error) {
129+
return new Response("Hook not found", { status: 404 });
130+
}
128131
}
129132
```
130133

@@ -397,10 +400,13 @@ export async function documentApprovalWorkflow(documentId: string) {
397400
export async function POST(request: Request) {
398401
const { documentId, ...approvalData } = await request.json();
399402

400-
// The schema validates the payload before resuming the workflow
401-
await approvalHook.resume(`approval:${documentId}`, approvalData);
402-
403-
return new Response("OK");
403+
try {
404+
// The schema validates the payload before resuming the workflow
405+
await approvalHook.resume(`approval:${documentId}`, approvalData);
406+
return new Response("OK");
407+
} catch (error) {
408+
return Response.json({ error: "Invalid token or validation failed" }, { status: 400 });
409+
}
404410
}
405411
```
406412

packages/core/src/define-hook.test.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,11 @@ describe('defineHook', () => {
6161
it('passes payload through when no schema is provided', async () => {
6262
const hook = defineHook<{ approved: boolean; comment: string }>();
6363

64-
resumeHookMock.mockResolvedValue(null);
64+
resumeHookMock.mockResolvedValue({
65+
hookId: 'hook-id',
66+
token: 'token',
67+
runId: 'run-id',
68+
});
6569

6670
const payload = { approved: true, comment: 'Looks good' };
6771
await hook.resume('token', payload);
@@ -72,7 +76,11 @@ describe('defineHook', () => {
7276
it('parses payload with schema before resuming', async () => {
7377
const hook = defineHook({ schema: approvalSchema });
7478

75-
resumeHookMock.mockResolvedValue(null);
79+
resumeHookMock.mockResolvedValue({
80+
hookId: 'hook-id',
81+
token: 'token',
82+
runId: 'run-id',
83+
});
7684

7785
await hook.resume('token', { approved: true, comment: ' Ready! ' });
7886

packages/core/src/define-hook.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,10 @@ export function defineHook<TInput, TOutput = TInput>({
6060
*
6161
* @param token - The unique token identifying the hook
6262
* @param payload - The payload to send; if a `schema` is configured it is validated/transformed before resuming
63-
* @returns Promise resolving to the hook entity, or null if the hook doesn't exist
63+
* @returns Promise resolving to the hook entity
64+
* @throws Error if the hook is not found or if there's an error during the process
6465
*/
65-
async resume(token: string, payload: TInput): Promise<HookEntity | null> {
66+
async resume(token: string, payload: TInput): Promise<HookEntity> {
6667
if (!schema?.['~standard']) {
6768
return await resumeHook(token, payload);
6869
}

0 commit comments

Comments
 (0)