@workflow/core 4.0.1-beta.9 → 4.1.0-beta.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/dist/builtins.js +1 -1
  2. package/dist/class-serialization.d.ts +26 -0
  3. package/dist/class-serialization.d.ts.map +1 -0
  4. package/dist/class-serialization.js +66 -0
  5. package/dist/create-hook.js +1 -1
  6. package/dist/define-hook.d.ts +40 -25
  7. package/dist/define-hook.d.ts.map +1 -1
  8. package/dist/define-hook.js +22 -27
  9. package/dist/events-consumer.d.ts.map +1 -1
  10. package/dist/events-consumer.js +5 -1
  11. package/dist/flushable-stream.d.ts +82 -0
  12. package/dist/flushable-stream.d.ts.map +1 -0
  13. package/dist/flushable-stream.js +214 -0
  14. package/dist/global.d.ts +4 -1
  15. package/dist/global.d.ts.map +1 -1
  16. package/dist/global.js +21 -9
  17. package/dist/index.d.ts +2 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +2 -2
  20. package/dist/logger.js +1 -1
  21. package/dist/observability.d.ts +60 -0
  22. package/dist/observability.d.ts.map +1 -1
  23. package/dist/observability.js +265 -32
  24. package/dist/private.d.ts +10 -1
  25. package/dist/private.d.ts.map +1 -1
  26. package/dist/private.js +6 -1
  27. package/dist/runtime/helpers.d.ts +52 -0
  28. package/dist/runtime/helpers.d.ts.map +1 -0
  29. package/dist/runtime/helpers.js +264 -0
  30. package/dist/runtime/resume-hook.d.ts +17 -12
  31. package/dist/runtime/resume-hook.d.ts.map +1 -1
  32. package/dist/runtime/resume-hook.js +79 -64
  33. package/dist/runtime/run.d.ts +100 -0
  34. package/dist/runtime/run.d.ts.map +1 -0
  35. package/dist/runtime/run.js +132 -0
  36. package/dist/runtime/start.d.ts +15 -1
  37. package/dist/runtime/start.d.ts.map +1 -1
  38. package/dist/runtime/start.js +72 -46
  39. package/dist/runtime/step-handler.d.ts +7 -0
  40. package/dist/runtime/step-handler.d.ts.map +1 -0
  41. package/dist/runtime/step-handler.js +337 -0
  42. package/dist/runtime/suspension-handler.d.ts +25 -0
  43. package/dist/runtime/suspension-handler.d.ts.map +1 -0
  44. package/dist/runtime/suspension-handler.js +182 -0
  45. package/dist/runtime/world.d.ts.map +1 -1
  46. package/dist/runtime/world.js +20 -21
  47. package/dist/runtime.d.ts +4 -105
  48. package/dist/runtime.d.ts.map +1 -1
  49. package/dist/runtime.js +97 -531
  50. package/dist/schemas.d.ts +1 -15
  51. package/dist/schemas.d.ts.map +1 -1
  52. package/dist/schemas.js +2 -15
  53. package/dist/serialization.d.ts +112 -21
  54. package/dist/serialization.d.ts.map +1 -1
  55. package/dist/serialization.js +469 -85
  56. package/dist/sleep.d.ts +10 -0
  57. package/dist/sleep.d.ts.map +1 -1
  58. package/dist/sleep.js +1 -1
  59. package/dist/source-map.d.ts +10 -0
  60. package/dist/source-map.d.ts.map +1 -0
  61. package/dist/source-map.js +56 -0
  62. package/dist/step/context-storage.d.ts +2 -1
  63. package/dist/step/context-storage.d.ts.map +1 -1
  64. package/dist/step/context-storage.js +1 -1
  65. package/dist/step/get-closure-vars.d.ts +9 -0
  66. package/dist/step/get-closure-vars.d.ts.map +1 -0
  67. package/dist/step/get-closure-vars.js +16 -0
  68. package/dist/step/get-step-metadata.js +1 -1
  69. package/dist/step/get-workflow-metadata.js +1 -1
  70. package/dist/step/writable-stream.d.ts +10 -2
  71. package/dist/step/writable-stream.d.ts.map +1 -1
  72. package/dist/step/writable-stream.js +6 -5
  73. package/dist/step.d.ts +1 -1
  74. package/dist/step.d.ts.map +1 -1
  75. package/dist/step.js +93 -47
  76. package/dist/symbols.d.ts +6 -0
  77. package/dist/symbols.d.ts.map +1 -1
  78. package/dist/symbols.js +7 -1
  79. package/dist/telemetry/semantic-conventions.d.ts +66 -38
  80. package/dist/telemetry/semantic-conventions.d.ts.map +1 -1
  81. package/dist/telemetry/semantic-conventions.js +16 -3
  82. package/dist/telemetry.d.ts +8 -4
  83. package/dist/telemetry.d.ts.map +1 -1
  84. package/dist/telemetry.js +39 -6
  85. package/dist/types.js +1 -1
  86. package/dist/util.d.ts +5 -24
  87. package/dist/util.d.ts.map +1 -1
  88. package/dist/util.js +19 -38
  89. package/dist/version.d.ts +2 -0
  90. package/dist/version.d.ts.map +1 -0
  91. package/dist/version.js +3 -0
  92. package/dist/vm/index.js +2 -2
  93. package/dist/vm/uuid.js +1 -1
  94. package/dist/workflow/create-hook.js +1 -1
  95. package/dist/workflow/define-hook.d.ts +3 -3
  96. package/dist/workflow/define-hook.d.ts.map +1 -1
  97. package/dist/workflow/define-hook.js +1 -1
  98. package/dist/workflow/get-workflow-metadata.js +1 -1
  99. package/dist/workflow/hook.d.ts.map +1 -1
  100. package/dist/workflow/hook.js +49 -14
  101. package/dist/workflow/index.d.ts +1 -1
  102. package/dist/workflow/index.d.ts.map +1 -1
  103. package/dist/workflow/index.js +2 -2
  104. package/dist/workflow/sleep.d.ts +1 -1
  105. package/dist/workflow/sleep.d.ts.map +1 -1
  106. package/dist/workflow/sleep.js +26 -39
  107. package/dist/workflow/writable-stream.d.ts +1 -1
  108. package/dist/workflow/writable-stream.d.ts.map +1 -1
  109. package/dist/workflow/writable-stream.js +1 -1
  110. package/dist/workflow.d.ts +1 -1
  111. package/dist/workflow.d.ts.map +1 -1
  112. package/dist/workflow.js +72 -9
  113. package/docs/api-reference/create-hook.mdx +134 -0
  114. package/docs/api-reference/create-webhook.mdx +226 -0
  115. package/docs/api-reference/define-hook.mdx +207 -0
  116. package/docs/api-reference/fatal-error.mdx +38 -0
  117. package/docs/api-reference/fetch.mdx +140 -0
  118. package/docs/api-reference/get-step-metadata.mdx +77 -0
  119. package/docs/api-reference/get-workflow-metadata.mdx +45 -0
  120. package/docs/api-reference/get-writable.mdx +292 -0
  121. package/docs/api-reference/index.mdx +56 -0
  122. package/docs/api-reference/meta.json +3 -0
  123. package/docs/api-reference/retryable-error.mdx +107 -0
  124. package/docs/api-reference/sleep.mdx +60 -0
  125. package/docs/foundations/common-patterns.mdx +254 -0
  126. package/docs/foundations/errors-and-retries.mdx +191 -0
  127. package/docs/foundations/hooks.mdx +456 -0
  128. package/docs/foundations/idempotency.mdx +56 -0
  129. package/docs/foundations/index.mdx +33 -0
  130. package/docs/foundations/meta.json +14 -0
  131. package/docs/foundations/serialization.mdx +158 -0
  132. package/docs/foundations/starting-workflows.mdx +212 -0
  133. package/docs/foundations/streaming.mdx +570 -0
  134. package/docs/foundations/workflows-and-steps.mdx +198 -0
  135. package/docs/how-it-works/code-transform.mdx +335 -0
  136. package/docs/how-it-works/event-sourcing.mdx +255 -0
  137. package/docs/how-it-works/framework-integrations.mdx +438 -0
  138. package/docs/how-it-works/meta.json +10 -0
  139. package/docs/how-it-works/understanding-directives.mdx +612 -0
  140. package/package.json +31 -25
  141. package/dist/builtins.js.map +0 -1
  142. package/dist/create-hook.js.map +0 -1
  143. package/dist/define-hook.js.map +0 -1
  144. package/dist/events-consumer.js.map +0 -1
  145. package/dist/global.js.map +0 -1
  146. package/dist/index.js.map +0 -1
  147. package/dist/logger.js.map +0 -1
  148. package/dist/observability.js.map +0 -1
  149. package/dist/parse-name.d.ts +0 -25
  150. package/dist/parse-name.d.ts.map +0 -1
  151. package/dist/parse-name.js +0 -40
  152. package/dist/parse-name.js.map +0 -1
  153. package/dist/private.js.map +0 -1
  154. package/dist/runtime/resume-hook.js.map +0 -1
  155. package/dist/runtime/start.js.map +0 -1
  156. package/dist/runtime/world.js.map +0 -1
  157. package/dist/runtime.js.map +0 -1
  158. package/dist/schemas.js.map +0 -1
  159. package/dist/serialization.js.map +0 -1
  160. package/dist/sleep.js.map +0 -1
  161. package/dist/step/context-storage.js.map +0 -1
  162. package/dist/step/get-step-metadata.js.map +0 -1
  163. package/dist/step/get-workflow-metadata.js.map +0 -1
  164. package/dist/step/writable-stream.js.map +0 -1
  165. package/dist/step.js.map +0 -1
  166. package/dist/symbols.js.map +0 -1
  167. package/dist/telemetry/semantic-conventions.js.map +0 -1
  168. package/dist/telemetry.js.map +0 -1
  169. package/dist/types.js.map +0 -1
  170. package/dist/util.js.map +0 -1
  171. package/dist/vm/index.js.map +0 -1
  172. package/dist/vm/uuid.js.map +0 -1
  173. package/dist/workflow/create-hook.js.map +0 -1
  174. package/dist/workflow/define-hook.js.map +0 -1
  175. package/dist/workflow/get-workflow-metadata.js.map +0 -1
  176. package/dist/workflow/hook.js.map +0 -1
  177. package/dist/workflow/index.js.map +0 -1
  178. package/dist/workflow/sleep.js.map +0 -1
  179. package/dist/workflow/writable-stream.js.map +0 -1
  180. package/dist/workflow.js.map +0 -1
  181. package/dist/writable-stream.d.ts +0 -23
  182. package/dist/writable-stream.d.ts.map +0 -1
  183. package/dist/writable-stream.js +0 -17
  184. package/dist/writable-stream.js.map +0 -1
@@ -0,0 +1,198 @@
1
+ ---
2
+ title: Workflows and Steps
3
+ description: Build long-running, stateful application logic that persists progress and resumes after failures.
4
+ ---
5
+
6
+ import { File, Folder, Files } from "fumadocs-ui/components/files";
7
+
8
+ Workflows (a.k.a. *durable functions*) are a programming model for building long-running, stateful application logic that can maintain its execution state across restarts, failures, or user events. Unlike traditional serverless functions that lose all state when they terminate, workflows persist their progress and can resume exactly where they left off.
9
+
10
+ Moreover, workflows let you easily model complex multi-step processes in simple, elegant code. To do this, we introduce two fundamental entities:
11
+
12
+ 1. **Workflow Functions**: Functions that orchestrate/organize steps
13
+ 2. **Step Functions**: Functions that carry out the actual work
14
+
15
+ ## Workflow Functions
16
+
17
+ *Directive: `"use workflow"`*
18
+
19
+ Workflow functions define the entrypoint of a workflow and organize how step functions are called. This type of function does not have access to the Node.js runtime, and usable `npm` packages are limited.
20
+
21
+ Although this may seem limiting initially, this feature is important in order to suspend and accurately resume execution of workflows.
22
+
23
+ It helps to think of the workflow function less like a full JavaScript runtime and more like "stitching together" various steps using conditionals, loops, try/catch handlers, `Promise.all`, and other language primitives.
24
+
25
+ ```typescript lineNumbers
26
+ export async function processOrderWorkflow(orderId: string) {
27
+ "use workflow"; // [!code highlight]
28
+
29
+ // Orchestrate multiple steps
30
+ const order = await fetchOrder(orderId);
31
+ const payment = await chargePayment(order);
32
+
33
+ return { orderId, status: "completed" };
34
+ }
35
+ ```
36
+
37
+ **Key Characteristics:**
38
+
39
+ - Runs in a sandboxed environment without full Node.js access
40
+ - All step results are persisted to the [event log](/docs/how-it-works/event-sourcing)
41
+ - Must be **deterministic** to allow resuming after failures
42
+
43
+ Determinism in the workflow is required to resume the workflow from a suspension. Essentially, the workflow code gets re-run multiple times during its lifecycle, each time using the [event log](/docs/how-it-works/event-sourcing) to resume the workflow to the correct spot.
44
+
45
+ The sandboxed environment that workflows run in already ensures determinism. For instance, `Math.random` and `Date` constructors are fixed in workflow runs, so you are safe to use them, and the framework ensures that the values don't change across replays.
46
+
47
+ ## Step Functions
48
+
49
+ *Directive: `"use step"`*
50
+
51
+ Step functions perform the actual work in a workflow and have full runtime access.
52
+
53
+ ```typescript lineNumbers
54
+ async function chargePayment(order: Order) {
55
+ "use step"; // [!code highlight]
56
+
57
+ // Full Node.js access - use any npm package
58
+ const stripe = new Stripe(process.env.STRIPE_KEY);
59
+
60
+ const charge = await stripe.charges.create({
61
+ amount: order.total,
62
+ currency: "usd",
63
+ source: order.paymentToken
64
+ });
65
+
66
+ return { chargeId: charge.id };
67
+ }
68
+ ```
69
+
70
+ **Key Characteristics:**
71
+
72
+ - Full Node.js runtime and npm package access
73
+ - Automatic retry on errors
74
+ - Results persisted for replay
75
+
76
+ By default, steps have a maximum of 3 retry attempts before they fail and propagate the error to the workflow. Learn more about errors and retrying in the [Errors & Retrying](/docs/foundations/errors-and-retries) page.
77
+
78
+ <Callout type="warning">
79
+ **Important:** Due to serialization, parameters are passed by **value, not by reference**. If you pass an object or array to a step and mutate it, those changes will **not** be visible in the workflow context. Always return modified data from your step functions instead. See [Pass-by-Value Semantics](/docs/foundations/serialization#pass-by-value-semantics) for details and examples.
80
+ </Callout>
81
+
82
+ <Callout type="info">
83
+ Step functions are primarily meant to be used inside a workflow.
84
+ </Callout>
85
+
86
+ Calling a step from outside a workflow or from another step will essentially run the step in the same process like a normal function (in other words, the `use step` directive is a no-op). This means you can reuse step functions in other parts of your codebase without needing to duplicate business logic.
87
+
88
+
89
+ {/* @skip-typecheck: incomplete code sample */}
90
+ ```typescript lineNumbers
91
+ async function updateUser(userId: string) {
92
+ "use step";
93
+ await db.insert(...);
94
+ }
95
+
96
+ // Used inside a workflow
97
+ export async function userOnboardingWorkflow(userId: string) {
98
+ "use workflow";
99
+ await updateUser(userId);
100
+ // ... more steps
101
+ }
102
+
103
+ // Used directly outside a workflow
104
+ export async function POST() {
105
+ await updateUser("123");
106
+ // ... more logic
107
+ }
108
+ ```
109
+
110
+ <Callout type="info">
111
+ Keep in mind that calling a step function outside of a workflow function will not have retry semantics, nor will it be observable. Additionally, certain workflow-specific functions like [`getStepMetadata()`](/docs/api-reference/workflow/get-step-metadata) will throw an error when used inside a step that's called outside a workflow.
112
+ </Callout>
113
+
114
+ ### Suspension and Resumption
115
+
116
+ Workflow functions have the ability to automatically suspend while they wait on asynchronous work. While suspended, the workflow's state is stored via the [event log](/docs/how-it-works/event-sourcing) and no compute resources are used until the workflow resumes execution.
117
+
118
+ There are multiple ways a workflow can suspend:
119
+
120
+ - Waiting on a step function: the workflow yields while the step runs in the step runtime.
121
+ - Using `sleep()` to pause for some fixed duration.
122
+ - Awaiting on a promise returned by [`createWebhook()`](/docs/api-reference/workflow/create-webhook), which resumes the workflow when an external system passes data into the workflow.
123
+
124
+ ```typescript lineNumbers
125
+ import { sleep, createWebhook } from "workflow";
126
+
127
+ export async function documentReviewProcess(userId: string) {
128
+ "use workflow";
129
+
130
+ await sleep("1 month"); // Sleep will suspend without consuming any resources [!code highlight]
131
+
132
+ // Create a webhook for external workflow resumption
133
+ const webhook = createWebhook();
134
+
135
+ // Send the webhook url to some external service or in an email, etc.
136
+ await sendHumanApprovalEmail("Click this link to accept the review", webhook.url)
137
+
138
+ const data = await webhook; // The workflow suspends till the URL is resumed [!code highlight]
139
+
140
+ console.log("Document reviewed!")
141
+ }
142
+ ```
143
+
144
+ ## Writing Workflows
145
+
146
+ ### Basic Structure
147
+
148
+ The simplest workflow consists of a workflow function and one or more step functions.
149
+
150
+ ```typescript lineNumbers
151
+ // Workflow function (orchestrates the steps)
152
+ export async function greetingWorkflow(name: string) {
153
+ "use workflow";
154
+
155
+ const message = await greet(name);
156
+ return { message };
157
+ }
158
+
159
+ // Step function (does the actual work)
160
+ async function greet(name: string) {
161
+ "use step";
162
+
163
+ // Access Node.js APIs
164
+ const message = `Hello ${name} at ${new Date().toISOString()}`;
165
+ console.log(message);
166
+ return message;
167
+ }
168
+ ```
169
+
170
+ ### Project structure
171
+
172
+ While you can organize workflow and step functions however you like, we find that larger projects benefit from some structure:
173
+ <Files>
174
+ <Folder name="workflows" defaultOpen disabled>
175
+ <Folder name="userOnboarding" defaultOpen disabled>
176
+ <File name="index.ts" />
177
+ <File name="steps.ts" />
178
+ </Folder>
179
+ <Folder name="aiVideoGeneration" defaultOpen disabled>
180
+ <File name="index.ts" />
181
+ <Folder name="steps" defaultOpen disabled>
182
+ <File name="transcribeUpload.ts" />
183
+ <File name="generateVideo.ts" />
184
+ <File name="notifyUser.ts" />
185
+ </Folder>
186
+ </Folder>
187
+ <Folder name="shared" defaultOpen disabled>
188
+ <File name="validateInput.ts" />
189
+ <File name="logActivity.ts" />
190
+ </Folder>
191
+ </Folder>
192
+ </Files>
193
+
194
+ You can choose to organize your steps into a single `steps.ts` file or separate files within a `steps` folder. The `shared` folder is a good place to put common steps that are used by multiple workflows.
195
+
196
+ <Callout type="info">
197
+ Splitting up steps and workflows will also help avoid most bundler related bugs with the Workflow DevKit.
198
+ </Callout>
@@ -0,0 +1,335 @@
1
+ ---
2
+ title: How the Directives Work
3
+ description: Deep dive into the internals of how Workflow DevKit directives transform your code.
4
+ ---
5
+
6
+ <Callout>
7
+ This is an advanced guide that dives into internals of the Workflow DevKit directive and is not required reading to use workflows. To simply use the Workflow DevKit, check out the [getting started](/docs/getting-started) guides for your framework.
8
+ </Callout>
9
+
10
+ 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.
11
+
12
+ ## Directives Overview
13
+
14
+ Workflows use two directives to mark functions for special handling:
15
+
16
+ {/* @skip-typecheck: incomplete code sample */}
17
+ ```typescript
18
+ export async function handleUserSignup(email: string) {
19
+ "use workflow"; // [!code highlight]
20
+
21
+ const user = await createUser(email);
22
+ await sendWelcomeEmail(user);
23
+
24
+ return { userId: user.id };
25
+ }
26
+
27
+ async function createUser(email: string) {
28
+ "use step"; // [!code highlight]
29
+
30
+ return { id: crypto.randomUUID(), email };
31
+ }
32
+ ```
33
+
34
+ **Key directives:**
35
+
36
+ - `"use workflow"`: Marks a function as a durable workflow entry point
37
+ - `"use step"`: Marks a function as an atomic, retryable step
38
+
39
+ These directives trigger the `@workflow/swc-plugin` compiler to transform your code in different ways depending on the execution context.
40
+
41
+ ## The Three Transformation Modes
42
+
43
+ The compiler operates in three distinct modes, transforming the same source code differently for each execution context:
44
+
45
+ ```mermaid
46
+ flowchart LR
47
+ A["Source Code<br/>with directives"] --> B["Step Mode"]
48
+ A --> C["Workflow Mode"]
49
+ A --> D["Client Mode"]
50
+ B --> E["step.js<br/>(Step Execution)"]
51
+ C --> F["flow.js<br/>(Workflow Execution)"]
52
+ D --> G["Your App Code<br/>(Enables `start`)"]
53
+ ```
54
+
55
+ ### Comparison Table
56
+
57
+ | Mode | Used In | Purpose | Output API Route | Required? |
58
+ |----------|------------|--------------------------------|------------------------------------|-----------|
59
+ | Step | Build time | Bundles step handlers | `.well-known/workflow/v1/step` | Yes |
60
+ | Workflow | Build time | Bundles workflow orchestrators | `.well-known/workflow/v1/flow` | Yes |
61
+ | Client | Build/Runtime | Provides workflow IDs and types to `start` | Your application code | Optional* |
62
+
63
+ \* 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.
64
+
65
+ ## Detailed Transformation Examples
66
+
67
+ <Tabs items={["Step Mode", "Workflow Mode", "Client Mode"]}>
68
+ <Tab value="Step Mode">
69
+
70
+ **Step Mode** creates the step execution bundle served at `/.well-known/workflow/v1/step`.
71
+
72
+ **Input:**
73
+
74
+ {/* @skip-typecheck: incomplete code sample */}
75
+ ```typescript
76
+ export async function createUser(email: string) {
77
+ "use step";
78
+ return { id: crypto.randomUUID(), email };
79
+ }
80
+ ```
81
+
82
+ **Output:**
83
+
84
+ {/* @skip-typecheck: incomplete code sample */}
85
+ ```typescript
86
+ import { registerStepFunction } from "workflow/internal/private"; // [!code highlight]
87
+
88
+ export async function createUser(email: string) {
89
+ return { id: crypto.randomUUID(), email };
90
+ }
91
+
92
+ registerStepFunction("step//workflows/user.js//createUser", createUser); // [!code highlight]
93
+ ```
94
+
95
+ **What happens:**
96
+
97
+ - The `"use step"` directive is removed
98
+ - The function body is kept completely intact (no transformation)
99
+ - The function is registered with the runtime using `registerStepFunction()`
100
+ - Step functions run with full Node.js/Deno/Bun access
101
+
102
+ **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.
103
+
104
+ **ID Format:** Step IDs follow the pattern `step//{filepath}//{functionName}`, where the filepath is relative to your project root.
105
+
106
+ </Tab>
107
+ <Tab value="Workflow Mode">
108
+
109
+ **Workflow Mode** creates the workflow execution bundle served at `/.well-known/workflow/v1/flow`.
110
+
111
+ **Input:**
112
+
113
+ {/* @skip-typecheck: incomplete code sample */}
114
+ ```typescript
115
+ export async function createUser(email: string) {
116
+ "use step";
117
+ return { id: crypto.randomUUID(), email };
118
+ }
119
+
120
+ export async function handleUserSignup(email: string) {
121
+ "use workflow";
122
+ const user = await createUser(email);
123
+ return { userId: user.id };
124
+ }
125
+ ```
126
+
127
+ **Output:**
128
+
129
+ {/* @skip-typecheck: incomplete code sample */}
130
+ ```typescript
131
+ export async function createUser(email: string) {
132
+ return globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//workflows/user.js//createUser")(email); // [!code highlight]
133
+ }
134
+
135
+ export async function handleUserSignup(email: string) {
136
+ const user = await createUser(email);
137
+ return { userId: user.id };
138
+ }
139
+ handleUserSignup.workflowId = "workflow//workflows/user.js//handleUserSignup"; // [!code highlight]
140
+ ```
141
+
142
+ **What happens:**
143
+
144
+ - Step function bodies are **replaced** with calls to `globalThis[Symbol.for("WORKFLOW_USE_STEP")]`
145
+ - Workflow function bodies remain **intact**—they execute deterministically during replay
146
+ - The workflow function gets a `workflowId` property for runtime identification
147
+ - The `"use workflow"` directive is removed
148
+
149
+ **Why this transformation?** When a workflow executes, it needs to replay past steps from the [event log](/docs/how-it-works/event-sourcing) rather than re-executing them. The `WORKFLOW_USE_STEP` symbol is a special runtime hook that:
150
+
151
+ 1. Checks if the step has already been executed (in the event log)
152
+ 2. If yes: Returns the cached result
153
+ 3. If no: Triggers a suspension and enqueues the step for background execution
154
+
155
+ **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.
156
+
157
+ </Tab>
158
+ <Tab value="Client Mode">
159
+
160
+ **Client Mode** transforms workflow functions in your application code to prevent direct execution.
161
+
162
+ **Input:**
163
+
164
+ {/* @skip-typecheck: incomplete code sample */}
165
+ ```typescript
166
+ export async function handleUserSignup(email: string) {
167
+ "use workflow";
168
+ const user = await createUser(email);
169
+ return { userId: user.id };
170
+ }
171
+ ```
172
+
173
+ **Output:**
174
+
175
+ {/* @skip-typecheck: incomplete code sample */}
176
+ ```typescript
177
+ export async function handleUserSignup(email: string) {
178
+ throw new Error("You attempted to execute ..."); // [!code highlight]
179
+ }
180
+ handleUserSignup.workflowId = "workflow//workflows/user.js//handleUserSignup"; // [!code highlight]
181
+ ```
182
+
183
+ **What happens:**
184
+
185
+ - Workflow function bodies are **replaced** with an error throw
186
+ - The `workflowId` property is added (same as workflow mode)
187
+ - Step functions are not transformed in client mode
188
+
189
+ **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.
190
+
191
+ The IDs are generated exactly like in workflow mode to ensure they can be directly referenced at runtime.
192
+
193
+ <Callout type="info">
194
+ **Client mode is optional:** While recommended for better developer experience (automatic IDs and type safety), you can skip client mode and instead:
195
+ - Manually construct workflow IDs using the pattern `workflow//{filepath}//{functionName}`
196
+ - Use the workflow manifest file generated during build to lookup IDs
197
+ - Pass IDs directly to `start()` as strings
198
+
199
+ All framework integrations include client mode as a loader by default.
200
+ </Callout>
201
+
202
+ </Tab>
203
+ </Tabs>
204
+
205
+ ## Generated Files
206
+
207
+ When you build your application, the Workflow DevKit generates three handler files in `.well-known/workflow/v1/`:
208
+
209
+ ### `flow.js`
210
+
211
+ 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`.
212
+
213
+ **How it's structured:**
214
+
215
+ 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:
216
+
217
+ - **Determinism**: The same inputs always produce the same outputs
218
+ - **Side-effect prevention**: Direct access to Node.js APIs, file system, network, etc. is blocked
219
+ - **Sandboxed execution**: Workflow orchestration logic is isolated from the main runtime
220
+
221
+ **Build-time validation:**
222
+
223
+ The workflow mode transformation validates your code during the build:
224
+
225
+ - Catches invalid Node.js API usage (like `fs`, `http`, `child_process`)
226
+ - Prevents imports of modules that would break determinism
227
+
228
+ Most invalid patterns cause **build-time errors**, catching issues before deployment.
229
+
230
+ **What it does:**
231
+
232
+ - Exports a `POST` handler that accepts Web standard `Request` objects
233
+ - Executes bundled workflow code inside a Node.js VM for each request
234
+ - Handles workflow execution, replay, and resumption
235
+ - Returns execution results to the orchestration layer
236
+
237
+ <Callout type="info">
238
+ **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.
239
+ </Callout>
240
+
241
+ ### `step.js`
242
+
243
+ 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`.
244
+
245
+ **What it does:**
246
+
247
+ - Exports a `POST` handler that accepts Web standard `Request` objects
248
+ - Executes individual steps with full runtime access
249
+ - Returns step results to the orchestration layer
250
+
251
+ ### `webhook.js`
252
+
253
+ Contains webhook handling logic for delivering external data to running workflows via [`createWebhook()`](/docs/api-reference/workflow/create-webhook).
254
+
255
+ **What it does:**
256
+
257
+ - Exports a `POST` handler that accepts webhook payloads
258
+ - Validates tokens and routes data to the correct workflow run
259
+ - Resumes workflow execution after webhook delivery
260
+
261
+ **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.
262
+
263
+ ## Why Three Modes?
264
+
265
+ The multi-mode transformation enables the Workflow DevKit's durable execution model:
266
+
267
+ 1. **Step Mode** (required) - Bundles executable step functions that can access the full runtime
268
+ 2. **Workflow Mode** (required) - Creates orchestration logic that can replay from event logs
269
+ 3. **Client Mode** (optional) - Prevents direct execution and enables type-safe workflow references
270
+
271
+ This separation allows:
272
+
273
+ - **Deterministic replay**: Workflows can be safely replayed from event logs without re-executing side effects
274
+ - **Sandboxed orchestration**: Workflow logic runs in a controlled VM without direct runtime access
275
+ - **Stateless execution**: Your compute can scale to zero and resume from any point in the workflow
276
+ - **Type safety**: TypeScript works seamlessly with workflow references (when using client mode)
277
+
278
+ ## Determinism and Replay
279
+
280
+ A key aspect of the transformation is maintaining **deterministic replay** for workflow functions.
281
+
282
+ **Workflow functions must be deterministic:**
283
+
284
+ - Same inputs always produce the same outputs
285
+ - No direct side effects (no API calls, no database writes, no file I/O)
286
+ - Can use seeded random/time APIs provided by the VM (`Math.random()`, `Date.now()`, etc.)
287
+
288
+ 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.
289
+
290
+ **Step functions can be non-deterministic:**
291
+
292
+ - Can make API calls, database queries, etc.
293
+ - Have full access to Node.js runtime and APIs
294
+ - Results are cached in the [event log](/docs/how-it-works/event-sourcing) after first execution
295
+
296
+ Learn more about [Workflows and Steps](/docs/foundations/workflows-and-steps).
297
+
298
+ ## ID Generation
299
+
300
+ The compiler generates stable IDs for workflows and steps based on file paths and function names:
301
+
302
+ **Pattern:** `{type}//{filepath}//{functionName}`
303
+
304
+ **Examples:**
305
+
306
+ - `workflow//workflows/user-signup.js//handleUserSignup`
307
+ - `step//workflows/user-signup.js//createUser`
308
+ - `step//workflows/payments/checkout.ts//processPayment`
309
+
310
+ **Key properties:**
311
+
312
+ - **Stable**: IDs don't change unless you rename files or functions
313
+ - **Unique**: Each workflow/step has a unique identifier
314
+ - **Portable**: Works across different runtimes and deployments
315
+
316
+ <Callout type="info">
317
+ 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.
318
+ </Callout>
319
+
320
+ ## Framework Integration
321
+
322
+ These transformations are framework-agnostic—they output standard JavaScript that works anywhere.
323
+
324
+ **For users**: Your framework handles all transformations automatically. See the [Getting Started](/docs/getting-started) guide for your framework.
325
+
326
+ **For framework authors**: Learn how to integrate these transformations into your framework in [Building Framework Integrations](/docs/how-it-works/framework-integrations).
327
+
328
+ ## Debugging Transformed Code
329
+
330
+ If you need to debug transformation issues, you can inspect the generated files:
331
+
332
+ 1. **Look in `.well-known/workflow/v1/`**: Check the generated `flow.js`, `step.js`,`webhook.js`, and other emitted debug files.
333
+ 2. **Check build logs**: Most frameworks log transformation activity during builds
334
+ 3. **Verify directives**: Ensure `"use workflow"` and `"use step"` are the first statements in functions
335
+ 4. **Check file locations**: Transformations only apply to files in configured source directories