workflow 4.2.0-beta.70 → 4.2.0-beta.71
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/internal/builtins.d.ts +3 -3
- package/dist/internal/builtins.d.ts.map +1 -1
- package/dist/internal/builtins.js +7 -7
- package/docs/ai/chat-session-modeling.mdx +4 -4
- package/docs/ai/defining-tools.mdx +1 -1
- package/docs/ai/index.mdx +5 -5
- package/docs/ai/message-queueing.mdx +6 -6
- package/docs/api-reference/index.mdx +3 -0
- package/docs/api-reference/meta.json +1 -1
- package/docs/api-reference/workflow-ai/durable-agent.mdx +5 -5
- package/docs/api-reference/workflow-api/start.mdx +24 -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/deploying/world/vercel-world.mdx +2 -1
- package/docs/foundations/common-patterns.mdx +4 -0
- package/docs/foundations/serialization.mdx +211 -0
- 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 +10 -10
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* similar to "stdlib" except that are not meant to be imported by users, but are instead "just available"
|
|
4
4
|
* alongside user defined steps. They are used internally by the runtime
|
|
5
5
|
*/
|
|
6
|
-
export declare function __builtin_response_array_buffer(
|
|
7
|
-
export declare function __builtin_response_json(
|
|
8
|
-
export declare function __builtin_response_text(
|
|
6
|
+
export declare function __builtin_response_array_buffer(this: Request | Response): Promise<ArrayBuffer>;
|
|
7
|
+
export declare function __builtin_response_json(this: Request | Response): Promise<unknown>;
|
|
8
|
+
export declare function __builtin_response_text(this: Request | Response): Promise<string>;
|
|
9
9
|
//# sourceMappingURL=builtins.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../../src/internal/builtins.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAsB,+BAA+B,
|
|
1
|
+
{"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../../src/internal/builtins.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAsB,+BAA+B,CACnD,IAAI,EAAE,OAAO,GAAG,QAAQ,wBAIzB;AAED,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,oBAGrE;AAED,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,mBAGrE"}
|
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
* similar to "stdlib" except that are not meant to be imported by users, but are instead "just available"
|
|
4
4
|
* alongside user defined steps. They are used internally by the runtime
|
|
5
5
|
*/
|
|
6
|
-
export async function __builtin_response_array_buffer(
|
|
6
|
+
export async function __builtin_response_array_buffer() {
|
|
7
7
|
'use step';
|
|
8
|
-
return
|
|
8
|
+
return this.arrayBuffer();
|
|
9
9
|
}
|
|
10
|
-
export async function __builtin_response_json(
|
|
10
|
+
export async function __builtin_response_json() {
|
|
11
11
|
'use step';
|
|
12
|
-
return
|
|
12
|
+
return this.json();
|
|
13
13
|
}
|
|
14
|
-
export async function __builtin_response_text(
|
|
14
|
+
export async function __builtin_response_text() {
|
|
15
15
|
'use step';
|
|
16
|
-
return
|
|
16
|
+
return this.text();
|
|
17
17
|
}
|
|
18
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
18
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVpbHRpbnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaW50ZXJuYWwvYnVpbHRpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7R0FJRztBQUVILE1BQU0sQ0FBQyxLQUFLLFVBQVUsK0JBQStCO0lBR25ELFVBQVUsQ0FBQztJQUNYLE9BQU8sSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO0FBQzVCLENBQUM7QUFFRCxNQUFNLENBQUMsS0FBSyxVQUFVLHVCQUF1QjtJQUMzQyxVQUFVLENBQUM7SUFDWCxPQUFPLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztBQUNyQixDQUFDO0FBRUQsTUFBTSxDQUFDLEtBQUssVUFBVSx1QkFBdUI7SUFDM0MsVUFBVSxDQUFDO0lBQ1gsT0FBTyxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7QUFDckIsQ0FBQyJ9
|
|
@@ -39,12 +39,12 @@ export async function chat(messages: UIMessage[]) {
|
|
|
39
39
|
|
|
40
40
|
const agent = new DurableAgent({
|
|
41
41
|
model: "bedrock/claude-haiku-4-5-20251001-v1",
|
|
42
|
-
|
|
42
|
+
instructions: FLIGHT_ASSISTANT_PROMPT,
|
|
43
43
|
tools: flightBookingTools,
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
await agent.stream({
|
|
47
|
-
messages: convertToModelMessages(messages), // [!code highlight] Full history from client
|
|
47
|
+
messages: await convertToModelMessages(messages), // [!code highlight] Full history from client
|
|
48
48
|
writable,
|
|
49
49
|
});
|
|
50
50
|
}
|
|
@@ -171,7 +171,7 @@ export async function chat(initialMessages: UIMessage[]) {
|
|
|
171
171
|
|
|
172
172
|
const { workflowRunId: runId } = getWorkflowMetadata();
|
|
173
173
|
const writable = getWritable<UIMessageChunk>();
|
|
174
|
-
const messages: ModelMessage[] = convertToModelMessages(initialMessages);
|
|
174
|
+
const messages: ModelMessage[] = await convertToModelMessages(initialMessages);
|
|
175
175
|
|
|
176
176
|
// Write markers for initial user messages (for replay) // [!code highlight]
|
|
177
177
|
for (const msg of initialMessages) { // [!code highlight]
|
|
@@ -183,7 +183,7 @@ export async function chat(initialMessages: UIMessage[]) {
|
|
|
183
183
|
|
|
184
184
|
const agent = new DurableAgent({
|
|
185
185
|
model: "bedrock/claude-haiku-4-5-20251001-v1",
|
|
186
|
-
|
|
186
|
+
instructions: FLIGHT_ASSISTANT_PROMPT,
|
|
187
187
|
tools: flightBookingTools,
|
|
188
188
|
});
|
|
189
189
|
|
|
@@ -25,7 +25,7 @@ When you tool needs access to the full message history, you can access it via th
|
|
|
25
25
|
```typescript title="tools.ts" lineNumbers
|
|
26
26
|
async function getWeather(
|
|
27
27
|
{ city }: { city: string },
|
|
28
|
-
{ messages, toolCallId }: { messages:
|
|
28
|
+
{ messages, toolCallId }: { messages: ModelMessage[], toolCallId: string }) { // [!code highlight]
|
|
29
29
|
"use step";
|
|
30
30
|
return `Weather in ${city} is sunny`;
|
|
31
31
|
}
|
package/docs/ai/index.mdx
CHANGED
|
@@ -127,11 +127,11 @@ export async function POST(req: Request) {
|
|
|
127
127
|
const { messages }: { messages: UIMessage[] } = await req.json();
|
|
128
128
|
const agent = new Agent({ // [!code highlight]
|
|
129
129
|
model: gateway("bedrock/claude-4-5-haiku-20251001-v1"),
|
|
130
|
-
|
|
130
|
+
instructions: FLIGHT_ASSISTANT_PROMPT,
|
|
131
131
|
tools: flightBookingTools,
|
|
132
132
|
});
|
|
133
|
-
const modelMessages = convertToModelMessages(messages);
|
|
134
|
-
const stream = agent.stream({ messages: modelMessages }); // [!code highlight]
|
|
133
|
+
const modelMessages = await convertToModelMessages(messages);
|
|
134
|
+
const stream = await agent.stream({ messages: modelMessages }); // [!code highlight]
|
|
135
135
|
return createUIMessageStreamResponse({
|
|
136
136
|
stream: stream.toUIMessageStream(),
|
|
137
137
|
});
|
|
@@ -273,7 +273,7 @@ export async function chatWorkflow(messages: ModelMessage[]) {
|
|
|
273
273
|
// ELSE if using a custom provider, pass the provider call as an argument:
|
|
274
274
|
model: openai("gpt-5.1"), // [!code highlight]
|
|
275
275
|
|
|
276
|
-
|
|
276
|
+
instructions: FLIGHT_ASSISTANT_PROMPT,
|
|
277
277
|
tools: flightBookingTools,
|
|
278
278
|
});
|
|
279
279
|
|
|
@@ -304,7 +304,7 @@ import { chatWorkflow } from "@/workflows/chat/workflow";
|
|
|
304
304
|
|
|
305
305
|
export async function POST(req: Request) {
|
|
306
306
|
const { messages }: { messages: UIMessage[] } = await req.json();
|
|
307
|
-
const modelMessages = convertToModelMessages(messages);
|
|
307
|
+
const modelMessages = await convertToModelMessages(messages);
|
|
308
308
|
|
|
309
309
|
const run = await start(chatWorkflow, [modelMessages]); // [!code highlight]
|
|
310
310
|
|
|
@@ -33,15 +33,15 @@ The `prepareStep` callback runs before each step in the agent loop. It receives
|
|
|
33
33
|
|
|
34
34
|
```typescript lineNumbers
|
|
35
35
|
interface PrepareStepInfo {
|
|
36
|
-
model: string | (() => Promise<
|
|
36
|
+
model: string | (() => Promise<LanguageModel>); // Current model
|
|
37
37
|
stepNumber: number; // 0-indexed step count
|
|
38
38
|
steps: StepResult[]; // Previous step results
|
|
39
|
-
messages:
|
|
39
|
+
messages: ModelMessage[]; // Messages to be sent
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
interface PrepareStepResult {
|
|
43
|
-
model?: string | (() => Promise<
|
|
44
|
-
messages?:
|
|
43
|
+
model?: string | (() => Promise<LanguageModel>); // Override model
|
|
44
|
+
messages?: ModelMessage[]; // Override messages
|
|
45
45
|
}
|
|
46
46
|
```
|
|
47
47
|
|
|
@@ -65,7 +65,7 @@ export async function chat(initialMessages: ModelMessage[]) {
|
|
|
65
65
|
|
|
66
66
|
const agent = new DurableAgent({
|
|
67
67
|
model: "bedrock/claude-haiku-4-5-20251001-v1",
|
|
68
|
-
|
|
68
|
+
instructions: FLIGHT_ASSISTANT_PROMPT,
|
|
69
69
|
tools: flightBookingTools,
|
|
70
70
|
});
|
|
71
71
|
|
|
@@ -101,7 +101,7 @@ export async function chat(initialMessages: ModelMessage[]) {
|
|
|
101
101
|
Messages sent via `chatMessageHook.resume()` accumulate in the queue and get injected before the next step, whether that's a tool call or another LLM request.
|
|
102
102
|
|
|
103
103
|
<Callout type="info">
|
|
104
|
-
The `prepareStep` callback receives messages in `
|
|
104
|
+
The `prepareStep` callback receives messages in `ModelMessage[]` format (with content arrays), which is the internal format used by the AI SDK.
|
|
105
105
|
</Callout>
|
|
106
106
|
|
|
107
107
|
## Combining with Multi-Turn Sessions
|
|
@@ -17,6 +17,9 @@ All the functions and primitives that come with Workflow DevKit by package.
|
|
|
17
17
|
<Card title="workflow/next" href="/docs/api-reference/workflow-next">
|
|
18
18
|
Next.js integration for Workflow DevKit that automatically configures bundling and runtime support.
|
|
19
19
|
</Card>
|
|
20
|
+
<Card title="@workflow/serde" href="/docs/api-reference/workflow-serde">
|
|
21
|
+
Serialization symbols for custom class serialization in workflows.
|
|
22
|
+
</Card>
|
|
20
23
|
<Card title="@workflow/ai" href="/docs/api-reference/workflow-ai">
|
|
21
24
|
Helpers for integrating AI SDK for building AI-powered workflows.
|
|
22
25
|
</Card>
|
|
@@ -34,7 +34,7 @@ async function myAgent() {
|
|
|
34
34
|
|
|
35
35
|
const agent = new DurableAgent({
|
|
36
36
|
model: "anthropic/claude-haiku-4.5",
|
|
37
|
-
|
|
37
|
+
instructions: "You are a helpful weather assistant.",
|
|
38
38
|
temperature: 0.7,
|
|
39
39
|
tools: {
|
|
40
40
|
getWeather: {
|
|
@@ -249,7 +249,7 @@ async function weatherAgentWorkflow(userQuery: string) {
|
|
|
249
249
|
execute: getWeather,
|
|
250
250
|
},
|
|
251
251
|
},
|
|
252
|
-
|
|
252
|
+
instructions: "You are a helpful weather assistant. Always provide accurate weather information.",
|
|
253
253
|
});
|
|
254
254
|
|
|
255
255
|
await agent.stream({
|
|
@@ -446,7 +446,7 @@ async function agentWithPrepareStep(userMessage: string) {
|
|
|
446
446
|
|
|
447
447
|
const agent = new DurableAgent({
|
|
448
448
|
model: "openai/gpt-4.1-mini", // Default model
|
|
449
|
-
|
|
449
|
+
instructions: "You are a helpful assistant.",
|
|
450
450
|
});
|
|
451
451
|
|
|
452
452
|
await agent.stream({
|
|
@@ -500,7 +500,7 @@ async function agentWithMessageQueue(initialMessage: string) {
|
|
|
500
500
|
|
|
501
501
|
const agent = new DurableAgent({
|
|
502
502
|
model: "anthropic/claude-haiku-4.5",
|
|
503
|
-
|
|
503
|
+
instructions: "You are a helpful assistant.",
|
|
504
504
|
});
|
|
505
505
|
|
|
506
506
|
await agent.stream({
|
|
@@ -812,7 +812,7 @@ async function agentWithUIMessages(userMessage: string) {
|
|
|
812
812
|
|
|
813
813
|
const agent = new DurableAgent({
|
|
814
814
|
model: "anthropic/claude-haiku-4.5",
|
|
815
|
-
|
|
815
|
+
instructions: "You are a helpful assistant.",
|
|
816
816
|
});
|
|
817
817
|
|
|
818
818
|
const result = await agent.stream({
|
|
@@ -76,3 +76,27 @@ const run = await start(myWorkflow, ["arg1", "arg2"], { // [!code highlight]
|
|
|
76
76
|
deploymentId: "custom-deployment-id" // [!code highlight]
|
|
77
77
|
}); // [!code highlight]
|
|
78
78
|
```
|
|
79
|
+
|
|
80
|
+
### Using `deploymentId: "latest"`
|
|
81
|
+
|
|
82
|
+
Set `deploymentId` to `"latest"` to automatically resolve the most recent deployment for the current environment. This is useful when you want to ensure a workflow run targets the latest deployed version of your application rather than the deployment that initiated the call.
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { start } from "workflow/api";
|
|
86
|
+
import { myWorkflow } from "./workflows/my-workflow";
|
|
87
|
+
|
|
88
|
+
const run = await start(myWorkflow, ["arg1", "arg2"], { // [!code highlight]
|
|
89
|
+
deploymentId: "latest" // [!code highlight]
|
|
90
|
+
}); // [!code highlight]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
<Callout type="info">
|
|
94
|
+
The `deploymentId` option is currently a Vercel-specific feature. The `"latest"` value resolves to the most recent deployment matching your current environment — the same production target for production deployments, or the same git branch for preview deployments.
|
|
95
|
+
</Callout>
|
|
96
|
+
|
|
97
|
+
<Callout type="warn">
|
|
98
|
+
When using `deploymentId: "latest"`, the workflow run will execute on a potentially different deployment than the one calling `start()`. Be mindful of forward and backward compatibility:
|
|
99
|
+
|
|
100
|
+
- **Workflow identity**: The workflow ID is derived from the function name and file path. If the latest deployment has renamed the workflow function or moved it to a different directory, the workflow ID will no longer match and the run will fail to start.
|
|
101
|
+
- **Input and output compatibility**: The arguments passed to `start()` are serialized by the calling deployment but deserialized by the target deployment. Similarly, the workflow's return value is serialized by the target deployment but deserialized by the caller. If the workflow's expected arguments or return type have changed (e.g. added required fields, removed fields, or changed types), the run may fail or behave unexpectedly. Ensure that input and output schemas remain backward-compatible across deployments.
|
|
102
|
+
</Callout>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "@workflow/serde"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Serialization symbols for custom class serialization in Workflow DevKit.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```package-install
|
|
10
|
+
npm i @workflow/serde
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
By default, Workflow DevKit can serialize standard JavaScript types like primitives, objects, arrays, `Date`, `Map`, `Set`, and more. However, custom class instances are not serializable by default because the serialization system doesn't know how to reconstruct them.
|
|
16
|
+
|
|
17
|
+
The `@workflow/serde` package provides two symbols that allow you to define custom serialization and deserialization logic for your classes, enabling them to be passed between workflow and step functions.
|
|
18
|
+
|
|
19
|
+
## Symbols
|
|
20
|
+
|
|
21
|
+
<Cards>
|
|
22
|
+
<Card href="/docs/api-reference/workflow-serde/workflow-serialize" title="WORKFLOW_SERIALIZE">
|
|
23
|
+
Symbol for defining how to serialize a class instance to plain data.
|
|
24
|
+
</Card>
|
|
25
|
+
<Card href="/docs/api-reference/workflow-serde/workflow-deserialize" title="WORKFLOW_DESERIALIZE">
|
|
26
|
+
Symbol for defining how to reconstruct a class instance from plain data.
|
|
27
|
+
</Card>
|
|
28
|
+
</Cards>
|
|
29
|
+
|
|
30
|
+
## Quick Example
|
|
31
|
+
|
|
32
|
+
{/* @expect-error:2351 */}
|
|
33
|
+
|
|
34
|
+
```typescript lineNumbers
|
|
35
|
+
import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde";
|
|
36
|
+
|
|
37
|
+
class Point {
|
|
38
|
+
constructor(public x: number, public y: number) {}
|
|
39
|
+
|
|
40
|
+
static [WORKFLOW_SERIALIZE](instance: Point) {
|
|
41
|
+
return { x: instance.x, y: instance.y };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static [WORKFLOW_DESERIALIZE](data: { x: number; y: number }) {
|
|
45
|
+
return new Point(data.x, data.y);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
<Callout>
|
|
51
|
+
For a complete guide on custom class serialization, see the [Serialization documentation](/docs/foundations/serialization#custom-class-serialization).
|
|
52
|
+
</Callout>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: WORKFLOW_DESERIALIZE
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
A symbol used to define custom deserialization for user-defined class instances. The static method should accept serialized data and return a new class instance.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
{/* @expect-error:2351 */}
|
|
10
|
+
|
|
11
|
+
```typescript lineNumbers
|
|
12
|
+
import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde";
|
|
13
|
+
|
|
14
|
+
class Point {
|
|
15
|
+
constructor(public x: number, public y: number) {}
|
|
16
|
+
|
|
17
|
+
static [WORKFLOW_SERIALIZE](instance: Point) {
|
|
18
|
+
return { x: instance.x, y: instance.y };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static [WORKFLOW_DESERIALIZE](data: { x: number; y: number }) {
|
|
22
|
+
return new Point(data.x, data.y);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## API Signature
|
|
28
|
+
|
|
29
|
+
{/* @skip-typecheck */}
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
static [WORKFLOW_DESERIALIZE](data: SerializableData): T
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Parameters
|
|
36
|
+
|
|
37
|
+
<TSDoc
|
|
38
|
+
definition={`
|
|
39
|
+
interface Parameters {
|
|
40
|
+
/**
|
|
41
|
+
* The serialized data to reconstruct into a class instance.
|
|
42
|
+
* This is the same data that was returned by WORKFLOW_SERIALIZE.
|
|
43
|
+
*/
|
|
44
|
+
data: SerializableData;
|
|
45
|
+
}
|
|
46
|
+
export default Parameters;`}
|
|
47
|
+
/>
|
|
48
|
+
|
|
49
|
+
### Returns
|
|
50
|
+
|
|
51
|
+
The method should return a new instance of the class, reconstructed from the serialized data.
|
|
52
|
+
|
|
53
|
+
## Requirements
|
|
54
|
+
|
|
55
|
+
<Callout type="warn">
|
|
56
|
+
The method must be implemented as a **static** method on the class. Instance methods are not supported.
|
|
57
|
+
</Callout>
|
|
58
|
+
|
|
59
|
+
- Both `WORKFLOW_SERIALIZE` and `WORKFLOW_DESERIALIZE` must be implemented together
|
|
60
|
+
- The method receives the exact data that was returned by `WORKFLOW_SERIALIZE`
|
|
61
|
+
- If `WORKFLOW_SERIALIZE` returns complex types (like `Map` or `Date`), they will be properly deserialized before being passed to this method
|
|
62
|
+
|
|
63
|
+
<Callout type="warn">
|
|
64
|
+
This method runs inside the workflow context and is subject to the same constraints as `"use workflow"` functions:
|
|
65
|
+
- No Node.js-specific APIs (like `fs`, `path`, `crypto`, etc.)
|
|
66
|
+
- No non-deterministic operations (like `Math.random()` or `Date.now()`)
|
|
67
|
+
- No external network calls
|
|
68
|
+
|
|
69
|
+
Keep this method simple and focused on reconstructing the instance from the provided data.
|
|
70
|
+
</Callout>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: WORKFLOW_SERIALIZE
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
A symbol used to define custom serialization for user-defined class instances. The static method should accept an instance and return serializable data.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
{/* @expect-error:2351 */}
|
|
10
|
+
|
|
11
|
+
```typescript lineNumbers
|
|
12
|
+
import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde";
|
|
13
|
+
|
|
14
|
+
class Point {
|
|
15
|
+
constructor(public x: number, public y: number) {}
|
|
16
|
+
|
|
17
|
+
static [WORKFLOW_SERIALIZE](instance: Point) {
|
|
18
|
+
return { x: instance.x, y: instance.y };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static [WORKFLOW_DESERIALIZE](data: { x: number; y: number }) {
|
|
22
|
+
return new Point(data.x, data.y);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## API Signature
|
|
28
|
+
|
|
29
|
+
{/* @skip-typecheck */}
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
static [WORKFLOW_SERIALIZE](instance: T): SerializableData
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Parameters
|
|
36
|
+
|
|
37
|
+
<TSDoc
|
|
38
|
+
definition={`
|
|
39
|
+
interface Parameters {
|
|
40
|
+
/**
|
|
41
|
+
* The class instance to serialize.
|
|
42
|
+
*/
|
|
43
|
+
instance: T;
|
|
44
|
+
}
|
|
45
|
+
export default Parameters;`}
|
|
46
|
+
/>
|
|
47
|
+
|
|
48
|
+
### Returns
|
|
49
|
+
|
|
50
|
+
The method should return serializable data. This can be:
|
|
51
|
+
|
|
52
|
+
- Primitives (`string`, `number`, `boolean`, `null`, `undefined`, `bigint`)
|
|
53
|
+
- Plain objects with serializable values
|
|
54
|
+
- Arrays of serializable values
|
|
55
|
+
- Built-in serializable types (`Date`, `Map`, `Set`, `RegExp`, `URL`, etc.)
|
|
56
|
+
- Other custom classes that implement serialization
|
|
57
|
+
|
|
58
|
+
## Requirements
|
|
59
|
+
|
|
60
|
+
<Callout type="warn">
|
|
61
|
+
The method must be implemented as a **static** method on the class. Instance methods are not supported.
|
|
62
|
+
</Callout>
|
|
63
|
+
|
|
64
|
+
- Both `WORKFLOW_SERIALIZE` and `WORKFLOW_DESERIALIZE` must be implemented together
|
|
65
|
+
- The returned data must itself be serializable
|
|
66
|
+
- The SWC compiler plugin automatically detects and registers classes that implement these symbols
|
|
67
|
+
|
|
68
|
+
<Callout type="warn">
|
|
69
|
+
This method runs inside the workflow context and is subject to the same constraints as `"use workflow"` functions:
|
|
70
|
+
- No Node.js-specific APIs (like `fs`, `path`, `crypto`, etc.)
|
|
71
|
+
- No non-deterministic operations (like `Math.random()` or `Date.now()`)
|
|
72
|
+
- No external network calls
|
|
73
|
+
|
|
74
|
+
Keep this method simple and focused on extracting data from the instance.
|
|
75
|
+
</Callout>
|
|
@@ -6,6 +6,7 @@ summary: Deploy workflows to Vercel with fully-managed storage, queuing, and aut
|
|
|
6
6
|
prerequisites:
|
|
7
7
|
- /docs/deploying
|
|
8
8
|
related:
|
|
9
|
+
- /docs/how-it-works/encryption
|
|
9
10
|
- /docs/deploying/world/local-world
|
|
10
11
|
- /docs/deploying/world/postgres-world
|
|
11
12
|
---
|
|
@@ -125,7 +126,7 @@ This ensures long-running workflows complete reliably without being affected by
|
|
|
125
126
|
|
|
126
127
|
The Vercel World uses Vercel's infrastructure for workflow execution:
|
|
127
128
|
|
|
128
|
-
- **Storage** - Workflow data is stored in Vercel's cloud with automatic replication and encryption
|
|
129
|
+
- **Storage** - Workflow data is stored in Vercel's cloud with automatic replication and [end-to-end encryption](/docs/how-it-works/encryption)
|
|
129
130
|
- **Queuing** - Steps are distributed across serverless functions with automatic retries
|
|
130
131
|
- **Authentication** - OIDC tokens provide secure, automatic authentication
|
|
131
132
|
|
|
@@ -252,6 +252,10 @@ export async function processOrder(orderId: string) {
|
|
|
252
252
|
|
|
253
253
|
With background execution, the parent workflow continues immediately after starting the child. The child workflow runs independently with its own event log and can be monitored separately using the returned `runId`.
|
|
254
254
|
|
|
255
|
+
<Callout type="info">
|
|
256
|
+
If you want the child workflow to run on the latest deployment rather than the current one, you can pass [`deploymentId: "latest"`](/docs/api-reference/workflow-api/start#using-deploymentid-latest) in the `start()` options. This is currently a Vercel-specific feature. Be aware that the child workflow's function name, file path, argument types, and return type must remain compatible across deployments — renaming the function or changing its location will change the workflow ID, and modifying expected inputs or outputs can cause serialization failures.
|
|
257
|
+
</Callout>
|
|
258
|
+
|
|
255
259
|
**Choose direct await when:**
|
|
256
260
|
- The parent needs the child's result before continuing
|
|
257
261
|
- You want a single, unified event log
|
|
@@ -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.
|
|
@@ -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.71",
|
|
4
4
|
"description": "Workflow DevKit - Build durable, resilient, and observable workflows",
|
|
5
5
|
"main": "dist/typescript-plugin.cjs",
|
|
6
6
|
"type": "module",
|
|
@@ -52,17 +52,17 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"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.
|
|
55
|
+
"@workflow/astro": "4.0.0-beta.45",
|
|
56
|
+
"@workflow/cli": "4.2.0-beta.71",
|
|
57
|
+
"@workflow/core": "4.2.0-beta.71",
|
|
58
58
|
"@workflow/errors": "4.1.0-beta.18",
|
|
59
59
|
"@workflow/typescript-plugin": "4.0.1-beta.5",
|
|
60
|
-
"@workflow/next": "4.0.1-beta.
|
|
61
|
-
"@workflow/nest": "0.0.0-beta.
|
|
62
|
-
"@workflow/nitro": "4.0.1-beta.
|
|
63
|
-
"@workflow/nuxt": "4.0.1-beta.
|
|
64
|
-
"@workflow/sveltekit": "4.0.0-beta.
|
|
65
|
-
"@workflow/rollup": "4.0.0-beta.
|
|
60
|
+
"@workflow/next": "4.0.1-beta.67",
|
|
61
|
+
"@workflow/nest": "0.0.0-beta.20",
|
|
62
|
+
"@workflow/nitro": "4.0.1-beta.66",
|
|
63
|
+
"@workflow/nuxt": "4.0.1-beta.55",
|
|
64
|
+
"@workflow/sveltekit": "4.0.0-beta.60",
|
|
65
|
+
"@workflow/rollup": "4.0.0-beta.28"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@types/ms": "2.1.0",
|