From d2998f3cf483e0d0fef28eec8646df3a1ec64db3 Mon Sep 17 00:00:00 2001
From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>
Date: Sat, 25 Oct 2025 00:32:41 -0700
Subject: [PATCH 01/20] wip
---
docs/content/docs/advanced/meta.json | 4 +
.../docs/advanced/runtime-integration.mdx | 391 ++++++++++++++++++
docs/content/docs/meta.json | 3 +
3 files changed, 398 insertions(+)
create mode 100644 docs/content/docs/advanced/meta.json
create mode 100644 docs/content/docs/advanced/runtime-integration.mdx
diff --git a/docs/content/docs/advanced/meta.json b/docs/content/docs/advanced/meta.json
new file mode 100644
index 000000000..de771556a
--- /dev/null
+++ b/docs/content/docs/advanced/meta.json
@@ -0,0 +1,4 @@
+{
+ "title": "Advanced",
+ "pages": ["runtime-integration"]
+}
diff --git a/docs/content/docs/advanced/runtime-integration.mdx b/docs/content/docs/advanced/runtime-integration.mdx
new file mode 100644
index 000000000..13d71f13c
--- /dev/null
+++ b/docs/content/docs/advanced/runtime-integration.mdx
@@ -0,0 +1,391 @@
+---
+title: Runtime Integration
+---
+
+# Runtime Integration
+
+This guide explains how Workflow DevKit integrates with JavaScript runtimes and frameworks at a fundamental level. Understanding these concepts is useful if you're:
+
+- Building a custom adapter for a framework or runtime
+- Curious about how the framework works under the hood
+- Debugging integration issues
+- Contributing to the project
+
+
+**Official adapters handle this automatically.** If you're using Next.js or Nitro, you don't need to implement any of this—the framework plugins take care of everything. We're actively working on adapters for more runtimes and frameworks.
+
+
+## Core Integration Concepts
+
+Every Workflow DevKit integration, regardless of the runtime, requires these fundamental components:
+
+1. **Build-time transformation** - Compiling workflow/step files into executable bundles
+2. **Runtime HTTP endpoints** - Routes that handle workflow and step execution
+3. **Queue processing** - Background workers that process workflow/step invocations
+4. **Storage backend** (World) - Persistent state and event log storage
+
+We'll walk through each concept using Bun as a concrete example.
+
+---
+
+
+
+
+## Example: Manual Bun Integration
+
+To illustrate these concepts, we'll build a complete manual integration with Bun. This same pattern applies to any JavaScript runtime.
+
+### Create Your Project
+
+Start by creating a new directory for your project:
+
+```bash
+mkdir bun-workflow-app
+cd bun-workflow-app
+bun init -y
+```
+
+### Install `workflow`
+
+```bash
+bun add workflow
+```
+
+### Install a World backend
+
+Choose a storage backend for workflow state:
+
+
+
+ bun add @workflow/world-local
+
For local development and testing. Uses the filesystem.
+
+
+ bun add @workflow/world-postgres
+
For production deployments with PostgreSQL.
+
+
+
+### Configure the CLI
+
+Create a `workflow.config.json` file:
+
+```json title="workflow.config.json"
+{
+ "buildTarget": "vercel-build-output-api",
+ "dirs": ["workflows"]
+}
+```
+
+
+
+
+
+## Write Example Workflows
+
+Create a workflows directory and an example workflow file to demonstrate the integration:
+
+```typescript title="workflows/user-signup.ts" lineNumbers
+import { sleep } from "workflow";
+
+export async function handleUserSignup(email: string) {
+ "use workflow"; // [!code highlight]
+
+ const user = await createUser(email);
+ await sendWelcomeEmail(user);
+
+ await sleep("5s"); // Pause for 5s - doesn't consume any resources
+ await sendOnboardingEmail(user);
+
+ return { userId: user.id, status: "onboarded" };
+}
+```
+
+We'll fill in those functions next, but let's take a look at this code:
+
+- We define a **workflow** function with the directive `"use workflow"`. Think of the workflow function as the _orchestrator_ of individual **steps**.
+- The Workflow DevKit's `sleep` function allows us to suspend execution of the workflow without using up any resources. A sleep can be a few seconds, hours, days, or even months long.
+
+### Define Step Functions
+
+Define the step functions for the workflow:
+
+```typescript title="workflows/user-signup.ts" lineNumbers
+import { FatalError } from "workflow";
+
+// Our workflow function defined earlier
+
+async function createUser(email: string) {
+ "use step"; // [!code highlight]
+
+ console.log(`Creating user with email: ${email}`);
+
+ // Full Node.js access - database calls, APIs, etc.
+ return { id: crypto.randomUUID(), email };
+}
+
+async function sendWelcomeEmail(user: { id: string; email: string }) {
+ "use step"; // [!code highlight]
+
+ console.log(`Sending welcome email to user: ${user.id}`);
+
+ if (Math.random() < 0.3) {
+ // By default, steps will be retried for unhandled errors
+ throw new Error("Retryable!");
+ }
+}
+
+async function sendOnboardingEmail(user: { id: string; email: string }) {
+ "use step"; // [!code highlight]
+
+ if (!user.email.includes("@")) {
+ // To skip retrying, throw a FatalError instead
+ throw new FatalError("Invalid Email");
+ }
+
+ console.log(`Sending onboarding email to user: ${user.id}`);
+}
+```
+
+Taking a look at this code:
+
+- Business logic lives inside **steps**. When a step is invoked inside a **workflow**, it gets enqueued to run on a separate request while the workflow is suspended, just like `sleep`.
+- If a step throws an error, like in `sendWelcomeEmail`, the step will automatically be retried until it succeeds (or hits the step's max retry count).
+- Steps can throw a `FatalError` if an error is intentional and should not be retried.
+
+
+ We'll dive deeper into workflows, steps, and other ways to suspend or handle
+ events in [Foundations](/docs/foundations).
+
+
+
+
+
+
+## 1. Build-Time Transformation
+
+The first integration requirement is compiling workflow files into executable bundles. Run the CLI build command:
+
+```bash
+bun x workflow build
+```
+
+This generates the `.well-known/workflow/v1/` directory containing:
+- `flow.js` - Workflow execution handler
+- `step.js` - Step execution handler
+- `manifest.debug.json` - Workflow metadata
+
+
+
+
+
+## 2. Runtime HTTP Endpoints
+
+The second requirement is exposing HTTP endpoints that the compiled bundles will call. Create a server that exposes these routes:
+
+```typescript title="server.ts" lineNumbers
+import { start, getRun, getWorld } from 'workflow/api';
+import flow from './.well-known/workflow/v1/flow.js';
+import step from './.well-known/workflow/v1/step.js';
+import manifest from './.well-known/workflow/v1/manifest.debug.json' with { type: 'json' };
+
+// IMPORTANT: Set PORT before calling getWorld() so the world backend knows where to send requests
+const PORT = process.env.PORT || 3000;
+process.env.PORT = String(PORT);
+
+const server = Bun.serve({
+ port: PORT,
+ routes: {
+ // Required: Workflow runtime endpoints
+ '/.well-known/workflow/v1/flow': {
+ POST: (req) => flow.POST(req),
+ },
+ '/.well-known/workflow/v1/step': {
+ POST: (req) => step.POST(req),
+ },
+
+ // Your application endpoints
+ '/api/signup': {
+ POST: async (req) => {
+ const { email } = await req.json();
+
+ // Get workflow metadata from the manifest
+ const workflowMetadata = manifest.workflows['workflows/user-signup.ts'].handleUserSignup;
+
+ // Start the workflow using metadata, not the function itself
+ const run = await start(workflowMetadata, [email]);
+
+ return Response.json({
+ message: 'User signup workflow started',
+ runId: run.runId,
+ });
+ },
+ },
+
+ // Optional: Check workflow status
+ '/api/runs/:runId': {
+ GET: async (req) => {
+ const run = getRun(req.params.runId);
+ const status = await run.status;
+ return Response.json({ status });
+ },
+ },
+ },
+});
+
+// Start the background queue processor
+const world = getWorld();
+if (world.start) {
+ await world.start();
+ console.log('Background queue processor started');
+}
+
+console.log(`Server listening on http://localhost:${server.port}`);
+```
+
+### Key Points
+
+**Setting the PORT environment variable:**
+- The local world backend (`@workflow/world-local`) uses HTTP to trigger workflow/step execution
+- It reads `process.env.PORT` to know where to send requests
+- **You must set `process.env.PORT` before calling `getWorld()` or importing `workflow/api`**
+- The port must match your server's actual listening port
+
+**Using the manifest instead of importing workflows directly:**
+- In a manual integration, you cannot import workflow functions directly (e.g., `import { handleUserSignup } from './workflows/user-signup'`)
+- Workflow functions need build-time transformation to get a `workflowId` property
+- Instead, use the generated `manifest.debug.json` which contains the workflow metadata
+- Pass the metadata object to `start()` instead of the function itself
+
+**Required Endpoints:**
+
+Every runtime integration must expose these endpoints:
+
+1. **`POST /.well-known/workflow/v1/flow`** - Handles workflow execution requests
+ - Called internally when workflows need to execute
+ - Imports and invokes the compiled `flow.js` bundle
+
+2. **`POST /.well-known/workflow/v1/step`** - Handles step execution requests
+ - Called internally when steps need to execute
+ - Imports and invokes the compiled `step.js` bundle
+
+3. **`POST /.well-known/workflow/v1/webhook/:token`** (optional) - Handles webhook/hook resumption
+ - Allows workflows to resume from external HTTP requests
+
+
+
+
+
+## 3. Queue Processing
+
+The third requirement is starting background queue processing. The World backend's `start()` method begins processing queued workflow and step invocations.
+
+## 4. Storage Backend Configuration
+
+The fourth requirement is configuring a World backend for persistent storage. Set the storage backend via environment variable:
+
+```bash title=".env"
+# For local development
+WORKFLOW_TARGET_WORLD="@workflow/world-local"
+
+# Or for production with Postgres
+# WORKFLOW_TARGET_WORLD="@workflow/world-postgres"
+# DATABASE_URL="postgresql://..."
+```
+
+
+ Bun automatically loads `.env` files, so you don't need to use `dotenv`.
+
+
+
+
+
+
+## Testing the Integration
+
+Start your server to test the complete integration:
+
+```bash
+bun --hot server.ts
+```
+
+Once your server is running, trigger your workflow:
+
+```bash
+curl -X POST -H "Content-Type: application/json" \
+ -d '{"email":"hello@example.com"}' \
+ http://localhost:3000/api/signup
+```
+
+Check the server logs to see your workflow execute and the steps being processed.
+
+You can also use the [Workflow DevKit CLI or Web UI](/docs/observability) to inspect your workflow runs:
+
+```bash
+bunx workflow inspect runs # add '--web' for an interactive Web based UI
+```
+
+
+
+
+
+---
+
+## Understanding the Integration
+
+This manual integration demonstrates the core concepts that any Workflow DevKit adapter must implement:
+
+### 1. Build Process
+The CLI compiles your workflow files into two executable bundles:
+- **`flow.js`** - Workflow orchestration handler (runs in a sandboxed VM)
+- **`step.js`** - Step execution handler (has full Node.js/Bun runtime access)
+
+### 2. Runtime Endpoints
+Three HTTP endpoints form the workflow execution contract:
+- **`POST /.well-known/workflow/v1/flow`** - Receives workflow execution requests
+- **`POST /.well-known/workflow/v1/step`** - Receives step execution requests
+- **`POST /.well-known/workflow/v1/webhook/:token`** (optional) - Handles webhook/hook resumption
+
+### 3. Queue Processing
+The `world.start()` call begins background processing of queued workflows and steps.
+
+### 4. Execution Flow
+1. Call `start()` to create a workflow run and enqueue it
+2. The workflow handler (`flow.js`) executes the workflow code in a VM
+3. When steps are called, they get enqueued for separate execution
+4. The step handler (`step.js`) processes each step with full runtime access
+5. Results are stored in the World backend and replayed on workflow resume
+
+
+**Framework adapters** like `@workflow/next` and `@workflow/nitro` handle all of this automatically—exposing the endpoints, running the build process, and configuring the runtime. This manual approach shows what's happening behind the scenes.
+
+
+## Adapting to Other Runtimes
+
+The same integration pattern applies to any JavaScript runtime or framework:
+
+1. **Compile workflows** using the CLI (`workflow build`)
+2. **Import the generated bundles** (`flow.js`, `step.js`)
+3. **Expose HTTP endpoints** at `/.well-known/workflow/v1/flow` and `/step`
+4. **Start queue processing** with `world.start()`
+5. **Configure a World backend** via `WORKFLOW_TARGET_WORLD`
+
+Whether you're using Express, Fastify, Hono, Deno, Cloudflare Workers, or any other runtime—these are the core building blocks.
+
+## Deploying to Production
+
+For production deployments:
+
+1. Use `@workflow/world-postgres` or another production-ready World backend
+2. Ensure your environment variables are configured
+3. Consider using a process manager or container orchestration
+4. Set up monitoring and observability
+
+Check the [Deploying](/docs/deploying) section to learn more about deployment options and World backends.
+
+## Next Steps
+
+- Learn more about the [Foundations](/docs/foundations)
+- Explore the [API Reference](/docs/api-reference)
+- See the [World backends](/docs/deploying/world) documentation for storage options
+- Check [Errors](/docs/errors) if you encounter issues
diff --git a/docs/content/docs/meta.json b/docs/content/docs/meta.json
index 8845f2e77..856780b02 100644
--- a/docs/content/docs/meta.json
+++ b/docs/content/docs/meta.json
@@ -6,6 +6,9 @@
"foundations",
"observability",
"deploying",
+ "---",
+ "advanced",
+ "---",
"errors",
"api-reference"
]
From c68639679bc70d79e348d502eda09cd15388a265 Mon Sep 17 00:00:00 2001
From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>
Date: Sat, 25 Oct 2025 01:01:44 -0700
Subject: [PATCH 02/20] almost good
---
docs/content/docs/advanced/meta.json | 4 -
.../docs/advanced/runtime-integration.mdx | 391 ------------------
docs/content/docs/foundations/meta.json | 3 +-
.../docs/foundations/runtime-adapters.mdx | 352 ++++++++++++++++
4 files changed, 354 insertions(+), 396 deletions(-)
delete mode 100644 docs/content/docs/advanced/meta.json
delete mode 100644 docs/content/docs/advanced/runtime-integration.mdx
create mode 100644 docs/content/docs/foundations/runtime-adapters.mdx
diff --git a/docs/content/docs/advanced/meta.json b/docs/content/docs/advanced/meta.json
deleted file mode 100644
index de771556a..000000000
--- a/docs/content/docs/advanced/meta.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "title": "Advanced",
- "pages": ["runtime-integration"]
-}
diff --git a/docs/content/docs/advanced/runtime-integration.mdx b/docs/content/docs/advanced/runtime-integration.mdx
deleted file mode 100644
index 13d71f13c..000000000
--- a/docs/content/docs/advanced/runtime-integration.mdx
+++ /dev/null
@@ -1,391 +0,0 @@
----
-title: Runtime Integration
----
-
-# Runtime Integration
-
-This guide explains how Workflow DevKit integrates with JavaScript runtimes and frameworks at a fundamental level. Understanding these concepts is useful if you're:
-
-- Building a custom adapter for a framework or runtime
-- Curious about how the framework works under the hood
-- Debugging integration issues
-- Contributing to the project
-
-
-**Official adapters handle this automatically.** If you're using Next.js or Nitro, you don't need to implement any of this—the framework plugins take care of everything. We're actively working on adapters for more runtimes and frameworks.
-
-
-## Core Integration Concepts
-
-Every Workflow DevKit integration, regardless of the runtime, requires these fundamental components:
-
-1. **Build-time transformation** - Compiling workflow/step files into executable bundles
-2. **Runtime HTTP endpoints** - Routes that handle workflow and step execution
-3. **Queue processing** - Background workers that process workflow/step invocations
-4. **Storage backend** (World) - Persistent state and event log storage
-
-We'll walk through each concept using Bun as a concrete example.
-
----
-
-
-
-
-## Example: Manual Bun Integration
-
-To illustrate these concepts, we'll build a complete manual integration with Bun. This same pattern applies to any JavaScript runtime.
-
-### Create Your Project
-
-Start by creating a new directory for your project:
-
-```bash
-mkdir bun-workflow-app
-cd bun-workflow-app
-bun init -y
-```
-
-### Install `workflow`
-
-```bash
-bun add workflow
-```
-
-### Install a World backend
-
-Choose a storage backend for workflow state:
-
-
-
- bun add @workflow/world-local
-
For local development and testing. Uses the filesystem.
-
-
- bun add @workflow/world-postgres
-
For production deployments with PostgreSQL.
-
-
-
-### Configure the CLI
-
-Create a `workflow.config.json` file:
-
-```json title="workflow.config.json"
-{
- "buildTarget": "vercel-build-output-api",
- "dirs": ["workflows"]
-}
-```
-
-
-
-
-
-## Write Example Workflows
-
-Create a workflows directory and an example workflow file to demonstrate the integration:
-
-```typescript title="workflows/user-signup.ts" lineNumbers
-import { sleep } from "workflow";
-
-export async function handleUserSignup(email: string) {
- "use workflow"; // [!code highlight]
-
- const user = await createUser(email);
- await sendWelcomeEmail(user);
-
- await sleep("5s"); // Pause for 5s - doesn't consume any resources
- await sendOnboardingEmail(user);
-
- return { userId: user.id, status: "onboarded" };
-}
-```
-
-We'll fill in those functions next, but let's take a look at this code:
-
-- We define a **workflow** function with the directive `"use workflow"`. Think of the workflow function as the _orchestrator_ of individual **steps**.
-- The Workflow DevKit's `sleep` function allows us to suspend execution of the workflow without using up any resources. A sleep can be a few seconds, hours, days, or even months long.
-
-### Define Step Functions
-
-Define the step functions for the workflow:
-
-```typescript title="workflows/user-signup.ts" lineNumbers
-import { FatalError } from "workflow";
-
-// Our workflow function defined earlier
-
-async function createUser(email: string) {
- "use step"; // [!code highlight]
-
- console.log(`Creating user with email: ${email}`);
-
- // Full Node.js access - database calls, APIs, etc.
- return { id: crypto.randomUUID(), email };
-}
-
-async function sendWelcomeEmail(user: { id: string; email: string }) {
- "use step"; // [!code highlight]
-
- console.log(`Sending welcome email to user: ${user.id}`);
-
- if (Math.random() < 0.3) {
- // By default, steps will be retried for unhandled errors
- throw new Error("Retryable!");
- }
-}
-
-async function sendOnboardingEmail(user: { id: string; email: string }) {
- "use step"; // [!code highlight]
-
- if (!user.email.includes("@")) {
- // To skip retrying, throw a FatalError instead
- throw new FatalError("Invalid Email");
- }
-
- console.log(`Sending onboarding email to user: ${user.id}`);
-}
-```
-
-Taking a look at this code:
-
-- Business logic lives inside **steps**. When a step is invoked inside a **workflow**, it gets enqueued to run on a separate request while the workflow is suspended, just like `sleep`.
-- If a step throws an error, like in `sendWelcomeEmail`, the step will automatically be retried until it succeeds (or hits the step's max retry count).
-- Steps can throw a `FatalError` if an error is intentional and should not be retried.
-
-
- We'll dive deeper into workflows, steps, and other ways to suspend or handle
- events in [Foundations](/docs/foundations).
-
-
-
-
-
-
-## 1. Build-Time Transformation
-
-The first integration requirement is compiling workflow files into executable bundles. Run the CLI build command:
-
-```bash
-bun x workflow build
-```
-
-This generates the `.well-known/workflow/v1/` directory containing:
-- `flow.js` - Workflow execution handler
-- `step.js` - Step execution handler
-- `manifest.debug.json` - Workflow metadata
-
-
-
-
-
-## 2. Runtime HTTP Endpoints
-
-The second requirement is exposing HTTP endpoints that the compiled bundles will call. Create a server that exposes these routes:
-
-```typescript title="server.ts" lineNumbers
-import { start, getRun, getWorld } from 'workflow/api';
-import flow from './.well-known/workflow/v1/flow.js';
-import step from './.well-known/workflow/v1/step.js';
-import manifest from './.well-known/workflow/v1/manifest.debug.json' with { type: 'json' };
-
-// IMPORTANT: Set PORT before calling getWorld() so the world backend knows where to send requests
-const PORT = process.env.PORT || 3000;
-process.env.PORT = String(PORT);
-
-const server = Bun.serve({
- port: PORT,
- routes: {
- // Required: Workflow runtime endpoints
- '/.well-known/workflow/v1/flow': {
- POST: (req) => flow.POST(req),
- },
- '/.well-known/workflow/v1/step': {
- POST: (req) => step.POST(req),
- },
-
- // Your application endpoints
- '/api/signup': {
- POST: async (req) => {
- const { email } = await req.json();
-
- // Get workflow metadata from the manifest
- const workflowMetadata = manifest.workflows['workflows/user-signup.ts'].handleUserSignup;
-
- // Start the workflow using metadata, not the function itself
- const run = await start(workflowMetadata, [email]);
-
- return Response.json({
- message: 'User signup workflow started',
- runId: run.runId,
- });
- },
- },
-
- // Optional: Check workflow status
- '/api/runs/:runId': {
- GET: async (req) => {
- const run = getRun(req.params.runId);
- const status = await run.status;
- return Response.json({ status });
- },
- },
- },
-});
-
-// Start the background queue processor
-const world = getWorld();
-if (world.start) {
- await world.start();
- console.log('Background queue processor started');
-}
-
-console.log(`Server listening on http://localhost:${server.port}`);
-```
-
-### Key Points
-
-**Setting the PORT environment variable:**
-- The local world backend (`@workflow/world-local`) uses HTTP to trigger workflow/step execution
-- It reads `process.env.PORT` to know where to send requests
-- **You must set `process.env.PORT` before calling `getWorld()` or importing `workflow/api`**
-- The port must match your server's actual listening port
-
-**Using the manifest instead of importing workflows directly:**
-- In a manual integration, you cannot import workflow functions directly (e.g., `import { handleUserSignup } from './workflows/user-signup'`)
-- Workflow functions need build-time transformation to get a `workflowId` property
-- Instead, use the generated `manifest.debug.json` which contains the workflow metadata
-- Pass the metadata object to `start()` instead of the function itself
-
-**Required Endpoints:**
-
-Every runtime integration must expose these endpoints:
-
-1. **`POST /.well-known/workflow/v1/flow`** - Handles workflow execution requests
- - Called internally when workflows need to execute
- - Imports and invokes the compiled `flow.js` bundle
-
-2. **`POST /.well-known/workflow/v1/step`** - Handles step execution requests
- - Called internally when steps need to execute
- - Imports and invokes the compiled `step.js` bundle
-
-3. **`POST /.well-known/workflow/v1/webhook/:token`** (optional) - Handles webhook/hook resumption
- - Allows workflows to resume from external HTTP requests
-
-
-
-
-
-## 3. Queue Processing
-
-The third requirement is starting background queue processing. The World backend's `start()` method begins processing queued workflow and step invocations.
-
-## 4. Storage Backend Configuration
-
-The fourth requirement is configuring a World backend for persistent storage. Set the storage backend via environment variable:
-
-```bash title=".env"
-# For local development
-WORKFLOW_TARGET_WORLD="@workflow/world-local"
-
-# Or for production with Postgres
-# WORKFLOW_TARGET_WORLD="@workflow/world-postgres"
-# DATABASE_URL="postgresql://..."
-```
-
-
- Bun automatically loads `.env` files, so you don't need to use `dotenv`.
-
-
-
-
-
-
-## Testing the Integration
-
-Start your server to test the complete integration:
-
-```bash
-bun --hot server.ts
-```
-
-Once your server is running, trigger your workflow:
-
-```bash
-curl -X POST -H "Content-Type: application/json" \
- -d '{"email":"hello@example.com"}' \
- http://localhost:3000/api/signup
-```
-
-Check the server logs to see your workflow execute and the steps being processed.
-
-You can also use the [Workflow DevKit CLI or Web UI](/docs/observability) to inspect your workflow runs:
-
-```bash
-bunx workflow inspect runs # add '--web' for an interactive Web based UI
-```
-
-
-
-
-
----
-
-## Understanding the Integration
-
-This manual integration demonstrates the core concepts that any Workflow DevKit adapter must implement:
-
-### 1. Build Process
-The CLI compiles your workflow files into two executable bundles:
-- **`flow.js`** - Workflow orchestration handler (runs in a sandboxed VM)
-- **`step.js`** - Step execution handler (has full Node.js/Bun runtime access)
-
-### 2. Runtime Endpoints
-Three HTTP endpoints form the workflow execution contract:
-- **`POST /.well-known/workflow/v1/flow`** - Receives workflow execution requests
-- **`POST /.well-known/workflow/v1/step`** - Receives step execution requests
-- **`POST /.well-known/workflow/v1/webhook/:token`** (optional) - Handles webhook/hook resumption
-
-### 3. Queue Processing
-The `world.start()` call begins background processing of queued workflows and steps.
-
-### 4. Execution Flow
-1. Call `start()` to create a workflow run and enqueue it
-2. The workflow handler (`flow.js`) executes the workflow code in a VM
-3. When steps are called, they get enqueued for separate execution
-4. The step handler (`step.js`) processes each step with full runtime access
-5. Results are stored in the World backend and replayed on workflow resume
-
-
-**Framework adapters** like `@workflow/next` and `@workflow/nitro` handle all of this automatically—exposing the endpoints, running the build process, and configuring the runtime. This manual approach shows what's happening behind the scenes.
-
-
-## Adapting to Other Runtimes
-
-The same integration pattern applies to any JavaScript runtime or framework:
-
-1. **Compile workflows** using the CLI (`workflow build`)
-2. **Import the generated bundles** (`flow.js`, `step.js`)
-3. **Expose HTTP endpoints** at `/.well-known/workflow/v1/flow` and `/step`
-4. **Start queue processing** with `world.start()`
-5. **Configure a World backend** via `WORKFLOW_TARGET_WORLD`
-
-Whether you're using Express, Fastify, Hono, Deno, Cloudflare Workers, or any other runtime—these are the core building blocks.
-
-## Deploying to Production
-
-For production deployments:
-
-1. Use `@workflow/world-postgres` or another production-ready World backend
-2. Ensure your environment variables are configured
-3. Consider using a process manager or container orchestration
-4. Set up monitoring and observability
-
-Check the [Deploying](/docs/deploying) section to learn more about deployment options and World backends.
-
-## Next Steps
-
-- Learn more about the [Foundations](/docs/foundations)
-- Explore the [API Reference](/docs/api-reference)
-- See the [World backends](/docs/deploying/world) documentation for storage options
-- Check [Errors](/docs/errors) if you encounter issues
diff --git a/docs/content/docs/foundations/meta.json b/docs/content/docs/foundations/meta.json
index a205cb29e..8ce4a82a9 100644
--- a/docs/content/docs/foundations/meta.json
+++ b/docs/content/docs/foundations/meta.json
@@ -6,7 +6,8 @@
"errors-and-retries",
"idempotency",
"hooks",
- "serialization"
+ "serialization",
+ "runtime-adapters"
],
"defaultOpen": true
}
diff --git a/docs/content/docs/foundations/runtime-adapters.mdx b/docs/content/docs/foundations/runtime-adapters.mdx
new file mode 100644
index 000000000..62c2f0624
--- /dev/null
+++ b/docs/content/docs/foundations/runtime-adapters.mdx
@@ -0,0 +1,352 @@
+---
+title: Building your own adapters
+---
+
+# Advanced Concepts: Building your own adapters
+
+This guide explains the underlying architecture of how the Workflow SDK integrates with different JavaScript runtimes, using Bun as a reference implementation. While this example uses Bun, the same principles apply to writing adapters for any JavaScript runtime (Node.js, Deno, Cloudflare Workers, etc.).
+
+The goal is for you to never need this document. We will be providing official adapters for everywhere you need to run workflows.
+
+The purpose of this document is to explain conceptually how the Workflow SDK works with different runtimes.
+
+---
+
+## Overview
+
+The Workflow SDK architecture is as follows:
+
+1. **Build-time transformation**: Workflow code is transformed by a compiler plugin
+2. **Generated handlers**: The build process generates HTTP handlers in `.well-known/workflow/v1/`
+3. **Runtime integration**: Your application server exposes these handlers as HTTP endpoints
+4. **Execution orchestration**: The Workflow SDK communicates with these endpoints to orchestrate durable execution
+
+```
+┌─────────────────┐
+│ Source Code │
+│ "use workflow" │
+└────────┬────────┘
+ │
+ ▼
+┌─────────────────┐
+│ SWC Transform │
+│ (Build Plugin) │
+└────────┬────────┘
+ │
+ ▼
+┌─────────────────┐
+│ Generated Files │
+│ flow.js/step.js │
+└────────┬────────┘
+ │
+ ▼
+┌─────────────────┐
+│ HTTP Server │
+│ (Your Runtime) │
+└─────────────────┘
+```
+
+---
+
+## The Transformation Pipeline
+
+### 1. Source Code Directives
+
+Workflows use special directives to mark code for transformation:
+
+```typescript
+export async function handleUserSignup(email: string) {
+ "use workflow";
+
+ const user = await createUser(email);
+ await sendWelcomeEmail(user);
+
+ return { userId: user.id };
+}
+
+async function createUser(email: string) {
+ "use step";
+
+ return { id: crypto.randomUUID(), email };
+}
+```
+
+**Key directives:**
+
+- `"use workflow"`: Marks a function as a durable workflow entry point
+- `"use step"`: Marks a function as an atomic, retryable step
+
+### 2. Build-Time Transformation
+
+The `@workflow/swc-plugin` transforms these functions into instrumented code that:
+
+- Captures function calls and their arguments
+- Generates serialization/deserialization logic
+- Creates step boundaries for durability
+- Produces execution graphs
+
+When you run `workflow build`, it:
+
+1. Scans directories specified in `workflow.config.json`
+2. Transforms files containing workflow directives
+3. Generates handler files in `.well-known/workflow/v1/`
+
+```json
+{
+ "buildTarget": "vercel-build-output-api",
+ "dirs": ["workflows"]
+}
+```
+
+### 3. Generated Artifacts
+
+The build process creates two critical files:
+
+- **`.well-known/workflow/v1/flow.js`**: Handles workflow execution requests
+- **`.well-known/workflow/v1/step.js`**: Handles individual step execution requests
+
+These files export handlers for POST requests that take in the web standard `Request` object.
+
+---
+
+## Required HTTP Endpoints
+
+Your runtime adapter must expose these two endpoints:
+
+### `POST /.well-known/workflow/v1/flow`
+
+Executes workflow-level operations (starting workflows, resuming after steps).
+
+**Request**: Binary payload from Workflow SDK
+**Response**: Binary payload with execution results
+
+### `POST /.well-known/workflow/v1/step`
+
+Executes individual steps within workflows.
+
+**Request**: Binary payload from Workflow SDK
+**Response**: Binary payload with step results
+
+The exact protocol is handled by the generated `flow.js` and `step.js` files. Your adapter just needs to route HTTP requests to these handlers.
+
+---
+
+## Writing a Runtime Adapter
+
+To integrate Workflow SDK with a runtime, you need three components:
+
+### Component 1: Build-Time Transformation Hook
+
+Transform workflow files during the build/load process using the SWC plugin.
+
+**What you need to implement:**
+
+- A plugin/loader hook in your runtime's build system
+- File filtering (typically `workflows/**/*.{ts,tsx,js,jsx}`)
+- SWC transformation with `@workflow/swc-plugin`
+- Module loading/resolution
+
+**Example pattern:**
+
+```typescript
+buildSystem.plugin({
+ name: "workflow-transform",
+ setup(build) {
+ build.onLoad(
+ { filter: /workflows\/.*\.(ts|tsx|js|jsx)$/ },
+ async (args) => {
+ const source = await readFile(args.path);
+
+ const result = await transform(source, {
+ filename: args.path,
+ jsc: {
+ experimental: {
+ plugins: [
+ [require.resolve("@workflow/swc-plugin"), { mode: "client" }],
+ ],
+ },
+ },
+ });
+
+ return {
+ contents: result.code,
+ loader: "ts",
+ };
+ }
+ );
+ },
+});
+```
+
+### Component 2: HTTP Server Integration
+
+Expose the generated workflow handlers as HTTP endpoints.
+
+**What you need to implement:**
+
+- HTTP server listening on a port
+- Route handling for `/.well-known/workflow/v1/flow`
+- Route handling for `/.well-known/workflow/v1/step`
+- Importing and delegating to generated handlers
+
+**Example pattern:**
+
+```typescript
+import flow from "./.well-known/workflow/v1/flow.js";
+import step from "./.well-known/workflow/v1/step.js";
+
+server.route("POST", "/.well-known/workflow/v1/flow", (req) => flow.POST(req));
+server.route("POST", "/.well-known/workflow/v1/step", (req) => step.POST(req));
+```
+
+### Component 3: Workflow Client Integration
+
+Import and use the Workflow SDK client API to start workflows.
+
+```typescript
+import { start } from "workflow/api";
+import { myWorkflow } from "./workflows/my-workflow.js";
+
+const run = await start(myWorkflow, [arg1, arg2]);
+console.log(run.runId);
+```
+
+---
+
+## Reference Implementation: Bun
+
+Here's how the three components are implemented for Bun:
+
+### 1. Bun Plugin (Build-Time Transformation)
+
+```typescript
+import { plugin } from "bun";
+import { transform } from "@swc/core";
+
+console.log("Workflow plugin loaded");
+
+plugin({
+ name: "workflow-transform",
+ setup(build) {
+ build.onLoad(
+ { filter: /workflows\/.*\.(ts|tsx|js|jsx)$/ },
+ async (args) => {
+ const source = await Bun.file(args.path).text();
+
+ const result = await transform(source, {
+ filename: args.path,
+ jsc: {
+ experimental: {
+ plugins: [
+ [require.resolve("@workflow/swc-plugin"), { mode: "client" }],
+ ],
+ },
+ },
+ });
+
+ return {
+ contents: result.code,
+ loader: "ts",
+ };
+ }
+ );
+ },
+});
+```
+
+**Key aspects:**
+
+- Uses Bun's native `plugin()` API
+- Filters workflow files using regex
+- Uses `Bun.file()` for file reading
+- Applies SWC transformation with workflow plugin
+- Returns transformed code with TypeScript loader
+
+### 2. HTTP Server (Runtime Integration)
+
+```typescript
+import { start } from "workflow/api";
+import flow from "./.well-known/workflow/v1/flow.js";
+import step from "./.well-known/workflow/v1/step.js";
+import { handleUserSignup } from "./workflows/user-signup.js";
+
+const server = Bun.serve({
+ port: process.env.PORT,
+ routes: {
+ "/.well-known/workflow/v1/flow": {
+ POST: (req) => flow.POST(req),
+ },
+
+ "/.well-known/workflow/v1/step": {
+ POST: (req) => step.POST(req),
+ },
+
+ "/": {
+ GET: async (req) => {
+ const email = `test-${crypto.randomUUID()}@test.com`;
+
+ const run = await start(handleUserSignup, [email]);
+
+ return Response.json({
+ message: "User signup workflow started",
+ runId: run.runId,
+ });
+ },
+ },
+ },
+});
+
+console.log(`Server listening on http://localhost:${server.port}`);
+```
+
+**Key aspects:**
+
+- Imports generated `flow.js` and `step.js` handlers
+- Uses Bun's routing API to map endpoints
+- Delegates POST requests directly to generated handlers
+- Example endpoint shows how to start a workflow using `start()`
+
+### 3. Workflow Definition
+
+```typescript
+import { sleep, FatalError } from "workflow";
+
+export async function handleUserSignup(email: string) {
+ "use workflow";
+
+ const user = await createUser(email);
+ await sendWelcomeEmail(user);
+
+ await sendOnboardingEmail(user);
+
+ return { userId: user.id, status: "onboarded" };
+}
+
+async function createUser(email: string) {
+ "use step";
+
+ console.log(`Creating a new user with email: ${email}`);
+
+ return { id: crypto.randomUUID(), email };
+}
+
+async function sendWelcomeEmail(user: { id: string; email: string }) {
+ "use step";
+
+ console.log(`Sending welcome email to user: ${user.id}`);
+}
+
+async function sendOnboardingEmail(user: { id: string; email: string }) {
+ "use step";
+
+ console.log(`Sending onboarding email to user: ${user.id}`);
+}
+```
+
+**Key aspects:**
+
+- `"use workflow"` marks the main workflow function
+- `"use step"` marks individual atomic operations
+- Steps can be retried independently
+- Return values flow between steps
+- Type-safe arguments and returns
From a15cf9bc6173a2fda755bb22906e7e58e57402e9 Mon Sep 17 00:00:00 2001
From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>
Date: Sat, 25 Oct 2025 01:02:24 -0700
Subject: [PATCH 03/20] Update meta.json
---
docs/content/docs/meta.json | 3 ---
1 file changed, 3 deletions(-)
diff --git a/docs/content/docs/meta.json b/docs/content/docs/meta.json
index 856780b02..8845f2e77 100644
--- a/docs/content/docs/meta.json
+++ b/docs/content/docs/meta.json
@@ -6,9 +6,6 @@
"foundations",
"observability",
"deploying",
- "---",
- "advanced",
- "---",
"errors",
"api-reference"
]
From f2b936612d312d911d60ef49128fe16aa50c1d34 Mon Sep 17 00:00:00 2001
From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>
Date: Sat, 25 Oct 2025 09:42:27 -0700
Subject: [PATCH 04/20] use mermaid diagram
---
docs/components/mermaid.tsx | 61 ++++
.../docs/foundations/runtime-adapters.mdx | 28 +-
docs/mdx-components.tsx | 2 +
docs/package.json | 5 +-
docs/source.config.ts | 4 +-
pnpm-lock.yaml | 268 +-----------------
6 files changed, 76 insertions(+), 292 deletions(-)
create mode 100644 docs/components/mermaid.tsx
diff --git a/docs/components/mermaid.tsx b/docs/components/mermaid.tsx
new file mode 100644
index 000000000..08c3d1040
--- /dev/null
+++ b/docs/components/mermaid.tsx
@@ -0,0 +1,61 @@
+'use client';
+
+import { useTheme } from 'next-themes';
+import { use, useEffect, useId, useState } from 'react';
+
+export function Mermaid({ chart }: { chart: string }) {
+ const [mounted, setMounted] = useState(false);
+
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ if (!mounted) return;
+ return ;
+}
+
+const cache = new Map>();
+
+function cachePromise(
+ key: string,
+ setPromise: () => Promise
+): Promise {
+ const cached = cache.get(key);
+ if (cached) return cached as Promise;
+
+ const promise = setPromise();
+ cache.set(key, promise);
+ return promise;
+}
+
+function MermaidContent({ chart }: { chart: string }) {
+ const id = useId();
+ const { resolvedTheme } = useTheme();
+ const { default: mermaid } = use(
+ cachePromise('mermaid', () => import('mermaid'))
+ );
+
+ mermaid.initialize({
+ startOnLoad: false,
+ securityLevel: 'loose',
+ fontFamily: 'inherit',
+ themeCSS: 'margin: 1.5rem auto 0;',
+ theme: resolvedTheme === 'dark' ? 'dark' : 'default',
+ });
+
+ const { svg, bindFunctions } = use(
+ cachePromise(`${chart}-${resolvedTheme}`, () => {
+ return mermaid.render(id, chart.replaceAll('\\n', '\n'));
+ })
+ );
+
+ return (
+
{
+ if (container) bindFunctions?.(container);
+ }}
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: Matching fumadocs + is only running trusted input from mdx files
+ dangerouslySetInnerHTML={{ __html: svg }}
+ />
+ );
+}
diff --git a/docs/content/docs/foundations/runtime-adapters.mdx b/docs/content/docs/foundations/runtime-adapters.mdx
index 62c2f0624..df603bcdb 100644
--- a/docs/content/docs/foundations/runtime-adapters.mdx
+++ b/docs/content/docs/foundations/runtime-adapters.mdx
@@ -21,29 +21,11 @@ The Workflow SDK architecture is as follows:
3. **Runtime integration**: Your application server exposes these handlers as HTTP endpoints
4. **Execution orchestration**: The Workflow SDK communicates with these endpoints to orchestrate durable execution
-```
-┌─────────────────┐
-│ Source Code │
-│ "use workflow" │
-└────────┬────────┘
- │
- ▼
-┌─────────────────┐
-│ SWC Transform │
-│ (Build Plugin) │
-└────────┬────────┘
- │
- ▼
-┌─────────────────┐
-│ Generated Files │
-│ flow.js/step.js │
-└────────┬────────┘
- │
- ▼
-┌─────────────────┐
-│ HTTP Server │
-│ (Your Runtime) │
-└─────────────────┘
+```mermaid
+flowchart TD
+ A["Source Code 'use workflow'"] --> B["SWC Transform (Build Plugin)"]
+ B --> C["Generated Files flow.js/step.js"]
+ C --> D["HTTP Server (Your Runtime)"]
```
---
diff --git a/docs/mdx-components.tsx b/docs/mdx-components.tsx
index 57a5c344c..91dd37804 100644
--- a/docs/mdx-components.tsx
+++ b/docs/mdx-components.tsx
@@ -8,6 +8,7 @@ import * as CalloutComponents from '@/components/ui/callout';
import { CodeBlock } from '@/components/ui/code-block';
import { TypeTable } from '@/components/ui/type-table';
import { TSDoc } from '@/lib/tsdoc';
+import { Mermaid } from './components/mermaid';
import { Step, Steps } from './components/steps';
import { Badge } from './components/ui/badge';
import { cn } from './lib/cn';
@@ -24,6 +25,7 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents {
CodeBlock,
Step,
Steps,
+ Mermaid,
...CalloutComponents,
h1: (props) => (
diff --git a/docs/package.json b/docs/package.json
index 825480178..d4f836696 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -25,14 +25,13 @@
"@types/react-dom": "^19.1.9",
"@vercel/analytics": "^1.5.0",
"@vercel/edge-config": "^1.4.0",
- "@workflow/swc-plugin": "workspace:*",
- "workflow": "workspace:*",
"@workflow/ai": "workspace:*",
"@workflow/cli": "workspace:*",
"@workflow/core": "workspace:*",
"@workflow/errors": "workspace:*",
"@workflow/next": "workspace:*",
"@workflow/nitro": "workspace:*",
+ "@workflow/swc-plugin": "workspace:*",
"@workflow/typescript-plugin": "workspace:*",
"@workflow/web": "workspace:*",
"@workflow/world": "workspace:*",
@@ -48,6 +47,7 @@
"github-slugger": "^2.0.0",
"input-otp": "^1.4.2",
"lucide-react": "^0.544.0",
+ "mermaid": "^11.12.0",
"motion": "^12.23.24",
"next": "15.6.0-canary.13",
"next-themes": "^0.4.6",
@@ -63,6 +63,7 @@
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1",
"vaul": "^1.1.2",
+ "workflow": "workspace:*",
"zod": "catalog:"
},
"devDependencies": {
diff --git a/docs/source.config.ts b/docs/source.config.ts
index 04714474f..4c3945bc5 100644
--- a/docs/source.config.ts
+++ b/docs/source.config.ts
@@ -1,3 +1,4 @@
+import { remarkMdxMermaid } from 'fumadocs-core/mdx-plugins';
import { defineConfig, defineDocs, metaSchema } from 'fumadocs-mdx/config';
import { remarkAutoTypeTable } from 'fumadocs-typescript';
@@ -16,10 +17,9 @@ export const docs = defineDocs({
export default defineConfig({
mdxOptions: {
- // MDX options
remarkCodeTabOptions: {
parseMdx: true,
},
- remarkPlugins: [[remarkAutoTypeTable]],
+ remarkPlugins: [remarkMdxMermaid, [remarkAutoTypeTable]],
},
});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d7d7b96e2..293ad027a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -179,6 +179,9 @@ importers:
lucide-react:
specifier: ^0.544.0
version: 0.544.0(react@19.1.1)
+ mermaid:
+ specifier: ^11.12.0
+ version: 11.12.0
motion:
specifier: ^12.23.24
version: 12.23.24(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -4561,9 +4564,6 @@ packages:
'@types/jsonlines@0.1.5':
resolution: {integrity: sha512-/zOl7I350g4/G6fEW9dktpTrkcKqZDMRkr2SuDla0utgwkUXrm7OFXq2WZT0W9Jl7BYoisGbn1EZsV/Z2F9LGg==}
- '@types/katex@0.16.7':
- resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==}
-
'@types/lodash.chunk@4.2.9':
resolution: {integrity: sha512-Z9VtFUSnmT0No/QymqfG9AGbfOA4O5qB/uyP89xeZBqDAsKsB4gQFTqt7d0pHjbsTwtQ4yZObQVHuKlSOhIJ5Q==}
@@ -5958,10 +5958,6 @@ packages:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
- entities@6.0.1:
- resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
- engines: {node: '>=0.12'}
-
environment@1.1.0:
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
engines: {node: '>=18'}
@@ -6426,27 +6422,6 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
- hast-util-from-dom@5.0.1:
- resolution: {integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==}
-
- hast-util-from-html-isomorphic@2.0.0:
- resolution: {integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==}
-
- hast-util-from-html@2.0.3:
- resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==}
-
- hast-util-from-parse5@8.0.3:
- resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==}
-
- hast-util-is-element@3.0.0:
- resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==}
-
- hast-util-parse-selector@4.0.0:
- resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==}
-
- hast-util-raw@9.1.0:
- resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==}
-
hast-util-to-estree@3.1.3:
resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==}
@@ -6456,21 +6431,12 @@ packages:
hast-util-to-jsx-runtime@2.3.6:
resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==}
- hast-util-to-parse5@8.0.0:
- resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==}
-
hast-util-to-string@3.0.1:
resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==}
- hast-util-to-text@4.0.2:
- resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==}
-
hast-util-whitespace@3.0.0:
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
- hastscript@9.0.1:
- resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==}
-
hono@4.9.10:
resolution: {integrity: sha512-AlI15ijFyKTXR7eHo7QK7OR4RoKIedZvBuRjO8iy4zrxvlY5oFCdiRG/V/lFJHCNXJ0k72ATgnyzx8Yqa5arug==}
engines: {node: '>=16.9.0'}
@@ -7004,11 +6970,6 @@ packages:
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
- lucide-react@0.542.0:
- resolution: {integrity: sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==}
- peerDependencies:
- react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
-
lucide-react@0.544.0:
resolution: {integrity: sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw==}
peerDependencies:
@@ -7071,9 +7032,6 @@ packages:
mdast-util-gfm@3.1.0:
resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==}
- mdast-util-math@3.0.0:
- resolution: {integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==}
-
mdast-util-mdx-expression@2.0.1:
resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==}
@@ -7138,9 +7096,6 @@ packages:
micromark-extension-gfm@3.0.0:
resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==}
- micromark-extension-math@3.1.0:
- resolution: {integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==}
-
micromark-extension-mdx-expression@3.0.1:
resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==}
@@ -7722,9 +7677,6 @@ packages:
resolution: {integrity: sha512-bCgsFI+GeGWPAvAiUv63ZorMeif3/U0zaXABGJbOWt5OH2KCaPHF6S+0ok4aqM9RuIPGyZdx9tR9l13PsW4AYQ==}
engines: {node: '>=14.13.0'}
- parse5@7.3.0:
- resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
-
parseurl@1.3.3:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
@@ -8099,9 +8051,6 @@ packages:
resolution: {integrity: sha512-z597WicA7nDZxK12kZqHr2TcvwNU1GCfA5UwfDY/HDp3hXPoPlb5rlEx9bwGTiJnc0OqbBTkU975jDToth8Gxw==}
engines: {node: '>=14'}
- property-information@6.5.0:
- resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
-
property-information@7.1.0:
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
@@ -8317,24 +8266,12 @@ packages:
resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==}
hasBin: true
- rehype-harden@1.1.5:
- resolution: {integrity: sha512-JrtBj5BVd/5vf3H3/blyJatXJbzQfRT9pJBmjafbTaPouQCAKxHwRyCc7dle9BXQKxv4z1OzZylz/tNamoiG3A==}
-
- rehype-katex@7.0.1:
- resolution: {integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==}
-
- rehype-raw@7.0.0:
- resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==}
-
rehype-recma@1.0.0:
resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==}
remark-gfm@4.0.1:
resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
- remark-math@6.0.0:
- resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==}
-
remark-mdx@3.1.1:
resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==}
@@ -8639,11 +8576,6 @@ packages:
resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==}
engines: {node: '>=18'}
- streamdown@1.4.0:
- resolution: {integrity: sha512-ylhDSQ4HpK5/nAH9v7OgIIdGJxlJB2HoYrYkJNGrO8lMpnWuKUcrz/A8xAMwA6eILA27469vIavcOTjmxctrKg==}
- peerDependencies:
- react: ^18.0.0 || ^19.0.0
-
streamx@2.23.0:
resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==}
@@ -9029,9 +8961,6 @@ packages:
resolution: {integrity: sha512-/JpWMG9s1nBSlXJAQ8EREFTFy3oy6USFd8T6AoBaw1q2GGcF4R9yp3ofg32UODZlYEO5VD0EWE1RpI9XDWyPYg==}
engines: {node: '>=18.12.0'}
- unist-util-find-after@5.0.0:
- resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==}
-
unist-util-is@6.0.0:
resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
@@ -9041,9 +8970,6 @@ packages:
unist-util-position@5.0.0:
resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
- unist-util-remove-position@5.0.0:
- resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==}
-
unist-util-stringify-position@4.0.0:
resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
@@ -9276,9 +9202,6 @@ packages:
react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
- vfile-location@5.0.3:
- resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==}
-
vfile-message@4.0.2:
resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==}
@@ -9473,9 +9396,6 @@ packages:
wcwidth@1.0.1:
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
- web-namespaces@2.0.1:
- resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
-
webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
@@ -13543,8 +13463,6 @@ snapshots:
dependencies:
'@types/node': 20.19.7
- '@types/katex@0.16.7': {}
-
'@types/lodash.chunk@4.2.9':
dependencies:
'@types/lodash': 4.17.20
@@ -14943,8 +14861,6 @@ snapshots:
entities@4.5.0: {}
- entities@6.0.1: {}
-
environment@1.1.0: {}
error-stack-parser-es@1.0.5: {}
@@ -15508,63 +15424,6 @@ snapshots:
dependencies:
function-bind: 1.1.2
- hast-util-from-dom@5.0.1:
- dependencies:
- '@types/hast': 3.0.4
- hastscript: 9.0.1
- web-namespaces: 2.0.1
-
- hast-util-from-html-isomorphic@2.0.0:
- dependencies:
- '@types/hast': 3.0.4
- hast-util-from-dom: 5.0.1
- hast-util-from-html: 2.0.3
- unist-util-remove-position: 5.0.0
-
- hast-util-from-html@2.0.3:
- dependencies:
- '@types/hast': 3.0.4
- devlop: 1.1.0
- hast-util-from-parse5: 8.0.3
- parse5: 7.3.0
- vfile: 6.0.3
- vfile-message: 4.0.2
-
- hast-util-from-parse5@8.0.3:
- dependencies:
- '@types/hast': 3.0.4
- '@types/unist': 3.0.3
- devlop: 1.1.0
- hastscript: 9.0.1
- property-information: 7.1.0
- vfile: 6.0.3
- vfile-location: 5.0.3
- web-namespaces: 2.0.1
-
- hast-util-is-element@3.0.0:
- dependencies:
- '@types/hast': 3.0.4
-
- hast-util-parse-selector@4.0.0:
- dependencies:
- '@types/hast': 3.0.4
-
- hast-util-raw@9.1.0:
- dependencies:
- '@types/hast': 3.0.4
- '@types/unist': 3.0.3
- '@ungap/structured-clone': 1.3.0
- hast-util-from-parse5: 8.0.3
- hast-util-to-parse5: 8.0.0
- html-void-elements: 3.0.0
- mdast-util-to-hast: 13.2.0
- parse5: 7.3.0
- unist-util-position: 5.0.0
- unist-util-visit: 5.0.0
- vfile: 6.0.3
- web-namespaces: 2.0.1
- zwitch: 2.0.4
-
hast-util-to-estree@3.1.3:
dependencies:
'@types/estree': 1.0.8
@@ -15620,39 +15479,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- hast-util-to-parse5@8.0.0:
- dependencies:
- '@types/hast': 3.0.4
- comma-separated-tokens: 2.0.3
- devlop: 1.1.0
- property-information: 6.5.0
- space-separated-tokens: 2.0.2
- web-namespaces: 2.0.1
- zwitch: 2.0.4
-
hast-util-to-string@3.0.1:
dependencies:
'@types/hast': 3.0.4
- hast-util-to-text@4.0.2:
- dependencies:
- '@types/hast': 3.0.4
- '@types/unist': 3.0.3
- hast-util-is-element: 3.0.0
- unist-util-find-after: 5.0.0
-
hast-util-whitespace@3.0.0:
dependencies:
'@types/hast': 3.0.4
- hastscript@9.0.1:
- dependencies:
- '@types/hast': 3.0.4
- comma-separated-tokens: 2.0.3
- hast-util-parse-selector: 4.0.0
- property-information: 7.1.0
- space-separated-tokens: 2.0.2
-
hono@4.9.10: {}
hookable@5.5.3: {}
@@ -16126,10 +15960,6 @@ snapshots:
dependencies:
react: 19.1.0
- lucide-react@0.542.0(react@19.1.1):
- dependencies:
- react: 19.1.1
-
lucide-react@0.544.0(react@19.1.1):
dependencies:
react: 19.1.1
@@ -16251,18 +16081,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- mdast-util-math@3.0.0:
- dependencies:
- '@types/hast': 3.0.4
- '@types/mdast': 4.0.4
- devlop: 1.1.0
- longest-streak: 3.1.0
- mdast-util-from-markdown: 2.0.2
- mdast-util-to-markdown: 2.1.2
- unist-util-remove-position: 5.0.0
- transitivePeerDependencies:
- - supports-color
-
mdast-util-mdx-expression@2.0.1:
dependencies:
'@types/estree-jsx': 1.0.5
@@ -16455,16 +16273,6 @@ snapshots:
micromark-util-combine-extensions: 2.0.1
micromark-util-types: 2.0.2
- micromark-extension-math@3.1.0:
- dependencies:
- '@types/katex': 0.16.7
- devlop: 1.1.0
- katex: 0.16.25
- micromark-factory-space: 2.0.1
- micromark-util-character: 2.1.1
- micromark-util-symbol: 2.0.1
- micromark-util-types: 2.0.2
-
micromark-extension-mdx-expression@3.0.1:
dependencies:
'@types/estree': 1.0.8
@@ -17422,10 +17230,6 @@ snapshots:
'@types/parse-path': 7.1.0
parse-path: 7.1.0
- parse5@7.3.0:
- dependencies:
- entities: 6.0.1
-
parseurl@1.3.3: {}
path-browserify@1.0.1: {}
@@ -17770,8 +17574,6 @@ snapshots:
dependencies:
mkdirp: 1.0.4
- property-information@6.5.0: {}
-
property-information@7.1.0: {}
protobufjs@7.5.4:
@@ -18115,24 +17917,6 @@ snapshots:
regexp-tree@0.1.27: {}
- rehype-harden@1.1.5: {}
-
- rehype-katex@7.0.1:
- dependencies:
- '@types/hast': 3.0.4
- '@types/katex': 0.16.7
- hast-util-from-html-isomorphic: 2.0.0
- hast-util-to-text: 4.0.2
- katex: 0.16.25
- unist-util-visit-parents: 6.0.1
- vfile: 6.0.3
-
- rehype-raw@7.0.0:
- dependencies:
- '@types/hast': 3.0.4
- hast-util-raw: 9.1.0
- vfile: 6.0.3
-
rehype-recma@1.0.0:
dependencies:
'@types/estree': 1.0.8
@@ -18152,15 +17936,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- remark-math@6.0.0:
- dependencies:
- '@types/mdast': 4.0.4
- mdast-util-math: 3.0.0
- micromark-extension-math: 3.1.0
- unified: 11.0.5
- transitivePeerDependencies:
- - supports-color
-
remark-mdx@3.1.1:
dependencies:
mdast-util-mdx: 3.0.0
@@ -18545,26 +18320,6 @@ snapshots:
stdin-discarder@0.2.2: {}
- streamdown@1.4.0(@types/react@19.1.13)(react@19.1.1):
- dependencies:
- clsx: 2.1.1
- katex: 0.16.25
- lucide-react: 0.542.0(react@19.1.1)
- marked: 16.4.1
- mermaid: 11.12.0
- react: 19.1.1
- react-markdown: 10.1.0(@types/react@19.1.13)(react@19.1.1)
- rehype-harden: 1.1.5
- rehype-katex: 7.0.1
- rehype-raw: 7.0.0
- remark-gfm: 4.0.1
- remark-math: 6.0.0
- shiki: 3.13.0
- tailwind-merge: 3.3.1
- transitivePeerDependencies:
- - '@types/react'
- - supports-color
-
streamx@2.23.0:
dependencies:
events-universal: 1.0.1
@@ -18988,11 +18743,6 @@ snapshots:
unplugin: 2.3.10
unplugin-utils: 0.3.1
- unist-util-find-after@5.0.0:
- dependencies:
- '@types/unist': 3.0.3
- unist-util-is: 6.0.0
-
unist-util-is@6.0.0:
dependencies:
'@types/unist': 3.0.3
@@ -19005,11 +18755,6 @@ snapshots:
dependencies:
'@types/unist': 3.0.3
- unist-util-remove-position@5.0.0:
- dependencies:
- '@types/unist': 3.0.3
- unist-util-visit: 5.0.0
-
unist-util-stringify-position@4.0.0:
dependencies:
'@types/unist': 3.0.3
@@ -19177,11 +18922,6 @@ snapshots:
- '@types/react'
- '@types/react-dom'
- vfile-location@5.0.3:
- dependencies:
- '@types/unist': 3.0.3
- vfile: 6.0.3
-
vfile-message@4.0.2:
dependencies:
'@types/unist': 3.0.3
@@ -19391,8 +19131,6 @@ snapshots:
defaults: 1.0.4
optional: true
- web-namespaces@2.0.1: {}
-
webidl-conversions@3.0.1: {}
webpack-virtual-modules@0.6.2: {}
From 1443151a539a14c56ba58a52eea307ac8fd210dd Mon Sep 17 00:00:00 2001
From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>
Date: Sat, 25 Oct 2025 09:43:22 -0700
Subject: [PATCH 05/20] sdk -> dev kit
---
.../docs/foundations/runtime-adapters.mdx | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/docs/content/docs/foundations/runtime-adapters.mdx b/docs/content/docs/foundations/runtime-adapters.mdx
index df603bcdb..8072351dd 100644
--- a/docs/content/docs/foundations/runtime-adapters.mdx
+++ b/docs/content/docs/foundations/runtime-adapters.mdx
@@ -4,22 +4,22 @@ title: Building your own adapters
# Advanced Concepts: Building your own adapters
-This guide explains the underlying architecture of how the Workflow SDK integrates with different JavaScript runtimes, using Bun as a reference implementation. While this example uses Bun, the same principles apply to writing adapters for any JavaScript runtime (Node.js, Deno, Cloudflare Workers, etc.).
+This guide explains the underlying architecture of how the Workflow DevKit integrates with different JavaScript runtimes, using Bun as a reference implementation. While this example uses Bun, the same principles apply to writing adapters for any JavaScript runtime (Node.js, Deno, Cloudflare Workers, etc.).
The goal is for you to never need this document. We will be providing official adapters for everywhere you need to run workflows.
-The purpose of this document is to explain conceptually how the Workflow SDK works with different runtimes.
+The purpose of this document is to explain conceptually how the Workflow DevKit works with different runtimes.
---
## Overview
-The Workflow SDK architecture is as follows:
+The Workflow DevKit architecture is as follows:
1. **Build-time transformation**: Workflow code is transformed by a compiler plugin
2. **Generated handlers**: The build process generates HTTP handlers in `.well-known/workflow/v1/`
3. **Runtime integration**: Your application server exposes these handlers as HTTP endpoints
-4. **Execution orchestration**: The Workflow SDK communicates with these endpoints to orchestrate durable execution
+4. **Execution orchestration**: The Workflow DevKit communicates with these endpoints to orchestrate durable execution
```mermaid
flowchart TD
@@ -99,14 +99,14 @@ Your runtime adapter must expose these two endpoints:
Executes workflow-level operations (starting workflows, resuming after steps).
-**Request**: Binary payload from Workflow SDK
+**Request**: Binary payload from Workflow DevKit
**Response**: Binary payload with execution results
### `POST /.well-known/workflow/v1/step`
Executes individual steps within workflows.
-**Request**: Binary payload from Workflow SDK
+**Request**: Binary payload from Workflow DevKit
**Response**: Binary payload with step results
The exact protocol is handled by the generated `flow.js` and `step.js` files. Your adapter just needs to route HTTP requests to these handlers.
@@ -115,7 +115,7 @@ The exact protocol is handled by the generated `flow.js` and `step.js` files. Yo
## Writing a Runtime Adapter
-To integrate Workflow SDK with a runtime, you need three components:
+To integrate Workflow DevKit with a runtime, you need three components:
### Component 1: Build-Time Transformation Hook
@@ -183,7 +183,7 @@ server.route("POST", "/.well-known/workflow/v1/step", (req) => step.POST(req));
### Component 3: Workflow Client Integration
-Import and use the Workflow SDK client API to start workflows.
+Import and use the Workflow DevKit client API to start workflows.
```typescript
import { start } from "workflow/api";
From 59e91f8ee655a4d9c79e8efdaba2fd1f3942b966 Mon Sep 17 00:00:00 2001
From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>
Date: Sat, 25 Oct 2025 10:01:59 -0700
Subject: [PATCH 06/20] fixes
---
docs/content/docs/foundations/runtime-adapters.mdx | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/docs/content/docs/foundations/runtime-adapters.mdx b/docs/content/docs/foundations/runtime-adapters.mdx
index 8072351dd..3545e019e 100644
--- a/docs/content/docs/foundations/runtime-adapters.mdx
+++ b/docs/content/docs/foundations/runtime-adapters.mdx
@@ -4,11 +4,13 @@ title: Building your own adapters
# Advanced Concepts: Building your own adapters
-This guide explains the underlying architecture of how the Workflow DevKit integrates with different JavaScript runtimes, using Bun as a reference implementation. While this example uses Bun, the same principles apply to writing adapters for any JavaScript runtime (Node.js, Deno, Cloudflare Workers, etc.).
+
+ The goal for Workflow DevKit is to have official adapters for everywhere you
+ need to run workflows. Go to the [getting started](/docs/getting-started)
+ guide to pick your framework and get started.
+
-The goal is for you to never need this document. We will be providing official adapters for everywhere you need to run workflows.
-
-The purpose of this document is to explain conceptually how the Workflow DevKit works with different runtimes.
+If you're interested in learning how Workflow integrates with frameworks and how you can build your own adapter, read on. We'll walk through building the Bun adapter together and explain the underlying architecture of how the Workflow DevKit integrates with different JavaScript runtimes. While this example uses Bun, the same principles apply to writing adapters for any JavaScript runtime (Node.js, Deno, Cloudflare Workers, etc.).
---
@@ -16,8 +18,8 @@ The purpose of this document is to explain conceptually how the Workflow DevKit
The Workflow DevKit architecture is as follows:
-1. **Build-time transformation**: Workflow code is transformed by a compiler plugin
-2. **Generated handlers**: The build process generates HTTP handlers in `.well-known/workflow/v1/`
+1. **Build-time transformation**: Workflow code is transformed by a framework agnostic compiler plugin
+2. **Generated handlers**: The build process generates HTTP handlers in `.well-known/workflow/v1/` bundled with your step, workflow, and webhook code
3. **Runtime integration**: Your application server exposes these handlers as HTTP endpoints
4. **Execution orchestration**: The Workflow DevKit communicates with these endpoints to orchestrate durable execution
From f816b6e1e8a2620e9d263bce4d33c7dd53313001 Mon Sep 17 00:00:00 2001
From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>
Date: Sat, 25 Oct 2025 10:04:24 -0700
Subject: [PATCH 07/20] security explanation
---
.../docs/foundations/runtime-adapters.mdx | 39 ++++++++++++++++++-
1 file changed, 38 insertions(+), 1 deletion(-)
diff --git a/docs/content/docs/foundations/runtime-adapters.mdx b/docs/content/docs/foundations/runtime-adapters.mdx
index 3545e019e..6ba9a5e99 100644
--- a/docs/content/docs/foundations/runtime-adapters.mdx
+++ b/docs/content/docs/foundations/runtime-adapters.mdx
@@ -20,7 +20,7 @@ The Workflow DevKit architecture is as follows:
1. **Build-time transformation**: Workflow code is transformed by a framework agnostic compiler plugin
2. **Generated handlers**: The build process generates HTTP handlers in `.well-known/workflow/v1/` bundled with your step, workflow, and webhook code
-3. **Runtime integration**: Your application server exposes these handlers as HTTP endpoints
+3. **Runtime integration**: Your application server exposes these handlers as HTTP endpoints ([learn about how security is handled](#security))
4. **Execution orchestration**: The Workflow DevKit communicates with these endpoints to orchestrate durable execution
```mermaid
@@ -115,6 +115,43 @@ The exact protocol is handled by the generated `flow.js` and `step.js` files. Yo
---
+## Security
+
+A natural question when exposing these HTTP endpoints is: how are they secured?
+
+The answer depends on the **world abstraction** you're using. Different world implementations provide different security mechanisms:
+
+### World-Specific Security
+
+**Vercel (via `@workflow/world-vercel`):**
+
+- Uses Vercel Queue as the backend orchestration layer
+- Vercel Queue will soon support **private invoke**, making these routes not publicly accessible from the internet
+- The handler receives only a **message ID** from the queue, which must be retrieved from the Vercel Queue backend
+- This architecture makes it impossible to craft custom payloads and directly invoke the endpoints with arbitrary data
+- Even if someone discovers the endpoint URL, they cannot execute workflows without valid message IDs from the queue system
+
+**Custom Implementations:**
+
+- Different world implementations can implement their own security measures
+- Framework middleware can add authentication and authorization layers
+- You can implement API key verification, JWT validation, or other authentication schemes
+- You can use network-level security (VPCs, private networks, firewall rules)
+- You can implement rate limiting and request validation
+
+### Best Practices
+
+When building your own world or adapter, consider:
+
+- **Authentication**: Verify that requests come from trusted sources (your orchestration backend, not external clients)
+- **Payload validation**: Ensure request payloads are cryptographically signed or come from a trusted internal system
+- **Network isolation**: Run workflow endpoints on private networks when possible
+- **Rate limiting**: Protect against abuse even from internal systems
+
+The generated handlers themselves don't include authentication because the security model is delegated to the world abstraction layer, allowing each runtime environment to implement the most appropriate security mechanism for its deployment context.
+
+---
+
## Writing a Runtime Adapter
To integrate Workflow DevKit with a runtime, you need three components:
From 97047adec959bc609a568ce1282d40f29506f3c6 Mon Sep 17 00:00:00 2001
From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>
Date: Sat, 25 Oct 2025 11:36:01 -0700
Subject: [PATCH 08/20] Update runtime-adapters.mdx
---
docs/content/docs/foundations/runtime-adapters.mdx | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/docs/content/docs/foundations/runtime-adapters.mdx b/docs/content/docs/foundations/runtime-adapters.mdx
index 6ba9a5e99..c522ef851 100644
--- a/docs/content/docs/foundations/runtime-adapters.mdx
+++ b/docs/content/docs/foundations/runtime-adapters.mdx
@@ -82,6 +82,14 @@ When you run `workflow build`, it:
}
```
+
+ **No Vendor Lock-In**: `workflow build` is completely agnostic from the world
+ abstraction and runtime environment. It outputs standard JavaScript HTTP
+ handler code that works anywhere. This separation means you can deploy your
+ workflows to any JavaScript runtime without being locked into a specific
+ platform or provider.
+
+
### 3. Generated Artifacts
The build process creates two critical files:
From 13329c825d9925eb913980c1260632b0d8b0be44 Mon Sep 17 00:00:00 2001
From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>
Date: Sat, 25 Oct 2025 12:16:26 -0700
Subject: [PATCH 09/20] move under how it works
---
docs/content/docs/foundations/meta.json | 3 +--
.../framework-integrations.mdx} | 4 ++--
docs/content/docs/how-it-works/meta.json | 5 +++++
docs/content/docs/meta.json | 1 +
4 files changed, 9 insertions(+), 4 deletions(-)
rename docs/content/docs/{foundations/runtime-adapters.mdx => how-it-works/framework-integrations.mdx} (99%)
create mode 100644 docs/content/docs/how-it-works/meta.json
diff --git a/docs/content/docs/foundations/meta.json b/docs/content/docs/foundations/meta.json
index 8ce4a82a9..a205cb29e 100644
--- a/docs/content/docs/foundations/meta.json
+++ b/docs/content/docs/foundations/meta.json
@@ -6,8 +6,7 @@
"errors-and-retries",
"idempotency",
"hooks",
- "serialization",
- "runtime-adapters"
+ "serialization"
],
"defaultOpen": true
}
diff --git a/docs/content/docs/foundations/runtime-adapters.mdx b/docs/content/docs/how-it-works/framework-integrations.mdx
similarity index 99%
rename from docs/content/docs/foundations/runtime-adapters.mdx
rename to docs/content/docs/how-it-works/framework-integrations.mdx
index c522ef851..77815626e 100644
--- a/docs/content/docs/foundations/runtime-adapters.mdx
+++ b/docs/content/docs/how-it-works/framework-integrations.mdx
@@ -1,8 +1,8 @@
---
-title: Building your own adapters
+title: Framework Integrations
---
-# Advanced Concepts: Building your own adapters
+# Framework Integrations
The goal for Workflow DevKit is to have official adapters for everywhere you
diff --git a/docs/content/docs/how-it-works/meta.json b/docs/content/docs/how-it-works/meta.json
new file mode 100644
index 000000000..29e018e83
--- /dev/null
+++ b/docs/content/docs/how-it-works/meta.json
@@ -0,0 +1,5 @@
+{
+ "title": "How it works",
+ "pages": ["framework-integrations"],
+ "defaultOpen": true
+}
diff --git a/docs/content/docs/meta.json b/docs/content/docs/meta.json
index 8845f2e77..b527dc11f 100644
--- a/docs/content/docs/meta.json
+++ b/docs/content/docs/meta.json
@@ -4,6 +4,7 @@
"---",
"getting-started",
"foundations",
+ "how-it-works",
"observability",
"deploying",
"errors",
From c11a08854924d76863cd27616b2458672f142f2c Mon Sep 17 00:00:00 2001
From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>
Date: Sat, 25 Oct 2025 12:17:40 -0700
Subject: [PATCH 10/20] update how workflow build works
---
docs/content/docs/how-it-works/framework-integrations.mdx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/content/docs/how-it-works/framework-integrations.mdx b/docs/content/docs/how-it-works/framework-integrations.mdx
index 77815626e..2343f7560 100644
--- a/docs/content/docs/how-it-works/framework-integrations.mdx
+++ b/docs/content/docs/how-it-works/framework-integrations.mdx
@@ -69,11 +69,11 @@ The `@workflow/swc-plugin` transforms these functions into instrumented code tha
- Creates step boundaries for durability
- Produces execution graphs
-When you run `workflow build`, it:
+When you run `workflow build`, the framework adapter:
-1. Scans directories specified in `workflow.config.json`
+1. Scans directories where the framework's source code typically lives (for example, `app/` or `pages/` in Next.js)
2. Transforms files containing workflow directives
-3. Generates handler files in `.well-known/workflow/v1/`
+3. Generates handler files for the framework to expose at `.well-known/workflow/v1/*`
```json
{
From eb269c483687b7f8d641fff51498de05ddb31257 Mon Sep 17 00:00:00 2001
From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>
Date: Sat, 25 Oct 2025 12:24:20 -0700
Subject: [PATCH 11/20] fix file filtering + remove component 3
---
.../how-it-works/framework-integrations.mdx | 109 ++++++++----------
1 file changed, 50 insertions(+), 59 deletions(-)
diff --git a/docs/content/docs/how-it-works/framework-integrations.mdx b/docs/content/docs/how-it-works/framework-integrations.mdx
index 2343f7560..6f97e6d35 100644
--- a/docs/content/docs/how-it-works/framework-integrations.mdx
+++ b/docs/content/docs/how-it-works/framework-integrations.mdx
@@ -71,14 +71,14 @@ The `@workflow/swc-plugin` transforms these functions into instrumented code tha
When you run `workflow build`, the framework adapter:
-1. Scans directories where the framework's source code typically lives (for example, `app/` or `pages/` in Next.js)
-2. Transforms files containing workflow directives
+1. Scans directories where the framework's source code typically lives (for example, `app/`, `pages/`, `src/app/`, or `src/pages/` in Next.js)
+2. Transforms files containing workflow directives (`"use workflow"` or `"use step"`)
3. Generates handler files for the framework to expose at `.well-known/workflow/v1/*`
-```json
+```typescript
{
- "buildTarget": "vercel-build-output-api",
- "dirs": ["workflows"]
+ buildTarget: "next",
+ dirs: ["pages", "app", "src/pages", "src/app"]
}
```
@@ -171,7 +171,7 @@ Transform workflow files during the build/load process using the SWC plugin.
**What you need to implement:**
- A plugin/loader hook in your runtime's build system
-- File filtering (typically `workflows/**/*.{ts,tsx,js,jsx}`)
+- Detection of workflow directives (`"use workflow"` or `"use step"`) in source files
- SWC transformation with `@workflow/swc-plugin`
- Module loading/resolution
@@ -181,28 +181,29 @@ Transform workflow files during the build/load process using the SWC plugin.
buildSystem.plugin({
name: "workflow-transform",
setup(build) {
- build.onLoad(
- { filter: /workflows\/.*\.(ts|tsx|js|jsx)$/ },
- async (args) => {
- const source = await readFile(args.path);
-
- const result = await transform(source, {
- filename: args.path,
- jsc: {
- experimental: {
- plugins: [
- [require.resolve("@workflow/swc-plugin"), { mode: "client" }],
- ],
- },
- },
- });
+ build.onLoad({ filter: /\.(ts|tsx|js|jsx)$/ }, async (args) => {
+ const source = await readFile(args.path);
- return {
- contents: result.code,
- loader: "ts",
- };
+ if (!source.match(/(use step|use workflow)/)) {
+ return { contents: source };
}
- );
+
+ const result = await transform(source, {
+ filename: args.path,
+ jsc: {
+ experimental: {
+ plugins: [
+ [require.resolve("@workflow/swc-plugin"), { mode: "client" }],
+ ],
+ },
+ },
+ });
+
+ return {
+ contents: result.code,
+ loader: "ts",
+ };
+ });
},
});
```
@@ -228,18 +229,6 @@ server.route("POST", "/.well-known/workflow/v1/flow", (req) => flow.POST(req));
server.route("POST", "/.well-known/workflow/v1/step", (req) => step.POST(req));
```
-### Component 3: Workflow Client Integration
-
-Import and use the Workflow DevKit client API to start workflows.
-
-```typescript
-import { start } from "workflow/api";
-import { myWorkflow } from "./workflows/my-workflow.js";
-
-const run = await start(myWorkflow, [arg1, arg2]);
-console.log(run.runId);
-```
-
---
## Reference Implementation: Bun
@@ -257,28 +246,29 @@ console.log("Workflow plugin loaded");
plugin({
name: "workflow-transform",
setup(build) {
- build.onLoad(
- { filter: /workflows\/.*\.(ts|tsx|js|jsx)$/ },
- async (args) => {
- const source = await Bun.file(args.path).text();
-
- const result = await transform(source, {
- filename: args.path,
- jsc: {
- experimental: {
- plugins: [
- [require.resolve("@workflow/swc-plugin"), { mode: "client" }],
- ],
- },
- },
- });
+ build.onLoad({ filter: /\.(ts|tsx|js|jsx)$/ }, async (args) => {
+ const source = await Bun.file(args.path).text();
- return {
- contents: result.code,
- loader: "ts",
- };
+ if (!source.match(/(use step|use workflow)/)) {
+ return { contents: source };
}
- );
+
+ const result = await transform(source, {
+ filename: args.path,
+ jsc: {
+ experimental: {
+ plugins: [
+ [require.resolve("@workflow/swc-plugin"), { mode: "client" }],
+ ],
+ },
+ },
+ });
+
+ return {
+ contents: result.code,
+ loader: "ts",
+ };
+ });
},
});
```
@@ -286,7 +276,8 @@ plugin({
**Key aspects:**
- Uses Bun's native `plugin()` API
-- Filters workflow files using regex
+- Checks for workflow directives in all TypeScript and JavaScript files
+- Only transforms files that contain `"use step"` or `"use workflow"`
- Uses `Bun.file()` for file reading
- Applies SWC transformation with workflow plugin
- Returns transformed code with TypeScript loader
From 14ab8f79c932a0fb4a8407292ec888eaa8e2c7ba Mon Sep 17 00:00:00 2001
From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>
Date: Sat, 25 Oct 2025 13:14:18 -0700
Subject: [PATCH 12/20] dedup http handler info
---
.../how-it-works/framework-integrations.mdx | 49 ++++++++++---------
1 file changed, 26 insertions(+), 23 deletions(-)
diff --git a/docs/content/docs/how-it-works/framework-integrations.mdx b/docs/content/docs/how-it-works/framework-integrations.mdx
index 6f97e6d35..28ca8587b 100644
--- a/docs/content/docs/how-it-works/framework-integrations.mdx
+++ b/docs/content/docs/how-it-works/framework-integrations.mdx
@@ -75,13 +75,6 @@ When you run `workflow build`, the framework adapter:
2. Transforms files containing workflow directives (`"use workflow"` or `"use step"`)
3. Generates handler files for the framework to expose at `.well-known/workflow/v1/*`
-```typescript
-{
- buildTarget: "next",
- dirs: ["pages", "app", "src/pages", "src/app"]
-}
-```
-
**No Vendor Lock-In**: `workflow build` is completely agnostic from the world
abstraction and runtime environment. It outputs standard JavaScript HTTP
@@ -90,36 +83,46 @@ When you run `workflow build`, the framework adapter:
platform or provider.
-### 3. Generated Artifacts
+---
+
+## Required HTTP Endpoints
-The build process creates two critical files:
+Your runtime adapter must expose two endpoints that correspond to the generated handler files:
-- **`.well-known/workflow/v1/flow.js`**: Handles workflow execution requests
-- **`.well-known/workflow/v1/step.js`**: Handles individual step execution requests
+### `POST /.well-known/workflow/v1/flow`
-These files export handlers for POST requests that take in the web standard `Request` object.
+Handles [workflow function](/docs/foundations/workflows-and-steps#workflow-functions) execution.
----
+**How it works:**
-## Required HTTP Endpoints
+The workflow function is "rendered" multiple times during the lifecycle of a workflow execution. Each time it runs, it progresses through the workflow logic until it encounters the next step that needs to be executed. Because [workflow functions are deterministic and have no side effects](/docs/foundations/workflows-and-steps#workflow-functions), they can be safely re-run multiple times to calculate what the next step should be.
-Your runtime adapter must expose these two endpoints:
+This endpoint is called when:
-### `POST /.well-known/workflow/v1/flow`
+- Starting a new workflow
+- Resuming execution after a step completes
+- Resuming after a webhook or hook is triggered
+- Recovering from failures
-Executes workflow-level operations (starting workflows, resuming after steps).
+**Interface:**
-**Request**: Binary payload from Workflow DevKit
-**Response**: Binary payload with execution results
+- **Request**: Web standard `Request` object with binary payload from Workflow DevKit
+- **Response**: Web standard `Response` object with binary payload containing execution results
### `POST /.well-known/workflow/v1/step`
-Executes individual steps within workflows.
+Handles [step function](/docs/foundations/workflows-and-steps#step-functions) execution.
+
+**How it works:**
+
+This endpoint executes individual atomic operations within workflows. Each step runs exactly once per execution (unless it fails and needs to be retried).
+
+**Interface:**
-**Request**: Binary payload from Workflow DevKit
-**Response**: Binary payload with step results
+- **Request**: Web standard `Request` object with binary payload from Workflow DevKit
+- **Response**: Web standard `Response` object with binary payload containing step results
-The exact protocol is handled by the generated `flow.js` and `step.js` files. Your adapter just needs to route HTTP requests to these handlers.
+**Implementation Note:** The exact protocol and payload handling is managed by the generated `flow.js` and `step.js` files. Your adapter only needs to route HTTP requests to these handlers.
---
From b56ce1457adbd4d82bb2dc644d45c8ec09a6bf15 Mon Sep 17 00:00:00 2001
From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>
Date: Sat, 25 Oct 2025 13:22:30 -0700
Subject: [PATCH 13/20] merge sections
---
.../how-it-works/framework-integrations.mdx | 163 +++++++++---------
1 file changed, 77 insertions(+), 86 deletions(-)
diff --git a/docs/content/docs/how-it-works/framework-integrations.mdx b/docs/content/docs/how-it-works/framework-integrations.mdx
index 28ca8587b..581d19bb0 100644
--- a/docs/content/docs/how-it-works/framework-integrations.mdx
+++ b/docs/content/docs/how-it-works/framework-integrations.mdx
@@ -32,9 +32,11 @@ flowchart TD
---
-## The Transformation Pipeline
+## Writing a Runtime Adapter
+
+To integrate Workflow DevKit with a runtime, you need two main components: a build-time transformation hook and HTTP server integration. Let's walk through each one.
-### 1. Source Code Directives
+### Transformation
Workflows use special directives to mark code for transformation:
@@ -60,7 +62,7 @@ async function createUser(email: string) {
- `"use workflow"`: Marks a function as a durable workflow entry point
- `"use step"`: Marks a function as an atomic, retryable step
-### 2. Build-Time Transformation
+**How the transformation works:**
The `@workflow/swc-plugin` transforms these functions into instrumented code that:
@@ -83,13 +85,59 @@ When you run `workflow build`, the framework adapter:
platform or provider.
----
+**What you need to implement:**
+
+- A plugin/loader hook in your runtime's build system
+- Detection of workflow directives (`"use workflow"` or `"use step"`) in source files
+- SWC transformation with `@workflow/swc-plugin`
+- Module loading/resolution
+
+**Example pattern:**
+
+```typescript
+buildSystem.plugin({
+ name: "workflow-transform",
+ setup(build) {
+ build.onLoad({ filter: /\.(ts|tsx|js|jsx)$/ }, async (args) => {
+ const source = await readFile(args.path);
+
+ if (!source.match(/(use step|use workflow)/)) {
+ return { contents: source };
+ }
+
+ const result = await transform(source, {
+ filename: args.path,
+ jsc: {
+ experimental: {
+ plugins: [
+ [require.resolve("@workflow/swc-plugin"), { mode: "client" }],
+ ],
+ },
+ },
+ });
+
+ return {
+ contents: result.code,
+ loader: "ts",
+ };
+ });
+ },
+});
+```
+
+### HTTP Server Integration
-## Required HTTP Endpoints
+Your runtime adapter must expose two HTTP endpoints that correspond to the generated handler files.
-Your runtime adapter must expose two endpoints that correspond to the generated handler files:
+
+ The exact protocol and payload handling is managed by the generated `flow.js`
+ and `step.js` files. Your adapter only needs to route HTTP requests to these
+ handlers.
+
-### `POST /.well-known/workflow/v1/flow`
+#### Workflow Handler
+
+**Endpoint:** `POST /.well-known/workflow/v1/flow`
Handles [workflow function](/docs/foundations/workflows-and-steps#workflow-functions) execution.
@@ -97,7 +145,7 @@ Handles [workflow function](/docs/foundations/workflows-and-steps#workflow-funct
The workflow function is "rendered" multiple times during the lifecycle of a workflow execution. Each time it runs, it progresses through the workflow logic until it encounters the next step that needs to be executed. Because [workflow functions are deterministic and have no side effects](/docs/foundations/workflows-and-steps#workflow-functions), they can be safely re-run multiple times to calculate what the next step should be.
-This endpoint is called when:
+**Called when:**
- Starting a new workflow
- Resuming execution after a step completes
@@ -109,7 +157,17 @@ This endpoint is called when:
- **Request**: Web standard `Request` object with binary payload from Workflow DevKit
- **Response**: Web standard `Response` object with binary payload containing execution results
-### `POST /.well-known/workflow/v1/step`
+**Example implementation:**
+
+```typescript
+import flow from "./.well-known/workflow/v1/flow.js";
+
+server.route("POST", "/.well-known/workflow/v1/flow", (req) => flow.POST(req));
+```
+
+#### Step Handler
+
+**Endpoint:** `POST /.well-known/workflow/v1/step`
Handles [step function](/docs/foundations/workflows-and-steps#step-functions) execution.
@@ -122,7 +180,13 @@ This endpoint executes individual atomic operations within workflows. Each step
- **Request**: Web standard `Request` object with binary payload from Workflow DevKit
- **Response**: Web standard `Response` object with binary payload containing step results
-**Implementation Note:** The exact protocol and payload handling is managed by the generated `flow.js` and `step.js` files. Your adapter only needs to route HTTP requests to these handlers.
+**Example implementation:**
+
+```typescript
+import step from "./.well-known/workflow/v1/step.js";
+
+server.route("POST", "/.well-known/workflow/v1/step", (req) => step.POST(req));
+```
---
@@ -163,89 +227,16 @@ The generated handlers themselves don't include authentication because the secur
---
-## Writing a Runtime Adapter
-
-To integrate Workflow DevKit with a runtime, you need three components:
-
-### Component 1: Build-Time Transformation Hook
-
-Transform workflow files during the build/load process using the SWC plugin.
-
-**What you need to implement:**
-
-- A plugin/loader hook in your runtime's build system
-- Detection of workflow directives (`"use workflow"` or `"use step"`) in source files
-- SWC transformation with `@workflow/swc-plugin`
-- Module loading/resolution
-
-**Example pattern:**
-
-```typescript
-buildSystem.plugin({
- name: "workflow-transform",
- setup(build) {
- build.onLoad({ filter: /\.(ts|tsx|js|jsx)$/ }, async (args) => {
- const source = await readFile(args.path);
-
- if (!source.match(/(use step|use workflow)/)) {
- return { contents: source };
- }
-
- const result = await transform(source, {
- filename: args.path,
- jsc: {
- experimental: {
- plugins: [
- [require.resolve("@workflow/swc-plugin"), { mode: "client" }],
- ],
- },
- },
- });
-
- return {
- contents: result.code,
- loader: "ts",
- };
- });
- },
-});
-```
-
-### Component 2: HTTP Server Integration
-
-Expose the generated workflow handlers as HTTP endpoints.
-
-**What you need to implement:**
-
-- HTTP server listening on a port
-- Route handling for `/.well-known/workflow/v1/flow`
-- Route handling for `/.well-known/workflow/v1/step`
-- Importing and delegating to generated handlers
-
-**Example pattern:**
-
-```typescript
-import flow from "./.well-known/workflow/v1/flow.js";
-import step from "./.well-known/workflow/v1/step.js";
-
-server.route("POST", "/.well-known/workflow/v1/flow", (req) => flow.POST(req));
-server.route("POST", "/.well-known/workflow/v1/step", (req) => step.POST(req));
-```
-
----
-
## Reference Implementation: Bun
-Here's how the three components are implemented for Bun:
+Here's how the two components are implemented for Bun:
-### 1. Bun Plugin (Build-Time Transformation)
+### 1. Bun Transformation
```typescript
import { plugin } from "bun";
import { transform } from "@swc/core";
-console.log("Workflow plugin loaded");
-
plugin({
name: "workflow-transform",
setup(build) {
@@ -329,7 +320,7 @@ console.log(`Server listening on http://localhost:${server.port}`);
- Delegates POST requests directly to generated handlers
- Example endpoint shows how to start a workflow using `start()`
-### 3. Workflow Definition
+### Example Workflow Definition
```typescript
import { sleep, FatalError } from "workflow";
From ff1d91e0e3d697e32bea281658ae964ad469e0c8 Mon Sep 17 00:00:00 2001
From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>
Date: Sat, 25 Oct 2025 13:28:50 -0700
Subject: [PATCH 14/20] explain difference between bun framework and runtime
---
.../how-it-works/framework-integrations.mdx | 20 ++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/docs/content/docs/how-it-works/framework-integrations.mdx b/docs/content/docs/how-it-works/framework-integrations.mdx
index 581d19bb0..de6022f67 100644
--- a/docs/content/docs/how-it-works/framework-integrations.mdx
+++ b/docs/content/docs/how-it-works/framework-integrations.mdx
@@ -229,9 +229,16 @@ The generated handlers themselves don't include authentication because the secur
## Reference Implementation: Bun
-Here's how the two components are implemented for Bun:
+Bun is a special case because it serves as both a **runtime** and a **framework**:
-### 1. Bun Transformation
+- **Runtime**: Bun needs transformations applied to compile workflow code
+- **Framework**: `Bun.serve()` handles HTTP routing and request handling
+
+Here's how both components are implemented:
+
+### 1. Runtime Transformation
+
+This transformation applies to the **Bun runtime** to resolve workflow code during the build process:
```typescript
import { plugin } from "bun";
@@ -269,14 +276,16 @@ plugin({
**Key aspects:**
-- Uses Bun's native `plugin()` API
+- Uses Bun's native `plugin()` API to hook into the build process
- Checks for workflow directives in all TypeScript and JavaScript files
- Only transforms files that contain `"use step"` or `"use workflow"`
- Uses `Bun.file()` for file reading
- Applies SWC transformation with workflow plugin
- Returns transformed code with TypeScript loader
-### 2. HTTP Server (Runtime Integration)
+### 2. HTTP Routing
+
+This integration uses **`Bun.serve()`** as the framework to route HTTP requests to the workflow handlers:
```typescript
import { start } from "workflow/api";
@@ -316,8 +325,9 @@ console.log(`Server listening on http://localhost:${server.port}`);
**Key aspects:**
- Imports generated `flow.js` and `step.js` handlers
-- Uses Bun's routing API to map endpoints
+- Uses `Bun.serve()`'s routing API to map HTTP endpoints to handlers
- Delegates POST requests directly to generated handlers
+- The framework handles request routing; the runtime handles code execution
- Example endpoint shows how to start a workflow using `start()`
### Example Workflow Definition
From 427c5e407077e0396ef673a0d1b36a3ed56abc5d Mon Sep 17 00:00:00 2001
From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>
Date: Sat, 25 Oct 2025 13:29:38 -0700
Subject: [PATCH 15/20] runtime adapter -> framework integration
---
docs/content/docs/how-it-works/framework-integrations.mdx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/content/docs/how-it-works/framework-integrations.mdx b/docs/content/docs/how-it-works/framework-integrations.mdx
index de6022f67..b7c3761a9 100644
--- a/docs/content/docs/how-it-works/framework-integrations.mdx
+++ b/docs/content/docs/how-it-works/framework-integrations.mdx
@@ -32,7 +32,7 @@ flowchart TD
---
-## Writing a Runtime Adapter
+## Writing a Framework Integration
To integrate Workflow DevKit with a runtime, you need two main components: a build-time transformation hook and HTTP server integration. Let's walk through each one.
From 8f98d9e3428f9c0671c8acdbb3f57820a39a99d1 Mon Sep 17 00:00:00 2001
From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>
Date: Sat, 25 Oct 2025 13:32:05 -0700
Subject: [PATCH 16/20] framework integration
---
docs/content/docs/how-it-works/framework-integrations.mdx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/content/docs/how-it-works/framework-integrations.mdx b/docs/content/docs/how-it-works/framework-integrations.mdx
index b7c3761a9..fc3580f8e 100644
--- a/docs/content/docs/how-it-works/framework-integrations.mdx
+++ b/docs/content/docs/how-it-works/framework-integrations.mdx
@@ -127,7 +127,7 @@ buildSystem.plugin({
### HTTP Server Integration
-Your runtime adapter must expose two HTTP endpoints that correspond to the generated handler files.
+Your framework integration must expose two HTTP endpoints that correspond to the generated handler files.
The exact protocol and payload handling is managed by the generated `flow.js`
From 1b9a57b90b5ffc1d9e267f8aafa698dadaeab177 Mon Sep 17 00:00:00 2001
From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>
Date: Sat, 25 Oct 2025 13:44:19 -0700
Subject: [PATCH 17/20] bunfig instructions
---
.../docs/how-it-works/framework-integrations.mdx | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/docs/content/docs/how-it-works/framework-integrations.mdx b/docs/content/docs/how-it-works/framework-integrations.mdx
index fc3580f8e..54ab5fb2b 100644
--- a/docs/content/docs/how-it-works/framework-integrations.mdx
+++ b/docs/content/docs/how-it-works/framework-integrations.mdx
@@ -240,7 +240,7 @@ Here's how both components are implemented:
This transformation applies to the **Bun runtime** to resolve workflow code during the build process:
-```typescript
+```typescript title="workflow-plugin.ts"
import { plugin } from "bun";
import { transform } from "@swc/core";
@@ -274,6 +274,14 @@ plugin({
});
```
+To run this plugin in bun, add it to your `bunfig.toml` file:
+
+```toml title="bunfig.toml"
+# scripts to run before `bun run`-ing a file or script
+# register plugins by adding them to this list
+preload = ["./workflow-plugin.ts"]
+```
+
**Key aspects:**
- Uses Bun's native `plugin()` API to hook into the build process
From b653a7b31a730e1da78cde5b1dc99481fea5e8fc Mon Sep 17 00:00:00 2001
From: Pranay Prakash
Date: Sat, 25 Oct 2025 15:54:40 -0700
Subject: [PATCH 18/20] Pranay's edits
---
.../how-it-works/framework-integrations.mdx | 293 +++++++++++-------
1 file changed, 183 insertions(+), 110 deletions(-)
diff --git a/docs/content/docs/how-it-works/framework-integrations.mdx b/docs/content/docs/how-it-works/framework-integrations.mdx
index 54ab5fb2b..1d14af05b 100644
--- a/docs/content/docs/how-it-works/framework-integrations.mdx
+++ b/docs/content/docs/how-it-works/framework-integrations.mdx
@@ -10,7 +10,9 @@ title: Framework Integrations
guide to pick your framework and get started.
-If you're interested in learning how Workflow integrates with frameworks and how you can build your own adapter, read on. We'll walk through building the Bun adapter together and explain the underlying architecture of how the Workflow DevKit integrates with different JavaScript runtimes. While this example uses Bun, the same principles apply to writing adapters for any JavaScript runtime (Node.js, Deno, Cloudflare Workers, etc.).
+If you're interested in learning how Workflow integrates with frameworks and how you can build your own framework integration, read on. We'll walk through building the Bun framework integration together and explain the underlying architecture of how the Workflow DevKit integrates with different JavaScript runtimes. While this example uses Bun, the same principles apply to writing framework integrations for any JavaScript runtime (Node.js, Deno, Cloudflare Workers, etc.).
+
+A working example of the bun integration walkthrough below can be [found here](https://github.com/vercel/workflow-examples/tree/main/custom-adapter). Note that this is just an example, and we recommend the official bun integration that is currently in development.
---
@@ -19,7 +21,7 @@ If you're interested in learning how Workflow integrates with frameworks and how
The Workflow DevKit architecture is as follows:
1. **Build-time transformation**: Workflow code is transformed by a framework agnostic compiler plugin
-2. **Generated handlers**: The build process generates HTTP handlers in `.well-known/workflow/v1/` bundled with your step, workflow, and webhook code
+2. **Generated handlers**: The build process generates HTTP handlers in `.well-known/workflow/v1/` bundled with your step, workflow, and webhook code (flow.js, step.js, and webhook.js)
3. **Runtime integration**: Your application server exposes these handlers as HTTP endpoints ([learn about how security is handled](#security))
4. **Execution orchestration**: The Workflow DevKit communicates with these endpoints to orchestrate durable execution
@@ -38,11 +40,11 @@ To integrate Workflow DevKit with a runtime, you need two main components: a bui
### Transformation
-Workflows use special directives to mark code for transformation:
+Workflows use directives to mark code for transformation:
```typescript
export async function handleUserSignup(email: string) {
- "use workflow";
+ "use workflow"; // [!code highlight]
const user = await createUser(email);
await sendWelcomeEmail(user);
@@ -51,7 +53,7 @@ export async function handleUserSignup(email: string) {
}
async function createUser(email: string) {
- "use step";
+ "use step"; // [!code highlight]
return { id: crypto.randomUUID(), email };
}
@@ -64,74 +66,132 @@ async function createUser(email: string) {
**How the transformation works:**
-The `@workflow/swc-plugin` transforms these functions into instrumented code that:
+The `@workflow/swc-plugin` operates in three modes, transforming code differently for each bundle:
-- Captures function calls and their arguments
-- Generates serialization/deserialization logic
-- Creates step boundaries for durability
-- Produces execution graphs
+
+
-When you run `workflow build`, the framework adapter:
+**Step Mode** creates the step execution bundle served at `/.well-known/workflow/v1/step`.
-1. Scans directories where the framework's source code typically lives (for example, `app/`, `pages/`, `src/app/`, or `src/pages/` in Next.js)
-2. Transforms files containing workflow directives (`"use workflow"` or `"use step"`)
-3. Generates handler files for the framework to expose at `.well-known/workflow/v1/*`
+**Input:**
-
- **No Vendor Lock-In**: `workflow build` is completely agnostic from the world
- abstraction and runtime environment. It outputs standard JavaScript HTTP
- handler code that works anywhere. This separation means you can deploy your
- workflows to any JavaScript runtime without being locked into a specific
- platform or provider.
-
+```typescript
+export async function createUser(email: string) {
+ "use step";
+ return { id: crypto.randomUUID(), email };
+}
+```
+
+**Output:**
-**What you need to implement:**
+```typescript
+import { registerStepFunction } from "workflow/internal/private"; // [!code highlight]
-- A plugin/loader hook in your runtime's build system
-- Detection of workflow directives (`"use workflow"` or `"use step"`) in source files
-- SWC transformation with `@workflow/swc-plugin`
-- Module loading/resolution
+export async function createUser(email: string) {
+ return { id: crypto.randomUUID(), email };
+}
+
+registerStepFunction("step//workflows/user.js//createUser", createUser); // [!code highlight]
+```
-**Example pattern:**
+The directive is removed from step functions, and they're registered with the runtime using `registerStepFunction()`.
+
+Step bodies are kept intact since they just run in the main runtime (Node, Deno, Bun, etc.). There's no transformation here.
+
+**ID Format:** Step IDs follow the pattern `step//{filepath}//{functionName}`, where the filepath is relative to your project root.
+
+
+
+
+**Workflow Mode** creates the workflow execution bundle served at `/.well-known/workflow/v1/flow`.
+
+**Input:**
```typescript
-buildSystem.plugin({
- name: "workflow-transform",
- setup(build) {
- build.onLoad({ filter: /\.(ts|tsx|js|jsx)$/ }, async (args) => {
- const source = await readFile(args.path);
+export async function createUser(email: string) {
+ "use step";
+ return { id: crypto.randomUUID(), email };
+}
- if (!source.match(/(use step|use workflow)/)) {
- return { contents: source };
- }
+export async function handleUserSignup(email: string) {
+ "use workflow";
+ const user = await createUser(email);
+ return { userId: user.id };
+}
+```
- const result = await transform(source, {
- filename: args.path,
- jsc: {
- experimental: {
- plugins: [
- [require.resolve("@workflow/swc-plugin"), { mode: "client" }],
- ],
- },
- },
- });
+**Output:**
- return {
- contents: result.code,
- loader: "ts",
- };
- });
- },
-});
+```typescript
+export async function createUser(email: string) {
+ return globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//workflows/user.js//createUser")(email); // [!code highlight]
+}
+
+export async function handleUserSignup(email: string) {
+ const user = await createUser(email);
+ return { userId: user.id };
+}
+handleUserSignup.workflowId = "workflow//workflows/user.js//handleUserSignup"; // [!code highlight]
```
+Step function bodies are replaced with calls to `globalThis[Symbol.for("WORKFLOW_USE_STEP")]`, which handles replaying the step result from the event log, or trigger a suspension that enqueues the step in the background.
+
+Workflow function bodies remain intact—they're executed deterministically during replay. The directive simply gets dropped, and a workflow ID is added to the function to allow [`start`](/docs/api-reference/workflow-api/start) to work at runtime.
+
+**ID Format:** Workflow IDs follow the pattern `workflow//{filepath}//{functionName}`. The `workflowId` property is attached to the function for runtime identification.
+
+
+
+
+**Client Mode** transforms workflow functions in your application code to prevent direct execution.
+
+**Input:**
+
+```typescript
+export async function handleUserSignup(email: string) {
+ "use workflow";
+ const user = await createUser(email);
+ return { userId: user.id };
+}
+```
+
+**Output:**
+
+```typescript
+export async function handleUserSignup(email: string) {
+ throw new Error("You attempted to execute ..."); // [!code highlight]
+}
+handleUserSignup.workflowId = "workflow//workflows/user.js//handleUserSignup"; // [!code highlight]
+```
+
+Workflow functions throw errors to prevent direct execution—you must use [`start()`](/docs/api-reference/workflow-api/start) instead.
+
+The IDs are generated exactly like in workflow mode to ensure they can be directly referenced at runtime.
+
+
+
+
+When running your framework's `build` command, the corresponding workflow integration simply needs to:
+
+1. Scan directories where the framework's source code typically lives (for example, `app/` and `pages/` in Next.js)
+2. Transform files containing workflow directives (`"use workflow"` or `"use step"`)
+3. Generate handler files for the framework to expose at `.well-known/workflow/v1/*`
+
+
+ **No Vendor Lock-In**: Everything here is completely agnostic from the world
+ abstraction and runtime environment. It outputs standard JavaScript HTTP
+ handler code that works anywhere. This separation means you can deploy your
+ workflows to any JavaScript runtime without being locked into a specific
+ platform or provider.
+
+
### HTTP Server Integration
-Your framework integration must expose two HTTP endpoints that correspond to the generated handler files.
+Your framework integration must expose three HTTP endpoints that correspond to the generated handler files.
- The exact protocol and payload handling is managed by the generated `flow.js`
- and `step.js` files. Your adapter only needs to route HTTP requests to these
+ The exact protocol and payload handling is managed by the generated `flow.js`,
+ `step.js`, and `webhook.js` files. Your framework integration only needs to route HTTP requests to these
handlers.
@@ -188,6 +248,39 @@ import step from "./.well-known/workflow/v1/step.js";
server.route("POST", "/.well-known/workflow/v1/step", (req) => step.POST(req));
```
+#### Webhook Handler
+
+**Endpoint:** `POST /.well-known/workflow/v1/webhook/:token`
+
+Handles webhook delivery to running workflows via [`createWebhook()`](/docs/api-reference/workflow/create-webhook).
+
+**How it works:**
+
+This endpoint allows external systems to send data to running workflows. When a workflow calls `createWebhook()`, it generates a unique token that external services can use to deliver data. The webhook handler validates the token, delivers the payload to the correct workflow run, and resumes execution.
+
+**Interface:**
+
+- **Request**: Web standard `Request` object with the webhook payload and token parameter
+- **Response**: Web standard `Response` object confirming webhook delivery
+
+**Example implementation (framework-dependent):**
+
+```typescript
+// Next.js example
+import webhook from "./.well-known/workflow/v1/webhook/[token]/route.js";
+
+// The route is automatically handled by Next.js App Router
+
+// Nitro example
+import webhook from "./.well-known/workflow/v1/webhook.mjs";
+
+server.route("POST", "/.well-known/workflow/v1/webhook/:token", (req) => webhook.POST(req));
+```
+
+
+ The generated webhook route structure varies by framework. Next.js generates a file at `webhook/[token]/route.js` to leverage App Router's dynamic routing, while other frameworks may generate a single `webhook.js` or `webhook.mjs` handler that your framework must route to with a `:token` parameter.
+
+
---
## Security
@@ -216,7 +309,7 @@ The answer depends on the **world abstraction** you're using. Different world im
### Best Practices
-When building your own world or adapter, consider:
+When building your own world or framework integration, consider:
- **Authentication**: Verify that requests come from trusted sources (your orchestration backend, not external clients)
- **Payload validation**: Ensure request payloads are cryptographically signed or come from a trusted internal system
@@ -234,13 +327,31 @@ Bun is a special case because it serves as both a **runtime** and a **framework*
- **Runtime**: Bun needs transformations applied to compile workflow code
- **Framework**: `Bun.serve()` handles HTTP routing and request handling
-Here's how both components are implemented:
+Here's how the bun example handles building the routes, implementing the client transform at runtime, and serves the routes from the right HTTP endpoints.
+
+### 1. Building the routes
-### 1. Runtime Transformation
+In this example, running the Step and Workflow mode transform actually happens using the `workflow` CLI. `workflow build` will, by default, look for a `workflows/` directory and will create the "step" and "workflow" bundles.
+So in this example, the `package.json` simply invokes workflow build before running the bun server.
+
+```json title="package.json"
+{
+ // ...
+ "scripts": {
+ "dev": "bun x workflow build && PORT=3152 bun run server.ts"
+ }
+}
+```
+
+
+ This implementation notably lacks file system watching for a local dev experience. Production framework implementations, like the [Next Plugin](https://github.com/vercel/workflow/tree/main/packages/next) are better optimized for this.
+
+
+### 2. Runtime Client Transformation
This transformation applies to the **Bun runtime** to resolve workflow code during the build process:
-```typescript title="workflow-plugin.ts"
+```typescript title="workflow-plugin.ts" lineNumbers
import { plugin } from "bun";
import { transform } from "@swc/core";
@@ -250,6 +361,8 @@ plugin({
build.onLoad({ filter: /\.(ts|tsx|js|jsx)$/ }, async (args) => {
const source = await Bun.file(args.path).text();
+ // Optimization: we can speed up the plugin by only transforming files
+ // that make use of the directive. False positives are safe.
if (!source.match(/(use step|use workflow)/)) {
return { contents: source };
}
@@ -259,7 +372,7 @@ plugin({
jsc: {
experimental: {
plugins: [
- [require.resolve("@workflow/swc-plugin"), { mode: "client" }],
+ [require.resolve("@workflow/swc-plugin"), { mode: "client" }], // [!code highlight]
],
},
},
@@ -277,8 +390,6 @@ plugin({
To run this plugin in bun, add it to your `bunfig.toml` file:
```toml title="bunfig.toml"
-# scripts to run before `bun run`-ing a file or script
-# register plugins by adding them to this list
preload = ["./workflow-plugin.ts"]
```
@@ -287,18 +398,18 @@ preload = ["./workflow-plugin.ts"]
- Uses Bun's native `plugin()` API to hook into the build process
- Checks for workflow directives in all TypeScript and JavaScript files
- Only transforms files that contain `"use step"` or `"use workflow"`
-- Uses `Bun.file()` for file reading
- Applies SWC transformation with workflow plugin
- Returns transformed code with TypeScript loader
-### 2. HTTP Routing
+### 3. HTTP Routing
This integration uses **`Bun.serve()`** as the framework to route HTTP requests to the workflow handlers:
-```typescript
+```typescript title="server.ts" lineNumbers
import { start } from "workflow/api";
import flow from "./.well-known/workflow/v1/flow.js";
import step from "./.well-known/workflow/v1/step.js";
+import webhook from "./.well-known/workflow/v1/webhook.js";
import { handleUserSignup } from "./workflows/user-signup.js";
const server = Bun.serve({
@@ -312,6 +423,10 @@ const server = Bun.serve({
POST: (req) => step.POST(req),
},
+ "/.well-known/workflow/v1/webhook/:token": {
+ POST: (req) => webhook.POST(req),
+ },
+
"/": {
GET: async (req) => {
const email = `test-${crypto.randomUUID()}@test.com`;
@@ -332,53 +447,11 @@ console.log(`Server listening on http://localhost:${server.port}`);
**Key aspects:**
-- Imports generated `flow.js` and `step.js` handlers
+- Imports generated `flow.js`, `step.js`, and `webhook.js` handlers
- Uses `Bun.serve()`'s routing API to map HTTP endpoints to handlers
- Delegates POST requests directly to generated handlers
+- The webhook route uses a `:token` parameter to identify which workflow run should receive the webhook
- The framework handles request routing; the runtime handles code execution
- Example endpoint shows how to start a workflow using `start()`
-### Example Workflow Definition
-
-```typescript
-import { sleep, FatalError } from "workflow";
-
-export async function handleUserSignup(email: string) {
- "use workflow";
-
- const user = await createUser(email);
- await sendWelcomeEmail(user);
-
- await sendOnboardingEmail(user);
-
- return { userId: user.id, status: "onboarded" };
-}
-
-async function createUser(email: string) {
- "use step";
-
- console.log(`Creating a new user with email: ${email}`);
-
- return { id: crypto.randomUUID(), email };
-}
-
-async function sendWelcomeEmail(user: { id: string; email: string }) {
- "use step";
-
- console.log(`Sending welcome email to user: ${user.id}`);
-}
-
-async function sendOnboardingEmail(user: { id: string; email: string }) {
- "use step";
-
- console.log(`Sending onboarding email to user: ${user.id}`);
-}
-```
-
-**Key aspects:**
-
-- `"use workflow"` marks the main workflow function
-- `"use step"` marks individual atomic operations
-- Steps can be retried independently
-- Return values flow between steps
-- Type-safe arguments and returns
+You can find the complete bun example implementation [here](https://github.com/vercel/workflow-examples/tree/main/custom-adapter).
From 8509c26d3980ed369424c214eab5755cbbfae2d6 Mon Sep 17 00:00:00 2001
From: Pranay Prakash
Date: Sat, 25 Oct 2025 17:47:21 -0700
Subject: [PATCH 19/20] Refactor to 2 pages
---
.../docs/how-it-works/code-transform.mdx | 343 +++++++++++
.../how-it-works/framework-integrations.mdx | 540 ++++++++++--------
docs/content/docs/how-it-works/meta.json | 2 +-
3 files changed, 660 insertions(+), 225 deletions(-)
create mode 100644 docs/content/docs/how-it-works/code-transform.mdx
diff --git a/docs/content/docs/how-it-works/code-transform.mdx b/docs/content/docs/how-it-works/code-transform.mdx
new file mode 100644
index 000000000..64b241c61
--- /dev/null
+++ b/docs/content/docs/how-it-works/code-transform.mdx
@@ -0,0 +1,343 @@
+---
+title: How the Directives Work
+---
+
+# How the Directives Work
+
+Workflows use special directives to mark code for transformation by the Workflow DevKit compiler. This page explains how `"use workflow"` and `"use step"` directives work, what transformations are applied, and why they're necessary for durable execution.
+
+---
+
+## Directives Overview
+
+Workflows use two directives to mark functions for special handling:
+
+```typescript
+export async function handleUserSignup(email: string) {
+ "use workflow"; // [!code highlight]
+
+ const user = await createUser(email);
+ await sendWelcomeEmail(user);
+
+ return { userId: user.id };
+}
+
+async function createUser(email: string) {
+ "use step"; // [!code highlight]
+
+ return { id: crypto.randomUUID(), email };
+}
+```
+
+**Key directives:**
+
+- `"use workflow"`: Marks a function as a durable workflow entry point
+- `"use step"`: Marks a function as an atomic, retryable step
+
+These directives trigger the `@workflow/swc-plugin` compiler to transform your code in different ways depending on the execution context.
+
+---
+
+## The Three Transformation Modes
+
+The compiler operates in three distinct modes, transforming the same source code differently for each execution context:
+
+```mermaid
+flowchart LR
+ A["Source Code with directives"] --> B["Step Mode"]
+ A --> C["Workflow Mode"]
+ A --> D["Client Mode"]
+ B --> E["step.js (Step Execution)"]
+ C --> F["flow.js (Workflow Execution)"]
+ D --> G["Your App Code (Enables `start`)"]
+```
+
+### Comparison Table
+
+| Mode | Used In | Purpose | Output Location | Applied To | Required? |
+|----------|------------|--------------------------------|------------------------------------|--------------------|-----------|
+| Step | Build time | Bundles step handlers | `.well-known/workflow/v1/step.js` | `"use step"` only | Yes |
+| Workflow | Build time | Bundles workflow orchestrators | `.well-known/workflow/v1/flow.js` | Both directives | Yes |
+| Client | Build/Runtime | Provides workflow IDs and types to `start` | Your application code | `"use workflow"` only | Optional* |
+
+\* Client mode is **recommended** for better developer experience—it provides automatic ID generation and type safety. Without it, you must manually construct workflow IDs or use the build manifest.
+
+---
+
+## Detailed Transformation Examples
+
+
+
+
+**Step Mode** creates the step execution bundle served at `/.well-known/workflow/v1/step`.
+
+**Input:**
+
+```typescript
+export async function createUser(email: string) {
+ "use step";
+ return { id: crypto.randomUUID(), email };
+}
+```
+
+**Output:**
+
+```typescript
+import { registerStepFunction } from "workflow/internal/private"; // [!code highlight]
+
+export async function createUser(email: string) {
+ return { id: crypto.randomUUID(), email };
+}
+
+registerStepFunction("step//workflows/user.js//createUser", createUser); // [!code highlight]
+```
+
+**What happens:**
+
+- The `"use step"` directive is removed
+- The function body is kept completely intact (no transformation)
+- The function is registered with the runtime using `registerStepFunction()`
+- Step functions run with full Node.js/Deno/Bun access
+
+**Why no transformation?** Step functions execute in your main runtime with full access to Node.js APIs, file system, databases, etc. They don't need any special handling—they just run normally.
+
+**ID Format:** Step IDs follow the pattern `step//{filepath}//{functionName}`, where the filepath is relative to your project root.
+
+
+
+
+**Workflow Mode** creates the workflow execution bundle served at `/.well-known/workflow/v1/flow`.
+
+**Input:**
+
+```typescript
+export async function createUser(email: string) {
+ "use step";
+ return { id: crypto.randomUUID(), email };
+}
+
+export async function handleUserSignup(email: string) {
+ "use workflow";
+ const user = await createUser(email);
+ return { userId: user.id };
+}
+```
+
+**Output:**
+
+```typescript
+export async function createUser(email: string) {
+ return globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//workflows/user.js//createUser")(email); // [!code highlight]
+}
+
+export async function handleUserSignup(email: string) {
+ const user = await createUser(email);
+ return { userId: user.id };
+}
+handleUserSignup.workflowId = "workflow//workflows/user.js//handleUserSignup"; // [!code highlight]
+```
+
+**What happens:**
+
+- Step function bodies are **replaced** with calls to `globalThis[Symbol.for("WORKFLOW_USE_STEP")]`
+- Workflow function bodies remain **intact**—they execute deterministically during replay
+- The workflow function gets a `workflowId` property for runtime identification
+- The `"use workflow"` directive is removed
+
+**Why this transformation?** When a workflow executes, it needs to replay past steps from the event log rather than re-executing them. The `WORKFLOW_USE_STEP` symbol is a special runtime hook that:
+
+1. Checks if the step has already been executed (in the event log)
+2. If yes: Returns the cached result
+3. If no: Triggers a suspension and enqueues the step for background execution
+
+**ID Format:** Workflow IDs follow the pattern `workflow//{filepath}//{functionName}`. The `workflowId` property is attached to the function to allow [`start()`](/docs/api-reference/workflow-api/start) to work at runtime.
+
+
+
+
+**Client Mode** transforms workflow functions in your application code to prevent direct execution.
+
+**Input:**
+
+```typescript
+export async function handleUserSignup(email: string) {
+ "use workflow";
+ const user = await createUser(email);
+ return { userId: user.id };
+}
+```
+
+**Output:**
+
+```typescript
+export async function handleUserSignup(email: string) {
+ throw new Error("You attempted to execute ..."); // [!code highlight]
+}
+handleUserSignup.workflowId = "workflow//workflows/user.js//handleUserSignup"; // [!code highlight]
+```
+
+**What happens:**
+
+- Workflow function bodies are **replaced** with an error throw
+- The `workflowId` property is added (same as workflow mode)
+- Step functions are not transformed in client mode
+
+**Why this transformation?** Workflow functions cannot be called directly—they must be started using [`start()`](/docs/api-reference/workflow-api/start). The error prevents accidental direct execution while the `workflowId` property allows the `start()` function to identify which workflow to launch.
+
+The IDs are generated exactly like in workflow mode to ensure they can be directly referenced at runtime.
+
+
+ **Client mode is optional:** While recommended for better developer experience (automatic IDs and type safety), you can skip client mode and instead:
+ - Manually construct workflow IDs using the pattern `workflow//{filepath}//{functionName}`
+ - Use the workflow manifest file generated during build to lookup IDs
+ - Pass IDs directly to `start()` as strings
+
+ All framework integrations include client mode as a loader by default.
+
+
+
+
+
+---
+
+## Generated Files
+
+When you build your application, the Workflow DevKit generates three handler files in `.well-known/workflow/v1/`:
+
+### `flow.js`
+
+Contains all workflow functions transformed in **workflow mode**. This file is imported by your framework to handle workflow execution requests at `POST /.well-known/workflow/v1/flow`.
+
+**How it's structured:**
+
+All workflow code is bundled together and embedded as a string inside `flow.js`. When a workflow needs to execute, this bundled code is run inside a **Node.js VM** (virtual machine) to ensure:
+
+- **Determinism**: The same inputs always produce the same outputs
+- **Side-effect prevention**: Direct access to Node.js APIs, file system, network, etc. is blocked
+- **Sandboxed execution**: Workflow orchestration logic is isolated from the main runtime
+
+**Build-time validation:**
+
+The workflow mode transformation validates your code during the build:
+
+- Catches invalid Node.js API usage (like `fs`, `http`, `child_process`)
+- Prevents imports of modules that would break determinism
+
+Most invalid patterns cause **build-time errors**, catching issues before deployment.
+
+**What it does:**
+
+- Exports a `POST` handler that accepts Web standard `Request` objects
+- Executes bundled workflow code inside a Node.js VM for each request
+- Handles workflow execution, replay, and resumption
+- Returns execution results to the orchestration layer
+
+
+ **Why a VM?** Workflow functions must be deterministic to support replay. The VM sandbox prevents accidental use of non-deterministic APIs or side effects. All side effects should be performed in [step functions](/docs/foundations/workflows-and-steps#step-functions) instead.
+
+
+### `step.js`
+
+Contains all step functions transformed in **step mode**. This file is imported by your framework to handle step execution requests at `POST /.well-known/workflow/v1/step`.
+
+**What it does:**
+
+- Exports a `POST` handler that accepts Web standard `Request` objects
+- Executes individual steps with full runtime access
+- Returns step results to the orchestration layer
+
+### `webhook.js`
+
+Contains webhook handling logic for delivering external data to running workflows via [`createWebhook()`](/docs/api-reference/workflow/create-webhook).
+
+**What it does:**
+
+- Exports a `POST` handler that accepts webhook payloads
+- Validates tokens and routes data to the correct workflow run
+- Resumes workflow execution after webhook delivery
+
+**Note:** The webhook file structure varies by framework. Next.js generates `webhook/[token]/route.js` to leverage App Router's dynamic routing, while other frameworks generate a single `webhook.js` or `webhook.mjs` handler.
+
+---
+
+## Why Three Modes?
+
+The multi-mode transformation enables the Workflow DevKit's durable execution model:
+
+1. **Step Mode** (required) - Bundles executable step functions that can access the full runtime
+2. **Workflow Mode** (required) - Creates orchestration logic that can replay from event logs
+3. **Client Mode** (optional) - Prevents direct execution and enables type-safe workflow references
+
+This separation allows:
+
+- **Deterministic replay**: Workflows can be safely replayed from event logs without re-executing side effects
+- **Sandboxed orchestration**: Workflow logic runs in a controlled VM without direct runtime access
+- **Stateless execution**: Your compute can scale to zero and resume from any point in the workflow
+- **Type safety**: TypeScript works seamlessly with workflow references (when using client mode)
+
+---
+
+## Determinism and Replay
+
+A key aspect of the transformation is maintaining **deterministic replay** for workflow functions.
+
+**Workflow functions must be deterministic:**
+
+- Same inputs always produce the same outputs
+- No direct side effects (no API calls, no database writes, no file I/O)
+- Can use seeded random/time APIs provided by the VM (`Math.random()`, `Date.now()`, etc.)
+
+Because workflow functions are deterministic and have no side effects, they can be safely re-run multiple times to calculate what the next step should be. This is why workflow function bodies remain intact in workflow mode—they're pure orchestration logic.
+
+**Step functions can be non-deterministic:**
+
+- Can make API calls, database queries, etc.
+- Have full access to Node.js runtime and APIs
+- Results are cached in the event log after first execution
+
+Learn more about [Workflows and Steps](/docs/foundations/workflows-and-steps).
+
+---
+
+## ID Generation
+
+The compiler generates stable IDs for workflows and steps based on file paths and function names:
+
+**Pattern:** `{type}//{filepath}//{functionName}`
+
+**Examples:**
+
+- `workflow//workflows/user-signup.js//handleUserSignup`
+- `step//workflows/user-signup.js//createUser`
+- `step//workflows/payments/checkout.ts//processPayment`
+
+**Key properties:**
+
+- **Stable**: IDs don't change unless you rename files or functions
+- **Unique**: Each workflow/step has a unique identifier
+- **Portable**: Works across different runtimes and deployments
+
+
+ Although IDs can change when files are moved or functions are renamed, Workflow DevKit function assume atomic versioning in the World. This means changing IDs won't break old workflows from running, but will prevent run from being upgraded and will cause your workflow/step names to change in the observability across deployments.
+
+
+---
+
+## Framework Integration
+
+These transformations are framework-agnostic—they output standard JavaScript that works anywhere.
+
+**For users**: Your framework handles all transformations automatically. See the [Getting Started](/docs/getting-started) guide for your framework.
+
+**For framework authors**: Learn how to integrate these transformations into your framework in [Building Framework Integrations](/docs/how-it-works/framework-integrations).
+
+---
+
+## Debugging Transformed Code
+
+If you need to debug transformation issues, you can inspect the generated files:
+
+1. **Look in `.well-known/workflow/v1/`**: Check the generated `flow.js`, `step.js`,`webhook.js`, and other emitted debug files.
+2. **Check build logs**: Most frameworks log transformation activity during builds
+3. **Verify directives**: Ensure `"use workflow"` and `"use step"` are the first statements in functions
+4. **Check file locations**: Transformations only apply to files in configured source directories
diff --git a/docs/content/docs/how-it-works/framework-integrations.mdx b/docs/content/docs/how-it-works/framework-integrations.mdx
index 1d14af05b..e49512dc3 100644
--- a/docs/content/docs/how-it-works/framework-integrations.mdx
+++ b/docs/content/docs/how-it-works/framework-integrations.mdx
@@ -2,218 +2,180 @@
title: Framework Integrations
---
-# Framework Integrations
+# Building Framework Integrations
- The goal for Workflow DevKit is to have official adapters for everywhere you
- need to run workflows. Go to the [getting started](/docs/getting-started)
- guide to pick your framework and get started.
+ If you just want to use Workflow DevKit with an existing framework, check out the [Getting Started](/docs/getting-started) guide instead. This page is for framework authors who want to integrate Workflow DevKit with their framework or runtime.
-If you're interested in learning how Workflow integrates with frameworks and how you can build your own framework integration, read on. We'll walk through building the Bun framework integration together and explain the underlying architecture of how the Workflow DevKit integrates with different JavaScript runtimes. While this example uses Bun, the same principles apply to writing framework integrations for any JavaScript runtime (Node.js, Deno, Cloudflare Workers, etc.).
+This guide walks you through building a framework integration for Workflow DevKit. We'll use Bun as our example, but the same principles apply to any JavaScript runtime (Node.js, Deno, Cloudflare Workers, etc.).
-A working example of the bun integration walkthrough below can be [found here](https://github.com/vercel/workflow-examples/tree/main/custom-adapter). Note that this is just an example, and we recommend the official bun integration that is currently in development.
+A working example of the integration walkthrough below can be [found here](https://github.com/vercel/workflow-examples/tree/main/custom-adapter). Note that this is just an example—we recommend using official framework integrations when available.
+
+
+ **Prerequisites:** Before building a framework integration, we recommend reading [How the Directives Work](/docs/how-it-works/code-transform) to understand the transformation system that powers Workflow DevKit.
+
---
-## Overview
+## Architecture Overview
-The Workflow DevKit architecture is as follows:
+A complete framework integration consists of two main components:
-1. **Build-time transformation**: Workflow code is transformed by a framework agnostic compiler plugin
-2. **Generated handlers**: The build process generates HTTP handlers in `.well-known/workflow/v1/` bundled with your step, workflow, and webhook code (flow.js, step.js, and webhook.js)
-3. **Runtime integration**: Your application server exposes these handlers as HTTP endpoints ([learn about how security is handled](#security))
-4. **Execution orchestration**: The Workflow DevKit communicates with these endpoints to orchestrate durable execution
+1. **Build-time transformation**: Transform workflow code and generate HTTP handlers
+2. **Runtime integration**: Expose HTTP handlers as endpoints in your application server
```mermaid
flowchart TD
- A["Source Code 'use workflow'"] --> B["SWC Transform (Build Plugin)"]
- B --> C["Generated Files flow.js/step.js"]
- C --> D["HTTP Server (Your Runtime)"]
+ A["Source Code 'use workflow'"] --> B["Build Plugin (Your Integration)"]
+ B --> C["SWC Transform (3 modes)"]
+ C --> D["Generated Handlers flow.js/step.js/webhook.js"]
+ D --> E["HTTP Server (Your Runtime)"]
+ E --> F["Workflow DevKit (Orchestration)"]
```
----
-
-## Writing a Framework Integration
-
-To integrate Workflow DevKit with a runtime, you need two main components: a build-time transformation hook and HTTP server integration. Let's walk through each one.
+**The flow:**
-### Transformation
-
-Workflows use directives to mark code for transformation:
-
-```typescript
-export async function handleUserSignup(email: string) {
- "use workflow"; // [!code highlight]
-
- const user = await createUser(email);
- await sendWelcomeEmail(user);
-
- return { userId: user.id };
-}
+1. Your build plugin scans source directories for workflow files
+2. Files with `"use workflow"` or `"use step"` directives are transformed
+3. Three handler files are generated in `.well-known/workflow/v1/`
+4. Your HTTP server exposes these handlers as endpoints
+5. Workflow DevKit communicates with these endpoints to orchestrate execution
-async function createUser(email: string) {
- "use step"; // [!code highlight]
+---
- return { id: crypto.randomUUID(), email };
-}
-```
+## Part 1: Build-Time Integration
-**Key directives:**
+Your framework integration needs to hook into your build process to apply transformations and generate handler files.
-- `"use workflow"`: Marks a function as a durable workflow entry point
-- `"use step"`: Marks a function as an atomic, retryable step
+### Step 1: Scan Source Directories
-**How the transformation works:**
+Identify where workflow code lives in your framework. For example:
-The `@workflow/swc-plugin` operates in three modes, transforming code differently for each bundle:
+- **Next.js**: `app/` and `pages/` directories
+- **Standalone mode**: `workflows/` directory
+- **SvelteKit**: `src/routes/` directory
-
-
+Your build plugin should:
+1. Scan these directories for TypeScript/JavaScript files
+2. Quickly check if files contain `"use workflow"` or `"use step"` (regex is fine)
+3. Only transform files that contain these directives
-**Step Mode** creates the step execution bundle served at `/.well-known/workflow/v1/step`.
+### Step 2: Apply Transformations
-**Input:**
+For files containing directives, apply the `@workflow/swc-plugin` transformations:
-```typescript
-export async function createUser(email: string) {
- "use step";
- return { id: crypto.randomUUID(), email };
-}
-```
-
-**Output:**
+
+ Learn more about what each transformation mode does in [How the Directives Work](/docs/how-it-works/code-transform).
+
+**Step mode (required)** - Generates `/.well-known/workflow/v1/step.js`:
```typescript
-import { registerStepFunction } from "workflow/internal/private"; // [!code highlight]
-
-export async function createUser(email: string) {
- return { id: crypto.randomUUID(), email };
-}
+import { transform } from "@swc/core";
-registerStepFunction("step//workflows/user.js//createUser", createUser); // [!code highlight]
+const result = await transform(sourceCode, {
+ filename: filePath,
+ jsc: {
+ experimental: {
+ plugins: [
+ [require.resolve("@workflow/swc-plugin"), { mode: "step" }],
+ ],
+ },
+ },
+});
```
-The directive is removed from step functions, and they're registered with the runtime using `registerStepFunction()`.
-
-Step bodies are kept intact since they just run in the main runtime (Node, Deno, Bun, etc.). There's no transformation here.
-
-**ID Format:** Step IDs follow the pattern `step//{filepath}//{functionName}`, where the filepath is relative to your project root.
-
-
-
-
-**Workflow Mode** creates the workflow execution bundle served at `/.well-known/workflow/v1/flow`.
-
-**Input:**
-
+**Workflow mode (required)** - Generates `/.well-known/workflow/v1/flow.js`:
```typescript
-export async function createUser(email: string) {
- "use step";
- return { id: crypto.randomUUID(), email };
-}
-
-export async function handleUserSignup(email: string) {
- "use workflow";
- const user = await createUser(email);
- return { userId: user.id };
-}
+const result = await transform(sourceCode, {
+ filename: filePath,
+ jsc: {
+ experimental: {
+ plugins: [
+ [require.resolve("@workflow/swc-plugin"), { mode: "workflow" }],
+ ],
+ },
+ },
+});
```
-**Output:**
-
+**Client mode (optional, recommended)** - Transforms your application code:
```typescript
-export async function createUser(email: string) {
- return globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//workflows/user.js//createUser")(email); // [!code highlight]
-}
-
-export async function handleUserSignup(email: string) {
- const user = await createUser(email);
- return { userId: user.id };
-}
-handleUserSignup.workflowId = "workflow//workflows/user.js//handleUserSignup"; // [!code highlight]
+const result = await transform(sourceCode, {
+ filename: filePath,
+ jsc: {
+ experimental: {
+ plugins: [
+ [require.resolve("@workflow/swc-plugin"), { mode: "client" }],
+ ],
+ },
+ },
+});
```
-Step function bodies are replaced with calls to `globalThis[Symbol.for("WORKFLOW_USE_STEP")]`, which handles replaying the step result from the event log, or trigger a suspension that enqueues the step in the background.
-
-Workflow function bodies remain intact—they're executed deterministically during replay. The directive simply gets dropped, and a workflow ID is added to the function to allow [`start`](/docs/api-reference/workflow-api/start) to work at runtime.
+
+ **Client mode provides better DX:** While optional, client mode is recommended because it:
+ - Automatically attaches workflow IDs to functions for use with `start()`
+ - Provides TypeScript type safety and autocomplete
+ - Prevents accidental direct execution of workflow functions
-**ID Format:** Workflow IDs follow the pattern `workflow//{filepath}//{functionName}`. The `workflowId` property is attached to the function for runtime identification.
+ Without client mode, developers must manually construct workflow IDs using the pattern `workflow//{filepath}//{functionName}` or reference the build manifest.
+
-
-
+### Step 3: Generate Handler Files
-**Client Mode** transforms workflow functions in your application code to prevent direct execution.
+Bundle the transformed code into three handler files in `.well-known/workflow/v1/`:
-**Input:**
+- `flow.js` - Workflow execution handler (from workflow mode)
+- `step.js` - Step execution handler (from step mode)
+- `webhook.js` (or `webhook.mjs`) - Webhook delivery handler
-```typescript
-export async function handleUserSignup(email: string) {
- "use workflow";
- const user = await createUser(email);
- return { userId: user.id };
-}
-```
+Each handler exports a `POST` function that accepts a Web standard `Request` object.
-**Output:**
+
+ **No Vendor Lock-In**: The generated handlers output standard JavaScript HTTP handler code that works anywhere. This separation means you can deploy workflows to any JavaScript runtime without being locked into a specific platform or provider.
+
-```typescript
-export async function handleUserSignup(email: string) {
- throw new Error("You attempted to execute ..."); // [!code highlight]
-}
-handleUserSignup.workflowId = "workflow//workflows/user.js//handleUserSignup"; // [!code highlight]
-```
+### Build Integration Summary
-Workflow functions throw errors to prevent direct execution—you must use [`start()`](/docs/api-reference/workflow-api/start) instead.
+When running your framework's `build` command, your integration needs to:
-The IDs are generated exactly like in workflow mode to ensure they can be directly referenced at runtime.
+1. Scan source directories where workflow code lives
+2. Transform files containing workflow directives:
+ - Step mode (required) - for step execution
+ - Workflow mode (required) - for workflow orchestration
+ - Client mode (optional, recommended) - for better developer experience
+3. Generate handler files at `.well-known/workflow/v1/*`
-
-
+---
-When running your framework's `build` command, the corresponding workflow integration simply needs to:
+## Part 2: HTTP Server Integration
-1. Scan directories where the framework's source code typically lives (for example, `app/` and `pages/` in Next.js)
-2. Transform files containing workflow directives (`"use workflow"` or `"use step"`)
-3. Generate handler files for the framework to expose at `.well-known/workflow/v1/*`
+Your framework must expose three HTTP endpoints that route to the generated handler files.
- **No Vendor Lock-In**: Everything here is completely agnostic from the world
- abstraction and runtime environment. It outputs standard JavaScript HTTP
- handler code that works anywhere. This separation means you can deploy your
- workflows to any JavaScript runtime without being locked into a specific
- platform or provider.
+ The exact protocol and payload handling is managed by the generated `flow.js`, `step.js`, and `webhook.js` files. Your framework integration only needs to route HTTP requests to these handlers—no need to parse or understand the payloads.
-### HTTP Server Integration
+### Required Endpoints
-Your framework integration must expose three HTTP endpoints that correspond to the generated handler files.
-
-
- The exact protocol and payload handling is managed by the generated `flow.js`,
- `step.js`, and `webhook.js` files. Your framework integration only needs to route HTTP requests to these
- handlers.
-
-
-#### Workflow Handler
+#### 1. Workflow Handler
**Endpoint:** `POST /.well-known/workflow/v1/flow`
-Handles [workflow function](/docs/foundations/workflows-and-steps#workflow-functions) execution.
+**Purpose:** Executes [workflow functions](/docs/foundations/workflows-and-steps#workflow-functions).
**How it works:**
The workflow function is "rendered" multiple times during the lifecycle of a workflow execution. Each time it runs, it progresses through the workflow logic until it encounters the next step that needs to be executed. Because [workflow functions are deterministic and have no side effects](/docs/foundations/workflows-and-steps#workflow-functions), they can be safely re-run multiple times to calculate what the next step should be.
**Called when:**
-
- Starting a new workflow
- Resuming execution after a step completes
- Resuming after a webhook or hook is triggered
- Recovering from failures
**Interface:**
-
- **Request**: Web standard `Request` object with binary payload from Workflow DevKit
- **Response**: Web standard `Response` object with binary payload containing execution results
@@ -225,18 +187,17 @@ import flow from "./.well-known/workflow/v1/flow.js";
server.route("POST", "/.well-known/workflow/v1/flow", (req) => flow.POST(req));
```
-#### Step Handler
+#### 2. Step Handler
**Endpoint:** `POST /.well-known/workflow/v1/step`
-Handles [step function](/docs/foundations/workflows-and-steps#step-functions) execution.
+**Purpose:** Executes [step functions](/docs/foundations/workflows-and-steps#step-functions).
**How it works:**
-This endpoint executes individual atomic operations within workflows. Each step runs exactly once per execution (unless it fails and needs to be retried).
+This endpoint executes individual atomic operations within workflows. Each step runs exactly once per execution (unless it fails and needs to be retried). Steps have full access to the runtime environment (Node.js APIs, file system, databases, etc.).
**Interface:**
-
- **Request**: Web standard `Request` object with binary payload from Workflow DevKit
- **Response**: Web standard `Response` object with binary payload containing step results
@@ -248,95 +209,58 @@ import step from "./.well-known/workflow/v1/step.js";
server.route("POST", "/.well-known/workflow/v1/step", (req) => step.POST(req));
```
-#### Webhook Handler
+#### 3. Webhook Handler
**Endpoint:** `POST /.well-known/workflow/v1/webhook/:token`
-Handles webhook delivery to running workflows via [`createWebhook()`](/docs/api-reference/workflow/create-webhook).
+**Purpose:** Delivers webhook data to running workflows via [`createWebhook()`](/docs/api-reference/workflow/create-webhook).
**How it works:**
This endpoint allows external systems to send data to running workflows. When a workflow calls `createWebhook()`, it generates a unique token that external services can use to deliver data. The webhook handler validates the token, delivers the payload to the correct workflow run, and resumes execution.
**Interface:**
-
- **Request**: Web standard `Request` object with the webhook payload and token parameter
- **Response**: Web standard `Response` object confirming webhook delivery
**Example implementation (framework-dependent):**
```typescript
-// Next.js example
+// Next.js example - uses App Router's file-based routing
import webhook from "./.well-known/workflow/v1/webhook/[token]/route.js";
-
// The route is automatically handled by Next.js App Router
-// Nitro example
-import webhook from "./.well-known/workflow/v1/webhook.mjs";
-
-server.route("POST", "/.well-known/workflow/v1/webhook/:token", (req) => webhook.POST(req));
+// Generic server example - requires route parameter handling
+import webhook from "./.well-known/workflow/v1/webhook.js";
+server.route("POST", "/.well-known/workflow/v1/webhook/:token", (req) =>
+ webhook.POST(req)
+);
```
- The generated webhook route structure varies by framework. Next.js generates a file at `webhook/[token]/route.js` to leverage App Router's dynamic routing, while other frameworks may generate a single `webhook.js` or `webhook.mjs` handler that your framework must route to with a `:token` parameter.
+ The generated webhook route structure varies by framework. Next.js generates a file at `webhook/[token]/route.js` to leverage App Router's dynamic routing, while other frameworks may generate a single `webhook.js` or `webhook.mjs` handler that your framework must route with a `:token` parameter.
---
-## Security
-
-A natural question when exposing these HTTP endpoints is: how are they secured?
-
-The answer depends on the **world abstraction** you're using. Different world implementations provide different security mechanisms:
-
-### World-Specific Security
-
-**Vercel (via `@workflow/world-vercel`):**
-
-- Uses Vercel Queue as the backend orchestration layer
-- Vercel Queue will soon support **private invoke**, making these routes not publicly accessible from the internet
-- The handler receives only a **message ID** from the queue, which must be retrieved from the Vercel Queue backend
-- This architecture makes it impossible to craft custom payloads and directly invoke the endpoints with arbitrary data
-- Even if someone discovers the endpoint URL, they cannot execute workflows without valid message IDs from the queue system
-
-**Custom Implementations:**
-
-- Different world implementations can implement their own security measures
-- Framework middleware can add authentication and authorization layers
-- You can implement API key verification, JWT validation, or other authentication schemes
-- You can use network-level security (VPCs, private networks, firewall rules)
-- You can implement rate limiting and request validation
-
-### Best Practices
-
-When building your own world or framework integration, consider:
-
-- **Authentication**: Verify that requests come from trusted sources (your orchestration backend, not external clients)
-- **Payload validation**: Ensure request payloads are cryptographically signed or come from a trusted internal system
-- **Network isolation**: Run workflow endpoints on private networks when possible
-- **Rate limiting**: Protect against abuse even from internal systems
-
-The generated handlers themselves don't include authentication because the security model is delegated to the world abstraction layer, allowing each runtime environment to implement the most appropriate security mechanism for its deployment context.
-
----
+## Complete Example: Bun
-## Reference Implementation: Bun
+Let's walk through a complete integration example using Bun. Bun is unique because it serves as both a **runtime** (needs code transformations) and a **framework** (provides `Bun.serve()` for HTTP routing).
-Bun is a special case because it serves as both a **runtime** and a **framework**:
+### Overview
-- **Runtime**: Bun needs transformations applied to compile workflow code
-- **Framework**: `Bun.serve()` handles HTTP routing and request handling
+Here's what we'll build:
-Here's how the bun example handles building the routes, implementing the client transform at runtime, and serves the routes from the right HTTP endpoints.
+1. **Build script**: Use `workflow` CLI to generate step/workflow mode bundles
+2. **Runtime plugin**: Apply client mode transforms at runtime
+3. **HTTP server**: Route requests to workflow handlers using `Bun.serve()`
-### 1. Building the routes
+### 1. Building the Handler Routes
-In this example, running the Step and Workflow mode transform actually happens using the `workflow` CLI. `workflow build` will, by default, look for a `workflows/` directory and will create the "step" and "workflow" bundles.
-So in this example, the `package.json` simply invokes workflow build before running the bun server.
+The step and workflow mode transformations can be handled by the `workflow` CLI, which looks for a `workflows/` directory by default and creates the handler bundles.
```json title="package.json"
{
- // ...
"scripts": {
"dev": "bun x workflow build && PORT=3152 bun run server.ts"
}
@@ -344,12 +268,12 @@ So in this example, the `package.json` simply invokes workflow build before runn
```
- This implementation notably lacks file system watching for a local dev experience. Production framework implementations, like the [Next Plugin](https://github.com/vercel/workflow/tree/main/packages/next) are better optimized for this.
+ This implementation lacks file system watching for a smooth dev experience. Production framework integrations, like the [Next.js Plugin](https://github.com/vercel/workflow/tree/main/packages/next), are better optimized with hot reloading.
### 2. Runtime Client Transformation
-This transformation applies to the **Bun runtime** to resolve workflow code during the build process:
+This Bun plugin applies **client mode** transforms to your application code at runtime:
```typescript title="workflow-plugin.ts" lineNumbers
import { plugin } from "bun";
@@ -361,8 +285,8 @@ plugin({
build.onLoad({ filter: /\.(ts|tsx|js|jsx)$/ }, async (args) => {
const source = await Bun.file(args.path).text();
- // Optimization: we can speed up the plugin by only transforming files
- // that make use of the directive. False positives are safe.
+ // Optimization: Only transform files with directives
+ // False positives are safe (transformation is a no-op)
if (!source.match(/(use step|use workflow)/)) {
return { contents: source };
}
@@ -387,23 +311,21 @@ plugin({
});
```
-To run this plugin in bun, add it to your `bunfig.toml` file:
+To activate this plugin, add it to your `bunfig.toml`:
```toml title="bunfig.toml"
preload = ["./workflow-plugin.ts"]
```
-**Key aspects:**
+**What this does:**
+- Hooks into Bun's native module loader
+- Checks for workflow directives in all TypeScript/JavaScript files
+- Only transforms files containing `"use step"` or `"use workflow"`
+- Applies client mode transformation (prevents direct workflow execution)
-- Uses Bun's native `plugin()` API to hook into the build process
-- Checks for workflow directives in all TypeScript and JavaScript files
-- Only transforms files that contain `"use step"` or `"use workflow"`
-- Applies SWC transformation with workflow plugin
-- Returns transformed code with TypeScript loader
+### 3. HTTP Server Routing
-### 3. HTTP Routing
-
-This integration uses **`Bun.serve()`** as the framework to route HTTP requests to the workflow handlers:
+Finally, wire up the HTTP endpoints using `Bun.serve()`:
```typescript title="server.ts" lineNumbers
import { start } from "workflow/api";
@@ -445,13 +367,183 @@ const server = Bun.serve({
console.log(`Server listening on http://localhost:${server.port}`);
```
-**Key aspects:**
-
+**What this does:**
- Imports generated `flow.js`, `step.js`, and `webhook.js` handlers
-- Uses `Bun.serve()`'s routing API to map HTTP endpoints to handlers
-- Delegates POST requests directly to generated handlers
-- The webhook route uses a `:token` parameter to identify which workflow run should receive the webhook
-- The framework handles request routing; the runtime handles code execution
-- Example endpoint shows how to start a workflow using `start()`
+- Maps HTTP endpoints to handler functions
+- Delegates all POST requests directly to generated handlers
+- The webhook route uses a `:token` parameter for identifying workflow runs
+- Includes an example endpoint showing how to start a workflow with `start()`
+
+You can find the complete Bun example implementation [here](https://github.com/vercel/workflow-examples/tree/main/custom-adapter).
+
+---
+
+## Security
+
+A natural question when exposing HTTP endpoints is: **how are they secured?**
+
+The security model depends on the **world abstraction** you're using. Different world implementations provide different security mechanisms.
+
+### World-Specific Security
+
+**Vercel (via `@workflow/world-vercel`):**
+
+- Uses Vercel Queue as the backend orchestration layer
+- Vercel Queue will soon support **private invoke**, making routes inaccessible from the public internet
+- Handlers receive only a **message ID** from the queue, which must be retrieved from Vercel's backend
+- This architecture makes it impossible to craft custom payloads—you need valid message IDs from the queue
+- Even if someone discovers the endpoint URL, they cannot execute workflows without queue-issued message IDs
+
+**Custom Implementations:**
+
+- World implementations can provide their own security mechanisms
+- Add authentication/authorization via framework middleware
+- Implement API key verification, JWT validation, or other auth schemes
+- Use network-level security (VPCs, private networks, firewall rules)
+- Implement rate limiting and request validation
+
+### Best Practices
+
+When building your own world or framework integration, consider:
+
+- **Authentication**: Verify requests come from trusted sources (your orchestration backend, not external clients)
+- **Payload validation**: Ensure payloads are cryptographically signed or from a trusted internal system
+- **Network isolation**: Run workflow endpoints on private networks when possible
+- **Rate limiting**: Protect against abuse even from internal systems
+
+The generated handlers don't include authentication because the security model is delegated to the world abstraction layer, allowing each runtime environment to implement the most appropriate security mechanism for its deployment context.
+
+Learn more about [world abstractions](/docs/deploying/world).
+
+---
+
+## Testing Your Integration
+
+After building your integration, verify it works correctly:
+
+### 1. Test Transformations
+
+Create a simple workflow file and verify transformations:
+
+```typescript title="test/fixtures/simple-workflow.ts"
+export async function testWorkflow() {
+ "use workflow";
+ return await testStep();
+}
+
+async function testStep() {
+ "use step";
+ return "hello";
+}
+```
+
+Run your build and check:
+- `.well-known/workflow/v1/flow.js` contains transformed workflow code
+- `.well-known/workflow/v1/step.js` contains step registration
+- Your app code has client mode transforms applied
+
+### 2. Test HTTP Endpoints
+
+Start your server and verify endpoints respond:
+
+```bash
+# Test that endpoints exist (should return 400/405, not 404)
+curl -X POST http://localhost:3000/.well-known/workflow/v1/flow
+curl -X POST http://localhost:3000/.well-known/workflow/v1/step
+curl -X POST http://localhost:3000/.well-known/workflow/v1/webhook/test-token
+```
+
+### 3. Run an End-to-End Workflow
+
+Create a test workflow and execute it:
+
+```typescript
+import { start } from "workflow/api";
+import { testWorkflow } from "./test/fixtures/simple-workflow";
+
+const run = await start(testWorkflow, []);
+console.log("Workflow started:", run.runId);
+```
+
+Verify the workflow completes successfully.
+
+---
+
+## Troubleshooting
+
+### Transformations Not Applied
+
+**Symptoms:** Workflow code isn't transformed, or you get errors about missing directives.
+
+**Solutions:**
+- Verify files are in scanned source directories
+- Check that `"use workflow"` or `"use step"` are the first statements in functions
+- Ensure the SWC plugin is correctly configured
+- Check build logs for transformation errors
+
+### Routes Return 404
+
+**Symptoms:** POST requests to `.well-known/workflow/v1/*` return 404.
+
+**Solutions:**
+- Verify handler files were generated in `.well-known/workflow/v1/`
+- Check that your HTTP server is correctly routing to these files
+- Ensure the route paths exactly match (including the `v1` version)
+- Check for framework-specific routing requirements (e.g., file-based vs code-based)
+
+### Serialization Errors
+
+**Symptoms:** Errors about non-serializable values being passed between workflows/steps.
+
+**Solutions:**
+- Only pass JSON-serializable values (primitives, arrays, plain objects)
+- Avoid passing functions, class instances, Symbols, or other non-serializable types
+- See [Serialization](/docs/foundations/serialization) for details
+
+### Module Resolution Errors
+
+**Symptoms:** Cannot find `workflow/api`, `workflow/internal/private`, etc.
+
+**Solutions:**
+- Ensure `@workflow/core` is installed
+- Check that your bundler/runtime resolves the `workflow` package correctly
+- Verify imports match the expected paths
+
+---
+
+## Publishing Your Integration
+
+If you're building an integration for the community:
+
+### Package Naming
+
+Follow these conventions:
+- Framework plugins: `@workflow/{framework-name}` (e.g., `@workflow/remix`)
+- Build plugins: `@workflow/{bundler-name}-plugin` (e.g., `@workflow/vite-plugin`)
+
+### Required Exports
+
+Your integration package should export:
+
+- Build plugin/configuration for the framework
+- TypeScript types for workflow functions
+- Documentation on how to configure and use it
+
+### Documentation
+
+Include:
+
+- Installation instructions
+- Configuration guide
+- Example projects
+- Migration guide (if replacing an existing solution)
+
+---
+
+## Related Documentation
-You can find the complete bun example implementation [here](https://github.com/vercel/workflow-examples/tree/main/custom-adapter).
+- [How the Directives Work](/docs/how-it-works/code-transform) - Understand the transformation system
+- [World Abstractions](/docs/deploying/world) - Learn about storage backends and security
+- [Getting Started](/docs/getting-started) - See official integrations in action
+- [Workflows and Steps](/docs/foundations/workflows-and-steps) - Core execution concepts
+- [Next.js Integration Source](https://github.com/vercel/workflow/tree/main/packages/next) - Reference implementation
diff --git a/docs/content/docs/how-it-works/meta.json b/docs/content/docs/how-it-works/meta.json
index 29e018e83..427460972 100644
--- a/docs/content/docs/how-it-works/meta.json
+++ b/docs/content/docs/how-it-works/meta.json
@@ -1,5 +1,5 @@
{
"title": "How it works",
- "pages": ["framework-integrations"],
+ "pages": ["code-transform", "framework-integrations"],
"defaultOpen": true
}
From 8ebcf8292b1cf584463008670b5d2d07fdec578e Mon Sep 17 00:00:00 2001
From: Pranay Prakash
Date: Sat, 25 Oct 2025 18:42:21 -0700
Subject: [PATCH 20/20] Improve framework integrations doc
---
.../how-it-works/framework-integrations.mdx | 609 +++++++-----------
1 file changed, 240 insertions(+), 369 deletions(-)
diff --git a/docs/content/docs/how-it-works/framework-integrations.mdx b/docs/content/docs/how-it-works/framework-integrations.mdx
index e49512dc3..1797990f0 100644
--- a/docs/content/docs/how-it-works/framework-integrations.mdx
+++ b/docs/content/docs/how-it-works/framework-integrations.mdx
@@ -5,12 +5,10 @@ title: Framework Integrations
# Building Framework Integrations
- If you just want to use Workflow DevKit with an existing framework, check out the [Getting Started](/docs/getting-started) guide instead. This page is for framework authors who want to integrate Workflow DevKit with their framework or runtime.
+ **For users:** If you just want to use Workflow DevKit with an existing framework, check out the [Getting Started](/docs/getting-started) guide instead. This page is for framework authors who want to integrate Workflow DevKit with their framework or runtime.
-This guide walks you through building a framework integration for Workflow DevKit. We'll use Bun as our example, but the same principles apply to any JavaScript runtime (Node.js, Deno, Cloudflare Workers, etc.).
-
-A working example of the integration walkthrough below can be [found here](https://github.com/vercel/workflow-examples/tree/main/custom-adapter). Note that this is just an example—we recommend using official framework integrations when available.
+This guide walks you through building a framework integration for Workflow DevKit using Bun as a concrete example. The same principles apply to any JavaScript runtime (Node.js, Deno, Cloudflare Workers, etc.).
**Prerequisites:** Before building a framework integration, we recommend reading [How the Directives Work](/docs/how-it-works/code-transform) to understand the transformation system that powers Workflow DevKit.
@@ -18,246 +16,48 @@ A working example of the integration walkthrough below can be [found here](https
---
-## Architecture Overview
+## What You'll Build
-A complete framework integration consists of two main components:
+A framework integration has two main components:
-1. **Build-time transformation**: Transform workflow code and generate HTTP handlers
-2. **Runtime integration**: Expose HTTP handlers as endpoints in your application server
+1. **Build-time**: Generate workflow handler files (`flow.js`, `step.js`, `webhook.js`)
+2. **Runtime**: Expose these handlers as HTTP endpoints in your application server
```mermaid
flowchart TD
- A["Source Code 'use workflow'"] --> B["Build Plugin (Your Integration)"]
- B --> C["SWC Transform (3 modes)"]
- C --> D["Generated Handlers flow.js/step.js/webhook.js"]
- D --> E["HTTP Server (Your Runtime)"]
- E --> F["Workflow DevKit (Orchestration)"]
-```
-
-**The flow:**
-
-1. Your build plugin scans source directories for workflow files
-2. Files with `"use workflow"` or `"use step"` directives are transformed
-3. Three handler files are generated in `.well-known/workflow/v1/`
-4. Your HTTP server exposes these handlers as endpoints
-5. Workflow DevKit communicates with these endpoints to orchestrate execution
-
----
-
-## Part 1: Build-Time Integration
-
-Your framework integration needs to hook into your build process to apply transformations and generate handler files.
-
-### Step 1: Scan Source Directories
-
-Identify where workflow code lives in your framework. For example:
-
-- **Next.js**: `app/` and `pages/` directories
-- **Standalone mode**: `workflows/` directory
-- **SvelteKit**: `src/routes/` directory
-
-Your build plugin should:
-1. Scan these directories for TypeScript/JavaScript files
-2. Quickly check if files contain `"use workflow"` or `"use step"` (regex is fine)
-3. Only transform files that contain these directives
-
-### Step 2: Apply Transformations
-
-For files containing directives, apply the `@workflow/swc-plugin` transformations:
-
-
- Learn more about what each transformation mode does in [How the Directives Work](/docs/how-it-works/code-transform).
-
-
-**Step mode (required)** - Generates `/.well-known/workflow/v1/step.js`:
-```typescript
-import { transform } from "@swc/core";
-
-const result = await transform(sourceCode, {
- filename: filePath,
- jsc: {
- experimental: {
- plugins: [
- [require.resolve("@workflow/swc-plugin"), { mode: "step" }],
- ],
- },
- },
-});
-```
-
-**Workflow mode (required)** - Generates `/.well-known/workflow/v1/flow.js`:
-```typescript
-const result = await transform(sourceCode, {
- filename: filePath,
- jsc: {
- experimental: {
- plugins: [
- [require.resolve("@workflow/swc-plugin"), { mode: "workflow" }],
- ],
- },
- },
-});
+ A["Source Code 'use workflow'"] --> B["Workflow Builder"]
+ B --> C["SWC Transform"]
+ C --> D["Step Mode"]
+ C --> E["Workflow Mode"]
+ C --> F["Client Mode"]
+ D --> G["Generated Handlers step.js"]
+ E --> H["Generated Handlers flow.js"]
+ B --> L["Generated Handlers webhook.js"]
+ F --> I["Used by framework loader"]
+ G --> J["HTTP Server (Your Runtime)"]
+ H --> J
+ L --> J
+
+ style B fill:#a78bfa,stroke:#8b5cf6,color:#000
+ style I fill:#a78bfa,stroke:#8b5cf6,color:#000
+ style J fill:#a78bfa,stroke:#8b5cf6,color:#000
```
-**Client mode (optional, recommended)** - Transforms your application code:
-```typescript
-const result = await transform(sourceCode, {
- filename: filePath,
- jsc: {
- experimental: {
- plugins: [
- [require.resolve("@workflow/swc-plugin"), { mode: "client" }],
- ],
- },
- },
-});
-```
-
-
- **Client mode provides better DX:** While optional, client mode is recommended because it:
- - Automatically attaches workflow IDs to functions for use with `start()`
- - Provides TypeScript type safety and autocomplete
- - Prevents accidental direct execution of workflow functions
-
- Without client mode, developers must manually construct workflow IDs using the pattern `workflow//{filepath}//{functionName}` or reference the build manifest.
-
-
-### Step 3: Generate Handler Files
-
-Bundle the transformed code into three handler files in `.well-known/workflow/v1/`:
-
-- `flow.js` - Workflow execution handler (from workflow mode)
-- `step.js` - Step execution handler (from step mode)
-- `webhook.js` (or `webhook.mjs`) - Webhook delivery handler
-
-Each handler exports a `POST` function that accepts a Web standard `Request` object.
-
-
- **No Vendor Lock-In**: The generated handlers output standard JavaScript HTTP handler code that works anywhere. This separation means you can deploy workflows to any JavaScript runtime without being locked into a specific platform or provider.
-
-
-### Build Integration Summary
-
-When running your framework's `build` command, your integration needs to:
-
-1. Scan source directories where workflow code lives
-2. Transform files containing workflow directives:
- - Step mode (required) - for step execution
- - Workflow mode (required) - for workflow orchestration
- - Client mode (optional, recommended) - for better developer experience
-3. Generate handler files at `.well-known/workflow/v1/*`
+The purple boxes are what you implement—everything else is provided by Workflow DevKit.
---
-## Part 2: HTTP Server Integration
+## Example: Bun Integration
-Your framework must expose three HTTP endpoints that route to the generated handler files.
+Let's build a complete integration for Bun. Bun is unique because it serves as both a runtime (needs code transformations) and a framework (provides `Bun.serve()` for HTTP routing).
- The exact protocol and payload handling is managed by the generated `flow.js`, `step.js`, and `webhook.js` files. Your framework integration only needs to route HTTP requests to these handlers—no need to parse or understand the payloads.
+ A working example can be [found here](https://github.com/vercel/workflow-examples/tree/main/custom-adapter). For a production-ready reference, see the [Next.js integration](https://github.com/vercel/workflow/tree/main/packages/next).
-### Required Endpoints
-
-#### 1. Workflow Handler
+### Step 1: Generate Handler Files
-**Endpoint:** `POST /.well-known/workflow/v1/flow`
-
-**Purpose:** Executes [workflow functions](/docs/foundations/workflows-and-steps#workflow-functions).
-
-**How it works:**
-
-The workflow function is "rendered" multiple times during the lifecycle of a workflow execution. Each time it runs, it progresses through the workflow logic until it encounters the next step that needs to be executed. Because [workflow functions are deterministic and have no side effects](/docs/foundations/workflows-and-steps#workflow-functions), they can be safely re-run multiple times to calculate what the next step should be.
-
-**Called when:**
-- Starting a new workflow
-- Resuming execution after a step completes
-- Resuming after a webhook or hook is triggered
-- Recovering from failures
-
-**Interface:**
-- **Request**: Web standard `Request` object with binary payload from Workflow DevKit
-- **Response**: Web standard `Response` object with binary payload containing execution results
-
-**Example implementation:**
-
-```typescript
-import flow from "./.well-known/workflow/v1/flow.js";
-
-server.route("POST", "/.well-known/workflow/v1/flow", (req) => flow.POST(req));
-```
-
-#### 2. Step Handler
-
-**Endpoint:** `POST /.well-known/workflow/v1/step`
-
-**Purpose:** Executes [step functions](/docs/foundations/workflows-and-steps#step-functions).
-
-**How it works:**
-
-This endpoint executes individual atomic operations within workflows. Each step runs exactly once per execution (unless it fails and needs to be retried). Steps have full access to the runtime environment (Node.js APIs, file system, databases, etc.).
-
-**Interface:**
-- **Request**: Web standard `Request` object with binary payload from Workflow DevKit
-- **Response**: Web standard `Response` object with binary payload containing step results
-
-**Example implementation:**
-
-```typescript
-import step from "./.well-known/workflow/v1/step.js";
-
-server.route("POST", "/.well-known/workflow/v1/step", (req) => step.POST(req));
-```
-
-#### 3. Webhook Handler
-
-**Endpoint:** `POST /.well-known/workflow/v1/webhook/:token`
-
-**Purpose:** Delivers webhook data to running workflows via [`createWebhook()`](/docs/api-reference/workflow/create-webhook).
-
-**How it works:**
-
-This endpoint allows external systems to send data to running workflows. When a workflow calls `createWebhook()`, it generates a unique token that external services can use to deliver data. The webhook handler validates the token, delivers the payload to the correct workflow run, and resumes execution.
-
-**Interface:**
-- **Request**: Web standard `Request` object with the webhook payload and token parameter
-- **Response**: Web standard `Response` object confirming webhook delivery
-
-**Example implementation (framework-dependent):**
-
-```typescript
-// Next.js example - uses App Router's file-based routing
-import webhook from "./.well-known/workflow/v1/webhook/[token]/route.js";
-// The route is automatically handled by Next.js App Router
-
-// Generic server example - requires route parameter handling
-import webhook from "./.well-known/workflow/v1/webhook.js";
-server.route("POST", "/.well-known/workflow/v1/webhook/:token", (req) =>
- webhook.POST(req)
-);
-```
-
-
- The generated webhook route structure varies by framework. Next.js generates a file at `webhook/[token]/route.js` to leverage App Router's dynamic routing, while other frameworks may generate a single `webhook.js` or `webhook.mjs` handler that your framework must route with a `:token` parameter.
-
-
----
-
-## Complete Example: Bun
-
-Let's walk through a complete integration example using Bun. Bun is unique because it serves as both a **runtime** (needs code transformations) and a **framework** (provides `Bun.serve()` for HTTP routing).
-
-### Overview
-
-Here's what we'll build:
-
-1. **Build script**: Use `workflow` CLI to generate step/workflow mode bundles
-2. **Runtime plugin**: Apply client mode transforms at runtime
-3. **HTTP server**: Route requests to workflow handlers using `Bun.serve()`
-
-### 1. Building the Handler Routes
-
-The step and workflow mode transformations can be handled by the `workflow` CLI, which looks for a `workflows/` directory by default and creates the handler bundles.
+Use the `workflow` CLI to generate the handler bundles. The CLI scans your `workflows/` directory and creates `flow.js`, `step.js`, and `webhook.js`.
```json title="package.json"
{
@@ -268,12 +68,20 @@ The step and workflow mode transformations can be handled by the `workflow` CLI,
```
- This implementation lacks file system watching for a smooth dev experience. Production framework integrations, like the [Next.js Plugin](https://github.com/vercel/workflow/tree/main/packages/next), are better optimized with hot reloading.
+ **For production integrations:** Instead of using the CLI, extend the `BaseBuilder` class directly in your framework plugin. This gives you control over file watching, custom output paths, and framework-specific hooks. See the [Next.js plugin](https://github.com/vercel/workflow/tree/main/packages/next) for an example.
-### 2. Runtime Client Transformation
+**What gets generated:**
+
+- `/.well-known/workflow/v1/flow.js` - Handles workflow execution (workflow mode transform)
+- `/.well-known/workflow/v1/step.js` - Handles step execution (step mode transform)
+- `/.well-known/workflow/v1/webhook.js` - Handles webhook delivery
-This Bun plugin applies **client mode** transforms to your application code at runtime:
+Each file exports a `POST` function that accepts Web standard `Request` objects.
+
+### Step 2: Add Client Mode Transform (Optional)
+
+Client mode transforms your application code to provide better DX. Add a Bun plugin to apply this transformation at runtime:
```typescript title="workflow-plugin.ts" lineNumbers
import { plugin } from "bun";
@@ -285,8 +93,7 @@ plugin({
build.onLoad({ filter: /\.(ts|tsx|js|jsx)$/ }, async (args) => {
const source = await Bun.file(args.path).text();
- // Optimization: Only transform files with directives
- // False positives are safe (transformation is a no-op)
+ // Optimization: Skip files that do not have any directives
if (!source.match(/(use step|use workflow)/)) {
return { contents: source };
}
@@ -302,36 +109,36 @@ plugin({
},
});
- return {
- contents: result.code,
- loader: "ts",
- };
+ return { contents: result.code, loader: "ts" };
});
},
});
```
-To activate this plugin, add it to your `bunfig.toml`:
+Activate the plugin in `bunfig.toml`:
```toml title="bunfig.toml"
preload = ["./workflow-plugin.ts"]
```
**What this does:**
-- Hooks into Bun's native module loader
-- Checks for workflow directives in all TypeScript/JavaScript files
-- Only transforms files containing `"use step"` or `"use workflow"`
-- Applies client mode transformation (prevents direct workflow execution)
-### 3. HTTP Server Routing
+- Attaches workflow IDs to functions for use with `start()`
+- Provides TypeScript type safety
+- Prevents accidental direct execution of workflows
+
+**Why optional?** Without client mode, you can still use workflows by manually constructing IDs or referencing the build manifest.
+
+### Step 3: Expose HTTP Endpoints
-Finally, wire up the HTTP endpoints using `Bun.serve()`:
+Wire up the generated handlers to HTTP endpoints using `Bun.serve()`:
```typescript title="server.ts" lineNumbers
-import { start } from "workflow/api";
import flow from "./.well-known/workflow/v1/flow.js";
import step from "./.well-known/workflow/v1/step.js";
-import webhook from "./.well-known/workflow/v1/webhook.js";
+import * as webhook from "./.well-known/workflow/v1/webhook.js";
+
+import { start } from "workflow/api";
import { handleUserSignup } from "./workflows/user-signup.js";
const server = Bun.serve({
@@ -340,21 +147,17 @@ const server = Bun.serve({
"/.well-known/workflow/v1/flow": {
POST: (req) => flow.POST(req),
},
-
"/.well-known/workflow/v1/step": {
POST: (req) => step.POST(req),
},
+ // webhook exports handlers for GET, POST, DELETE, etc.
+ "/.well-known/workflow/v1/webhook/:token": webhook,
- "/.well-known/workflow/v1/webhook/:token": {
- POST: (req) => webhook.POST(req),
- },
-
+ // Example: Start a workflow
"/": {
GET: async (req) => {
const email = `test-${crypto.randomUUID()}@test.com`;
-
const run = await start(handleUserSignup, [email]);
-
return Response.json({
message: "User signup workflow started",
runId: run.runId,
@@ -367,183 +170,251 @@ const server = Bun.serve({
console.log(`Server listening on http://localhost:${server.port}`);
```
-**What this does:**
-- Imports generated `flow.js`, `step.js`, and `webhook.js` handlers
-- Maps HTTP endpoints to handler functions
-- Delegates all POST requests directly to generated handlers
-- The webhook route uses a `:token` parameter for identifying workflow runs
-- Includes an example endpoint showing how to start a workflow with `start()`
-
-You can find the complete Bun example implementation [here](https://github.com/vercel/workflow-examples/tree/main/custom-adapter).
+**That's it!** Your Bun integration is complete.
---
-## Security
+## Understanding the Endpoints
-A natural question when exposing HTTP endpoints is: **how are they secured?**
+Your integration must expose three HTTP endpoints. The generated handlers manage all protocol details—you just route requests.
-The security model depends on the **world abstraction** you're using. Different world implementations provide different security mechanisms.
+### Workflow Endpoint
-### World-Specific Security
+**Route:** `POST /.well-known/workflow/v1/flow`
-**Vercel (via `@workflow/world-vercel`):**
+Executes workflow orchestration logic. The workflow function is "rendered" multiple times during execution—each time it progresses until it encounters the next step.
-- Uses Vercel Queue as the backend orchestration layer
-- Vercel Queue will soon support **private invoke**, making routes inaccessible from the public internet
-- Handlers receive only a **message ID** from the queue, which must be retrieved from Vercel's backend
-- This architecture makes it impossible to craft custom payloads—you need valid message IDs from the queue
-- Even if someone discovers the endpoint URL, they cannot execute workflows without queue-issued message IDs
+**Called when:**
-**Custom Implementations:**
+- Starting a new workflow
+- Resuming after a step completes
+- Resuming after a webhook or hook triggers
+- Recovering from failures
-- World implementations can provide their own security mechanisms
-- Add authentication/authorization via framework middleware
-- Implement API key verification, JWT validation, or other auth schemes
-- Use network-level security (VPCs, private networks, firewall rules)
-- Implement rate limiting and request validation
+### Step Endpoint
-### Best Practices
+**Route:** `POST /.well-known/workflow/v1/step`
-When building your own world or framework integration, consider:
+Executes individual atomic operations within workflows. Each step runs exactly once per execution (unless retried due to failure). Steps have full runtime access (Node.js APIs, file system, databases, etc.).
-- **Authentication**: Verify requests come from trusted sources (your orchestration backend, not external clients)
-- **Payload validation**: Ensure payloads are cryptographically signed or from a trusted internal system
-- **Network isolation**: Run workflow endpoints on private networks when possible
-- **Rate limiting**: Protect against abuse even from internal systems
+### Webhook Endpoint
-The generated handlers don't include authentication because the security model is delegated to the world abstraction layer, allowing each runtime environment to implement the most appropriate security mechanism for its deployment context.
+**Route:** `POST /.well-known/workflow/v1/webhook/:token`
-Learn more about [world abstractions](/docs/deploying/world).
+Delivers webhook data to running workflows via [`createWebhook()`](/docs/api-reference/workflow/create-webhook). The `:token` parameter identifies which workflow run should receive the data.
+
+
+ The webhook file structure varies by framework. Next.js generates `webhook/[token]/route.js` to leverage App Router's dynamic routing, while other frameworks generate a single `webhook.js` handler.
+
---
-## Testing Your Integration
+## Adapting to Other Frameworks
-After building your integration, verify it works correctly:
+The Bun example demonstrates the core pattern. To adapt for your framework:
-### 1. Test Transformations
+### Build-Time
-Create a simple workflow file and verify transformations:
+**Option 1: Use the CLI** (simplest)
-```typescript title="test/fixtures/simple-workflow.ts"
-export async function testWorkflow() {
- "use workflow";
- return await testStep();
-}
+```bash
+workflow build
+```
-async function testStep() {
- "use step";
- return "hello";
+This will default to scanning the `./workflows` top-level directory for workflow files, and will output bundled files directly into your working directory.
+
+**Option 2: Extend `BaseBuilder`** (recommended)
+
+```typescript lineNumbers
+import { BaseBuilder } from '@workflow/cli/dist/lib/builders/base-builder';
+
+class MyFrameworkBuilder extends BaseBuilder {
+ constructor(options) {
+ super({
+ dirs: ['workflows'],
+ workingDir: options.rootDir,
+ watch: options.dev,
+ });
+ }
+
+ override async build(): Promise {
+ const inputFiles = await this.getInputFiles();
+
+ await this.createWorkflowsBundle({
+ outfile: '/path/to/.well-known/workflow/v1/flow.js',
+ format: 'esm',
+ inputFiles,
+ });
+
+ await this.createStepsBundle({
+ outfile: '/path/to/.well-known/workflow/v1/step.js',
+ format: 'esm',
+ inputFiles,
+ });
+
+ await this.createWebhookBundle({
+ outfile: '/path/to/.well-known/workflow/v1/webhook.js',
+ });
+ }
}
```
-Run your build and check:
-- `.well-known/workflow/v1/flow.js` contains transformed workflow code
-- `.well-known/workflow/v1/step.js` contains step registration
-- Your app code has client mode transforms applied
+If your framework supports virtual server routes and dev mode watching, make sure to adapt accordingly. Please open a PR to the Workflow DevKit if the base builder class is missing necessary functionality.
-### 2. Test HTTP Endpoints
-
-Start your server and verify endpoints respond:
+Hook into your framework's build:
-```bash
-# Test that endpoints exist (should return 400/405, not 404)
-curl -X POST http://localhost:3000/.well-known/workflow/v1/flow
-curl -X POST http://localhost:3000/.well-known/workflow/v1/step
-curl -X POST http://localhost:3000/.well-known/workflow/v1/webhook/test-token
+```typescript title="pseudocode.ts" lineNumbers
+framework.hooks.hook('build:before', async () => {
+ await new MyFrameworkBuilder(framework).build();
+});
```
-### 3. Run an End-to-End Workflow
+### Runtime (Client Mode)
-Create a test workflow and execute it:
+Add a loader/plugin for your bundler:
-```typescript
-import { start } from "workflow/api";
-import { testWorkflow } from "./test/fixtures/simple-workflow";
+**Rollup/Vite:**
-const run = await start(testWorkflow, []);
-console.log("Workflow started:", run.runId);
+```typescript lineNumbers
+export function workflowPlugin() {
+ return {
+ name: 'workflow-client-transform',
+ async transform(code, id) {
+ if (!code.match(/(use step|use workflow)/)) return null;
+
+ const result = await transform(code, {
+ filename: id,
+ jsc: {
+ experimental: {
+ plugins: [[require.resolve("@workflow/swc-plugin"), { mode: "client" }]], // [!code highlight]
+ },
+ },
+ });
+
+ return { code: result.code, map: result.map };
+ },
+ };
+}
```
-Verify the workflow completes successfully.
+**Webpack:**
+
+```javascript lineNumbers
+module.exports = {
+ module: {
+ rules: [
+ {
+ test: /\.(ts|tsx|js|jsx)$/,
+ use: 'workflow-client-loader', // Similar implementation
+ },
+ ],
+ },
+};
+```
+
+### HTTP Server
+
+Route the three endpoints to the generated handlers. The exact implementation depends on your framework's routing API.
---
-## Troubleshooting
+## Security
-### Transformations Not Applied
+**How are these HTTP endpoints secured?**
-**Symptoms:** Workflow code isn't transformed, or you get errors about missing directives.
+Security is handled by the **world abstraction** you're using:
-**Solutions:**
-- Verify files are in scanned source directories
-- Check that `"use workflow"` or `"use step"` are the first statements in functions
-- Ensure the SWC plugin is correctly configured
-- Check build logs for transformation errors
+**Vercel (`@workflow/world-vercel`):**
-### Routes Return 404
+- Vercel Queue will support private invoke, making routes inaccessible from the public internet
+- Handlers receive only a message ID that must be retrieved from Vercel's backend
+- Impossible to craft custom payloads without valid queue-issued message IDs
-**Symptoms:** POST requests to `.well-known/workflow/v1/*` return 404.
+**Custom implementations:**
-**Solutions:**
-- Verify handler files were generated in `.well-known/workflow/v1/`
-- Check that your HTTP server is correctly routing to these files
-- Ensure the route paths exactly match (including the `v1` version)
-- Check for framework-specific routing requirements (e.g., file-based vs code-based)
+- Implement authentication via framework middleware
+- Use API keys, JWT validation, or other auth schemes
+- Network-level security (VPCs, private networks, firewall rules)
+- Rate limiting and request validation
-### Serialization Errors
+Learn more about [world abstractions](/docs/deploying/world).
-**Symptoms:** Errors about non-serializable values being passed between workflows/steps.
+---
-**Solutions:**
-- Only pass JSON-serializable values (primitives, arrays, plain objects)
-- Avoid passing functions, class instances, Symbols, or other non-serializable types
-- See [Serialization](/docs/foundations/serialization) for details
+## Testing Your Integration
-### Module Resolution Errors
+### 1. Test Build Output
-**Symptoms:** Cannot find `workflow/api`, `workflow/internal/private`, etc.
+Create a test workflow:
-**Solutions:**
-- Ensure `@workflow/core` is installed
-- Check that your bundler/runtime resolves the `workflow` package correctly
-- Verify imports match the expected paths
+```typescript title="workflows/test.ts" lineNumbers
+import { sleep, createWebhook } from "workflow";
----
+export async function handleUserSignup(email: string) {
+ "use workflow";
-## Publishing Your Integration
+ const user = await createUser(email);
+ await sendWelcomeEmail(user);
-If you're building an integration for the community:
+ await sleep("5s");
-### Package Naming
+ const webhook = createWebhook();
+ await sendOnboardingEmail(user, webhook.url);
-Follow these conventions:
-- Framework plugins: `@workflow/{framework-name}` (e.g., `@workflow/remix`)
-- Build plugins: `@workflow/{bundler-name}-plugin` (e.g., `@workflow/vite-plugin`)
+ await webhook;
+ console.log("Webhook Resolved");
-### Required Exports
+ return { userId: user.id, status: "onboarded" };
+}
-Your integration package should export:
+async function createUser(email: string) {
+ "use step";
-- Build plugin/configuration for the framework
-- TypeScript types for workflow functions
-- Documentation on how to configure and use it
+ console.log(`Creating a new user with email: ${email}`);
-### Documentation
+ return { id: crypto.randomUUID(), email };
+}
-Include:
+async function sendWelcomeEmail(user: { id: string; email: string }) {
+ "use step";
-- Installation instructions
-- Configuration guide
-- Example projects
-- Migration guide (if replacing an existing solution)
+ console.log(`Sending welcome email to user: ${user.id}`);
+}
----
+async function sendOnboardingEmail(user: { id: string; email: string }, callback: string) {
+ "use step";
+
+ console.log(`Sending onboarding email to user: ${user.id}`);
+
+ console.log(`Click this link to resolve the webhook: ${callback}`);
+}
+
+```
+
+Run your build and verify:
+
+- `.well-known/workflow/v1/flow.js` exists
+- `.well-known/workflow/v1/step.js` exists
+- `.well-known/workflow/v1/webhook.js` exists
+
+### 2. Test HTTP Endpoints
-## Related Documentation
+Start your server and verify routes respond:
-- [How the Directives Work](/docs/how-it-works/code-transform) - Understand the transformation system
-- [World Abstractions](/docs/deploying/world) - Learn about storage backends and security
-- [Getting Started](/docs/getting-started) - See official integrations in action
-- [Workflows and Steps](/docs/foundations/workflows-and-steps) - Core execution concepts
-- [Next.js Integration Source](https://github.com/vercel/workflow/tree/main/packages/next) - Reference implementation
+```bash
+curl -X POST http://localhost:3000/.well-known/workflow/v1/flow
+curl -X POST http://localhost:3000/.well-known/workflow/v1/step
+curl -X POST http://localhost:3000/.well-known/workflow/v1/webhook/test
+```
+
+(Should respond but not trigger meaningful code without authentication/proper workflow run)
+
+### 3. Run a Workflow End-to-End
+
+```typescript
+import { start } from "workflow/api";
+import { handleUserSignup } from "./workflows/test";
+
+const run = await start(handleUserSignup, ["test@example.com"]);
+console.log("Workflow started:", run.runId);
+```