From 3891212ae2c5ed755e46bc9fc39077906eb610f7 Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Tue, 24 Mar 2026 22:20:32 -0600 Subject: [PATCH 01/15] docs: align hook API reference docs Keep the API reference aligned with the current hook and webhook runtime behavior so readers do not rely on stale token, overload, or validation semantics. These docs changes clarify the intended runtime story for deterministic hooks versus public webhooks and document the current typed-hook contract exposed by the public API. Ploop-Iter: 1 --- .../workflow-api/get-hook-by-token.mdx | 2 +- .../docs/api-reference/workflow-api/index.mdx | 6 ++-- .../workflow-api/resume-hook.mdx | 25 ++++++------- .../workflow-api/resume-webhook.mdx | 3 +- .../api-reference/workflow/create-hook.mdx | 8 +++-- .../api-reference/workflow/create-webhook.mdx | 13 +++++-- .../api-reference/workflow/define-hook.mdx | 35 ++++++++++--------- .../docs/api-reference/workflow/index.mdx | 6 ++-- 8 files changed, 56 insertions(+), 42 deletions(-) diff --git a/docs/content/docs/api-reference/workflow-api/get-hook-by-token.mdx b/docs/content/docs/api-reference/workflow-api/get-hook-by-token.mdx index 028c9b4047..17a180b40b 100644 --- a/docs/content/docs/api-reference/workflow-api/get-hook-by-token.mdx +++ b/docs/content/docs/api-reference/workflow-api/get-hook-by-token.mdx @@ -7,7 +7,7 @@ prerequisites: - /docs/foundations/hooks --- -Retrieves a hook by its unique token, returning the associated workflow run information and any metadata that was set when the hook was created. This function is useful for inspecting hook details before deciding whether to resume a workflow. +Retrieves a hook by its unique token, returning the associated workflow run information and any metadata that was set when the hook was created. Metadata is automatically hydrated (deserialized) before being returned, so you receive the original values rather than raw serialized data. This function is useful for inspecting hook details before deciding whether to resume a workflow. `getHookByToken` is a runtime function that must be called from outside a workflow function. diff --git a/docs/content/docs/api-reference/workflow-api/index.mdx b/docs/content/docs/api-reference/workflow-api/index.mdx index 5941e9068d..e1e62e82cb 100644 --- a/docs/content/docs/api-reference/workflow-api/index.mdx +++ b/docs/content/docs/api-reference/workflow-api/index.mdx @@ -16,13 +16,13 @@ The API package is for access and introspection of workflow data to inspect runs Start/enqueue a new workflow run. - Resume a workflow by sending a payload to a hook. + Resume a hook created with `createHook()` by sending an arbitrary payload. - Resume a workflow by sending a `Request` to a webhook. + Resume a webhook created with `createWebhook()` by forwarding an HTTP `Request`. - Get hook details and metadata by its token. + Retrieve hook details, metadata, and run information by token. Get workflow run status and metadata without waiting for completion. diff --git a/docs/content/docs/api-reference/workflow-api/resume-hook.mdx b/docs/content/docs/api-reference/workflow-api/resume-hook.mdx index e98b8c092f..576e79e5bd 100644 --- a/docs/content/docs/api-reference/workflow-api/resume-hook.mdx +++ b/docs/content/docs/api-reference/workflow-api/resume-hook.mdx @@ -9,9 +9,9 @@ related: - /docs/api-reference/workflow-api/resume-webhook --- -Resumes a workflow run by sending a payload to a hook identified by its token. +Resumes a workflow run by sending a payload to a hook identified by its token or hook object. -It creates a `hook_received` event and re-triggers the workflow to continue execution. +It creates a `hook_received` event and re-triggers the workflow to continue execution. Use `resumeHook()` for hooks created with [`createHook()`](/docs/api-reference/workflow/create-hook) (server-side, deterministic tokens). For hooks created with [`createWebhook()`](/docs/api-reference/workflow/create-webhook), use [`resumeWebhook()`](/docs/api-reference/workflow-api/resume-webhook) instead. `resumeHook` is a runtime function that must be called from outside a workflow function. @@ -128,26 +128,23 @@ export async function approveRequest(token: string, approved: boolean) { } ``` -### Webhook Handler +### Passing a Hook Object -Using `resumeHook` in a generic webhook handler to resume a hook: +Instead of a token string, you can pass a `Hook` object directly (e.g. one returned by [`getHookByToken()`](/docs/api-reference/workflow-api/get-hook-by-token)): ```typescript lineNumbers -import { resumeHook } from "workflow/api"; +import { getHookByToken, resumeHook } from "workflow/api"; -// Generic webhook handler that forwards data to a hook export async function POST(request: Request) { - const url = new URL(request.url); - const token = url.searchParams.get("token"); - - if (!token) { - return Response.json({ error: "Missing token" }, { status: 400 }); - } + const { token, data } = await request.json(); try { - const body = await request.json(); - const result = await resumeHook(token, body); + const hook = await getHookByToken(token); + + // Validate metadata before resuming + console.log("Hook metadata:", hook.metadata); + const result = await resumeHook(hook, data); // [!code highlight] return Response.json({ success: true, runId: result.runId }); } catch (error) { return Response.json({ error: "Hook not found" }, { status: 404 }); diff --git a/docs/content/docs/api-reference/workflow-api/resume-webhook.mdx b/docs/content/docs/api-reference/workflow-api/resume-webhook.mdx index b4ee9f2008..623bb12dee 100644 --- a/docs/content/docs/api-reference/workflow-api/resume-webhook.mdx +++ b/docs/content/docs/api-reference/workflow-api/resume-webhook.mdx @@ -52,7 +52,8 @@ showSections={['parameters']} Returns a `Promise` that resolves to: -- `Response`: The HTTP response from the workflow's `respondWith()` call +- A `202 Accepted` response if the webhook was created with the default mode (no `respondWith` option or a `Response` object passed to `respondWith`) +- The workflow's custom `Response` if the webhook was created with `createWebhook({ respondWith: 'manual' })` and the workflow called `request.respondWith(response)` from a step function Throws an error if the webhook token is not found or invalid. diff --git a/docs/content/docs/api-reference/workflow/create-hook.mdx b/docs/content/docs/api-reference/workflow/create-hook.mdx index 00e5afccd1..9ffd3330a9 100644 --- a/docs/content/docs/api-reference/workflow/create-hook.mdx +++ b/docs/content/docs/api-reference/workflow/create-hook.mdx @@ -10,9 +10,9 @@ related: - /docs/api-reference/workflow/create-webhook --- -Creates a low-level hook primitive that can be used to resume a workflow run with arbitrary payloads. +Creates a deterministic-token hook primitive that can be used to resume a workflow run with arbitrary payloads. -Hooks allow external systems to send data to a paused workflow without the HTTP-specific constraints of webhooks. They're identified by a token and can receive any serializable payload. +Unlike [`createWebhook()`](/docs/api-reference/workflow/create-webhook), which always generates a random token for its public HTTP endpoint, `createHook()` lets you specify a deterministic `token` that external systems can reconstruct without prior communication. Hooks are resumed server-side via [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) and can receive any serializable payload. ```ts lineNumbers import { createHook } from "workflow" @@ -65,6 +65,10 @@ export default Hook;`} The returned `Hook` object also implements `AsyncIterable`, which allows you to iterate over incoming payloads using `for await...of` syntax. + +The `isWebhook` option in `HookOptions` controls whether the hook can be resumed via the public webhook endpoint (`/.well-known/workflow/v1/webhook/{token}`). It defaults to `false`, meaning hooks created with `createHook()` can only be resumed server-side via `resumeHook()`. The `createWebhook()` function sets this to `true` automatically. + + ## Examples ### Basic Usage diff --git a/docs/content/docs/api-reference/workflow/create-webhook.mdx b/docs/content/docs/api-reference/workflow/create-webhook.mdx index ac63878d4d..105a56a20c 100644 --- a/docs/content/docs/api-reference/workflow/create-webhook.mdx +++ b/docs/content/docs/api-reference/workflow/create-webhook.mdx @@ -17,6 +17,15 @@ Webhooks provide a way for external systems to send HTTP requests directly to yo `createWebhook()` creates a public endpoint at `/.well-known/workflow/v1/webhook/:token`, and the token in that URL is the only authorization performed for incoming requests resuming that webhook. This is convenient for prototypes and simple resume links because it avoids 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. + +`createWebhook()` does **not** accept a `token` option. Webhook tokens are always randomly generated. Use [`createHook()`](/docs/api-reference/workflow/create-hook) with [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) for deterministic token patterns. + + +`createWebhook` has two overloads: + +- **Default mode** — `createWebhook(options?)` returns `Webhook`. The caller automatically receives a `202 Accepted` response (or the `Response` object you pass as `respondWith`). +- **Manual-response mode** — `createWebhook({ respondWith: 'manual' })` returns `Webhook`. Each request exposes a `respondWith()` method so the workflow can send a custom HTTP response from within a step function. + ```ts lineNumbers import { createWebhook } from "workflow" @@ -54,7 +63,7 @@ showSections={['returns']} The returned `Webhook` object has: - `url`: The HTTP endpoint URL that external systems can call -- `token`: The unique token identifying this webhook +- `token`: The unique, randomly generated token identifying this webhook - Implements `AsyncIterable` for handling multiple requests, where `T` is `Request` (default) or `RequestWithResponse` (manual mode) When using `createWebhook({ respondWith: 'manual' })`, the resolved request type is `RequestWithResponse`, which extends the standard `Request` interface with a `respondWith(response: Response): Promise` method for sending custom responses back to the caller. @@ -122,7 +131,7 @@ async function sendResponse(request: RequestWithResponse): Promise { export async function respondingWebhookWorkflow() { "use workflow"; - using webhook = createWebhook({ respondWith: "manual" }); + using webhook = createWebhook({ respondWith: "manual" }); // [!code highlight] console.log("Webhook URL:", webhook.url); const request = await webhook; diff --git a/docs/content/docs/api-reference/workflow/define-hook.mdx b/docs/content/docs/api-reference/workflow/define-hook.mdx index ad0d697f30..137da798d4 100644 --- a/docs/content/docs/api-reference/workflow/define-hook.mdx +++ b/docs/content/docs/api-reference/workflow/define-hook.mdx @@ -13,6 +13,8 @@ Creates a type-safe hook helper that ensures the payload type is consistent betw 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. +When a `schema` is provided, the hook is typed as `TypedHook` — the `resume()` method accepts `TInput` (the raw payload), while the workflow receives `TOutput` (the validated and potentially transformed result). Without a schema, `TOutput` defaults to `TInput`. + We recommend using `defineHook()` over `createHook()` in production codebases for better type safety and optional runtime validation. @@ -48,20 +50,21 @@ showSections={['parameters']} { +interface TypedHook { /** -* Creates a new hook with the defined payload type. +* Creates a new hook with the defined output type. */ - create: (options?: HookOptions) => Hook; + create: (options?: HookOptions) => Hook; /** -* Resumes a hook by sending a payload with the defined type. +* Resumes a hook by sending a payload with the defined input type. +* Throws an error if the hook is not found or if schema validation fails. */ - resume: (token: string, payload: T) => Promise; + resume: (token: string, payload: TInput) => Promise; } -export default DefineHook;`} +export default TypedHook;`} /> ## Examples @@ -93,24 +96,24 @@ export async function workflowWithApproval() { ### Resuming with Type Safety -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. +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. The `resume()` method throws an error if the hook is not found or if schema validation fails. ```typescript lineNumbers // Use the same defined hook to resume export async function POST(request: Request) { const { token, approved, comment } = await request.json(); - // Type-safe resumption - TypeScript ensures the payload matches - const result = await approvalHook.resume(token, { // [!code highlight] - approved, // [!code highlight] - comment, // [!code highlight] - }); // [!code highlight] + try { + // Type-safe resumption - TypeScript ensures the payload matches + const result = await approvalHook.resume(token, { // [!code highlight] + approved, // [!code highlight] + comment, // [!code highlight] + }); // [!code highlight] - if (!result) { - return Response.json({ error: "Hook not found" }, { status: 404 }); + return Response.json({ success: true, runId: result.runId }); + } catch (error) { + return Response.json({ error: "Hook not found or invalid payload" }, { status: 400 }); } - - return Response.json({ success: true, runId: result.runId }); } ``` diff --git a/docs/content/docs/api-reference/workflow/index.mdx b/docs/content/docs/api-reference/workflow/index.mdx index 8544b47d70..dac061956a 100644 --- a/docs/content/docs/api-reference/workflow/index.mdx +++ b/docs/content/docs/api-reference/workflow/index.mdx @@ -36,13 +36,13 @@ Workflow SDK contains the following functions you can use inside your workflow f Make HTTP requests from within a workflow with automatic retry semantics. - Create a low-level hook to receive arbitrary payloads from external systems. + Create a hook with a deterministic token, resumed server-side via `resumeHook()`. - Type-safe hook helper for consistent payload types. + Type-safe hook helper with optional schema validation for consistent payload types. - Create a webhook that suspends the workflow until an HTTP request is received. + Create a webhook with a randomly generated token and public HTTP endpoint. Access the current workflow run's default stream. From 5717f1c21560c5522f8340a805c983341cdf411b Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Tue, 24 Mar 2026 22:51:54 -0600 Subject: [PATCH 02/15] docs: align workflow runtime API docs Why: the runtime API docs were implying an incorrect import surface and incomplete resumeHook behavior, which can mislead users integrating hook resumption and world access. Recording these fixes with a regression test helps keep the docs aligned with the actual runtime entrypoints and overloads as the packages evolve. Ploop-Iter: 2 --- .../docs/api-reference/workflow-api/index.mdx | 10 ++-- .../workflow-api/resume-hook.mdx | 4 +- .../src/__tests__/hook-runtime-docs.test.ts | 53 +++++++++++++++++++ 3 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 packages/docs-typecheck/src/__tests__/hook-runtime-docs.test.ts diff --git a/docs/content/docs/api-reference/workflow-api/index.mdx b/docs/content/docs/api-reference/workflow-api/index.mdx index e1e62e82cb..d1420233a5 100644 --- a/docs/content/docs/api-reference/workflow-api/index.mdx +++ b/docs/content/docs/api-reference/workflow-api/index.mdx @@ -1,15 +1,15 @@ --- title: "workflow/api" -description: Runtime functions to inspect runs, start workflows, and access world data. +description: Runtime functions for starting workflows, inspecting runs, resuming hooks, and accessing world data. type: overview -summary: Explore runtime functions for starting workflows, inspecting runs, and managing hooks. +summary: Explore runtime functions across the workflow/api and workflow/runtime entrypoints. --- -API reference for runtime functions from the `workflow/api` package. +API reference for runtime functions from the `workflow/api` and `workflow/runtime` entrypoints. ## Functions -The API package is for access and introspection of workflow data to inspect runs, start new runs, or access anything else directly accessible by the world. +Most functions in this section are imported from `workflow/api`. `getWorld()` is imported from `workflow/runtime`. @@ -28,7 +28,7 @@ The API package is for access and introspection of workflow data to inspect runs Get workflow run status and metadata without waiting for completion. - Async: resolve the World instance for storage, queuing, and streaming backends. + Get direct access to workflow storage, queuing, and streaming backends from `workflow/runtime`. Low-level API for inspecting runs, steps, events, hooks, streams, and queues. diff --git a/docs/content/docs/api-reference/workflow-api/resume-hook.mdx b/docs/content/docs/api-reference/workflow-api/resume-hook.mdx index 576e79e5bd..ee59ea6b43 100644 --- a/docs/content/docs/api-reference/workflow-api/resume-hook.mdx +++ b/docs/content/docs/api-reference/workflow-api/resume-hook.mdx @@ -1,8 +1,8 @@ --- title: resumeHook -description: Resume a paused workflow by sending a payload to a hook token. +description: Resume a paused workflow by sending a payload to a hook token or hook object. type: reference -summary: Use resumeHook to send a payload to a hook token and resume a paused workflow. +summary: Use resumeHook to send a payload to a hook token or hook object and resume a paused workflow. prerequisites: - /docs/foundations/hooks related: diff --git a/packages/docs-typecheck/src/__tests__/hook-runtime-docs.test.ts b/packages/docs-typecheck/src/__tests__/hook-runtime-docs.test.ts new file mode 100644 index 0000000000..4776caf3ee --- /dev/null +++ b/packages/docs-typecheck/src/__tests__/hook-runtime-docs.test.ts @@ -0,0 +1,53 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(__dirname, '../../../..'); + +const read = (relativePath: string) => + fs.readFileSync(path.join(repoRoot, relativePath), 'utf-8'); + +describe('hook runtime API docs stay aligned with runtime behavior', () => { + it('documents hydrated hook metadata and the hook-object resumeHook overload', () => { + const source = read('packages/core/src/runtime/resume-hook.ts'); + const getHookDoc = read( + 'docs/content/docs/api-reference/workflow-api/get-hook-by-token.mdx' + ); + const resumeHookDoc = read( + 'docs/content/docs/api-reference/workflow-api/resume-hook.mdx' + ); + + expect(source).toContain('hook.metadata = await hydrateStepArguments'); + expect(source).toContain('tokenOrHook: string | Hook'); + + expect(getHookDoc).toContain( + 'Metadata is automatically hydrated (deserialized)' + ); + expect(resumeHookDoc).toContain('token or hook object'); + expect(resumeHookDoc).toContain('await resumeHook(hook, data)'); + expect(resumeHookDoc).toContain('hook token or hook object'); + }); + + it('does not imply getWorld() is imported from workflow/api', () => { + const apiIndexDoc = read( + 'docs/content/docs/api-reference/workflow-api/index.mdx' + ); + const apiEntry = read('packages/workflow/src/api.ts'); + const getWorldDoc = read( + 'docs/content/docs/api-reference/workflow-api/get-world.mdx' + ); + + expect(apiEntry).not.toContain('getWorld'); + expect(getWorldDoc).toContain( + 'import { getWorld } from "workflow/runtime";' + ); + + expect(apiIndexDoc).toContain('workflow/api and workflow/runtime'); + expect(apiIndexDoc).toContain('from `workflow/runtime`'); + expect(apiIndexDoc).not.toContain( + 'API reference for runtime functions from the `workflow/api` package.' + ); + }); +}); From dcaa71fa4db8c799c49132df69cd3d91860aa569 Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Tue, 24 Mar 2026 23:21:06 -0600 Subject: [PATCH 03/15] docs: align resumeHook reference Why: the runtime API docs had drifted from current resumeHook behavior, which risks misleading users about return values and failure handling. This update keeps the reference aligned with the runtime contract so external integrations can handle hook resumption correctly and distinguish missing hooks from operational failures. Ploop-Iter: 3 --- .../workflow-api/resume-hook.mdx | 80 +++++++++++++++---- 1 file changed, 63 insertions(+), 17 deletions(-) diff --git a/docs/content/docs/api-reference/workflow-api/resume-hook.mdx b/docs/content/docs/api-reference/workflow-api/resume-hook.mdx index ee59ea6b43..7ab1dd3ebd 100644 --- a/docs/content/docs/api-reference/workflow-api/resume-hook.mdx +++ b/docs/content/docs/api-reference/workflow-api/resume-hook.mdx @@ -9,9 +9,11 @@ related: - /docs/api-reference/workflow-api/resume-webhook --- -Resumes a workflow run by sending a payload to a hook identified by its token or hook object. +Resumes a workflow run by sending a payload to a hook identified by its token or an existing hook object. -It creates a `hook_received` event and re-triggers the workflow to continue execution. Use `resumeHook()` for hooks created with [`createHook()`](/docs/api-reference/workflow/create-hook) (server-side, deterministic tokens). For hooks created with [`createWebhook()`](/docs/api-reference/workflow/create-webhook), use [`resumeWebhook()`](/docs/api-reference/workflow-api/resume-webhook) instead. +`resumeHook()` returns the resumed hook entity, not the workflow's eventual return value. This is useful when you need the associated `runId`, `hookId`, or hook metadata immediately after resumption. + +It creates a `hook_received` event and re-queues the workflow so execution can continue. Use `resumeHook()` for hooks created with [`createHook()`](/docs/api-reference/workflow/create-hook), whether the token was explicitly set or auto-generated. For hooks created with [`createWebhook()`](/docs/api-reference/workflow/create-webhook), use [`resumeWebhook()`](/docs/api-reference/workflow-api/resume-webhook) instead. `resumeHook` is a runtime function that must be called from outside a workflow function. @@ -24,12 +26,41 @@ export async function POST(request: Request) { const { token, data } = await request.json(); try { - const result = await resumeHook(token, data); // [!code highlight] + const hook = await resumeHook(token, data); // [!code highlight] + + console.info(JSON.stringify({ + event: "workflow.hook.resumed", + token: hook.token, + hookId: hook.hookId, + runId: hook.runId, + })); + return Response.json({ - runId: result.runId + runId: hook.runId, }); } catch (error) { - return new Response("Hook not found", { status: 404 }); + if (error instanceof Error && error.name === "HookNotFoundError") { + console.warn(JSON.stringify({ + event: "workflow.hook.not_found", + token, + })); + + return Response.json( + { error: "Hook not found", token }, + { status: 404 } + ); + } + + console.error(JSON.stringify({ + event: "workflow.hook.resume_failed", + token, + error: error instanceof Error ? error.message : String(error), + })); + + return Response.json( + { error: "Failed to resume hook" }, + { status: 500 } + ); } } ``` @@ -56,6 +87,12 @@ export default Hook;`} showSections={["returns"]} /> +## Error Behavior + +A missing hook token is only one failure mode. `resumeHook()` can also fail while dehydrating the payload, creating the `hook_received` event, or re-queueing the workflow. In HTTP handlers, map missing hooks to `404` and unexpected failures to `500` so operational failures stay visible. + +`resumeHook()` resolves to the hook record that was resumed. Use `hook.runId` with [`getRun()`](/docs/api-reference/workflow-api/get-run) if you want to inspect the workflow after resumption; it does not wait for the workflow to finish. + ## Examples ### Basic API Route @@ -69,14 +106,17 @@ export async function POST(request: Request) { const { token, data } = await request.json(); try { - const result = await resumeHook(token, data); // [!code highlight] + const hook = await resumeHook(token, data); // [!code highlight] return Response.json({ success: true, - runId: result.runId + runId: hook.runId }); } catch (error) { - return new Response("Hook not found", { status: 404 }); + if (error instanceof Error && error.name === "HookNotFoundError") { + return Response.json({ error: "Hook not found" }, { status: 404 }); + } + return Response.json({ error: "Failed to resume hook" }, { status: 500 }); } } ``` @@ -97,14 +137,17 @@ export async function POST(request: Request) { const { token, approved, comment } = await request.json(); try { - const result = await resumeHook(token, { // [!code highlight] + const hook = await resumeHook(token, { // [!code highlight] approved, // [!code highlight] comment, // [!code highlight] }); // [!code highlight] - return Response.json({ runId: result.runId }); + return Response.json({ runId: hook.runId }); } catch (error) { - return Response.json({ error: "Invalid token" }, { status: 404 }); + if (error instanceof Error && error.name === "HookNotFoundError") { + return Response.json({ error: "Hook not found" }, { status: 404 }); + } + return Response.json({ error: "Failed to resume hook" }, { status: 500 }); } } ``` @@ -120,10 +163,10 @@ import { resumeHook } from "workflow/api"; export async function approveRequest(token: string, approved: boolean) { try { - const result = await resumeHook(token, { approved }); - return result.runId; + const hook = await resumeHook(token, { approved }); + return hook.runId; } catch (error) { - throw new Error("Invalid approval token"); + throw new Error("Failed to resume hook"); } } ``` @@ -144,10 +187,13 @@ export async function POST(request: Request) { // Validate metadata before resuming console.log("Hook metadata:", hook.metadata); - const result = await resumeHook(hook, data); // [!code highlight] - return Response.json({ success: true, runId: result.runId }); + const resumedHook = await resumeHook(hook, data); // [!code highlight] + return Response.json({ success: true, runId: resumedHook.runId }); } catch (error) { - return Response.json({ error: "Hook not found" }, { status: 404 }); + if (error instanceof Error && error.name === "HookNotFoundError") { + return Response.json({ error: "Hook not found" }, { status: 404 }); + } + return Response.json({ error: "Failed to resume hook" }, { status: 500 }); } } ``` From dbdd1adb8f74d16aecb93726b275f7ce69cdf2ab Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Tue, 24 Mar 2026 23:58:01 -0600 Subject: [PATCH 04/15] docs: sync workflow API reference docs Keep the oldest workflow API reference pages aligned with the current public exports so developers do not rely on stale signatures, runtime behavior, or retry semantics.\n\nThis captures the current workflow metadata surface and error/fetch/sleep behavior in the docs, reducing drift in the most stale reference pages before broader API auditing continues.\n\nPloop-Iter: 1 --- .../api-reference/workflow/fatal-error.mdx | 34 +++++++++----- .../docs/api-reference/workflow/fetch.mdx | 15 +++--- .../workflow/get-workflow-metadata.mdx | 19 ++++++-- .../docs/api-reference/workflow/index.mdx | 21 ++++++--- .../workflow/retryable-error.mdx | 46 ++++++++++++------- .../docs/api-reference/workflow/sleep.mdx | 27 +++++++++-- 6 files changed, 112 insertions(+), 50 deletions(-) diff --git a/docs/content/docs/api-reference/workflow/fatal-error.mdx b/docs/content/docs/api-reference/workflow/fatal-error.mdx index 8c31e1e611..d00ff14493 100644 --- a/docs/content/docs/api-reference/workflow/fatal-error.mdx +++ b/docs/content/docs/api-reference/workflow/fatal-error.mdx @@ -29,16 +29,28 @@ async function fallibleStep() { ## API Signature -### Parameters +### Constructor - +| Parameter | Type | Description | +| --------- | -------- | ----------------- | +| `message` | `string` | The error message | + +### Instance Properties + +| Property | Type | Description | +| -------- | ------ | ------------------------------------------------ | +| `fatal` | `true` | Always `true`. Marks the error as non-retryable. | + +### Static Methods + +#### `FatalError.is(value)` + +```typescript +FatalError.is(value: unknown): value is FatalError +``` + +Returns `true` if `value` is a `FatalError` instance. Useful for checking caught errors without `instanceof`. diff --git a/docs/content/docs/api-reference/workflow/fetch.mdx b/docs/content/docs/api-reference/workflow/fetch.mdx index e1d18f44ac..99855354dd 100644 --- a/docs/content/docs/api-reference/workflow/fetch.mdx +++ b/docs/content/docs/api-reference/workflow/fetch.mdx @@ -1,20 +1,20 @@ --- title: fetch -description: Make HTTP requests from workflows with automatic serialization and retry semantics. +description: Make HTTP requests from workflows using a step-wrapped fetch. type: reference -summary: Use the workflow-aware fetch to make HTTP requests with automatic serialization and retry semantics. +summary: Use the workflow-aware fetch to make HTTP requests from workflow code. prerequisites: - /docs/foundations/workflows-and-steps related: - /docs/errors/fetch-in-workflow --- -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. +Makes HTTP requests from within a workflow. This is a hoisted `"use step"` wrapper over `globalThis.fetch`, allowing you to call `fetch` directly inside a `"use workflow"` function. The call executes as a step with full Node.js runtime access. This is useful when you need to call external APIs or services from within your workflow. -`fetch` is a *special* type of step function provided and should be called directly inside workflow functions. +`fetch` is a `"use step"` wrapper and should be called directly inside workflow functions. It does not include built-in retry logic — use a custom step with [`RetryableError`](/docs/api-reference/workflow/retryable-error) for retry control. ```typescript lineNumbers @@ -82,9 +82,9 @@ async function apiWorkflow() { } ``` -We call `fetch()` with a URL and optional request options, just like the standard fetch API. The workflow runtime automatically handles the response serialization. +We call `fetch()` with a URL and optional request options, just like the standard `fetch` API. Because `fetch` runs as a step, the workflow runtime handles serialization and replay. -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. +This API is provided as a convenience to easily use `fetch` in a workflow, but you may want to write your own `"use step"` wrapper for more control over error handling and retry logic. ### Customizing Fetch Behavior @@ -140,7 +140,6 @@ export async function customFetch( This example demonstrates: -- Setting custom `maxRetries` to 5 retries (6 total attempts including the initial attempt). - Throwing [`FatalError`](/docs/api-reference/workflow/fatal-error) for client errors (400-499) to prevent retries. - Handling 429 rate limiting by reading the `Retry-After` header and using [`RetryableError`](/docs/api-reference/workflow/retryable-error). -- Allowing automatic retries for server errors (5xx). +- Allowing automatic retries for server errors (5xx) by throwing a plain `Error`. diff --git a/docs/content/docs/api-reference/workflow/get-workflow-metadata.mdx b/docs/content/docs/api-reference/workflow/get-workflow-metadata.mdx index c5c24c6b8d..bb3c43bad9 100644 --- a/docs/content/docs/api-reference/workflow/get-workflow-metadata.mdx +++ b/docs/content/docs/api-reference/workflow/get-workflow-metadata.mdx @@ -1,22 +1,23 @@ --- title: getWorkflowMetadata -description: Access run IDs and timing information within workflow functions. +description: Access run IDs and timing information within workflow or step functions. type: reference -summary: Call getWorkflowMetadata inside a workflow to access the run ID and timing information. +summary: Call getWorkflowMetadata inside a workflow or step function to access the run ID and timing information. prerequisites: - /docs/foundations/workflows-and-steps --- -Returns additional metadata available in the current workflow function. +Returns metadata about the current workflow run. This function can be called from either a workflow function or a step function. You may want to use this function when you need to: * Log workflow run IDs * Access timing information of a workflow * Detect whether encryption is enabled for the current run +* Retrieve the workflow URL from within a step -If you need to access step context, take a look at [`getStepMetadata`](/docs/api-reference/workflow/get-step-metadata). +If you need to access step-specific context, take a look at [`getStepMetadata`](/docs/api-reference/workflow/get-step-metadata). ```typescript lineNumbers @@ -103,6 +104,16 @@ showSections={['parameters']} ### Returns +Returns a `WorkflowMetadata` object with the following fields: + +| Field | Type | Description | +| --- | --- | --- | +| `workflowName` | `string` | The name of the workflow. | +| `workflowRunId` | `string` | Unique identifier for the workflow run. | +| `workflowStartedAt` | `Date` | Timestamp when the workflow run started. | +| `url` | `string` | The URL where the workflow can be triggered. | +| `features.encryption` | `boolean` | Whether serialized workflow data is encrypted at rest for the current run. | + A function that returns context about the current workflow execution. - - A function that returns context about the current step execution. - Sleeping workflows for a specified duration. Deterministic and replay-safe. - Make HTTP requests from within a workflow with automatic retry semantics. + Make HTTP requests from within a workflow. Runs as a step under the hood. Create a hook with a deterministic token, resumed server-side via `resumeHook()`. @@ -49,9 +46,19 @@ Workflow SDK contains the following functions you can use inside your workflow f +## Step Functions + +These functions are available inside `"use step"` functions: + + + + A function that returns context about the current step execution. + + + ## Error Classes -Workflow SDK includes error classes that can be thrown in a workflow or step to change the error exit strategy of a workflow. +Error classes that can be thrown inside a step to control retry behavior. diff --git a/docs/content/docs/api-reference/workflow/retryable-error.mdx b/docs/content/docs/api-reference/workflow/retryable-error.mdx index 7783e4c981..a79bcb383f 100644 --- a/docs/content/docs/api-reference/workflow/retryable-error.mdx +++ b/docs/content/docs/api-reference/workflow/retryable-error.mdx @@ -33,26 +33,38 @@ The difference between `Error` and `RetryableError` may not be entirely obvious, ## API Signature -### Parameters - - +| Property | Type | Description | +| ------------ | ------ | --------------------------------------------------------------------------- | +| `retryAfter` | `Date` | The date/time when the step should be retried. Defaults to 1 second from now when `options.retryAfter` is omitted. | -#### RetryableErrorOptions +### `RetryableErrorOptions` + +| Property | Type | Description | +| ------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------- | +| `retryAfter?` | `number \| StringValue \| Date` | Delay before retrying. A `number` is milliseconds, a `StringValue` is a duration string (e.g. `"5s"`, `"2m"`), or a `Date` for an absolute time. Defaults to 1 second (1000 ms) when omitted. | + +### Static Methods + +#### `RetryableError.is(value)` + +```typescript +RetryableError.is(value: unknown): value is RetryableError +``` - +Returns `true` if `value` is a `RetryableError` instance. Useful for checking caught errors without `instanceof`. ## Examples diff --git a/docs/content/docs/api-reference/workflow/sleep.mdx b/docs/content/docs/api-reference/workflow/sleep.mdx index 86d7b14f9e..556804743e 100644 --- a/docs/content/docs/api-reference/workflow/sleep.mdx +++ b/docs/content/docs/api-reference/workflow/sleep.mdx @@ -14,7 +14,7 @@ Suspends a workflow for a specified duration or until an end date without consum This is useful when you want to resume a workflow after some duration or date. -`sleep` is a *special* type of step function and should be called directly inside workflow functions. +`sleep` is a built-in workflow runtime function and should be called directly inside workflow functions. ```typescript lineNumbers @@ -37,11 +37,19 @@ export default sleep;`} showSections={['parameters']} /> +## Overloads + +`sleep` accepts three types of arguments: + +- **`StringValue`** — a human-readable duration string such as `"10s"`, `"1m"`, `"1h"`, or `"1d"`. +- **`Date`** — a future `Date` object to sleep until. +- **`number`** — a duration in milliseconds. + ## Examples -### Sleeping With a Duration +### Sleeping With a Duration String -You can specify a duration for `sleep` to suspend the workflow for a fixed amount of time. +You can specify a duration string for `sleep` to suspend the workflow for a fixed amount of time. ```typescript lineNumbers import { sleep } from "workflow" @@ -52,6 +60,19 @@ async function testWorkflow() { } ``` +### Sleeping With Milliseconds + +You can specify a number of milliseconds for `sleep` to suspend the workflow. + +```typescript lineNumbers +import { sleep } from "workflow" + +async function testWorkflow() { + "use workflow" + await sleep(10_000) // [!code highlight] +} +``` + ### Sleeping Until an End Date You can specify a future `Date` object for `sleep` to suspend the workflow until a specific date. From d9ecb18bce21a1df53b894564d77ebb4160fb8c3 Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Wed, 25 Mar 2026 00:25:57 -0600 Subject: [PATCH 05/15] docs: correct fatal error and fetch references Keep the workflow API reference aligned with current runtime behavior so developers do not rely on stale retry semantics or error guarantees. Accurate docs here matter because these pages are used as normative guidance for step failure handling and fetch behavior inside workflows. Ploop-Iter: 2 --- docs/content/docs/api-reference/workflow/fatal-error.mdx | 6 +++--- docs/content/docs/api-reference/workflow/fetch.mdx | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/content/docs/api-reference/workflow/fatal-error.mdx b/docs/content/docs/api-reference/workflow/fatal-error.mdx index d00ff14493..f8167a3b90 100644 --- a/docs/content/docs/api-reference/workflow/fatal-error.mdx +++ b/docs/content/docs/api-reference/workflow/fatal-error.mdx @@ -9,9 +9,9 @@ related: - /docs/api-reference/workflow/retryable-error --- -When a `FatalError` is thrown in a step, it indicates that the workflow should not retry a step, marking it as failure. +When a `FatalError` is thrown in a step, the step fails without retrying and the error bubbles back to the workflow logic. -You should use this when you don't want a specific step to retry. +Use `FatalError` when a failure is intentional or unrecoverable and retrying would be wasteful or harmful. ```typescript lineNumbers import { FatalError } from "workflow" @@ -43,7 +43,7 @@ new FatalError(message: string) | Property | Type | Description | | -------- | ------ | ------------------------------------------------ | -| `fatal` | `true` | Always `true`. Marks the error as non-retryable. | +| `fatal` | `boolean` | Always initialized to `true`. Marks the error as non-retryable. | ### Static Methods diff --git a/docs/content/docs/api-reference/workflow/fetch.mdx b/docs/content/docs/api-reference/workflow/fetch.mdx index 99855354dd..29f899bb83 100644 --- a/docs/content/docs/api-reference/workflow/fetch.mdx +++ b/docs/content/docs/api-reference/workflow/fetch.mdx @@ -9,12 +9,12 @@ related: - /docs/errors/fetch-in-workflow --- -Makes HTTP requests from within a workflow. This is a hoisted `"use step"` wrapper over `globalThis.fetch`, allowing you to call `fetch` directly inside a `"use workflow"` function. The call executes as a step with full Node.js runtime access. +Makes HTTP requests from within a workflow. This is a hoisted `"use step"` wrapper over `globalThis.fetch`, allowing you to call `fetch` directly inside a `"use workflow"` function. Because it runs as a step, thrown request failures follow the normal step retry policy, while ordinary HTTP responses (including `4xx` and `5xx`) are returned as normal `Response` objects. This is useful when you need to call external APIs or services from within your workflow. -`fetch` is a `"use step"` wrapper and should be called directly inside workflow functions. It does not include built-in retry logic — use a custom step with [`RetryableError`](/docs/api-reference/workflow/retryable-error) for retry control. +`fetch` is a `"use step"` wrapper and should be called directly inside workflow functions. It does not add HTTP-status-aware retry behavior on top of the standard `fetch` API. If you want retries to depend on `response.status`, headers, or body content, wrap `globalThis.fetch` in your own `"use step"` function and throw [`FatalError`](/docs/api-reference/workflow/fatal-error) or [`RetryableError`](/docs/api-reference/workflow/retryable-error) yourself. ```typescript lineNumbers @@ -82,9 +82,9 @@ async function apiWorkflow() { } ``` -We call `fetch()` with a URL and optional request options, just like the standard `fetch` API. Because `fetch` runs as a step, the workflow runtime handles serialization and replay. +We call `fetch()` with a URL and optional request options, just like the standard `fetch` API. Because `fetch` runs as a step, the workflow runtime handles serialization and replay. If the request throws, normal step retry behavior applies; if it returns a `Response`, your code decides whether that response should be treated as success, fatal failure, or retryable failure. -This API is provided as a convenience to easily use `fetch` in a workflow, but you may want to write your own `"use step"` wrapper for more control over error handling and retry logic. +This API is provided as a convenience to easily use `fetch` in a workflow, but you may want to write your own `"use step"` wrapper when retries should depend on HTTP response details such as `status`, headers, or body content. ### Customizing Fetch Behavior From 30b9bc3a90a8819b222ab54675fc555b0b385f5d Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Wed, 25 Mar 2026 00:59:32 -0600 Subject: [PATCH 06/15] docs: correct fetch example Keep the API reference aligned with the real workflow fetch wrapper so readers do not accidentally recurse into the documented helper when writing custom step-based wrappers. Ploop-Iter: 3 --- docs/content/docs/api-reference/workflow/fetch.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/api-reference/workflow/fetch.mdx b/docs/content/docs/api-reference/workflow/fetch.mdx index 29f899bb83..51e71a3b48 100644 --- a/docs/content/docs/api-reference/workflow/fetch.mdx +++ b/docs/content/docs/api-reference/workflow/fetch.mdx @@ -99,7 +99,7 @@ export async function customFetch( ) { "use step" - const response = await fetch(url, init) + const response = await globalThis.fetch(url, init) // Handle client errors (4xx) - don't retry if (response.status >= 400 && response.status < 500) { From 4f2b0acbc74d5a428735bd922170c0e430bece86 Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Wed, 25 Mar 2026 01:44:33 -0600 Subject: [PATCH 07/15] ploop: iteration 1 checkpoint Automated checkpoint commit. Ploop-Iter: 1 --- .../workflow-next/with-workflow.mdx | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/content/docs/api-reference/workflow-next/with-workflow.mdx b/docs/content/docs/api-reference/workflow-next/with-workflow.mdx index 85090227d0..e540007d61 100644 --- a/docs/content/docs/api-reference/workflow-next/with-workflow.mdx +++ b/docs/content/docs/api-reference/workflow-next/with-workflow.mdx @@ -9,6 +9,40 @@ prerequisites: Configures webpack/turbopack loaders to transform workflow code (`"use step"`/`"use workflow"` directives) +## API Signature + +### Parameters + + + +#### Options + +The second parameter accepts an optional configuration object: + +| Property | Type | Description | +|---|---|---| +| `workflows.lazyDiscovery` | `boolean` | Enable lazy discovery mode. When `true`, workflows are discovered incrementally via loader notifications instead of eagerly scanning at build time. | +| `workflows.local.port` | `number` | Override the local development server port. Sets the `PORT` environment variable. | +| `workflows.local.dataDir` | `string` | Override the local data directory path. Defaults to `'.next/workflow-data'` when running locally. | + +### Returns + +Returns an async function `(phase: string, ctx: { defaultConfig: NextConfig }) => Promise` compatible with the `next.config.ts` default export. + +### Environment Behavior + +When running locally (no `VERCEL_DEPLOYMENT_ID`): +- Sets `WORKFLOW_TARGET_WORLD` to `'local'` if not already set +- Sets `WORKFLOW_LOCAL_DATA_DIR` to `'.next/workflow-data'` if not already set + +When running on Vercel: +- Sets `WORKFLOW_TARGET_WORLD` to `'vercel'` if not already set + ## Usage To enable `"use step"` and `"use workflow"` directives while developing locally or deploying to production, wrap your `nextConfig` with `withWorkflow`. From fe9ae9946a65b3d607b1f1c01f2143d327f76b96 Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Wed, 25 Mar 2026 02:31:21 -0600 Subject: [PATCH 08/15] docs: sync withWorkflow reference Align the withWorkflow API reference with the current Next.js integration so developers are not misled about environment variables, lazy discovery behavior, or unsupported options while configuring workflow apps.\n\nPloop-Iter: 2 --- .../workflow-next/with-workflow.mdx | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/content/docs/api-reference/workflow-next/with-workflow.mdx b/docs/content/docs/api-reference/workflow-next/with-workflow.mdx index e540007d61..65e94ece34 100644 --- a/docs/content/docs/api-reference/workflow-next/with-workflow.mdx +++ b/docs/content/docs/api-reference/workflow-next/with-workflow.mdx @@ -26,9 +26,9 @@ The second parameter accepts an optional configuration object: | Property | Type | Description | |---|---|---| -| `workflows.lazyDiscovery` | `boolean` | Enable lazy discovery mode. When `true`, workflows are discovered incrementally via loader notifications instead of eagerly scanning at build time. | -| `workflows.local.port` | `number` | Override the local development server port. Sets the `PORT` environment variable. | -| `workflows.local.dataDir` | `string` | Override the local data directory path. Defaults to `'.next/workflow-data'` when running locally. | +| `workflows.lazyDiscovery` | `boolean` | Enable lazy discovery mode. Sets the `WORKFLOW_NEXT_LAZY_DISCOVERY` flag. Deferred discovery only activates on Next.js `>= 16.2.0-canary.48`; on older versions, Workflow logs a warning and falls back to eager scanning. | +| `workflows.local.port` | `number` | Override the local development server port. Sets the `PORT` environment variable when running locally (no `VERCEL_DEPLOYMENT_ID`). | +| `workflows.local.dataDir` | `string` | Typed but not currently applied by the `withWorkflow` implementation. The local data directory is always set to `'.next/workflow-data'` via the `WORKFLOW_LOCAL_DATA_DIR` environment variable. | ### Returns @@ -36,12 +36,18 @@ Returns an async function `(phase: string, ctx: { defaultConfig: NextConfig }) = ### Environment Behavior +When running locally (no `VERCEL_DEPLOYMENT_ID`) and `WORKFLOW_TARGET_WORLD` is not already set: +- Sets `WORKFLOW_TARGET_WORLD` to `'local'` +- Sets `WORKFLOW_LOCAL_DATA_DIR` to `'.next/workflow-data'` + When running locally (no `VERCEL_DEPLOYMENT_ID`): -- Sets `WORKFLOW_TARGET_WORLD` to `'local'` if not already set -- Sets `WORKFLOW_LOCAL_DATA_DIR` to `'.next/workflow-data'` if not already set +- If `workflows.local.port` is provided, sets `PORT` to that value + +When running on Vercel (`VERCEL_DEPLOYMENT_ID` is present) and `WORKFLOW_TARGET_WORLD` is not already set: +- Sets `WORKFLOW_TARGET_WORLD` to `'vercel'` -When running on Vercel: -- Sets `WORKFLOW_TARGET_WORLD` to `'vercel'` if not already set +During the development server phase (`phase-development-server`): +- Sets `WORKFLOW_PUBLIC_MANIFEST` to `'1'` if not already set ## Usage From b0cc86b5cd94b601740f12572acb9d952274f408 Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Wed, 25 Mar 2026 03:06:24 -0600 Subject: [PATCH 09/15] docs: align withWorkflow reference Keep the Next.js API reference aligned with the current implementation so readers do not rely on a config option shape or environment behavior that no longer reflects runtime behavior. This preserves the API audit effort by documenting the typed-but-ignored local dataDir option accurately and by showing a supported workflow config example instead of an empty placeholder. Ploop-Iter: 3 --- .../api-reference/workflow-next/with-workflow.mdx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/api-reference/workflow-next/with-workflow.mdx b/docs/content/docs/api-reference/workflow-next/with-workflow.mdx index 65e94ece34..f187dd142f 100644 --- a/docs/content/docs/api-reference/workflow-next/with-workflow.mdx +++ b/docs/content/docs/api-reference/workflow-next/with-workflow.mdx @@ -28,7 +28,7 @@ The second parameter accepts an optional configuration object: |---|---|---| | `workflows.lazyDiscovery` | `boolean` | Enable lazy discovery mode. Sets the `WORKFLOW_NEXT_LAZY_DISCOVERY` flag. Deferred discovery only activates on Next.js `>= 16.2.0-canary.48`; on older versions, Workflow logs a warning and falls back to eager scanning. | | `workflows.local.port` | `number` | Override the local development server port. Sets the `PORT` environment variable when running locally (no `VERCEL_DEPLOYMENT_ID`). | -| `workflows.local.dataDir` | `string` | Typed but not currently applied by the `withWorkflow` implementation. The local data directory is always set to `'.next/workflow-data'` via the `WORKFLOW_LOCAL_DATA_DIR` environment variable. | +| `workflows.local.dataDir` | `string` | Currently typed but ignored by `withWorkflow()`. In local mode, when `WORKFLOW_TARGET_WORLD` is unset, the implementation hardcodes `WORKFLOW_LOCAL_DATA_DIR` to `'.next/workflow-data'`. | ### Returns @@ -61,8 +61,14 @@ const nextConfig: NextConfig = { // … rest of your Next.js config }; -// not required but allows configuring workflow options -const workflowConfig = {} +// optional, but this shows the actual supported shape +const workflowConfig = { + workflows: { + local: { + port: 3001, + }, + }, +}; export default withWorkflow(nextConfig, workflowConfig); // [!code highlight] ``` From 8c44c77188a0e3b790494816aad5928ec3e49d62 Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Wed, 25 Mar 2026 03:54:09 -0600 Subject: [PATCH 10/15] docs: sync workflow API reference Keep the workflow API reference aligned with the current core package so developers do not rely on stale runtime constraints or examples. Document the current workflow-only, step-only, and shared API boundaries to reduce misuse of context-sensitive helpers. Ploop-Iter: 1 --- .../workflow/get-step-metadata.mdx | 9 +++++++ .../docs/api-reference/workflow/index.mdx | 27 ++++++++++++------- .../docs/api-reference/workflow/sleep.mdx | 6 ++--- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/docs/content/docs/api-reference/workflow/get-step-metadata.mdx b/docs/content/docs/api-reference/workflow/get-step-metadata.mdx index 27cdfaa754..a42affd85e 100644 --- a/docs/content/docs/api-reference/workflow/get-step-metadata.mdx +++ b/docs/content/docs/api-reference/workflow/get-step-metadata.mdx @@ -74,6 +74,15 @@ export default getStepMetadata;`} ### Returns +Returns a `StepMetadata` object with the following fields: + +| Field | Type | Description | +| --- | --- | --- | +| `stepName` | `string` | The name of the current step. | +| `stepId` | `string` | Unique identifier for the current step execution. Useful as part of an idempotency key. | +| `stepStartedAt` | `Date` | Timestamp when the current step started. | +| `attempt` | `number` | The number of times the current step has been executed. Increases with each retry. | + - - A function that returns context about the current workflow execution. - - Sleeping workflows for a specified duration. Deterministic and replay-safe. + Suspend a workflow for a specified duration or until a date. Deterministic and replay-safe. Make HTTP requests from within a workflow. Runs as a step under the hood. @@ -41,18 +38,28 @@ These functions are available inside `"use workflow"` functions: Create a webhook with a randomly generated token and public HTTP endpoint. + + +## Workflow and Step APIs + +These functions can be called from either `"use workflow"` or `"use step"` functions: + + + + Return context about the current workflow execution. + - Access the current workflow run's default stream. + Access the current workflow run's stream. -## Step Functions +## Step-only APIs -These functions are available inside `"use step"` functions: +These functions must be called inside `"use step"` functions: - A function that returns context about the current step execution. + Return context about the current step execution. diff --git a/docs/content/docs/api-reference/workflow/sleep.mdx b/docs/content/docs/api-reference/workflow/sleep.mdx index 556804743e..92cade0753 100644 --- a/docs/content/docs/api-reference/workflow/sleep.mdx +++ b/docs/content/docs/api-reference/workflow/sleep.mdx @@ -75,13 +75,13 @@ async function testWorkflow() { ### Sleeping Until an End Date -You can specify a future `Date` object for `sleep` to suspend the workflow until a specific date. +Pass a `Date` into the workflow and sleep until that exact timestamp. ```typescript lineNumbers import { sleep } from "workflow" -async function testWorkflow() { +async function testWorkflow(wakeAt: Date) { "use workflow" - await sleep(new Date(Date.now() + 10_000)) // [!code highlight] + await sleep(wakeAt) // [!code highlight] } ``` From 5750e73bcdeeb08a721e84f0ed0c8698231bbdb3 Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Wed, 25 Mar 2026 04:38:11 -0600 Subject: [PATCH 11/15] docs: clarify workflow API overview Align the workflow package overview with the current hook API boundaries so readers are not misled about where hook helpers can be created or resumed. Ploop-Iter: 2 --- .../content/docs/api-reference/workflow/index.mdx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/content/docs/api-reference/workflow/index.mdx b/docs/content/docs/api-reference/workflow/index.mdx index ef6b3391a3..404812d033 100644 --- a/docs/content/docs/api-reference/workflow/index.mdx +++ b/docs/content/docs/api-reference/workflow/index.mdx @@ -30,10 +30,7 @@ These functions must be called inside `"use workflow"` functions: Make HTTP requests from within a workflow. Runs as a step under the hood. - Create a hook with a deterministic token, resumed server-side via `resumeHook()`. - - - Type-safe hook helper with optional schema validation for consistent payload types. + Create a hook with an optional custom token, resumed server-side via `resumeHook()`. Create a webhook with a randomly generated token and public HTTP endpoint. @@ -53,6 +50,16 @@ These functions can be called from either `"use workflow"` or `"use step"` funct +## Cross-context Helpers + +These helpers span workflow and runtime contexts: + + + + Define a type-safe hook helper whose `.create()` method is workflow-only and whose `.resume()` method is runtime-side. + + + ## Step-only APIs These functions must be called inside `"use step"` functions: From f7414ff8dc198909423861ed98c52e1e848a4f01 Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Wed, 25 Mar 2026 05:09:26 -0600 Subject: [PATCH 12/15] docs: align hook API references Update the hook API reference docs so they match current runtime semantics and reduce confusion around token generation and execution-context boundaries. This keeps existing guidance accurate for users integrating hook creation and resumption across workflow and runtime code. Ploop-Iter: 3 --- .../api-reference/workflow/create-hook.mdx | 33 +++++++++++++++++-- .../api-reference/workflow/define-hook.mdx | 4 +++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/api-reference/workflow/create-hook.mdx b/docs/content/docs/api-reference/workflow/create-hook.mdx index 9ffd3330a9..df1bbfd5d5 100644 --- a/docs/content/docs/api-reference/workflow/create-hook.mdx +++ b/docs/content/docs/api-reference/workflow/create-hook.mdx @@ -10,9 +10,9 @@ related: - /docs/api-reference/workflow/create-webhook --- -Creates a deterministic-token hook primitive that can be used to resume a workflow run with arbitrary payloads. +Creates a hook primitive that can be used to suspend a workflow and later resume it with an arbitrary serializable payload. -Unlike [`createWebhook()`](/docs/api-reference/workflow/create-webhook), which always generates a random token for its public HTTP endpoint, `createHook()` lets you specify a deterministic `token` that external systems can reconstruct without prior communication. Hooks are resumed server-side via [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) and can receive any serializable payload. +Unlike [`createWebhook()`](/docs/api-reference/workflow/create-webhook), which always generates a random token for its public HTTP endpoint, `createHook()` accepts an optional custom `token`. If you omit `token`, Workflow generates a unique token automatically. Use a custom token only when the sender can deterministically reconstruct it. Hooks are resumed server-side via [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) and can receive any serializable payload. ```ts lineNumbers import { createHook } from "workflow" @@ -92,9 +92,36 @@ export async function approvalWorkflow() { } ``` +### Machine-readable logging + +Emit a structured log line when a hook is created so external systems can discover its token programmatically: + +```typescript lineNumbers +import { createHook } from "workflow" + +export async function approvalWorkflow() { + "use workflow"; + + using hook = createHook<{ approved: boolean }>(); + + console.info(JSON.stringify({ // [!code highlight] + event: "workflow.hook.created", // [!code highlight] + token: hook.token, // [!code highlight] + })); // [!code highlight] + + return await hook; +} +``` + +Example log line: + +```json +{"event":"workflow.hook.created","token":"nk_abc123"} +``` + ### Customizing Tokens -Tokens are used to identify a specific hook. You can customize the token to be more specific to a use case. +By default, Workflow generates a unique token for each hook. You can provide a custom `token` when the resuming side needs to reconstruct the token without prior communication. ```typescript lineNumbers import { createHook } from "workflow"; diff --git a/docs/content/docs/api-reference/workflow/define-hook.mdx b/docs/content/docs/api-reference/workflow/define-hook.mdx index 137da798d4..ca6b78e891 100644 --- a/docs/content/docs/api-reference/workflow/define-hook.mdx +++ b/docs/content/docs/api-reference/workflow/define-hook.mdx @@ -15,6 +15,10 @@ This is a lightweight wrapper around [`createHook()`](/docs/api-reference/workfl When a `schema` is provided, the hook is typed as `TypedHook` — the `resume()` method accepts `TInput` (the raw payload), while the workflow receives `TOutput` (the validated and potentially transformed result). Without a schema, `TOutput` defaults to `TInput`. + +`defineHook()` spans both execution contexts: call `.create()` inside `"use workflow"` code to create the hook, and call `.resume()` from runtime code (such as API routes or server actions) to resume it. Calling `.create()` outside a workflow or `.resume()` inside one will throw an error. + + We recommend using `defineHook()` over `createHook()` in production codebases for better type safety and optional runtime validation. From a8d51c14680c095ddfac9cba676f94f1e4f600b3 Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Wed, 25 Mar 2026 06:58:28 -0600 Subject: [PATCH 13/15] docs: align api reference exports Keep the API reference aligned with the current public package surface and runtime response behavior so developers are not guided toward stale imports or incorrect webhook handling assumptions. Ploop-Iter: 1 --- .../docs/api-reference/workflow-ai/index.mdx | 30 ++++++++++++++++--- .../workflow-api/resume-webhook.mdx | 9 +++--- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/docs/content/docs/api-reference/workflow-ai/index.mdx b/docs/content/docs/api-reference/workflow-ai/index.mdx index b532a1455f..8422dbf8db 100644 --- a/docs/content/docs/api-reference/workflow-ai/index.mdx +++ b/docs/content/docs/api-reference/workflow-ai/index.mdx @@ -9,13 +9,35 @@ related: Helpers for integrating AI SDK for building AI-powered workflows. -## Classes +## Root Exports (`@workflow/ai`) + +The root `@workflow/ai` entrypoint exports: - - A class for building durable AI agents that maintain state across workflow steps and handle tool execution with automatic retries. - A drop-in transport for the AI SDK for automatic reconnection in interrupted streams. + +It also re-exports the `ModelMessage` type from the AI SDK for convenience. + +## Agent (`@workflow/ai/agent`) + +The `@workflow/ai/agent` subpath exports the `DurableAgent` class and related types: + + + + A class for building durable AI agents that maintain state across workflow steps and handle tool execution with automatic retries. + + + +## Provider Subpaths + +Model provider wrappers are available as separate subpath imports: + +- `@workflow/ai/anthropic` — Anthropic provider +- `@workflow/ai/openai` — OpenAI provider +- `@workflow/ai/google` — Google provider +- `@workflow/ai/gateway` — Gateway provider +- `@workflow/ai/xai` — xAI provider +- `@workflow/ai/test` — Mock provider for testing diff --git a/docs/content/docs/api-reference/workflow-api/resume-webhook.mdx b/docs/content/docs/api-reference/workflow-api/resume-webhook.mdx index 623bb12dee..3a3cac61b3 100644 --- a/docs/content/docs/api-reference/workflow-api/resume-webhook.mdx +++ b/docs/content/docs/api-reference/workflow-api/resume-webhook.mdx @@ -50,10 +50,11 @@ showSections={['parameters']} ### Returns -Returns a `Promise` that resolves to: +Returns a `Promise` that resolves to one of three outcomes: -- A `202 Accepted` response if the webhook was created with the default mode (no `respondWith` option or a `Response` object passed to `respondWith`) -- The workflow's custom `Response` if the webhook was created with `createWebhook({ respondWith: 'manual' })` and the workflow called `request.respondWith(response)` from a step function +- A `202 Accepted` response when the webhook was created with the default mode (no `respondWith` option) +- The exact `Response` object configured with `createWebhook({ respondWith: new Response(...) })` +- The workflow's manual `Response` when the webhook was created with `createWebhook({ respondWith: 'manual' })` and a step calls `request.respondWith(response)` Throws an error if the webhook token is not found or invalid. @@ -82,7 +83,7 @@ export async function POST(request: Request) { try { const response = await resumeWebhook(token, request); // [!code highlight] - return response; // Returns the workflow's custom response + return response; // May be 202 Accepted, a configured static Response, or a manual workflow response } catch (error) { return new Response("Webhook not found", { status: 404 }); } From eb161c798d2089472c33bfe932e1e2e225b9664f Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Mon, 6 Apr 2026 14:08:43 -0600 Subject: [PATCH 14/15] docs: address review feedback on API reference audit - Remove type signature code blocks from fatal-error.mdx and retryable-error.mdx (match pattern used by other API ref pages) - Use HookNotFoundError.is() static method instead of fragile string-based error.name checks in resume-hook.mdx examples - Distinguish 404 (hook not found) from 500 (other errors) in define-hook.mdx resume example - Remove dataDir from with-workflow.mdx options table (unused option, removal PR: #1619) --- .../docs/api-reference/workflow-api/resume-hook.mdx | 13 +++++++++---- .../api-reference/workflow-next/with-workflow.mdx | 1 - .../docs/api-reference/workflow/define-hook.mdx | 7 ++++++- .../docs/api-reference/workflow/fatal-error.mdx | 8 -------- .../docs/api-reference/workflow/retryable-error.mdx | 8 -------- 5 files changed, 15 insertions(+), 22 deletions(-) diff --git a/docs/content/docs/api-reference/workflow-api/resume-hook.mdx b/docs/content/docs/api-reference/workflow-api/resume-hook.mdx index 7ab1dd3ebd..cc94422a4e 100644 --- a/docs/content/docs/api-reference/workflow-api/resume-hook.mdx +++ b/docs/content/docs/api-reference/workflow-api/resume-hook.mdx @@ -21,6 +21,7 @@ It creates a `hook_received` event and re-queues the workflow so execution can c ```typescript lineNumbers import { resumeHook } from "workflow/api"; +import { HookNotFoundError } from "workflow/internal/errors"; export async function POST(request: Request) { const { token, data } = await request.json(); @@ -39,7 +40,7 @@ export async function POST(request: Request) { runId: hook.runId, }); } catch (error) { - if (error instanceof Error && error.name === "HookNotFoundError") { + if (HookNotFoundError.is(error)) { console.warn(JSON.stringify({ event: "workflow.hook.not_found", token, @@ -72,6 +73,7 @@ export async function POST(request: Request) { @@ -101,6 +103,7 @@ Using `resumeHook` in a basic API route to resume a hook: ```typescript lineNumbers import { resumeHook } from "workflow/api"; +import { HookNotFoundError } from "workflow/internal/errors"; export async function POST(request: Request) { const { token, data } = await request.json(); @@ -113,7 +116,7 @@ export async function POST(request: Request) { runId: hook.runId }); } catch (error) { - if (error instanceof Error && error.name === "HookNotFoundError") { + if (HookNotFoundError.is(error)) { return Response.json({ error: "Hook not found" }, { status: 404 }); } return Response.json({ error: "Failed to resume hook" }, { status: 500 }); @@ -127,6 +130,7 @@ Defining a payload type and using `resumeHook` to resume a hook with type safety ```typescript lineNumbers import { resumeHook } from "workflow/api"; +import { HookNotFoundError } from "workflow/internal/errors"; type ApprovalPayload = { approved: boolean; @@ -144,7 +148,7 @@ export async function POST(request: Request) { return Response.json({ runId: hook.runId }); } catch (error) { - if (error instanceof Error && error.name === "HookNotFoundError") { + if (HookNotFoundError.is(error)) { return Response.json({ error: "Hook not found" }, { status: 404 }); } return Response.json({ error: "Failed to resume hook" }, { status: 500 }); @@ -160,6 +164,7 @@ Using `resumeHook` in Next.js server actions to resume a hook: "use server"; import { resumeHook } from "workflow/api"; +import { HookNotFoundError } from "workflow/internal/errors"; export async function approveRequest(token: string, approved: boolean) { try { @@ -190,7 +195,7 @@ export async function POST(request: Request) { const resumedHook = await resumeHook(hook, data); // [!code highlight] return Response.json({ success: true, runId: resumedHook.runId }); } catch (error) { - if (error instanceof Error && error.name === "HookNotFoundError") { + if (HookNotFoundError.is(error)) { return Response.json({ error: "Hook not found" }, { status: 404 }); } return Response.json({ error: "Failed to resume hook" }, { status: 500 }); diff --git a/docs/content/docs/api-reference/workflow-next/with-workflow.mdx b/docs/content/docs/api-reference/workflow-next/with-workflow.mdx index f187dd142f..cfa94fe04c 100644 --- a/docs/content/docs/api-reference/workflow-next/with-workflow.mdx +++ b/docs/content/docs/api-reference/workflow-next/with-workflow.mdx @@ -28,7 +28,6 @@ The second parameter accepts an optional configuration object: |---|---|---| | `workflows.lazyDiscovery` | `boolean` | Enable lazy discovery mode. Sets the `WORKFLOW_NEXT_LAZY_DISCOVERY` flag. Deferred discovery only activates on Next.js `>= 16.2.0-canary.48`; on older versions, Workflow logs a warning and falls back to eager scanning. | | `workflows.local.port` | `number` | Override the local development server port. Sets the `PORT` environment variable when running locally (no `VERCEL_DEPLOYMENT_ID`). | -| `workflows.local.dataDir` | `string` | Currently typed but ignored by `withWorkflow()`. In local mode, when `WORKFLOW_TARGET_WORLD` is unset, the implementation hardcodes `WORKFLOW_LOCAL_DATA_DIR` to `'.next/workflow-data'`. | ### Returns diff --git a/docs/content/docs/api-reference/workflow/define-hook.mdx b/docs/content/docs/api-reference/workflow/define-hook.mdx index ca6b78e891..74442eb196 100644 --- a/docs/content/docs/api-reference/workflow/define-hook.mdx +++ b/docs/content/docs/api-reference/workflow/define-hook.mdx @@ -103,6 +103,8 @@ export async function workflowWithApproval() { 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. The `resume()` method throws an error if the hook is not found or if schema validation fails. ```typescript lineNumbers +import { HookNotFoundError } from "workflow/internal/errors"; + // Use the same defined hook to resume export async function POST(request: Request) { const { token, approved, comment } = await request.json(); @@ -116,7 +118,10 @@ export async function POST(request: Request) { return Response.json({ success: true, runId: result.runId }); } catch (error) { - return Response.json({ error: "Hook not found or invalid payload" }, { status: 400 }); + if (HookNotFoundError.is(error)) { // [!code highlight] + return Response.json({ error: "Hook not found" }, { status: 404 }); // [!code highlight] + } // [!code highlight] + return Response.json({ error: "Resume failed" }, { status: 500 }); } } ``` diff --git a/docs/content/docs/api-reference/workflow/fatal-error.mdx b/docs/content/docs/api-reference/workflow/fatal-error.mdx index f8167a3b90..57b5f53ecc 100644 --- a/docs/content/docs/api-reference/workflow/fatal-error.mdx +++ b/docs/content/docs/api-reference/workflow/fatal-error.mdx @@ -31,10 +31,6 @@ async function fallibleStep() { ### Constructor -```typescript -new FatalError(message: string) -``` - | Parameter | Type | Description | | --------- | -------- | ----------------- | | `message` | `string` | The error message | @@ -49,8 +45,4 @@ new FatalError(message: string) #### `FatalError.is(value)` -```typescript -FatalError.is(value: unknown): value is FatalError -``` - Returns `true` if `value` is a `FatalError` instance. Useful for checking caught errors without `instanceof`. diff --git a/docs/content/docs/api-reference/workflow/retryable-error.mdx b/docs/content/docs/api-reference/workflow/retryable-error.mdx index a79bcb383f..a383b84a21 100644 --- a/docs/content/docs/api-reference/workflow/retryable-error.mdx +++ b/docs/content/docs/api-reference/workflow/retryable-error.mdx @@ -35,10 +35,6 @@ The difference between `Error` and `RetryableError` may not be entirely obvious, ### Constructor -```typescript -new RetryableError(message: string, options?: RetryableErrorOptions) -``` - | Parameter | Type | Description | | --------- | ------------------------ | ----------------------------------- | | `message` | `string` | The error message | @@ -60,10 +56,6 @@ new RetryableError(message: string, options?: RetryableErrorOptions) #### `RetryableError.is(value)` -```typescript -RetryableError.is(value: unknown): value is RetryableError -``` - Returns `true` if `value` is a `RetryableError` instance. Useful for checking caught errors without `instanceof`. ## Examples From 5627d290df866541e3c33eb6d1f79a928f4603a0 Mon Sep 17 00:00:00 2001 From: John Lindquist Date: Tue, 28 Apr 2026 14:58:39 -0600 Subject: [PATCH 15/15] docs: tighten API reference verification - Replace internal error imports in docs examples with public workflow/errors - Remove brittle source-string assertions from docs runtime checks - Add API reference public contract coverage against exported declarations - Map documented workflow runtime, Next, and utils imports in docs typechecking - Align stale invalid-start error text with the runtime message Oracle-Session: pr-docs-final-review --- .../workflow-api/get-hook-by-token.mdx | 6 +- .../workflow-api/resume-hook.mdx | 11 +- .../docs/api-reference/workflow-api/start.mdx | 2 +- .../api-reference/workflow/define-hook.mdx | 17 +- docs/content/docs/getting-started/astro.mdx | 2 +- docs/content/docs/getting-started/express.mdx | 2 +- docs/content/docs/getting-started/fastify.mdx | 2 +- docs/content/docs/getting-started/hono.mdx | 2 +- docs/content/docs/getting-started/nestjs.mdx | 2 +- docs/content/docs/getting-started/next.mdx | 2 +- docs/content/docs/getting-started/nitro.mdx | 2 +- docs/content/docs/getting-started/nuxt.mdx | 2 +- .../docs/getting-started/sveltekit.mdx | 2 +- docs/content/docs/getting-started/vite.mdx | 2 +- .../api-reference-public-contract.test.ts | 403 ++++++++++++++++++ .../src/__tests__/hook-runtime-docs.test.ts | 6 - packages/docs-typecheck/src/type-checker.ts | 5 + 17 files changed, 430 insertions(+), 40 deletions(-) create mode 100644 packages/docs-typecheck/src/__tests__/api-reference-public-contract.test.ts diff --git a/docs/content/docs/api-reference/workflow-api/get-hook-by-token.mdx b/docs/content/docs/api-reference/workflow-api/get-hook-by-token.mdx index 17a180b40b..2bc170ee07 100644 --- a/docs/content/docs/api-reference/workflow-api/get-hook-by-token.mdx +++ b/docs/content/docs/api-reference/workflow-api/get-hook-by-token.mdx @@ -1,13 +1,13 @@ --- title: getHookByToken -description: Retrieve hook details and workflow run information by token. +description: Retrieve hook details, metadata, and associated run ID by token. type: reference -summary: Use getHookByToken to look up a hook's metadata and associated workflow run before resuming it. +summary: Use getHookByToken to look up a hook's metadata and associated run ID before resuming it. prerequisites: - /docs/foundations/hooks --- -Retrieves a hook by its unique token, returning the associated workflow run information and any metadata that was set when the hook was created. Metadata is automatically hydrated (deserialized) before being returned, so you receive the original values rather than raw serialized data. This function is useful for inspecting hook details before deciding whether to resume a workflow. +Retrieves a hook by its unique token, returning hook details, the associated run ID, and any metadata that was set when the hook was created. Metadata is automatically hydrated (deserialized) before being returned, so you receive the original values rather than raw serialized data. This function is useful for inspecting hook details before deciding whether to resume a workflow. `getHookByToken` is a runtime function that must be called from outside a workflow function. diff --git a/docs/content/docs/api-reference/workflow-api/resume-hook.mdx b/docs/content/docs/api-reference/workflow-api/resume-hook.mdx index cc94422a4e..8530c9ac71 100644 --- a/docs/content/docs/api-reference/workflow-api/resume-hook.mdx +++ b/docs/content/docs/api-reference/workflow-api/resume-hook.mdx @@ -21,7 +21,7 @@ It creates a `hook_received` event and re-queues the workflow so execution can c ```typescript lineNumbers import { resumeHook } from "workflow/api"; -import { HookNotFoundError } from "workflow/internal/errors"; +import { HookNotFoundError } from "workflow/errors"; export async function POST(request: Request) { const { token, data } = await request.json(); @@ -73,7 +73,7 @@ export async function POST(request: Request) { @@ -103,7 +103,7 @@ Using `resumeHook` in a basic API route to resume a hook: ```typescript lineNumbers import { resumeHook } from "workflow/api"; -import { HookNotFoundError } from "workflow/internal/errors"; +import { HookNotFoundError } from "workflow/errors"; export async function POST(request: Request) { const { token, data } = await request.json(); @@ -130,7 +130,7 @@ Defining a payload type and using `resumeHook` to resume a hook with type safety ```typescript lineNumbers import { resumeHook } from "workflow/api"; -import { HookNotFoundError } from "workflow/internal/errors"; +import { HookNotFoundError } from "workflow/errors"; type ApprovalPayload = { approved: boolean; @@ -164,7 +164,7 @@ Using `resumeHook` in Next.js server actions to resume a hook: "use server"; import { resumeHook } from "workflow/api"; -import { HookNotFoundError } from "workflow/internal/errors"; +import { HookNotFoundError } from "workflow/errors"; export async function approveRequest(token: string, approved: boolean) { try { @@ -182,6 +182,7 @@ Instead of a token string, you can pass a `Hook` object directly (e.g. one retur ```typescript lineNumbers import { getHookByToken, resumeHook } from "workflow/api"; +import { HookNotFoundError } from "workflow/errors"; export async function POST(request: Request) { const { token, data } = await request.json(); diff --git a/docs/content/docs/api-reference/workflow-api/start.mdx b/docs/content/docs/api-reference/workflow-api/start.mdx index a72dde3681..a88bb10aa8 100644 --- a/docs/content/docs/api-reference/workflow-api/start.mdx +++ b/docs/content/docs/api-reference/workflow-api/start.mdx @@ -57,7 +57,7 @@ Learn more about [`WorkflowReadableStreamOptions`](/docs/api-reference/workflow- * When `deploymentId` is provided, the argument types and return type become `unknown` since there is no guarantee the workflow function's types will be consistent across different deployments. -If `start()` throws `'start' received an invalid workflow function. Ensure the Workflow Development Kit is configured correctly and the function includes a 'use workflow' directive.`, the passed function was not transformed as a workflow. The two most common causes are a missing `"use workflow"` directive or missing framework integration. See [start-invalid-workflow-function](/docs/errors/start-invalid-workflow-function). +If `start()` throws `'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive.`, the passed function was not transformed as a workflow. The two most common causes are a missing `"use workflow"` directive or missing framework integration. See [start-invalid-workflow-function](/docs/errors/start-invalid-workflow-function). ## Examples diff --git a/docs/content/docs/api-reference/workflow/define-hook.mdx b/docs/content/docs/api-reference/workflow/define-hook.mdx index 74442eb196..4bf6d7fb71 100644 --- a/docs/content/docs/api-reference/workflow/define-hook.mdx +++ b/docs/content/docs/api-reference/workflow/define-hook.mdx @@ -54,20 +54,7 @@ showSections={['parameters']} { - /** - -* Creates a new hook with the defined output type. - */ - create: (options?: HookOptions) => Hook; - - /** - -* Resumes a hook by sending a payload with the defined input type. -* Throws an error if the hook is not found or if schema validation fails. - */ - resume: (token: string, payload: TInput) => Promise; -} +import type { TypedHook } from "workflow"; export default TypedHook;`} /> @@ -103,7 +90,7 @@ export async function workflowWithApproval() { 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. The `resume()` method throws an error if the hook is not found or if schema validation fails. ```typescript lineNumbers -import { HookNotFoundError } from "workflow/internal/errors"; +import { HookNotFoundError } from "workflow/errors"; // Use the same defined hook to resume export async function POST(request: Request) { diff --git a/docs/content/docs/getting-started/astro.mdx b/docs/content/docs/getting-started/astro.mdx index 37012bc810..9acdaec62c 100644 --- a/docs/content/docs/getting-started/astro.mdx +++ b/docs/content/docs/getting-started/astro.mdx @@ -242,7 +242,7 @@ Additionally, check the [Deploying](/docs/deploying) section to learn how your w If you see this error: ``` -'start' received an invalid workflow function. Ensure the Workflow Development Kit is configured correctly and the function includes a 'use workflow' directive. +'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive. ``` Check both of these first: diff --git a/docs/content/docs/getting-started/express.mdx b/docs/content/docs/getting-started/express.mdx index cc50a2aedd..b643a60293 100644 --- a/docs/content/docs/getting-started/express.mdx +++ b/docs/content/docs/getting-started/express.mdx @@ -269,7 +269,7 @@ Check the [Deploying](/docs/deploying) section to learn how your workflows can b If you see this error: ``` -'start' received an invalid workflow function. Ensure the Workflow Development Kit is configured correctly and the function includes a 'use workflow' directive. +'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive. ``` Check both of these first: diff --git a/docs/content/docs/getting-started/fastify.mdx b/docs/content/docs/getting-started/fastify.mdx index 15ccd343ae..bc7bacb38d 100644 --- a/docs/content/docs/getting-started/fastify.mdx +++ b/docs/content/docs/getting-started/fastify.mdx @@ -256,7 +256,7 @@ Check the [Deploying](/docs/deploying) section to learn how your workflows can b If you see this error: ``` -'start' received an invalid workflow function. Ensure the Workflow Development Kit is configured correctly and the function includes a 'use workflow' directive. +'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive. ``` Check both of these first: diff --git a/docs/content/docs/getting-started/hono.mdx b/docs/content/docs/getting-started/hono.mdx index 55a56b7d9b..3ea2f4d2b4 100644 --- a/docs/content/docs/getting-started/hono.mdx +++ b/docs/content/docs/getting-started/hono.mdx @@ -251,7 +251,7 @@ Check the [Deploying](/docs/deploying) section to learn how your workflows can b If you see this error: ``` -'start' received an invalid workflow function. Ensure the Workflow Development Kit is configured correctly and the function includes a 'use workflow' directive. +'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive. ``` Check both of these first: diff --git a/docs/content/docs/getting-started/nestjs.mdx b/docs/content/docs/getting-started/nestjs.mdx index afe193c0e9..6e1d0fecae 100644 --- a/docs/content/docs/getting-started/nestjs.mdx +++ b/docs/content/docs/getting-started/nestjs.mdx @@ -396,7 +396,7 @@ WorkflowModule.forRoot({ If you see this error: ``` -'start' received an invalid workflow function. Ensure the Workflow Development Kit is configured correctly and the function includes a 'use workflow' directive. +'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive. ``` Check both of these first: diff --git a/docs/content/docs/getting-started/next.mdx b/docs/content/docs/getting-started/next.mdx index c9785d4aec..32c4de3d4c 100644 --- a/docs/content/docs/getting-started/next.mdx +++ b/docs/content/docs/getting-started/next.mdx @@ -310,7 +310,7 @@ Without this configuration, you may experience intermittent issues where workflo If you see this error: ``` -'start' received an invalid workflow function. Ensure the Workflow Development Kit is configured correctly and the function includes a 'use workflow' directive. +'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive. ``` Check both of these first: diff --git a/docs/content/docs/getting-started/nitro.mdx b/docs/content/docs/getting-started/nitro.mdx index 688b965ebe..fdcf2d20ad 100644 --- a/docs/content/docs/getting-started/nitro.mdx +++ b/docs/content/docs/getting-started/nitro.mdx @@ -235,7 +235,7 @@ Check the [Deploying](/docs/deploying) section to learn how your workflows can b If you see this error: ``` -'start' received an invalid workflow function. Ensure the Workflow Development Kit is configured correctly and the function includes a 'use workflow' directive. +'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive. ``` Check both of these first: diff --git a/docs/content/docs/getting-started/nuxt.mdx b/docs/content/docs/getting-started/nuxt.mdx index fa50472c40..0d2bb3bb36 100644 --- a/docs/content/docs/getting-started/nuxt.mdx +++ b/docs/content/docs/getting-started/nuxt.mdx @@ -236,7 +236,7 @@ Check the [Deploying](/docs/deploying) section to learn how your workflows can b If you see this error: ``` -'start' received an invalid workflow function. Ensure the Workflow Development Kit is configured correctly and the function includes a 'use workflow' directive. +'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive. ``` Check both of these first: diff --git a/docs/content/docs/getting-started/sveltekit.mdx b/docs/content/docs/getting-started/sveltekit.mdx index 9dd77e158b..636c9120eb 100644 --- a/docs/content/docs/getting-started/sveltekit.mdx +++ b/docs/content/docs/getting-started/sveltekit.mdx @@ -235,7 +235,7 @@ Check the [Deploying](/docs/deploying) section to learn how your workflows can b If you see this error: ``` -'start' received an invalid workflow function. Ensure the Workflow Development Kit is configured correctly and the function includes a 'use workflow' directive. +'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive. ``` Check both of these first: diff --git a/docs/content/docs/getting-started/vite.mdx b/docs/content/docs/getting-started/vite.mdx index 983825513d..f7ff67dc13 100644 --- a/docs/content/docs/getting-started/vite.mdx +++ b/docs/content/docs/getting-started/vite.mdx @@ -241,7 +241,7 @@ Check the [Deploying](/docs/deploying) section to learn how your workflows can b If you see this error: ``` -'start' received an invalid workflow function. Ensure the Workflow Development Kit is configured correctly and the function includes a 'use workflow' directive. +'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive. ``` Check both of these first: diff --git a/packages/docs-typecheck/src/__tests__/api-reference-public-contract.test.ts b/packages/docs-typecheck/src/__tests__/api-reference-public-contract.test.ts new file mode 100644 index 0000000000..7001ab3307 --- /dev/null +++ b/packages/docs-typecheck/src/__tests__/api-reference-public-contract.test.ts @@ -0,0 +1,403 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { globSync } from 'glob'; +import ts from 'typescript'; +import { describe, expect, it } from 'vitest'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(__dirname, '../../../..'); + +type PackageJson = { + name?: string; + exports?: Record | string; +}; + +type DocumentedContract = { + file: string; + module: string; + values?: string[]; + types?: string[]; +}; + +const documentedContracts: DocumentedContract[] = [ + { + file: 'docs/content/docs/api-reference/workflow/create-hook.mdx', + module: 'workflow', + values: ['createHook'], + types: ['HookOptions', 'Hook'], + }, + { + file: 'docs/content/docs/api-reference/workflow/create-webhook.mdx', + module: 'workflow', + values: ['createWebhook'], + types: ['RequestWithResponse', 'WebhookOptions'], + }, + { + file: 'docs/content/docs/api-reference/workflow/sleep.mdx', + module: 'workflow', + values: ['sleep'], + }, + { + file: 'docs/content/docs/api-reference/workflow/fetch.mdx', + module: 'workflow', + values: ['fetch'], + }, + { + file: 'docs/content/docs/api-reference/workflow/fatal-error.mdx', + module: 'workflow', + values: ['FatalError'], + }, + { + file: 'docs/content/docs/api-reference/workflow/retryable-error.mdx', + module: 'workflow', + values: ['RetryableError'], + types: ['RetryableErrorOptions'], + }, + { + file: 'docs/content/docs/api-reference/workflow/get-workflow-metadata.mdx', + module: 'workflow', + values: ['getWorkflowMetadata'], + types: ['WorkflowMetadata'], + }, + { + file: 'docs/content/docs/api-reference/workflow/get-step-metadata.mdx', + module: 'workflow', + values: ['getStepMetadata'], + types: ['StepMetadata'], + }, + { + file: 'docs/content/docs/api-reference/workflow/define-hook.mdx', + module: 'workflow', + values: ['defineHook'], + types: ['TypedHook'], + }, + { + file: 'docs/content/docs/api-reference/workflow-api/resume-hook.mdx', + module: 'workflow/api', + values: ['resumeHook'], + }, + { + file: 'docs/content/docs/api-reference/workflow-api/resume-webhook.mdx', + module: 'workflow/api', + values: ['resumeWebhook'], + }, + { + file: 'docs/content/docs/api-reference/workflow-api/get-hook-by-token.mdx', + module: 'workflow/api', + values: ['getHookByToken'], + }, + { + file: 'docs/content/docs/api-reference/workflow-api/start.mdx', + module: 'workflow/api', + values: ['Run', 'start'], + types: ['StartOptions'], + }, + { + file: 'docs/content/docs/api-reference/workflow-api/get-run.mdx', + module: 'workflow/api', + values: ['Run', 'getRun'], + types: [ + 'StopSleepOptions', + 'StopSleepResult', + 'WorkflowReadableStream', + 'WorkflowReadableStreamOptions', + ], + }, + { + file: 'docs/content/docs/api-reference/workflow-api/get-world.mdx', + module: 'workflow/runtime', + values: ['getWorld'], + }, + { + file: 'docs/content/docs/api-reference/workflow-api/get-world.mdx', + module: '@workflow/world', + types: ['World'], + }, + { + file: 'docs/content/docs/api-reference/workflow-next/with-workflow.mdx', + module: 'workflow/next', + values: ['withWorkflow'], + }, + { + file: 'docs/content/docs/api-reference/workflow-ai/index.mdx', + module: '@workflow/ai', + values: ['WorkflowChatTransport'], + types: ['ModelMessage'], + }, + { + file: 'docs/content/docs/api-reference/workflow-api/resume-hook.mdx', + module: 'workflow/errors', + values: ['HookNotFoundError'], + }, +]; + +const extraPublicModules = [ + '@workflow/ai', + '@workflow/ai/agent', + '@workflow/ai/anthropic', + '@workflow/ai/gateway', + '@workflow/ai/google', + '@workflow/ai/openai', + '@workflow/ai/xai', + '@workflow/ai/test', + '@workflow/next', + '@workflow/utils/parse-name', +]; + +const publicModules = Array.from( + new Set([ + ...documentedContracts.map((contract) => contract.module), + ...extraPublicModules, + ]) +); + +function readJson(relativePath: string): PackageJson { + return JSON.parse( + fs.readFileSync(path.join(repoRoot, relativePath), 'utf-8') + ); +} + +function getPackageRoots(): Map { + return new Map( + globSync(path.join(repoRoot, 'packages/*/package.json')) + .map((packageJsonPath) => { + const packageJson = JSON.parse( + fs.readFileSync(packageJsonPath, 'utf-8') + ) as PackageJson; + if (!packageJson.name) return undefined; + return [ + packageJson.name, + path.relative(repoRoot, path.dirname(packageJsonPath)), + ] as const; + }) + .filter((entry): entry is readonly [string, string] => Boolean(entry)) + ); +} + +const packageRoots = getPackageRoots(); + +function splitPackageSpecifier(specifier: string): { + packageName: string; + subpath: string; +} { + if (specifier.startsWith('@')) { + const [scope, name, ...rest] = specifier.split('/'); + return { + packageName: `${scope}/${name}`, + subpath: rest.length === 0 ? '.' : `./${rest.join('/')}`, + }; + } + + const [name, ...rest] = specifier.split('/'); + return { + packageName: name, + subpath: rest.length === 0 ? '.' : `./${rest.join('/')}`, + }; +} + +function getExportEntry(packageJson: PackageJson, subpath: string): unknown { + if (typeof packageJson.exports === 'string') { + return subpath === '.' ? packageJson.exports : undefined; + } + return packageJson.exports?.[subpath]; +} + +function getExportTarget(entry: unknown): string | undefined { + if (typeof entry === 'string') return entry; + if (!entry || typeof entry !== 'object') return undefined; + + const record = entry as Record; + for (const key of ['types', 'default', 'workflow', 'require']) { + const value = record[key]; + if (typeof value === 'string') return value; + } +} + +function declarationCandidates(target: string): string[] { + if (target.endsWith('.d.ts') || target.endsWith('.d.cts')) return [target]; + if (target.endsWith('.cjs')) return [target.replace(/\.cjs$/, '.d.cts')]; + if (target.endsWith('.js')) return [target.replace(/\.js$/, '.d.ts')]; + return [`${target}.d.ts`, path.join(target, 'index.d.ts')]; +} + +function modulePathMapping(specifier: string): string[] { + const { packageName, subpath } = splitPackageSpecifier(specifier); + const packageRoot = packageRoots.get(packageName); + if (!packageRoot) + throw new Error(`No package root configured for ${specifier}`); + + const packageJson = readJson(path.join(packageRoot, 'package.json')); + const entry = getExportEntry(packageJson, subpath); + const target = getExportTarget(entry); + if (!target) throw new Error(`No export target for ${specifier}`); + + return declarationCandidates(target).map((candidate) => + path.join(repoRoot, packageRoot, candidate) + ); +} + +function formatDiagnostics(diagnostics: readonly ts.Diagnostic[]): string { + return diagnostics + .map((diagnostic) => { + const message = ts.flattenDiagnosticMessageText( + diagnostic.messageText, + '\n' + ); + if (!diagnostic.file || diagnostic.start === undefined) { + return `TS${diagnostic.code}: ${message}`; + } + + const { line, character } = diagnostic.file.getLineAndCharacterOfPosition( + diagnostic.start + ); + return `${path.basename(diagnostic.file.fileName)}:${line + 1}:${ + character + 1 + } TS${diagnostic.code}: ${message}`; + }) + .join('\n'); +} + +function compileVirtualContract(source: string): readonly ts.Diagnostic[] { + const contractPath = path.join(repoRoot, '__api_reference_contract__.ts'); + const virtualFiles = new Map([[contractPath, source]]); + + const compilerOptions: ts.CompilerOptions = { + target: ts.ScriptTarget.ES2022, + module: ts.ModuleKind.ESNext, + moduleResolution: ts.ModuleResolutionKind.Bundler, + moduleDetection: ts.ModuleDetectionKind.Force, + lib: [ + 'lib.es2022.d.ts', + 'lib.dom.d.ts', + 'lib.dom.iterable.d.ts', + 'lib.dom.asynciterable.d.ts', + 'lib.esnext.disposable.d.ts', + ], + strict: true, + skipLibCheck: true, + noEmit: true, + esModuleInterop: true, + allowSyntheticDefaultImports: true, + baseUrl: repoRoot, + paths: Object.fromEntries( + publicModules.map((specifier) => [ + specifier, + modulePathMapping(specifier), + ]) + ), + types: ['node'], + typeRoots: [ + path.join(__dirname, '../../node_modules/@types'), + path.join(repoRoot, 'node_modules/@types'), + ], + }; + + const defaultHost = ts.createCompilerHost(compilerOptions); + const host: ts.CompilerHost = { + ...defaultHost, + getSourceFile: (fileName, languageVersion) => { + const content = virtualFiles.get(fileName); + if (content !== undefined) { + return ts.createSourceFile(fileName, content, languageVersion, true); + } + return defaultHost.getSourceFile(fileName, languageVersion); + }, + fileExists: (fileName) => + virtualFiles.has(fileName) || defaultHost.fileExists(fileName), + readFile: (fileName) => + virtualFiles.get(fileName) ?? defaultHost.readFile(fileName), + getCurrentDirectory: () => repoRoot, + }; + + const program = ts.createProgram([contractPath], compilerOptions, host); + return ts.getPreEmitDiagnostics(program); +} + +describe('API reference public contract', () => { + it('covers API reference files that exist', () => { + for (const contract of documentedContracts) { + expect( + fs.existsSync(path.join(repoRoot, contract.file)), + `${contract.file} should exist` + ).toBe(true); + } + }); + + it('documents import paths that exist in package exports and declaration output', () => { + for (const specifier of publicModules) { + const candidates = modulePathMapping(specifier); + expect( + candidates.some((candidate) => fs.existsSync(candidate)), + `${specifier} should resolve to one of:\n${candidates.join('\n')}` + ).toBe(true); + } + }); + + it('documents symbols that are exported from their public modules', () => { + const imports = documentedContracts + .flatMap((contract, index) => { + const values = (contract.values ?? []).map((exportName) => { + const alias = `contract_${index}_${exportName}`; + return `import { ${exportName} as ${alias} } from '${contract.module}';`; + }); + const types = (contract.types ?? []).map((exportName) => { + const alias = `contract_${index}_${exportName}`; + return `import type { ${exportName} as ${alias} } from '${contract.module}';`; + }); + return [...values, ...types]; + }) + .join('\n'); + + const diagnostics = compileVirtualContract(imports); + expect(formatDiagnostics(diagnostics)).toBe(''); + }); + + it('preserves the high-risk API signatures described by the reference docs', () => { + const source = ` + import { getHookByToken, resumeHook, resumeWebhook } from 'workflow/api'; + import { getWorld } from 'workflow/runtime'; + import { createWebhook, fetch, RetryableError, sleep } from 'workflow'; + import type { Hook, World } from '@workflow/world'; + + declare const hook: Hook; + + const resumedFromToken: Promise = resumeHook('token', { ok: true }); + const resumedFromHook: Promise = resumeHook(hook, { ok: true }); + const lookedUpHook: Promise = getHookByToken('token'); + const webhookResponse: Promise = resumeWebhook( + 'token', + new Request('https://example.test') + ); + + async function manualWebhookCheck() { + const request = await createWebhook({ respondWith: 'manual' }); + await request.respondWith(new Response('ok')); + } + + async function sleepCheck() { + await sleep('10s'); + await sleep(1000); + await sleep(new Date(Date.now() + 1000)); + } + + const retryable = new RetryableError('retry', { retryAfter: '5s' }); + const retryAfter: Date = retryable.retryAfter; + const worldPromise: Promise = getWorld(); + const responsePromise: Promise = fetch('https://example.test'); + + void manualWebhookCheck; + void sleepCheck; + void retryAfter; + void worldPromise; + void responsePromise; + void resumedFromToken; + void resumedFromHook; + void lookedUpHook; + void webhookResponse; + `; + + const diagnostics = compileVirtualContract(source); + expect(formatDiagnostics(diagnostics)).toBe(''); + }); +}); diff --git a/packages/docs-typecheck/src/__tests__/hook-runtime-docs.test.ts b/packages/docs-typecheck/src/__tests__/hook-runtime-docs.test.ts index 4776caf3ee..4a4de3166d 100644 --- a/packages/docs-typecheck/src/__tests__/hook-runtime-docs.test.ts +++ b/packages/docs-typecheck/src/__tests__/hook-runtime-docs.test.ts @@ -11,7 +11,6 @@ const read = (relativePath: string) => describe('hook runtime API docs stay aligned with runtime behavior', () => { it('documents hydrated hook metadata and the hook-object resumeHook overload', () => { - const source = read('packages/core/src/runtime/resume-hook.ts'); const getHookDoc = read( 'docs/content/docs/api-reference/workflow-api/get-hook-by-token.mdx' ); @@ -19,9 +18,6 @@ describe('hook runtime API docs stay aligned with runtime behavior', () => { 'docs/content/docs/api-reference/workflow-api/resume-hook.mdx' ); - expect(source).toContain('hook.metadata = await hydrateStepArguments'); - expect(source).toContain('tokenOrHook: string | Hook'); - expect(getHookDoc).toContain( 'Metadata is automatically hydrated (deserialized)' ); @@ -34,12 +30,10 @@ describe('hook runtime API docs stay aligned with runtime behavior', () => { const apiIndexDoc = read( 'docs/content/docs/api-reference/workflow-api/index.mdx' ); - const apiEntry = read('packages/workflow/src/api.ts'); const getWorldDoc = read( 'docs/content/docs/api-reference/workflow-api/get-world.mdx' ); - expect(apiEntry).not.toContain('getWorld'); expect(getWorldDoc).toContain( 'import { getWorld } from "workflow/runtime";' ); diff --git a/packages/docs-typecheck/src/type-checker.ts b/packages/docs-typecheck/src/type-checker.ts index c0e23dce11..0d26084886 100644 --- a/packages/docs-typecheck/src/type-checker.ts +++ b/packages/docs-typecheck/src/type-checker.ts @@ -70,17 +70,22 @@ const compilerOptions: ts.CompilerOptions = { // have "require" conditions that TS picks up incorrectly with Bundler resolution. workflow: [path.join(repoRoot, 'packages/workflow/dist/index')], 'workflow/api': [path.join(repoRoot, 'packages/workflow/dist/api')], + 'workflow/runtime': [path.join(repoRoot, 'packages/workflow/dist/runtime')], 'workflow/errors': [ path.join(repoRoot, 'packages/workflow/dist/internal/errors'), ], 'workflow/observability': [ path.join(repoRoot, 'packages/workflow/dist/observability'), ], + 'workflow/next': [path.join(repoRoot, 'packages/workflow/dist/next.d.cts')], '@workflow/core': [path.join(repoRoot, 'packages/core/dist/index')], '@workflow/core/serialization-format': [ path.join(repoRoot, 'packages/core/dist/serialization-format'), ], '@workflow/utils': [path.join(repoRoot, 'packages/utils/dist/index')], + '@workflow/utils/parse-name': [ + path.join(repoRoot, 'packages/utils/dist/parse-name'), + ], '@workflow/ai': [path.join(repoRoot, 'packages/ai/dist/index')], '@workflow/ai/agent': [ path.join(repoRoot, 'packages/ai/dist/agent/durable-agent'),