workflow 4.2.0-beta.70 → 4.2.0-beta.72
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.
- package/dist/api.d.ts +1 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +1 -1
- package/dist/internal/builtins.d.ts +3 -3
- package/dist/internal/builtins.d.ts.map +1 -1
- package/dist/internal/builtins.js +7 -7
- package/dist/internal/errors.d.ts +1 -1
- package/dist/internal/errors.d.ts.map +1 -1
- package/dist/internal/errors.js +2 -2
- package/dist/observability.d.ts +20 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +20 -0
- package/docs/ai/chat-session-modeling.mdx +4 -4
- package/docs/ai/defining-tools.mdx +7 -1
- package/docs/ai/index.mdx +8 -5
- package/docs/ai/message-queueing.mdx +8 -6
- package/docs/ai/resumable-streams.mdx +37 -4
- package/docs/ai/sleep-and-delays.mdx +2 -0
- package/docs/api-reference/index.mdx +3 -0
- package/docs/api-reference/meta.json +1 -1
- package/docs/api-reference/workflow/define-hook.mdx +2 -0
- package/docs/api-reference/workflow/get-writable.mdx +1 -0
- package/docs/api-reference/workflow-ai/durable-agent.mdx +7 -5
- package/docs/api-reference/workflow-ai/workflow-chat-transport.mdx +2 -0
- package/docs/api-reference/workflow-api/get-run.mdx +14 -0
- package/docs/api-reference/workflow-api/get-world.mdx +105 -0
- package/docs/api-reference/workflow-api/start.mdx +24 -0
- package/docs/api-reference/workflow-errors/entity-conflict-error.mdx +60 -0
- package/docs/api-reference/workflow-errors/hook-not-found-error.mdx +90 -0
- package/docs/api-reference/workflow-errors/meta.json +16 -0
- package/docs/api-reference/workflow-errors/run-expired-error.mdx +58 -0
- package/docs/api-reference/workflow-errors/step-not-registered-error.mdx +56 -0
- package/docs/api-reference/workflow-errors/throttle-error.mdx +62 -0
- package/docs/api-reference/workflow-errors/too-early-error.mdx +62 -0
- package/docs/api-reference/workflow-errors/workflow-not-registered-error.mdx +57 -0
- package/docs/api-reference/workflow-errors/workflow-run-cancelled-error.mdx +56 -0
- package/docs/api-reference/workflow-errors/workflow-run-failed-error.mdx +62 -0
- package/docs/api-reference/workflow-errors/workflow-run-not-found-error.mdx +56 -0
- package/docs/api-reference/workflow-errors/workflow-world-error.mdx +79 -0
- package/docs/api-reference/workflow-serde/index.mdx +52 -0
- package/docs/api-reference/workflow-serde/meta.json +3 -0
- package/docs/api-reference/workflow-serde/workflow-deserialize.mdx +70 -0
- package/docs/api-reference/workflow-serde/workflow-serialize.mdx +75 -0
- package/docs/changelog/index.mdx +15 -0
- package/docs/changelog/meta.json +5 -0
- package/docs/deploying/building-a-world.mdx +20 -0
- package/docs/deploying/world/vercel-world.mdx +2 -1
- package/docs/errors/hook-conflict.mdx +9 -3
- package/docs/errors/index.mdx +6 -0
- package/docs/errors/step-not-registered.mdx +66 -0
- package/docs/errors/webhook-invalid-respond-with-value.mdx +10 -0
- package/docs/errors/webhook-response-not-sent.mdx +8 -0
- package/docs/errors/workflow-not-registered.mdx +64 -0
- package/docs/foundations/common-patterns.mdx +4 -0
- package/docs/foundations/errors-and-retries.mdx +29 -0
- package/docs/foundations/serialization.mdx +211 -0
- package/docs/foundations/streaming.mdx +22 -0
- package/docs/getting-started/index.mdx +3 -3
- package/docs/getting-started/meta.json +15 -0
- package/docs/getting-started/nestjs.mdx +4 -8
- package/docs/how-it-works/encryption.mdx +93 -0
- package/docs/how-it-works/meta.json +2 -1
- package/docs/observability/index.mdx +3 -0
- package/package.json +18 -12
|
@@ -56,6 +56,10 @@ These types have special handling and are explained in detail in the sections be
|
|
|
56
56
|
- `ReadableStream<Serializable>`
|
|
57
57
|
- `WritableStream<Serializable>`
|
|
58
58
|
|
|
59
|
+
**Custom Classes:**
|
|
60
|
+
|
|
61
|
+
- Class instances that implement [`WORKFLOW_SERIALIZE` and `WORKFLOW_DESERIALIZE`](#custom-class-serialization)
|
|
62
|
+
|
|
59
63
|
## Streaming
|
|
60
64
|
|
|
61
65
|
`ReadableStream` and `WritableStream` are supported as serializable types with special handling. These streams can be passed between workflow and step functions while maintaining their streaming capabilities.
|
|
@@ -121,6 +125,213 @@ export async function fetch(...args: Parameters<typeof globalThis.fetch>) {
|
|
|
121
125
|
|
|
122
126
|
This allows you to make HTTP requests directly in workflow functions while maintaining deterministic replay behavior through automatic caching.
|
|
123
127
|
|
|
128
|
+
## Custom Class Serialization
|
|
129
|
+
|
|
130
|
+
By default, custom class instances cannot be serialized because the serialization system doesn't know how to reconstruct them. You can make your classes serializable by implementing two static methods using special symbols from the `@workflow/serde` package.
|
|
131
|
+
|
|
132
|
+
### Basic Example
|
|
133
|
+
|
|
134
|
+
{/* @expect-error:2351 */}
|
|
135
|
+
|
|
136
|
+
```typescript title="workflows/custom-class.ts" lineNumbers
|
|
137
|
+
import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde"; // [!code highlight]
|
|
138
|
+
|
|
139
|
+
class Point {
|
|
140
|
+
constructor(
|
|
141
|
+
public x: number,
|
|
142
|
+
public y: number
|
|
143
|
+
) {}
|
|
144
|
+
|
|
145
|
+
// Define how to serialize an instance to plain data
|
|
146
|
+
static [WORKFLOW_SERIALIZE](instance: Point) { // [!code highlight]
|
|
147
|
+
return { x: instance.x, y: instance.y }; // [!code highlight]
|
|
148
|
+
} // [!code highlight]
|
|
149
|
+
|
|
150
|
+
// Define how to reconstruct an instance from plain data
|
|
151
|
+
static [WORKFLOW_DESERIALIZE](data: { x: number; y: number }) { // [!code highlight]
|
|
152
|
+
return new Point(data.x, data.y); // [!code highlight]
|
|
153
|
+
} // [!code highlight]
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Once you've implemented these methods, instances of your class can be passed between workflow and step functions:
|
|
158
|
+
|
|
159
|
+
{/* @expect-error:2351 */}
|
|
160
|
+
|
|
161
|
+
```typescript title="workflows/geometry.ts" lineNumbers
|
|
162
|
+
import { Point } from "./custom-class";
|
|
163
|
+
|
|
164
|
+
export async function geometryWorkflow() {
|
|
165
|
+
"use workflow";
|
|
166
|
+
|
|
167
|
+
const point = new Point(10, 20);
|
|
168
|
+
// Point is serialized automatically
|
|
169
|
+
const doubled = await doublePoint(point); // [!code highlight]
|
|
170
|
+
|
|
171
|
+
console.log(doubled.x, doubled.y); // 20, 40
|
|
172
|
+
return doubled;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function doublePoint(point: Point) {
|
|
176
|
+
"use step";
|
|
177
|
+
// Returns a new Point instance
|
|
178
|
+
return new Point(point.x * 2, point.y * 2); // [!code highlight]
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### How It Works
|
|
183
|
+
|
|
184
|
+
1. **`WORKFLOW_SERIALIZE`**: A static method that receives a class instance and returns serializable data (primitives, plain objects, arrays, etc.)
|
|
185
|
+
|
|
186
|
+
2. **`WORKFLOW_DESERIALIZE`**: A static method that receives the serialized data and returns a new class instance
|
|
187
|
+
|
|
188
|
+
3. **Automatic Registration**: The SWC compiler plugin automatically detects classes that implement these symbols and registers them for serialization
|
|
189
|
+
|
|
190
|
+
### Requirements
|
|
191
|
+
|
|
192
|
+
<Callout type="warn">
|
|
193
|
+
Both methods must be implemented as **static** methods on the class. Instance methods are not supported.
|
|
194
|
+
</Callout>
|
|
195
|
+
|
|
196
|
+
- The data returned by `WORKFLOW_SERIALIZE` must itself be serializable (see [Supported Serializable Types](#supported-serializable-types))
|
|
197
|
+
- Both symbols must be implemented together - a class with only one will not be serializable
|
|
198
|
+
|
|
199
|
+
<Callout type="warn">
|
|
200
|
+
The `WORKFLOW_SERIALIZE` and `WORKFLOW_DESERIALIZE` methods run inside the workflow context and are subject to the same constraints as `"use workflow"` functions. This means:
|
|
201
|
+
- No Node.js-specific APIs (like `fs`, `path`, `crypto`, etc.)
|
|
202
|
+
- No non-deterministic operations (like `Math.random()` or `Date.now()`)
|
|
203
|
+
- No external network calls
|
|
204
|
+
|
|
205
|
+
Keep these methods simple and focused on data transformation only.
|
|
206
|
+
</Callout>
|
|
207
|
+
|
|
208
|
+
### Complex Example
|
|
209
|
+
|
|
210
|
+
A class that uses Node.js APIs or other non-deterministic operations cannot be used directly inside a workflow function. The recommended approach is to make the class workflow-compatible by adding `"use step"` to its instance methods. The SWC compiler will strip the method bodies from the workflow bundle and replace them with proxy functions that invoke the method as a step — with full Node.js runtime access. The `this` context (the class instance) is automatically serialized and deserialized across the workflow/step boundary.
|
|
211
|
+
|
|
212
|
+
This requires the class to implement `WORKFLOW_SERIALIZE` and `WORKFLOW_DESERIALIZE`, so that the instance can be passed to the step execution context.
|
|
213
|
+
|
|
214
|
+
{/* @expect-error:2351 */}
|
|
215
|
+
|
|
216
|
+
```typescript title="workflows/order.ts" lineNumbers
|
|
217
|
+
import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde"; // [!code highlight]
|
|
218
|
+
import { db } from "../lib/db";
|
|
219
|
+
|
|
220
|
+
class Order {
|
|
221
|
+
constructor(
|
|
222
|
+
public id: string,
|
|
223
|
+
public items: Map<string, number>,
|
|
224
|
+
public createdAt: Date
|
|
225
|
+
) {}
|
|
226
|
+
|
|
227
|
+
// Custom serialization — data must be serializable types
|
|
228
|
+
static [WORKFLOW_SERIALIZE](instance: Order) { // [!code highlight]
|
|
229
|
+
return { // [!code highlight]
|
|
230
|
+
id: instance.id, // [!code highlight]
|
|
231
|
+
items: instance.items, // Map is serializable // [!code highlight]
|
|
232
|
+
createdAt: instance.createdAt, // Date is serializable // [!code highlight]
|
|
233
|
+
}; // [!code highlight]
|
|
234
|
+
} // [!code highlight]
|
|
235
|
+
|
|
236
|
+
static [WORKFLOW_DESERIALIZE](data: { // [!code highlight]
|
|
237
|
+
id: string; // [!code highlight]
|
|
238
|
+
items: Map<string, number>; // [!code highlight]
|
|
239
|
+
createdAt: Date; // [!code highlight]
|
|
240
|
+
}) { // [!code highlight]
|
|
241
|
+
return new Order(data.id, data.items, data.createdAt); // [!code highlight]
|
|
242
|
+
} // [!code highlight]
|
|
243
|
+
|
|
244
|
+
// Methods without "use step" run in the workflow context
|
|
245
|
+
// and must follow the same constraints as workflow functions
|
|
246
|
+
total(): number {
|
|
247
|
+
let sum = 0;
|
|
248
|
+
for (const quantity of this.items.values()) {
|
|
249
|
+
sum += quantity;
|
|
250
|
+
}
|
|
251
|
+
return sum;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Instance methods with "use step" run as step functions
|
|
255
|
+
// with full Node.js access — `this` is automatically serialized
|
|
256
|
+
async save(): Promise<void> {
|
|
257
|
+
"use step"; // [!code highlight]
|
|
258
|
+
await db.orders.insert({ // [!code highlight]
|
|
259
|
+
id: this.id, // [!code highlight]
|
|
260
|
+
items: Object.fromEntries(this.items), // [!code highlight]
|
|
261
|
+
createdAt: this.createdAt, // [!code highlight]
|
|
262
|
+
}); // [!code highlight]
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async sendConfirmation(email: string): Promise<string> {
|
|
266
|
+
"use step"; // [!code highlight]
|
|
267
|
+
const res = await fetch("https://api.example.com/email", { // [!code highlight]
|
|
268
|
+
method: "POST", // [!code highlight]
|
|
269
|
+
body: JSON.stringify({ // [!code highlight]
|
|
270
|
+
to: email, // [!code highlight]
|
|
271
|
+
orderId: this.id, // [!code highlight]
|
|
272
|
+
itemCount: this.items.size, // [!code highlight]
|
|
273
|
+
}), // [!code highlight]
|
|
274
|
+
}); // [!code highlight]
|
|
275
|
+
const { messageId } = await res.json();
|
|
276
|
+
return messageId;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The class can then be used naturally inside a workflow function. Instance methods marked with `"use step"` are each executed as a step — with automatic caching, retry semantics, and full Node.js runtime access. Methods _without_ `"use step"` run directly in the workflow context, so they must follow the same constraints as workflow functions:
|
|
282
|
+
|
|
283
|
+
{/* @expect-error:2693 */}
|
|
284
|
+
|
|
285
|
+
```typescript title="workflows/process-order.ts" lineNumbers
|
|
286
|
+
export async function processOrderWorkflow(
|
|
287
|
+
orderId: string,
|
|
288
|
+
items: Map<string, number>,
|
|
289
|
+
email: string
|
|
290
|
+
) {
|
|
291
|
+
"use workflow";
|
|
292
|
+
|
|
293
|
+
const order = new Order(orderId, items, new Date()); // [!code highlight]
|
|
294
|
+
|
|
295
|
+
// Runs in the workflow context — no "use step" needed
|
|
296
|
+
const itemCount = order.total(); // [!code highlight]
|
|
297
|
+
|
|
298
|
+
// Each "use step" instance method call runs as a separate step
|
|
299
|
+
await order.save(); // [!code highlight]
|
|
300
|
+
const messageId = await order.sendConfirmation(email); // [!code highlight]
|
|
301
|
+
|
|
302
|
+
return { orderId, itemCount, messageId };
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Note that [pass-by-value semantics](#pass-by-value-semantics) also apply to the `this` context of `"use step"` instance methods. Modifying instance properties inside a step method will not affect the original instance in the workflow. If you need to update instance state, return `this` from the step method and re-assign the variable in the workflow:
|
|
307
|
+
|
|
308
|
+
{/* @expect-error:2351 */}
|
|
309
|
+
|
|
310
|
+
```typescript title="workflows/order.ts" lineNumbers
|
|
311
|
+
export class Order {
|
|
312
|
+
// ...
|
|
313
|
+
|
|
314
|
+
async addItem(name: string, quantity: number): Promise<Order> {
|
|
315
|
+
"use step";
|
|
316
|
+
this.items.set(name, quantity);
|
|
317
|
+
return this; // [!code highlight]
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
{/* @expect-error:2693,2552,2304 */}
|
|
323
|
+
|
|
324
|
+
```typescript title="workflows/process-order.ts" lineNumbers
|
|
325
|
+
export async function processOrderWorkflow() {
|
|
326
|
+
"use workflow";
|
|
327
|
+
|
|
328
|
+
let order = new Order(orderId, items, new Date());
|
|
329
|
+
|
|
330
|
+
// Re-assign to capture the updated instance
|
|
331
|
+
order = await order.addItem("Widget", 3); // [!code highlight]
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
124
335
|
## Pass-by-Value Semantics
|
|
125
336
|
|
|
126
337
|
**Parameters are passed by value, not by reference.** Steps receive deserialized copies of data. Mutations inside a step won't affect the original in the workflow.
|
|
@@ -87,6 +87,22 @@ export async function GET(
|
|
|
87
87
|
|
|
88
88
|
This allows clients to reconnect and continue receiving data from where they left off, rather than restarting from the beginning.
|
|
89
89
|
|
|
90
|
+
`startIndex` also supports **negative values** to read relative to the end of the stream. For example, `startIndex: -5` starts 5 chunks before the current end. This is useful when you want to show the most recent output without reading the entire stream history.
|
|
91
|
+
|
|
92
|
+
On an active (not-yet-closed) stream, the negative index resolves relative to the chunk count at connection time; any chunks written afterward are still delivered normally.
|
|
93
|
+
|
|
94
|
+
{/* @skip-typecheck: incomplete code sample */}
|
|
95
|
+
```typescript
|
|
96
|
+
// Read only the last 10 chunks
|
|
97
|
+
const stream = run.getReadable({ startIndex: -10 });
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
If the absolute value exceeds the total number of chunks, reading starts from the beginning (the value is clamped to 0).
|
|
101
|
+
|
|
102
|
+
<Callout type="warn">
|
|
103
|
+
Because streams are live and continue receiving chunks, negative `startIndex` values resolve to different absolute positions on each call. Accurate pagination over a live stream requires cursor-based access, which is not yet supported. Keep this in mind when building clients that paginate over stream data.
|
|
104
|
+
</Callout>
|
|
105
|
+
|
|
90
106
|
## Streams as Data Types
|
|
91
107
|
|
|
92
108
|
[`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) and [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream) are standard Web Streams API types that Workflow DevKit makes serializable. These are not custom types - they follow the web standard - but Workflow DevKit adds the ability to pass them between functions while maintaining their streaming capabilities.
|
|
@@ -164,6 +180,8 @@ Workflow functions must be deterministic to support replay. Since streams bypass
|
|
|
164
180
|
For more on determinism and replay, see [Workflows and Steps](/docs/foundations/workflows-and-steps).
|
|
165
181
|
|
|
166
182
|
```typescript title="workflows/bad-example.ts" lineNumbers
|
|
183
|
+
import { getWritable } from "workflow";
|
|
184
|
+
|
|
167
185
|
export async function badWorkflow() {
|
|
168
186
|
"use workflow";
|
|
169
187
|
|
|
@@ -176,6 +194,8 @@ export async function badWorkflow() {
|
|
|
176
194
|
```
|
|
177
195
|
|
|
178
196
|
```typescript title="workflows/good-example.ts" lineNumbers
|
|
197
|
+
import { getWritable } from "workflow";
|
|
198
|
+
|
|
179
199
|
export async function goodWorkflow() {
|
|
180
200
|
"use workflow";
|
|
181
201
|
|
|
@@ -501,6 +521,8 @@ If a lock is not released, the step function's HTTP request cannot terminate. Ev
|
|
|
501
521
|
**Close streams when done:**
|
|
502
522
|
|
|
503
523
|
```typescript lineNumbers
|
|
524
|
+
import { getWritable } from "workflow";
|
|
525
|
+
|
|
504
526
|
async function finalizeStream() {
|
|
505
527
|
"use step";
|
|
506
528
|
|
|
@@ -63,11 +63,11 @@ import { Next, Nitro, SvelteKit, Nuxt, Hono, Bun, AstroDark, AstroLight, TanStac
|
|
|
63
63
|
<span className="font-medium">SvelteKit</span>
|
|
64
64
|
</div>
|
|
65
65
|
</Card>
|
|
66
|
-
<Card
|
|
66
|
+
<Card className="opacity-50">
|
|
67
67
|
<div className="flex flex-col items-center justify-center gap-2">
|
|
68
|
-
<Nest className="size-16" />
|
|
68
|
+
<Nest className="size-16 dark:invert grayscale" />
|
|
69
69
|
<span className="font-medium">NestJS</span>
|
|
70
|
-
<Badge variant="secondary">
|
|
70
|
+
<Badge variant="secondary">Coming soon</Badge>
|
|
71
71
|
</div>
|
|
72
72
|
</Card>
|
|
73
73
|
<Card className="opacity-50">
|
|
@@ -11,6 +11,10 @@ related:
|
|
|
11
11
|
|
|
12
12
|
This guide will walk through setting up your first workflow in a NestJS app. Along the way, you'll learn more about the concepts that are fundamental to using the development kit in your own projects.
|
|
13
13
|
|
|
14
|
+
<Callout>
|
|
15
|
+
NestJS integration is experimental and not yet supported for deployment to Vercel.
|
|
16
|
+
</Callout>
|
|
17
|
+
|
|
14
18
|
---
|
|
15
19
|
|
|
16
20
|
<Steps>
|
|
@@ -323,14 +327,6 @@ WorkflowModule.forRoot({
|
|
|
323
327
|
});
|
|
324
328
|
```
|
|
325
329
|
|
|
326
|
-
## Deploying to production
|
|
327
|
-
|
|
328
|
-
Workflow DevKit apps currently work best when deployed to [Vercel](https://vercel.com/home) and needs no special configuration.
|
|
329
|
-
|
|
330
|
-
<FluidComputeCallout />
|
|
331
|
-
|
|
332
|
-
Check the [Deploying](/docs/deploying) section to learn how your workflows can be deployed elsewhere.
|
|
333
|
-
|
|
334
330
|
## Next Steps
|
|
335
331
|
|
|
336
332
|
- Learn more about the [Foundations](/docs/foundations).
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Encryption
|
|
3
|
+
description: Learn how Workflow DevKit encrypts user data end-to-end in the event log.
|
|
4
|
+
type: conceptual
|
|
5
|
+
summary: Understand how workflow and step data is encrypted at rest.
|
|
6
|
+
prerequisites:
|
|
7
|
+
- /docs/how-it-works/event-sourcing
|
|
8
|
+
related:
|
|
9
|
+
- /docs/observability
|
|
10
|
+
- /docs/deploying/world/vercel-world
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<Callout>
|
|
14
|
+
This guide explains how Workflow DevKit encrypts user data in the event log. Understanding these details is not required to use workflows — encryption is automatic and requires no code changes. For getting started, see the [getting started](/docs/getting-started) guides for your framework.
|
|
15
|
+
</Callout>
|
|
16
|
+
|
|
17
|
+
Workflow DevKit supports automatic end-to-end encryption of all user data before it is written to the event log. When a `World` implementation provides encryption support, it is safe to pass sensitive data — such as API keys, tokens, or user credentials — as workflow inputs, step arguments, and return values. The storage backend only ever sees ciphertext.
|
|
18
|
+
|
|
19
|
+
Encryption support varies by `World` implementation. See the [Worlds](/worlds) page to check which worlds support this feature. `World` implementations opt into encryption by providing a `getEncryptionKeyForRun()` method — the core runtime will use it automatically when present.
|
|
20
|
+
|
|
21
|
+
## What Is Encrypted
|
|
22
|
+
|
|
23
|
+
All user data flowing through the event log is encrypted:
|
|
24
|
+
|
|
25
|
+
- **Workflow inputs** — arguments passed when starting a workflow
|
|
26
|
+
- **Workflow return values** — the final output of a workflow
|
|
27
|
+
- **Step inputs** — arguments passed to step functions
|
|
28
|
+
- **Step return values** — the result returned by step functions
|
|
29
|
+
- **Hook metadata** — data attached when creating a hook
|
|
30
|
+
- **Hook payloads** — data received by hooks and webhooks
|
|
31
|
+
- **Stream data** — each frame in a `ReadableStream` or `WritableStream`
|
|
32
|
+
|
|
33
|
+
Metadata such as workflow names, step names, entity IDs, timestamps, and lifecycle states are **not** encrypted. This allows the observability tools to display run structure and timelines without requiring decryption.
|
|
34
|
+
|
|
35
|
+
## How It Works
|
|
36
|
+
|
|
37
|
+
### Key Management
|
|
38
|
+
|
|
39
|
+
Each workflow run is encrypted with its own unique key, provided by the `World` implementation via `getEncryptionKeyForRun()`. How the key is generated and stored is up to the `World`.
|
|
40
|
+
|
|
41
|
+
For example, the [Vercel World](/docs/deploying/world/vercel-world) provides unique keys per run and execution environment, ensuring that a given run can only decrypt data from that run itself.
|
|
42
|
+
|
|
43
|
+
### Encryption Algorithm
|
|
44
|
+
|
|
45
|
+
Data is encrypted using **AES-256-GCM** via the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API):
|
|
46
|
+
|
|
47
|
+
- A random 12-byte nonce is generated for each encryption operation
|
|
48
|
+
- The GCM authentication tag provides integrity verification — any tampering with the ciphertext is detected
|
|
49
|
+
- The same plaintext produces different ciphertext each time due to the random nonce
|
|
50
|
+
|
|
51
|
+
## Decrypting Data
|
|
52
|
+
|
|
53
|
+
When viewing workflow runs through the observability tools, encrypted fields display as locked placeholders until you explicitly choose to decrypt them.
|
|
54
|
+
|
|
55
|
+
### Permissions
|
|
56
|
+
|
|
57
|
+
Decryption access is controlled by the `World` implementation. On Vercel, decryption follows the same permissions model as project environment variables — if you don't have permission to view environment variable values for a project, you won't be able to decrypt workflow data either. Each decryption request is recorded in your [Vercel audit log](https://vercel.com/docs/audit-log), giving your team full visibility into when and by whom workflow data was accessed.
|
|
58
|
+
|
|
59
|
+
### Web Dashboard
|
|
60
|
+
|
|
61
|
+
Click the **Decrypt** button in the run detail panel to decrypt all data fields. Decryption happens entirely in the browser via the Web Crypto API — the observability server retrieves the encryption key but never sees your plaintext data.
|
|
62
|
+
|
|
63
|
+
### CLI
|
|
64
|
+
|
|
65
|
+
Add the `--decrypt` flag to any `inspect` command:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Inspect a specific run
|
|
69
|
+
npx workflow inspect run <run-id> --decrypt
|
|
70
|
+
|
|
71
|
+
# Inspect a specific step
|
|
72
|
+
npx workflow inspect step <step-id> --run <run-id> --decrypt
|
|
73
|
+
|
|
74
|
+
# List events for a run
|
|
75
|
+
npx workflow inspect events --run <run-id> --decrypt
|
|
76
|
+
|
|
77
|
+
# Inspect a specific stream
|
|
78
|
+
npx workflow inspect stream <stream-id> --run <run-id> --decrypt
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Without `--decrypt`, encrypted fields display as `🔒 Encrypted` placeholders.
|
|
82
|
+
|
|
83
|
+
## Custom World Implementations
|
|
84
|
+
|
|
85
|
+
The core runtime encrypts data automatically when the `World` implementation provides a `getEncryptionKeyForRun()` method. This method receives the run ID and returns the raw encryption key bytes.
|
|
86
|
+
|
|
87
|
+
To add encryption support to a custom `World`:
|
|
88
|
+
|
|
89
|
+
1. Implement `getEncryptionKeyForRun(runId: string)` on your `World` class
|
|
90
|
+
2. Return the raw 32-byte key as a `Uint8Array` — the core runtime uses it for AES-256-GCM operations
|
|
91
|
+
3. Ensure the same key is returned for the same run ID across invocations (for decryption during replay)
|
|
92
|
+
|
|
93
|
+
The [Vercel World](/docs/deploying/world/vercel-world) implementation uses HKDF derivation from a deployment-scoped key, but any consistent key management scheme will work.
|
|
@@ -7,6 +7,7 @@ prerequisites:
|
|
|
7
7
|
- /docs/foundations
|
|
8
8
|
related:
|
|
9
9
|
- /docs/how-it-works/event-sourcing
|
|
10
|
+
- /docs/how-it-works/encryption
|
|
10
11
|
---
|
|
11
12
|
|
|
12
13
|
Workflow DevKit provides powerful tools to inspect, monitor, and debug your workflows through the CLI and Web UI. These tools allow you to inspect workflow runs, steps, webhooks, [events](/docs/how-it-works/event-sourcing), and stream output.
|
|
@@ -60,3 +61,5 @@ To inspect workflows running on Vercel, ensure you're logged in to the Vercel CL
|
|
|
60
61
|
# Inspect workflows running on Vercel
|
|
61
62
|
npx workflow inspect runs --backend vercel
|
|
62
63
|
```
|
|
64
|
+
|
|
65
|
+
When deployed to Vercel, workflow data is [encrypted end-to-end](/docs/how-it-works/encryption). Encrypted fields display as locked placeholders until you choose to decrypt them using the **Decrypt** button in the web UI or the `--decrypt` flag in the CLI.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "workflow",
|
|
3
|
-
"version": "4.2.0-beta.
|
|
3
|
+
"version": "4.2.0-beta.72",
|
|
4
4
|
"description": "Workflow DevKit - Build durable, resilient, and observable workflows",
|
|
5
5
|
"main": "dist/typescript-plugin.cjs",
|
|
6
6
|
"type": "module",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"workflow": "./dist/api-workflow.js",
|
|
38
38
|
"default": "./dist/api.js"
|
|
39
39
|
},
|
|
40
|
+
"./errors": "./dist/internal/errors.js",
|
|
40
41
|
"./internal/errors": "./dist/internal/errors.js",
|
|
41
42
|
"./internal/builtins": "./dist/internal/builtins.js",
|
|
42
43
|
"./internal/private": "./dist/internal/private.js",
|
|
@@ -48,21 +49,26 @@
|
|
|
48
49
|
"./astro": "./dist/astro.js",
|
|
49
50
|
"./vite": "./dist/vite.js",
|
|
50
51
|
"./nest": "./dist/nest.js",
|
|
51
|
-
"./runtime": "./dist/runtime.js"
|
|
52
|
+
"./runtime": "./dist/runtime.js",
|
|
53
|
+
"./observability": {
|
|
54
|
+
"types": "./dist/observability.d.ts",
|
|
55
|
+
"default": "./dist/observability.js"
|
|
56
|
+
}
|
|
52
57
|
},
|
|
53
58
|
"dependencies": {
|
|
54
59
|
"ms": "2.1.3",
|
|
55
|
-
"@workflow/astro": "4.0.0-beta.
|
|
56
|
-
"@workflow/cli": "4.2.0-beta.
|
|
57
|
-
"@workflow/core": "4.2.0-beta.
|
|
58
|
-
"@workflow/errors": "4.1.0-beta.
|
|
60
|
+
"@workflow/astro": "4.0.0-beta.46",
|
|
61
|
+
"@workflow/cli": "4.2.0-beta.72",
|
|
62
|
+
"@workflow/core": "4.2.0-beta.72",
|
|
63
|
+
"@workflow/errors": "4.1.0-beta.19",
|
|
59
64
|
"@workflow/typescript-plugin": "4.0.1-beta.5",
|
|
60
|
-
"@workflow/
|
|
61
|
-
"@workflow/
|
|
62
|
-
"@workflow/
|
|
63
|
-
"@workflow/
|
|
64
|
-
"@workflow/
|
|
65
|
-
"@workflow/
|
|
65
|
+
"@workflow/utils": "4.1.0-beta.13",
|
|
66
|
+
"@workflow/next": "4.0.1-beta.68",
|
|
67
|
+
"@workflow/nest": "0.0.0-beta.21",
|
|
68
|
+
"@workflow/nitro": "4.0.1-beta.67",
|
|
69
|
+
"@workflow/nuxt": "4.0.1-beta.56",
|
|
70
|
+
"@workflow/sveltekit": "4.0.0-beta.61",
|
|
71
|
+
"@workflow/rollup": "4.0.0-beta.29"
|
|
66
72
|
},
|
|
67
73
|
"devDependencies": {
|
|
68
74
|
"@types/ms": "2.1.0",
|