> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/vercel/workflow/llms.txt
> Use this file to discover all available pages before exploring further.

# Compilation Process

> Deep dive into how the Workflow DevKit compiler transforms directive-annotated code.

Workflows use special directives to mark code for transformation by the Workflow DevKit compiler. This page explains the compilation pipeline, the three transformation modes, and how they work together to enable durable execution.

## Overview

The Workflow DevKit compiler operates in three distinct modes, transforming the same source code differently for each execution context:

```mermaid theme={null}
flowchart LR
    A["Source Code<br/>with directives"] --> B["Step Mode"]
    A --> C["Workflow Mode"]
    A --> D["Client Mode"]
    B --> E["step.js<br/>(Step Execution)"]
    C --> F["flow.js<br/>(Workflow Execution)"]
    D --> G["Your App Code<br/>(Enables start)"]
```

### Comparison Table

| Mode | Used In | Purpose | Output API Route | Required? |
| - | - | - | - | - |
| Step | Build time | Bundles step handlers | `.well-known/workflow/v1/step` | Yes |
| Workflow | Build time | Bundles workflow orchestrators | `.well-known/workflow/v1/flow` | Yes |
| Client | Build/Runtime | Provides workflow IDs and types to `start` | Your application code | 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.

## The Compilation Pipeline

The Workflow DevKit uses an SWC-based compiler plugin (`@workflow/swc-plugin`) that performs the following steps:

### 1. Parse and Identify Directives

The compiler scans each function for directive strings at the beginning of the function body:

```typescript theme={null}
export async function myWorkflow() {
  "use workflow"; // ← Directive must be first statement
  // workflow code...
}
```

**Valid directive placement:**

```typescript theme={null}
// ✓ Valid - first statement in function body
export async function myWorkflow() {
  "use workflow";
  const result = await myStep();
  return result;
}

// ✗ Invalid - directive after other statements
export async function badWorkflow() {
  const x = 1;
  "use workflow"; // Too late!
  return x;
}
```

### 2. Generate Stable IDs

For each function with a directive, the compiler generates a stable identifier based on the file path and function name:

**Pattern:** `{type}//{filepath}//{functionName}`

**Examples:**

* `workflow//workflows/user-signup.js//handleUserSignup`
* `step//workflows/user-signup.js//createUser`
* `step//workflows/payments/checkout.ts//processPayment`

**Why stable IDs matter:**

* IDs don't change unless you rename files or functions
* Each workflow/step has a unique identifier across your application
* IDs work across different runtimes and deployments
* The World layer can track function execution history

### 3. Transform Based on Mode

Depending on the compilation mode, the compiler applies different transformations to the same source code.

## Step Mode Transformation

**Purpose:** Create executable step handlers that run in the full Node.js runtime.

**Input:**

```typescript theme={null}
export async function sendEmail(to: string, subject: string) {
  "use step";
  
  await emailClient.send({
    to,
    subject,
    body: "Hello!"
  });
  
  return { sent: true };
}
```

**Output (step.js):**

```typescript theme={null}
import { registerStepFunction } from "workflow/internal/private";

export async function sendEmail(to: string, subject: string) {
  await emailClient.send({
    to,
    subject,
    body: "Hello!"
  });
  
  return { sent: true };
}

registerStepFunction(
  "step//workflows/email.js//sendEmail",
  sendEmail
);
```

**Transformations applied:**

1. Remove the `"use step"` directive
2. Keep the function body completely intact
3. Call `registerStepFunction()` to register the step with the runtime
4. Generate a stable step ID based on file path and function name

**No transformation needed** for the function body because step functions execute in the full Node.js runtime with access to all APIs, file system, databases, etc.

## Workflow Mode Transformation

**Purpose:** Create deterministic orchestrators that can replay from event logs.

**Input:**

```typescript theme={null}
export async function sendEmail(to: string, subject: string) {
  "use step";
  await emailClient.send({ to, subject, body: "Hello!" });
  return { sent: true };
}

export async function welcomeUser(email: string) {
  "use workflow";
  
  await sendEmail(email, "Welcome!");
  
  return { success: true };
}
```

**Output (flow\.js):**

```typescript theme={null}
export async function sendEmail(to: string, subject: string) {
  return globalThis[Symbol.for("WORKFLOW_USE_STEP")](
    "step//workflows/email.js//sendEmail"
  )(to, subject);
}

export async function welcomeUser(email: string) {
  await sendEmail(email, "Welcome!");
  return { success: true };
}
welcomeUser.workflowId = "workflow//workflows/email.js//welcomeUser";
```

**Transformations applied:**

1. **Step functions**: Replace body with `WORKFLOW_USE_STEP` call
2. **Workflow functions**: Keep body intact, add `workflowId` property
3. Remove all directives

**Why replace step bodies?** During workflow replay, we need to:

* Check if the step has already been executed (in the event log)
* Return cached results for completed steps
* Suspend execution for steps that haven't completed yet

The `WORKFLOW_USE_STEP` symbol provides this behavior. From `workflow.ts`:

```typescript theme={null}
const useStep = createUseStep(workflowContext);
vmGlobalThis[WORKFLOW_USE_STEP] = useStep;
```

**Why keep workflow bodies intact?** Workflow functions are pure orchestration logic. They need to execute the same way during replay to determine what the next step should be.

## Client Mode Transformation

**Purpose:** Prevent direct workflow execution and provide type-safe workflow references for `start()`.

**Input:**

```typescript theme={null}
export async function welcomeUser(email: string) {
  "use workflow";
  await sendEmail(email, "Welcome!");
  return { success: true };
}
```

**Output (your application code):**

```typescript theme={null}
export async function welcomeUser(email: string) {
  throw new Error(
    "You attempted to execute workflow 'welcomeUser' directly. " +
    "Workflows must be started using the start() function."
  );
}
welcomeUser.workflowId = "workflow//workflows/email.js//welcomeUser";
```

**Transformations applied:**

1. Replace workflow body with error throw
2. Add `workflowId` property
3. Step functions are not transformed in client mode

**Why throw an error?** Workflow functions cannot be called directly—they must be started using `start()`. The error prevents accidental direct execution.

**Why add workflowId?** The `start()` function reads this property to identify which workflow to launch:

```typescript theme={null}
import { start } from "workflow";
import { welcomeUser } from "./workflows/email";

// start() reads welcomeUser.workflowId
const run = await start(welcomeUser, "user@example.com");
```

## Generated Files

When you build your application, the Workflow DevKit generates handler files in `.well-known/workflow/v1/`:

### flow\.js

Contains all workflow functions transformed in **workflow mode**.

**Structure:**

```typescript theme={null}
// Bundled workflow code is embedded as a string
const workflowCode = `
  // All workflow functions and dependencies
  export async function welcomeUser(email) {
    await sendEmail(email, "Welcome!");
    return { success: true };
  }
  // ...
`;

// POST handler that executes workflows in a VM
export async function POST(request: Request) {
  const { runId, workflowName, events } = await request.json();
  
  // Execute in Node.js VM for sandboxing
  const result = await runWorkflow(
    workflowCode,
    workflowRun,
    events,
    encryptionKey
  );
  
  return Response.json({ result });
}
```

**VM Sandbox:** Workflow code runs in a Node.js VM context to ensure:

* **Determinism**: Same inputs always produce same outputs
* **Side-effect prevention**: Direct access to Node.js APIs is blocked
* **Isolation**: Workflow logic is isolated from the main runtime

**Build-time validation:** The compiler validates workflows during 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 before deployment

### step.js

Contains all step functions transformed in **step mode**.

**Structure:**

```typescript theme={null}
import { registerStepFunction } from "workflow/internal/private";

export async function sendEmail(to, subject) {
  await emailClient.send({ to, subject, body: "Hello!" });
  return { sent: true };
}

registerStepFunction(
  "step//workflows/email.js//sendEmail",
  sendEmail
);

// POST handler that executes individual steps
export async function POST(request: Request) {
  const { stepId, args } = await request.json();
  
  // Look up registered step function
  const stepFn = getStepFunction(stepId);
  
  // Execute with full runtime access
  const result = await stepFn(...args);
  
  return Response.json({ result });
}
```

### webhook.js

Contains webhook handling logic for delivering external data to running workflows.

**Structure varies by framework:**

* **Next.js**: Generates `webhook/[token]/route.js` (leverages App Router dynamic routing)
* **Other frameworks**: Generates `webhook.js` or `webhook.mjs` handler

## Determinism Enforcement

The compiler and runtime work together to enforce deterministic execution:

### Compiler-Level Enforcement

**Blocked imports in workflows:**

```typescript theme={null}
export async function badWorkflow() {
  "use workflow";
  
  // Build error: Node.js module in workflow
  const crypto = require("crypto");
  return crypto.randomBytes(16);
}
```

**Error:** `Node.js module in workflow` with link to error documentation.

### Runtime-Level Enforcement

From `workflow.ts`, the VM overrides non-deterministic APIs:

```typescript theme={null}
// Block global fetch
vmGlobalThis.fetch = () => {
  throw new vmGlobalThis.Error(
    'Global "fetch" is unavailable in workflow functions. ' +
    'Use the "fetch" step function from "workflow".'
  );
};

// Block timeout functions
vmGlobalThis.setTimeout = () => {
  throw new WorkflowRuntimeError(
    'Timeout functions are not supported in workflow functions. ' +
    'Use the "sleep" function from "workflow".'
  );
};

// Provide seeded random
const { context, globalThis: vmGlobalThis } = createContext({
  seed: workflowRun.runId,
  fixedTimestamp: +startedAt,
});
```

**Deterministic APIs provided:**

* `Math.random()`: Seeded based on run ID
* `Date.now()`: Fixed timestamp that updates with event replay
* Custom `Request`/`Response` classes with step-based body parsing

## Closure Variable Handling

Step functions can close over variables from their defining scope. The compiler captures these automatically:

**Input:**

```typescript theme={null}
export async function processOrder(orderId: string) {
  "use workflow";
  
  const config = { timeout: 5000 };
  
  async function fetchOrder() {
    "use step";
    // Closes over 'config' and 'orderId'
    return await api.get(`/orders/${orderId}`, config);
  }
  
  return await fetchOrder();
}
```

**The compiler generates:**

```typescript theme={null}
const fetchOrder = useStep(
  "step//workflows/order.js//fetchOrder",
  () => ({ config, orderId }) // Closure variables function
);
```

From `step.ts`, the `useStep` implementation serializes closure variables:

```typescript theme={null}
const queueItem: StepInvocationQueueItem = {
  type: 'step',
  correlationId,
  stepName,
  args,
};

// Invoke the closure variables function to get the closure scope
const closureVars = closureVarsFn?.();
if (closureVars) {
  queueItem.closureVars = closureVars;
}
```

These variables are serialized along with the step arguments and restored when the step executes.

## Framework Integration

The compilation process is framework-agnostic—it outputs standard JavaScript that works anywhere.

**For users:** Your framework handles all transformations automatically. See the Getting Started guide for your framework.

**For framework authors:** The `@workflow/swc-plugin` can be integrated into any build pipeline:

```typescript theme={null}
import { transform } from "@swc/core";
import workflowPlugin from "@workflow/swc-plugin";

const result = await transform(code, {
  plugin: (m) => workflowPlugin(m, {
    mode: "workflow", // or "step" or "client"
  }),
});
```

## Debugging Transformed Code

If you need to debug transformation issues:

1. **Look in `.well-known/workflow/v1/`**: Check the generated `flow.js`, `step.js`, and `webhook.js` 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

**Common issues:**

* Directive not first statement: Move directive to the very beginning of the function body
* Node.js import in workflow: Move the import to a step function instead
* Missing workflowId: Ensure the function is exported and has the directive

## Conclusion

The Workflow DevKit's compilation process transforms directive-annotated code into three execution contexts:

* **Step mode**: Full runtime access for side effects
* **Workflow mode**: Sandboxed orchestration for deterministic replay
* **Client mode**: Type-safe references for starting workflows

This multi-mode transformation enables durable execution, deterministic replay, and type safety—all while maintaining familiar JavaScript syntax and semantics.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.