Overview
The Workflow DevKit compiler operates in three distinct modes, transforming the same source code differently for each execution context: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* |
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: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//handleUserSignupstep//workflows/user-signup.js//createUserstep//workflows/payments/checkout.ts//processPayment
- 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:- Remove the
"use step"directive - Keep the function body completely intact
- Call
registerStepFunction()to register the step with the runtime - Generate a stable step ID based on file path and function name
Workflow Mode Transformation
Purpose: Create deterministic orchestrators that can replay from event logs. Input:- Step functions: Replace body with
WORKFLOW_USE_STEPcall - Workflow functions: Keep body intact, add
workflowIdproperty - Remove all directives
- 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
WORKFLOW_USE_STEP symbol provides this behavior. From workflow.ts:
Client Mode Transformation
Purpose: Prevent direct workflow execution and provide type-safe workflow references forstart().
Input:
- Replace workflow body with error throw
- Add
workflowIdproperty - Step functions are not transformed in client mode
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:- 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
- 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.jsorwebhook.mjshandler
Determinism Enforcement
The compiler and runtime work together to enforce deterministic execution:Compiler-Level Enforcement
Blocked imports in workflows:Node.js module in workflow with link to error documentation.
Runtime-Level Enforcement
Fromworkflow.ts, the VM overrides non-deterministic APIs:
Math.random(): Seeded based on run IDDate.now(): Fixed timestamp that updates with event replay- Custom
Request/Responseclasses with step-based body parsing
Closure Variable Handling
Step functions can close over variables from their defining scope. The compiler captures these automatically: Input:step.ts, the useStep implementation serializes closure variables:
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:- Look in
.well-known/workflow/v1/: Check the generatedflow.js,step.js, andwebhook.jsfiles - Check build logs: Most frameworks log transformation activity during builds
- Verify directives: Ensure
"use workflow"and"use step"are the first statements in functions - Check file locations: Transformations only apply to files in configured source directories
- 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