From 98c174d5c74ee73cc8e46287eed0c643dfbabf92 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Wed, 5 Nov 2025 12:10:44 -0800 Subject: [PATCH 1/2] Make `@standard-schema/spec` be a regular dependency The Standard Schema docs explicitly say that it should be a regular dep, not a dev dep: https://standardschema.dev/#can-i-add-it-as-a-dev-dependency --- .changeset/easy-donkeys-lie.md | 5 +++++ .../docs/api-reference/workflow/define-hook.mdx | 15 ++++++++------- packages/core/package.json | 2 +- pnpm-lock.yaml | 6 +++--- 4 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 .changeset/easy-donkeys-lie.md diff --git a/.changeset/easy-donkeys-lie.md b/.changeset/easy-donkeys-lie.md new file mode 100644 index 000000000..49d9bb06b --- /dev/null +++ b/.changeset/easy-donkeys-lie.md @@ -0,0 +1,5 @@ +--- +"@workflow/core": patch +--- + +Make `@standard-schema/spec` be a regular dependency diff --git a/docs/content/docs/api-reference/workflow/define-hook.mdx b/docs/content/docs/api-reference/workflow/define-hook.mdx index e68ae1755..56e8a07e2 100644 --- a/docs/content/docs/api-reference/workflow/define-hook.mdx +++ b/docs/content/docs/api-reference/workflow/define-hook.mdx @@ -116,20 +116,21 @@ export async function POST(request: Request) { ``` ### Validate and Transform with Schema -The optional `schema` accepts any validator that implements the [Standard Schema v1](https://standardschema.dev/) contract. + +The optional `schema` accepts any validator that conforms to [Standard Schema v1](https://standardschema.dev). + Zod is shown below as one example, but libraries like Valibot, ArkType, Effect Schema, or your own custom validator work as well. + ```typescript lineNumbers import { defineHook } from "workflow"; import { z } from "zod"; -const approvalSchema = z.object({ - approved: z.boolean(), - comment: z.string().min(1).transform((value) => value.trim()), -}); - export const approvalHook = defineHook({ // Provide a schema to validate/transform payloads. - schema: approvalSchema, // [!code highlight] + schema: z.object({ // [!code highlight] + approved: z.boolean(), // [!code highlight] + comment: z.string().min(1).transform((value) => value.trim()), // [!code highlight] + }), // [!code highlight] }); export async function approvalWorkflow(approvalId: string) { diff --git a/packages/core/package.json b/packages/core/package.json index 67863a8bc..00fb1a6d1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -47,6 +47,7 @@ }, "dependencies": { "@aws-sdk/credential-provider-web-identity": "3.609.0", + "@standard-schema/spec": "^1.0.0", "@types/ms": "^2.1.0", "@vercel/functions": "catalog:", "@workflow/errors": "workspace:*", @@ -64,7 +65,6 @@ }, "devDependencies": { "@opentelemetry/api": "^1.9.0", - "@standard-schema/spec": "^1.0.0", "@types/debug": "^4.1.12", "@types/node": "catalog:", "@types/seedrandom": "^3.0.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d4ce25ff8..abb0ee66c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -414,6 +414,9 @@ importers: '@aws-sdk/credential-provider-web-identity': specifier: 3.609.0 version: 3.609.0(@aws-sdk/client-sts@3.844.0) + '@standard-schema/spec': + specifier: ^1.0.0 + version: 1.0.0 '@types/ms': specifier: ^2.1.0 version: 2.1.0 @@ -460,9 +463,6 @@ importers: '@opentelemetry/api': specifier: ^1.9.0 version: 1.9.0 - '@standard-schema/spec': - specifier: ^1.0.0 - version: 1.0.0 '@types/debug': specifier: ^4.1.12 version: 4.1.12 From eca41b91502c6558783d8894aae1d6e9bdfa5fd4 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Wed, 5 Nov 2025 12:30:18 -0800 Subject: [PATCH 2/2] Support input/output types --- packages/core/src/define-hook.ts | 31 +++++++++++------------ packages/core/src/workflow/define-hook.ts | 8 +++--- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/packages/core/src/define-hook.ts b/packages/core/src/define-hook.ts index d5c55d5c4..adb8adf0b 100644 --- a/packages/core/src/define-hook.ts +++ b/packages/core/src/define-hook.ts @@ -6,11 +6,11 @@ import { resumeHook } from './runtime/resume-hook.js'; /** * Defines a typed hook for type-safe hook creation and resumption. * - * This helper provides type safety by allowing you to define the payload type once - * and reuse it when creating hooks and resuming them. + * This helper provides type safety by allowing you to define the input and output types + * for the hook's payload, with optional validation and transformation via a schema. * - * @param schema - Schema used to validate and transform the payload before resuming - * @returns An object with `create` and `resume` functions pre-typed with the payload type + * @param schema - Schema used to validate and transform the input payload before resuming + * @returns An object with `create` and `resume` functions pre-typed with the input and output types * * @example * @@ -23,49 +23,48 @@ import { resumeHook } from './runtime/resume-hook.js'; * "use workflow"; * * const hook = approvalHook.create(); - * const result = await hook; // Fully typed as { approved: boolean; comment: string } + * const result = await hook; // Fully typed as { approved: boolean; comment: string; } * } * * // In an API route * export async function POST(request: Request) { * const { token, approved, comment } = await request.json(); - * await approvalHook.resume(token, { approved, comment }); + * await approvalHook.resume(token, { approved, comment }); // Input type * return Response.json({ success: true }); * } * ``` */ -export function defineHook({ +export function defineHook({ schema, }: { - schema?: StandardSchemaV1; + schema?: StandardSchemaV1; } = {}) { return { /** - * Creates a new hook with the defined payload type. + * Creates a new hook with the defined output type. * * Note: This method is not available in runtime bundles. Use it from workflow contexts only. * * @param _options - Optional hook configuration - * @returns A Hook that resolves to the defined payload type + * @returns A Hook that resolves to the defined output type */ - // @ts-expect-error `options` is here for types/docs - create(options?: HookOptions): Hook { + create(_options?: HookOptions): Hook { throw new Error( '`defineHook().create()` can only be called inside a workflow function.' ); }, /** - * Resumes a hook by sending a payload with the defined type. + * Resumes a hook by sending a payload with the defined input type. * This is a type-safe wrapper around the `resumeHook` runtime function. * * @param token - The unique token identifying the hook * @param payload - The payload to send; if a `schema` is configured it is validated/transformed before resuming * @returns Promise resolving to the hook entity, or null if the hook doesn't exist */ - async resume(token: string, payload: T): Promise { + async resume(token: string, payload: TInput): Promise { if (!schema?.['~standard']) { - return await resumeHook(token, payload); + return await resumeHook(token, payload); } let result = schema['~standard'].validate(payload); @@ -78,7 +77,7 @@ export function defineHook({ throw new Error(JSON.stringify(result.issues, null, 2)); } - return await resumeHook(token, result.value); + return await resumeHook(token, result.value); }, }; } diff --git a/packages/core/src/workflow/define-hook.ts b/packages/core/src/workflow/define-hook.ts index a70668148..ad463c745 100644 --- a/packages/core/src/workflow/define-hook.ts +++ b/packages/core/src/workflow/define-hook.ts @@ -5,13 +5,13 @@ import { createHook } from './create-hook.js'; /** * NOTE: This is the implementation of `defineHook()` that is used in workflow contexts. */ -export function defineHook() { +export function defineHook() { return { - create(options?: HookOptions): Hook { - return createHook(options); + create(options?: HookOptions): Hook { + return createHook(options); }, - resume(_token: string, _payload: T): Promise { + resume(_token: string, _payload: TInput): Promise { throw new Error( '`defineHook().resume()` can only be called from external contexts (e.g. API routes).' );