diff --git a/.changeset/young-humans-create.md b/.changeset/young-humans-create.md
new file mode 100644
index 00000000..a845151c
--- /dev/null
+++ b/.changeset/young-humans-create.md
@@ -0,0 +1,2 @@
+---
+---
diff --git a/docs/content/docs/api-reference/workflow/define-hook.mdx b/docs/content/docs/api-reference/workflow/define-hook.mdx
index 56e8a07e..f8e8d873 100644
--- a/docs/content/docs/api-reference/workflow/define-hook.mdx
+++ b/docs/content/docs/api-reference/workflow/define-hook.mdx
@@ -8,10 +8,10 @@ import { generateDefinition } from "@/lib/tsdoc"
Creates a type-safe hook helper that ensures the payload type is consistent between hook creation and resumption.
-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.
+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.
-We recommend using `defineHook()` over `createHook()` in production codebases for better type safety.
+We recommend using `defineHook()` over `createHook()` in production codebases for better type safety and optional runtime validation.
```ts lineNumbers
@@ -67,7 +67,7 @@ export default DefineHook;`
## Examples
-### Type-Safe Hook Definition
+### Basic Type-Safe Hook Definition
By defining the hook once with a specific payload type, you can reuse it in multiple workflows and API routes with automatic type safety.
@@ -117,16 +117,21 @@ export async function POST(request: Request) {
### Validate and Transform with Schema
-The optional `schema` accepts any validator that conforms to [Standard Schema v1](https://standardschema.dev).
+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.
-Zod is shown below as one example, but libraries like Valibot, ArkType, Effect Schema, or your own custom validator work as well.
+
+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.
+
+
+#### Using Zod with defineHook
+
+Here's an example using [Zod](https://zod.dev) to validate and transform hook payloads:
```typescript lineNumbers
import { defineHook } from "workflow";
import { z } from "zod";
export const approvalHook = defineHook({
- // Provide a schema to validate/transform payloads.
schema: z.object({ // [!code highlight]
approved: z.boolean(), // [!code highlight]
comment: z.string().min(1).transform((value) => value.trim()), // [!code highlight]
@@ -140,29 +145,50 @@ export async function approvalWorkflow(approvalId: string) {
token: `approval:${approvalId}`,
});
+ // Payload is automatically typed based on the schema
const { approved, comment } = await hook;
console.log('Approved:', approved);
- console.log('Comment:', comment);
+ console.log('Comment (trimmed):', comment);
}
```
-In your route handler, resume the hook with the same definition; the schema validates and transforms the payload before the workflow continues.
+When resuming the hook from an API route, the schema validates and transforms the incoming payload before the workflow resumes:
```typescript lineNumbers
export async function POST(request: Request) {
- // comment is " Ready! " here
+ // Incoming payload: { token: "...", approved: true, comment: " Ready! " }
const { token, approved, comment } = await request.json();
- // If validation fails, Zod throws and the hook is not resumed.
+ // The schema validates and transforms the payload:
+ // - Checks that `approved` is a boolean
+ // - Checks that `comment` is a non-empty string
+ // - Trims whitespace from the comment
+ // If validation fails, an error is thrown and the hook is not resumed
await approvalHook.resume(token, { // [!code highlight]
approved, // [!code highlight]
- comment, // transformed to "Ready!" [!code highlight]
+ comment, // Automatically trimmed to "Ready!" // [!code highlight]
}); // [!code highlight]
return Response.json({ success: true });
}
```
+#### Using Other Standard Schema Libraries
+
+The same pattern works with any Standard Schema v1 compliant library. Here's an example with [Valibot](https://valibot.dev):
+
+```typescript lineNumbers
+import { defineHook } from "workflow";
+import * as v from "valibot";
+
+export const approvalHook = defineHook({
+ schema: v.object({ // [!code highlight]
+ approved: v.boolean(), // [!code highlight]
+ comment: v.pipe(v.string(), v.minLength(1), v.trim()), // [!code highlight]
+ }), // [!code highlight]
+});
+```
+
### Customizing Tokens
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.
diff --git a/docs/content/docs/foundations/hooks.mdx b/docs/content/docs/foundations/hooks.mdx
index 9479ab7a..a84224bf 100644
--- a/docs/content/docs/foundations/hooks.mdx
+++ b/docs/content/docs/foundations/hooks.mdx
@@ -362,20 +362,21 @@ async function postToSlack(channelId: string, message: string) {
### Type-Safe Hooks with `defineHook()`
-The [`defineHook()`](/docs/api-reference/workflow/define-hook) helper provides type safety between creating and resuming hooks:
+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:
```typescript lineNumbers
import { defineHook } from "workflow";
-
-// Define the hook type once
-type ApprovalRequest = {
- requestId: string;
- approved: boolean;
- approvedBy: string;
- comment: string;
-};
-
-const approvalHook = defineHook();
+import { z } from "zod";
+
+// Define the hook with schema for type safety and runtime validation
+const approvalHook = defineHook({ // [!code highlight]
+ schema: z.object({ // [!code highlight]
+ requestId: z.string(), // [!code highlight]
+ approved: z.boolean(), // [!code highlight]
+ approvedBy: z.string(), // [!code highlight]
+ comment: z.string().transform((value) => value.trim()), // [!code highlight]
+ }), // [!code highlight]
+}); // [!code highlight]
// In your workflow
export async function documentApprovalWorkflow(documentId: string) {
@@ -385,24 +386,25 @@ export async function documentApprovalWorkflow(documentId: string) {
token: `approval:${documentId}`
});
+ // Payload is type-safe and validated
const approval = await hook;
console.log(`Document ${approval.requestId} ${approval.approved ? "approved" : "rejected"}`);
console.log(`By: ${approval.approvedBy}, Comment: ${approval.comment}`);
}
-// In your API route - TypeScript ensures the payload matches!
+// In your API route - both type-safe and runtime-validated!
export async function POST(request: Request) {
const { documentId, ...approvalData } = await request.json();
- // This is type-safe - TypeScript knows the exact shape required
+ // The schema validates the payload before resuming the workflow
await approvalHook.resume(`approval:${documentId}`, approvalData);
return new Response("OK");
}
```
-This pattern is especially valuable in larger applications where the workflow and API code are in separate files.
+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.
## Best Practices