@workflow/core 5.0.0-beta.35 → 5.0.0-beta.37

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 (137) hide show
  1. package/dist/capabilities.d.ts +6 -0
  2. package/dist/capabilities.d.ts.map +1 -1
  3. package/dist/capabilities.js +13 -1
  4. package/dist/classify-error.d.ts.map +1 -1
  5. package/dist/classify-error.js +5 -2
  6. package/dist/create-hook.d.ts +41 -22
  7. package/dist/create-hook.d.ts.map +1 -1
  8. package/dist/create-hook.js +1 -1
  9. package/dist/describe-error.d.ts.map +1 -1
  10. package/dist/describe-error.js +12 -1
  11. package/dist/encryption.d.ts +15 -2
  12. package/dist/encryption.d.ts.map +1 -1
  13. package/dist/encryption.js +28 -7
  14. package/dist/flushable-stream.d.ts +55 -0
  15. package/dist/flushable-stream.d.ts.map +1 -1
  16. package/dist/flushable-stream.js +106 -4
  17. package/dist/global.d.ts +2 -0
  18. package/dist/global.d.ts.map +1 -1
  19. package/dist/global.js +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +2 -2
  23. package/dist/log-format.js +3 -3
  24. package/dist/private.d.ts +82 -50
  25. package/dist/private.d.ts.map +1 -1
  26. package/dist/private.js +149 -22
  27. package/dist/replay-payload-cache.d.ts +57 -0
  28. package/dist/replay-payload-cache.d.ts.map +1 -0
  29. package/dist/replay-payload-cache.js +143 -0
  30. package/dist/runtime/constants.d.ts +11 -0
  31. package/dist/runtime/constants.d.ts.map +1 -1
  32. package/dist/runtime/constants.js +26 -1
  33. package/dist/runtime/count-step-started-events.d.ts +52 -0
  34. package/dist/runtime/count-step-started-events.d.ts.map +1 -0
  35. package/dist/runtime/count-step-started-events.js +72 -0
  36. package/dist/runtime/get-port-lazy.js +4 -4
  37. package/dist/runtime/helpers.d.ts +47 -16
  38. package/dist/runtime/helpers.d.ts.map +1 -1
  39. package/dist/runtime/helpers.js +97 -28
  40. package/dist/runtime/resume-hook.d.ts +6 -3
  41. package/dist/runtime/resume-hook.d.ts.map +1 -1
  42. package/dist/runtime/resume-hook.js +180 -58
  43. package/dist/runtime/run.d.ts.map +1 -1
  44. package/dist/runtime/run.js +3 -3
  45. package/dist/runtime/runs.d.ts.map +1 -1
  46. package/dist/runtime/runs.js +5 -3
  47. package/dist/runtime/start.d.ts.map +1 -1
  48. package/dist/runtime/start.js +65 -18
  49. package/dist/runtime/step-executor.d.ts +48 -8
  50. package/dist/runtime/step-executor.d.ts.map +1 -1
  51. package/dist/runtime/step-executor.js +90 -20
  52. package/dist/runtime/suspension-handler.js +3 -3
  53. package/dist/runtime/world.d.ts +2 -7
  54. package/dist/runtime/world.d.ts.map +1 -1
  55. package/dist/runtime/world.js +3 -10
  56. package/dist/runtime.d.ts +1 -1
  57. package/dist/runtime.d.ts.map +1 -1
  58. package/dist/runtime.js +398 -184
  59. package/dist/sealed-box.d.ts +167 -0
  60. package/dist/sealed-box.d.ts.map +1 -0
  61. package/dist/sealed-box.js +571 -0
  62. package/dist/serialization/client.d.ts +3 -3
  63. package/dist/serialization/client.d.ts.map +1 -1
  64. package/dist/serialization/client.js +1 -1
  65. package/dist/serialization/codec.d.ts +6 -4
  66. package/dist/serialization/codec.d.ts.map +1 -1
  67. package/dist/serialization/codec.js +1 -1
  68. package/dist/serialization/encryption.d.ts +118 -17
  69. package/dist/serialization/encryption.d.ts.map +1 -1
  70. package/dist/serialization/encryption.js +131 -15
  71. package/dist/serialization/step.d.ts +4 -4
  72. package/dist/serialization/step.d.ts.map +1 -1
  73. package/dist/serialization/step.js +2 -2
  74. package/dist/serialization/types.d.ts +20 -0
  75. package/dist/serialization/types.d.ts.map +1 -1
  76. package/dist/serialization/types.js +11 -1
  77. package/dist/serialization-format.d.ts +44 -4
  78. package/dist/serialization-format.d.ts.map +1 -1
  79. package/dist/serialization-format.js +66 -9
  80. package/dist/serialization.d.ts +61 -21
  81. package/dist/serialization.d.ts.map +1 -1
  82. package/dist/serialization.js +556 -200
  83. package/dist/set-attributes.d.ts +2 -7
  84. package/dist/set-attributes.d.ts.map +1 -1
  85. package/dist/set-attributes.js +1 -6
  86. package/dist/step/context-storage.d.ts +2 -2
  87. package/dist/step/context-storage.d.ts.map +1 -1
  88. package/dist/step/context-storage.js +2 -2
  89. package/dist/step/writable-stream.d.ts.map +1 -1
  90. package/dist/step/writable-stream.js +14 -3
  91. package/dist/step.d.ts.map +1 -1
  92. package/dist/step.js +87 -22
  93. package/dist/symbols.d.ts +26 -0
  94. package/dist/symbols.d.ts.map +1 -1
  95. package/dist/symbols.js +27 -1
  96. package/dist/telemetry/semantic-conventions.d.ts +4 -4
  97. package/dist/telemetry/semantic-conventions.js +2 -2
  98. package/dist/version.d.ts +1 -1
  99. package/dist/version.js +2 -2
  100. package/dist/vm/index.d.ts.map +1 -1
  101. package/dist/vm/index.js +76 -9
  102. package/dist/workflow/abort-controller.d.ts +1 -1
  103. package/dist/workflow/abort-controller.d.ts.map +1 -1
  104. package/dist/workflow/abort-controller.js +38 -3
  105. package/dist/workflow/hook.d.ts.map +1 -1
  106. package/dist/workflow/hook.js +85 -30
  107. package/dist/workflow/index.d.ts +1 -1
  108. package/dist/workflow/index.d.ts.map +1 -1
  109. package/dist/workflow/index.js +2 -2
  110. package/dist/workflow/set-attributes.d.ts +0 -9
  111. package/dist/workflow/set-attributes.d.ts.map +1 -1
  112. package/dist/workflow/set-attributes.js +1 -6
  113. package/dist/workflow/sleep.d.ts.map +1 -1
  114. package/dist/workflow/sleep.js +21 -9
  115. package/dist/workflow.d.ts +14 -11
  116. package/dist/workflow.d.ts.map +1 -1
  117. package/dist/workflow.js +37 -92
  118. package/docs/api-reference/create-hook.mdx +43 -2
  119. package/docs/api-reference/define-hook.mdx +26 -24
  120. package/docs/api-reference/fatal-error.mdx +29 -7
  121. package/docs/api-reference/fetch.mdx +3 -4
  122. package/docs/api-reference/set-attributes.mdx +0 -4
  123. package/docs/api-reference/sleep.mdx +1 -1
  124. package/docs/foundations/hooks.mdx +1 -1
  125. package/docs/foundations/idempotency.mdx +16 -9
  126. package/docs/how-it-works/cancellation.mdx +2 -2
  127. package/docs/how-it-works/code-transform.mdx +19 -15
  128. package/docs/how-it-works/encryption.mdx +3 -3
  129. package/docs/how-it-works/event-sourcing.mdx +6 -6
  130. package/docs/how-it-works/framework-integrations.mdx +96 -342
  131. package/package.json +6 -6
  132. package/dist/runtime/step-handler.d.ts +0 -2
  133. package/dist/runtime/step-handler.d.ts.map +0 -1
  134. package/dist/runtime/step-handler.js +0 -839
  135. package/dist/step-hydration-cache.d.ts +0 -148
  136. package/dist/step-hydration-cache.d.ts.map +0 -1
  137. package/dist/step-hydration-cache.js +0 -171
@@ -2,63 +2,53 @@
2
2
  title: Framework Integrations
3
3
  description: Guide for framework authors to integrate Workflow SDK with custom frameworks or runtimes.
4
4
  type: guide
5
- summary: Build a custom framework integration using the Workflow SDK compiler and runtime.
5
+ summary: Build a custom framework integration using the Workflow SDK compiler and combined runtime handler.
6
6
  prerequisites:
7
7
  - /docs/foundations/workflows-and-steps
8
8
  related:
9
- - /docs/deploying/building-a-world
9
+ - /worlds/building-a-world
10
10
  ---
11
11
 
12
12
  <Callout>
13
- **For users:** If you just want to use Workflow SDK 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 SDK with their framework or runtime.
13
+ **For users:** If you just want to use Workflow SDK with an existing framework, see [Getting Started](/docs/getting-started). This page is for framework authors.
14
14
  </Callout>
15
15
 
16
- This guide walks you through building a framework integration for Workflow SDK using Bun as a concrete example. The same principles apply to any JavaScript runtime (Node.js, Deno, Cloudflare Workers, etc.).
16
+ This guide uses Bun as a concrete example, but the same build and routing model applies to other JavaScript frameworks and runtimes.
17
17
 
18
18
  <Callout type="info">
19
- **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 SDK.
19
+ Read [How the Directives Work](/docs/how-it-works/code-transform) first if you are not familiar with the three compiler modes.
20
20
  </Callout>
21
21
 
22
- ## What You'll Build
22
+ ## Integration shape
23
23
 
24
- A framework integration has two main components:
24
+ A framework integration has two responsibilities:
25
25
 
26
- 1. **Build-time**: Generate workflow handler files (`flow.js`, `step.js`, `webhook.js`)
27
- 2. **Runtime**: Expose these handlers as HTTP endpoints in your application server
26
+ 1. **Build time:** transform workflow and step code, register the steps, and generate the combined flow handler.
27
+ 2. **Runtime:** expose the generated flow and webhook handlers as HTTP routes.
28
28
 
29
29
  ```mermaid
30
30
  flowchart TD
31
- A["Source Code<br/>'use workflow'"] --> B["Workflow Builder"]
32
- B --> C["SWC Transform"]
33
- C --> D["Step Mode"]
34
- C --> E["Workflow Mode"]
35
- C --> F["Client Mode"]
36
- D --> G["Generated Handlers<br/>step.js"]
37
- E --> H["Generated Handlers<br/>flow.js"]
38
- B --> L["Generated Handlers<br/>webhook.js"]
39
- F --> I["Used by framework loader"]
40
- G --> J["HTTP Server<br/>(Your Runtime)"]
41
- H --> J
42
- L --> J
31
+ A["Source code<br/>'use workflow' / 'use step'"] --> B["Workflow builder"]
32
+ B --> C["Workflow-mode bundle"]
33
+ B --> D["Step registration bundle"]
34
+ D --> E["Combined flow handler"]
35
+ C --> E
36
+ B --> F["Webhook handler"]
37
+ E --> G["POST /.well-known/workflow/v1/flow"]
38
+ F --> H["/.well-known/workflow/v1/webhook/:token"]
43
39
 
44
40
  style B fill:#a78bfa,stroke:#8b5cf6,color:#000
45
- style I fill:#a78bfa,stroke:#8b5cf6,color:#000
46
- style J fill:#a78bfa,stroke:#8b5cf6,color:#000
41
+ style G fill:#a78bfa,stroke:#8b5cf6,color:#000
42
+ style H fill:#a78bfa,stroke:#8b5cf6,color:#000
47
43
  ```
48
44
 
49
- The purple boxes are what you implement—everything else is provided by Workflow SDK.
45
+ Steps do not have their own HTTP route. A queued step invocation contains `stepId` and `stepName` and is delivered to the flow handler on the workflow queue. The handler executes the registered step in the full host runtime, then replays the workflow in its sandboxed VM.
50
46
 
51
- ## Example: Bun Integration
47
+ ## Example: Bun integration
52
48
 
53
- 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).
49
+ ### 1. Generate the bundles
54
50
 
55
- <Callout type="info">
56
- 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).
57
- </Callout>
58
-
59
- ### Step 1: Generate Handler Files
60
-
61
- Use the `workflow` CLI to generate the handler bundles. The CLI scans your `workflows/` directory and creates `flow.js`, `step.js`, and `webhook.js`.
51
+ The standalone CLI scans `workflows/` and creates the combined flow handler, an internal step registration module, and the webhook handler.
62
52
 
63
53
  ```json title="package.json"
64
54
  {
@@ -68,21 +58,19 @@ Use the `workflow` CLI to generate the handler bundles. The CLI scans your `work
68
58
  }
69
59
  ```
70
60
 
71
- <Callout>
72
- **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.
73
- </Callout>
74
-
75
- **What gets generated:**
61
+ The default output is:
76
62
 
77
- - `/.well-known/workflow/v1/flow.js` - Handles workflow execution (workflow mode transform)
78
- - `/.well-known/workflow/v1/step.js` - Handles step execution (step mode transform)
79
- - `/.well-known/workflow/v1/webhook.js` - Handles webhook delivery
63
+ - `/.well-known/workflow/v1/flow.mjs` the combined workflow and step queue consumer
64
+ - `/.well-known/workflow/v1/__step_registrations.mjs` an internal module imported by `flow.mjs`; do not route to it
65
+ - `/.well-known/workflow/v1/webhook.mjs` webhook delivery
80
66
 
81
- Each file exports a `POST` function that accepts Web standard `Request` objects.
67
+ <Callout>
68
+ Production integrations should extend `BaseBuilder` from `@workflow/builders` so they can participate in the framework's build, watch, and routing lifecycle.
69
+ </Callout>
82
70
 
83
- ### Step 2: Add Client Mode Transform (Optional)
71
+ ### 2. Add the client transform
84
72
 
85
- Client mode transforms your application code to provide better DX. Add a Bun plugin to apply this transformation at runtime:
73
+ Client mode gives application code the workflow IDs used by `start()` and prevents accidental direct workflow execution.
86
74
 
87
75
  {/* @skip-typecheck: incomplete code sample */}
88
76
  ```typescript title="workflow-plugin.ts" lineNumbers
@@ -94,8 +82,6 @@ plugin({
94
82
  setup(build) {
95
83
  build.onLoad({ filter: /\.(ts|tsx|js|jsx)$/ }, async (args) => {
96
84
  const source = await Bun.file(args.path).text();
97
-
98
- // Optimization: Skip files that do not have any directives
99
85
  if (!source.match(/(use step|use workflow)/)) {
100
86
  return { contents: source };
101
87
  }
@@ -105,7 +91,7 @@ plugin({
105
91
  jsc: {
106
92
  experimental: {
107
93
  plugins: [
108
- [require.resolve("@workflow/swc-plugin"), { mode: "client" }], // [!code highlight]
94
+ [require.resolve("@workflow/swc-plugin"), { mode: "client" }],
109
95
  ],
110
96
  },
111
97
  },
@@ -123,23 +109,14 @@ Activate the plugin in `bunfig.toml`:
123
109
  preload = ["./workflow-plugin.ts"]
124
110
  ```
125
111
 
126
- **What this does:**
127
-
128
- - Attaches workflow IDs to functions for use with `start()`
129
- - Provides TypeScript type safety
130
- - Prevents accidental direct execution of workflows
131
-
132
- **Why optional?** Without client mode, you can still use workflows by manually constructing IDs or referencing the build manifest.
133
-
134
- ### Step 3: Expose HTTP Endpoints
112
+ ### 3. Expose the HTTP routes
135
113
 
136
- Wire up the generated handlers to HTTP endpoints using `Bun.serve()`:
114
+ Only the combined flow handler and webhook handler are routable:
137
115
 
138
116
  {/* @skip-typecheck: incomplete code sample */}
139
117
  ```typescript title="server.ts" lineNumbers
140
- import flow from "./.well-known/workflow/v1/flow.js";
141
- import step from "./.well-known/workflow/v1/step.js";
142
- import * as webhook from "./.well-known/workflow/v1/webhook.js";
118
+ import * as flow from "./.well-known/workflow/v1/flow.mjs";
119
+ import * as webhook from "./.well-known/workflow/v1/webhook.mjs";
143
120
 
144
121
  import { start } from "workflow/api";
145
122
  import { handleUserSignup } from "./workflows/user-signup.js";
@@ -148,23 +125,14 @@ const server = Bun.serve({
148
125
  port: process.env.PORT,
149
126
  routes: {
150
127
  "/.well-known/workflow/v1/flow": {
151
- POST: (req) => flow.POST(req),
128
+ POST: flow.POST,
152
129
  },
153
- "/.well-known/workflow/v1/step": {
154
- POST: (req) => step.POST(req),
155
- },
156
- // webhook exports handlers for GET, POST, DELETE, etc.
157
130
  "/.well-known/workflow/v1/webhook/:token": webhook,
158
-
159
- // Example: Start a workflow
160
131
  "/": {
161
- GET: async (req) => {
132
+ GET: async () => {
162
133
  const email = `test-${crypto.randomUUID()}@test.com`;
163
134
  const run = await start(handleUserSignup, [email]);
164
- return Response.json({
165
- message: "User signup workflow started",
166
- runId: run.runId,
167
- });
135
+ return Response.json({ runId: run.runId });
168
136
  },
169
137
  },
170
138
  },
@@ -173,222 +141,90 @@ const server = Bun.serve({
173
141
  console.log(`Server listening on http://localhost:${server.port}`);
174
142
  ```
175
143
 
176
- **That's it!** Your Bun integration is complete.
177
-
178
- ## Understanding the Endpoints
179
-
180
- Your integration must expose three HTTP endpoints. The generated handlers manage all protocol details—you just route requests.
144
+ ## Runtime routes
181
145
 
182
- ### Workflow Endpoint
146
+ ### Combined flow endpoint
183
147
 
184
148
  **Route:** `POST /.well-known/workflow/v1/flow`
185
149
 
186
- Executes workflow orchestration logic. The workflow function is "rendered" multiple times during execution—each time it progresses until it encounters the next step.
150
+ The handler consumes every workflow queue message. Depending on the payload and event log, it can:
187
151
 
188
- **Called when:**
152
+ - start or replay workflow orchestration in the sandboxed VM;
153
+ - execute a queued step in the host runtime;
154
+ - continue replay inline after a step completes;
155
+ - resume a run after a hook, webhook, sleep, retry, or recovery event.
189
156
 
190
- - Starting a new workflow
191
- - Resuming after a step completes
192
- - Resuming after a webhook or hook triggers
193
- - Recovering from failures
157
+ ### Webhook endpoint
194
158
 
195
- ### Step Endpoint
159
+ **Route:** `/.well-known/workflow/v1/webhook/:token`
196
160
 
197
- **Route:** `POST /.well-known/workflow/v1/step`
161
+ This handler delivers data to [`createWebhook()`](/docs/api-reference/workflow/create-webhook). Its generated file structure varies by framework; for example, Next.js uses `webhook/[token]/route.js`.
198
162
 
199
- 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.).
163
+ ## Building with `BaseBuilder`
200
164
 
201
- ### Webhook Endpoint
165
+ Use `createCombinedBundle()` so the flow bundle imports the generated step registrations. Calling `createWorkflowsBundle()` and `createStepsBundle()` independently does not create a complete runtime route.
202
166
 
203
- **Route:** `POST /.well-known/workflow/v1/webhook/:token`
204
-
205
- 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.
206
-
207
- <Callout type="info">
208
- 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.
209
- </Callout>
210
-
211
- ## Adapting to Other Frameworks
212
-
213
- The Bun example demonstrates the core pattern. To adapt for your framework:
214
-
215
- ### Build-Time
216
-
217
- **Option 1: Use the CLI** (simplest)
218
-
219
- ```bash
220
- workflow build
221
- ```
222
-
223
- This will default to scanning the `./workflows` top-level directory for workflow files, and will output bundled files directly into your working directory.
224
-
225
- **Option 2: Extend `BaseBuilder`** (recommended)
226
-
227
- {/* @skip-typecheck: @workflow/cli internal module */}
228
- ```typescript lineNumbers
229
- import { BaseBuilder } from "@workflow/cli/dist/lib/builders/base-builder";
167
+ {/* @skip-typecheck: incomplete framework adapter */}
168
+ ```typescript title="my-framework-builder.ts" lineNumbers
169
+ import { join } from "node:path";
170
+ import { BaseBuilder } from "@workflow/builders";
230
171
 
231
172
  class MyFrameworkBuilder extends BaseBuilder {
232
- constructor(options) {
233
- super({
234
- dirs: ["workflows"],
235
- workingDir: options.rootDir,
236
- watch: options.dev,
237
- });
238
- }
239
-
240
173
  override async build(): Promise<void> {
241
174
  const inputFiles = await this.getInputFiles();
175
+ const tsconfigPath = await this.findTsConfigPath();
176
+ const outputDir = join(this.config.workingDir, ".workflow");
242
177
 
243
- await this.createWorkflowsBundle({
244
- outfile: "/path/to/.well-known/workflow/v1/flow.js",
245
- format: "esm",
178
+ await this.createCombinedBundle({
246
179
  inputFiles,
247
- });
248
-
249
- await this.createStepsBundle({
250
- outfile: "/path/to/.well-known/workflow/v1/step.js",
180
+ stepsOutfile: join(outputDir, "__step_registrations.mjs"),
181
+ flowOutfile: join(outputDir, "flow.mjs"),
182
+ tsconfigPath,
251
183
  format: "esm",
252
- inputFiles,
253
184
  });
254
185
 
255
186
  await this.createWebhookBundle({
256
- outfile: "/path/to/.well-known/workflow/v1/webhook.js",
187
+ outfile: join(outputDir, "webhook.mjs"),
257
188
  });
258
189
  }
259
190
  }
260
191
  ```
261
192
 
262
- If your framework supports virtual server routes and dev mode watching, make sure to adapt accordingly. Please open a PR to the Workflow SDK if the base builder class is missing necessary functionality.
263
-
264
- ### Monorepos and Workspace Imports
265
-
266
- If your framework integration lives in a subdirectory and your workflows import code from sibling workspace packages, pass `projectRoot` to `BaseBuilder`. Use the smallest directory that contains every workspace package imported by your workflows.
267
-
268
- {/* @skip-typecheck: @workflow/cli internal module */}
269
- ```typescript title="my-framework-builder.ts" lineNumbers
270
- import { BaseBuilder } from "@workflow/cli/dist/lib/builders/base-builder";
193
+ If workflows import sibling workspace packages, set `projectRoot` to the smallest directory containing all imported packages.
271
194
 
272
- class MyFrameworkBuilder extends BaseBuilder {
273
- constructor(options: {
274
- rootDir: string;
275
- workspaceRoot?: string;
276
- dev: boolean;
277
- }) {
278
- super({
279
- dirs: ["workflows"],
280
- workingDir: options.rootDir,
281
- projectRoot: options.workspaceRoot ?? options.rootDir, // [!code highlight]
282
- watch: options.dev,
283
- });
284
- }
285
-
286
- override async build(): Promise<void> {
287
- const inputFiles = await this.getInputFiles();
288
- // ...
289
- }
290
- }
291
- ```
292
-
293
- Hook into your framework's build:
294
-
295
- {/* @skip-typecheck: incomplete code sample */}
296
- ```typescript title="pseudocode.ts" lineNumbers
297
- framework.hooks.hook("build:before", async () => {
298
- await new MyFrameworkBuilder(framework).build();
195
+ {/* @skip-typecheck: partial constructor configuration */}
196
+ ```typescript
197
+ super({
198
+ dirs: ["workflows"],
199
+ workingDir: options.rootDir,
200
+ projectRoot: options.workspaceRoot ?? options.rootDir,
201
+ watch: options.dev,
299
202
  });
300
203
  ```
301
204
 
302
- ### Runtime (Client Mode)
303
-
304
- Add a loader/plugin for your bundler:
305
-
306
- **Rollup/Vite:**
307
-
308
- ```typescript lineNumbers
309
- export function workflowPlugin() {
310
- return {
311
- name: "workflow-client-transform",
312
- async transform(code, id) {
313
- if (!code.match(/(use step|use workflow)/)) return null;
205
+ Your integration can expose physical files, virtual modules, or framework-native routes. In every case, the step registration module is a dependency of the flow handler, not a route of its own.
314
206
 
315
- const result = await transform(code, {
316
- filename: id,
317
- jsc: {
318
- experimental: {
319
- plugins: [[require.resolve("@workflow/swc-plugin"), { mode: "client" }]], // [!code highlight]
320
- },
321
- },
322
- });
207
+ ## Vercel queue configuration
323
208
 
324
- return { code: result.code, map: result.map };
325
- },
326
- };
327
- }
328
- ```
209
+ On Vercel, configure the flow function as the sole queue consumer. `getWorkflowQueueTrigger()` handles the optional queue namespace and `WORKFLOW_SEQUENTIAL_REPLAYS=1` behavior.
329
210
 
330
- **Webpack:**
211
+ ```typescript
212
+ import { getWorkflowQueueTrigger } from "@workflow/builders";
331
213
 
332
- ```javascript lineNumbers
333
- module.exports = {
334
- module: {
335
- rules: [
336
- {
337
- test: /\.(ts|tsx|js|jsx)$/,
338
- use: "workflow-client-loader", // Similar implementation
339
- },
340
- ],
341
- },
214
+ const flowConfig = {
215
+ maxDuration: "max",
216
+ experimentalTriggers: [getWorkflowQueueTrigger()],
342
217
  };
343
218
  ```
344
219
 
345
- ### HTTP Server
346
-
347
- Route the three endpoints to the generated handlers. The exact implementation depends on your framework's routing API.
348
-
349
- In the bun example above, we left routing to the user. Essentially, the user has to serve routes like this:
220
+ The generated trigger listens to one topic family:
350
221
 
351
- {/* @skip-typecheck: incomplete code sample */}
352
- ```typescript title="server.ts" lineNumbers
353
- import flow from "./.well-known/workflow/v1/flow.js";
354
- import step from "./.well-known/workflow/v1/step.js";
355
- import * as webhook from "./.well-known/workflow/v1/webhook.js";
356
-
357
- // Expose the 3 generated routes
358
- const server = Bun.serve({
359
- routes: {
360
- "/.well-known/workflow/v1/flow": {
361
- POST: (req) => flow.POST(req),
362
- },
363
- "/.well-known/workflow/v1/step": {
364
- POST: (req) => step.POST(req),
365
- },
366
- // webhook exports handlers for GET, POST, DELETE, etc.
367
- "/.well-known/workflow/v1/webhook/:token": webhook,
368
- },
369
- });
370
- ```
371
-
372
- Production framework integrations should handle this routing in the plugin instead of leaving it to the user, and this depends on each framework's unique implementaiton.
373
- Check the Workflow SDK source code for examples of production framework implementations.
374
- In the future, the Workflow SDK will emit more routes under the `.well-known/workflow` namespace.
375
-
376
- ## Security
377
-
378
- The workflow and step handler endpoints are invoked by the world's queuing infrastructure, not by end users. How they're secured depends on which world you're deploying to.
379
-
380
- ### Vercel (`@workflow/world-vercel`)
381
-
382
- On Vercel, workflow handler functions are not accessible through public endpoints. Handlers use the same [consumer function security](https://vercel.com/docs/queues/concepts#consumer-function-security) mechanism that secures [Vercel Queues](https://vercel.com/docs/queues) consumers.
383
-
384
- During the build step, the Workflow SDK automatically configures each handler as a queue consumer by writing `experimentalTriggers` to the function's `.vc-config.json`:
385
-
386
- ```json title=".vc-config.json (generated by Workflow SDK)"
222
+ ```json title=".vc-config.json (excerpt)"
387
223
  {
388
224
  "experimentalTriggers": [
389
225
  {
390
226
  "type": "queue/v2beta",
391
- "topic": "__wkf_step_*",
227
+ "topic": "__wkf_workflow_*",
392
228
  "consumer": "default",
393
229
  "retryAfterSeconds": 5,
394
230
  "initialDelaySeconds": 0
@@ -397,110 +233,28 @@ During the build step, the Workflow SDK automatically configures each handler as
397
233
  }
398
234
  ```
399
235
 
236
+ Both orchestration messages and step messages use this topic family. A step message is distinguished by its payload, not by a second topic or function.
400
237
 
401
- Two queue topics are created per deployment:
402
-
403
- | Handler | Topic | Description |
404
- | --- | --- | --- |
405
- | `step.func` | `__wkf_step_*` | Step execution (long-running, `maxDuration: max`) |
406
- | `flow.func` | `__wkf_workflow_*` | Workflow orchestration (`maxDuration: 60`) |
407
-
408
- If you're building a framework integration that targets Vercel, you should write these triggers into the `.vc-config.json` for each generated function. Use `getWorkflowQueueTrigger()` for flow functions so `WORKFLOW_SEQUENTIAL_REPLAYS=1` is reflected in the generated trigger configuration (it also accepts a `namespace` option, matching `createWorkflowQueueTrigger`); `STEP_QUEUE_TRIGGER` is exported for step functions:
409
-
410
- ```typescript
411
- import { getWorkflowQueueTrigger, STEP_QUEUE_TRIGGER } from "@workflow/builders";
412
-
413
- const flowTriggers = [getWorkflowQueueTrigger()];
414
- const stepTriggers = [STEP_QUEUE_TRIGGER];
415
- ```
416
-
417
- If your integration constructs the flow trigger object itself instead of calling `getWorkflowQueueTrigger()`, it must add `maxConcurrency: 1` to that trigger when sequential replays are enabled at build time (`WORKFLOW_SEQUENTIAL_REPLAYS=1` — the exported `isSequentialReplaysEnabled()` helper implements this check). The runtime half of the feature (per-run queue topics) activates from the environment variable alone — without the trigger half, those per-run topics are not serialized and the setting only adds queue-topic cardinality.
418
-
238
+ If you construct the trigger yourself, add `maxConcurrency: 1` when `WORKFLOW_SEQUENTIAL_REPLAYS=1`. The exported `isSequentialReplaysEnabled()` helper implements that build-time check.
419
239
 
420
- ### Custom implementations
421
-
422
- For self-hosted or non-Vercel deployments, you are responsible for securing the handler endpoints:
423
-
424
- - **Framework middleware** — Add authentication (API keys, JWT, OIDC) in front of the `/.well-known/workflow/v1/*` routes
425
- - **Network-level security** — Deploy handlers behind a VPC, private network, or firewall rules so only your queue infrastructure can reach them
426
- - **Rate limiting** — Add request validation and rate limiting to prevent abuse
427
-
428
- Learn more about [building custom Worlds](/docs/deploying/building-a-world).
429
-
430
- ## Testing Your Integration
431
-
432
- ### 1. Test Build Output
433
-
434
- Create a test workflow:
435
-
436
- ```typescript title="workflows/test.ts" lineNumbers
437
- import { sleep, createWebhook } from "workflow";
438
-
439
- export async function handleUserSignup(email: string) {
440
- "use workflow";
441
-
442
- const user = await createUser(email);
443
- await sendWelcomeEmail(user);
444
-
445
- await sleep("5s");
446
-
447
- const webhook = createWebhook();
448
- await sendOnboardingEmail(user, webhook.url);
449
-
450
- await webhook;
451
- console.log("Webhook Resolved");
452
-
453
- return { userId: user.id, status: "onboarded" };
454
- }
455
-
456
- async function createUser(email: string) {
457
- "use step";
458
-
459
- console.log(`Creating a new user with email: ${email}`);
460
-
461
- return { id: crypto.randomUUID(), email };
462
- }
463
-
464
- async function sendWelcomeEmail(user: { id: string; email: string }) {
465
- "use step";
466
-
467
- console.log(`Sending welcome email to user: ${user.id}`);
468
- }
469
-
470
- async function sendOnboardingEmail(user: { id: string; email: string }, callback: string) {
471
- "use step";
472
-
473
- console.log(`Sending onboarding email to user: ${user.id}`);
474
-
475
- console.log(`Click this link to resolve the webhook: ${callback}`);
476
- }
240
+ ## Security
477
241
 
478
- ```
242
+ On Vercel, the flow handler uses [queue consumer security](https://vercel.com/docs/queues/concepts#consumer-function-security). For self-hosted worlds, protect the flow and webhook routes with the authentication, network controls, and rate limits appropriate to your queue transport.
479
243
 
480
- Run your build and verify:
244
+ ## Testing an integration
481
245
 
482
- - `.well-known/workflow/v1/flow.js` exists
483
- - `.well-known/workflow/v1/step.js` exists
484
- - `.well-known/workflow/v1/webhook.js` exists
246
+ After a build, verify that:
485
247
 
486
- ### 2. Test HTTP Endpoints
248
+ - the flow handler exists and imports or embeds the step registration bundle;
249
+ - the webhook handler exists;
250
+ - no step HTTP route or step queue trigger is generated;
251
+ - the flow function has the workflow queue trigger and the required maximum duration;
252
+ - starting a workflow executes a real step and resumes the workflow.
487
253
 
488
- Start your server and verify routes respond:
254
+ The flow route exposes a lightweight direct health mode:
489
255
 
490
256
  ```bash
491
- curl -X POST http://localhost:3000/.well-known/workflow/v1/flow
492
- curl -X POST http://localhost:3000/.well-known/workflow/v1/step
493
- curl -X POST http://localhost:3000/.well-known/workflow/v1/webhook/test
257
+ curl -X POST "http://localhost:3000/.well-known/workflow/v1/flow?__health"
494
258
  ```
495
259
 
496
- (Should respond but not trigger meaningful code without authentication/proper workflow run)
497
-
498
- ### 3. Run a Workflow End-to-End
499
-
500
- ```typescript
501
- import { start } from "workflow/api";
502
- import { handleUserSignup } from "./workflows/test";
503
-
504
- const run = await start(handleUserSignup, ["test@example.com"]);
505
- console.log("Workflow started:", run.runId);
506
- ```
260
+ For an end-to-end queue check, use [`healthCheck()`](/docs/api-reference/workflow-runtime/health-check).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workflow/core",
3
- "version": "5.0.0-beta.35",
3
+ "version": "5.0.0-beta.37",
4
4
  "description": "Core runtime and engine for Workflow SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -98,12 +98,12 @@
98
98
  "semver": "7.7.4",
99
99
  "ulid": "~3.0.1",
100
100
  "zod": "~4.3.6",
101
- "@workflow/errors": "5.0.0-beta.11",
101
+ "@workflow/errors": "5.0.0-beta.13",
102
102
  "@workflow/serde": "5.0.0-beta.2",
103
- "@workflow/utils": "5.0.0-beta.6",
104
- "@workflow/world": "5.0.0-beta.21",
105
- "@workflow/world-local": "5.0.0-beta.29",
106
- "@workflow/world-vercel": "5.0.0-beta.31"
103
+ "@workflow/utils": "5.0.0-beta.7",
104
+ "@workflow/world": "5.0.0-beta.23",
105
+ "@workflow/world-local": "5.0.0-beta.31",
106
+ "@workflow/world-vercel": "5.0.0-beta.33"
107
107
  },
108
108
  "devDependencies": {
109
109
  "@opentelemetry/api": "1.9.0",
@@ -1,2 +0,0 @@
1
- export declare const stepEntrypoint: (req: Request) => Promise<Response>;
2
- //# sourceMappingURL=step-handler.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"step-handler.d.ts","sourceRoot":"","sources":["../../src/runtime/step-handler.ts"],"names":[],"mappings":"AAqpCA,eAAO,MAAM,cAAc,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAM3D,CAAC"}