Skip to main content
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:

Comparison Table

* 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:
Valid directive placement:

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:
Output (step.js):
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:
Output (flow.js):
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:
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:
Output (your application code):
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:

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:
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:

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:
Error: Node.js module in workflow with link to error documentation.

Runtime-Level Enforcement

From workflow.ts, the VM overrides non-deterministic APIs:
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:
The compiler generates:
From step.ts, the useStep implementation serializes closure variables:
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:

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.