惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cisco Talos Blog
Cisco Talos Blog
V
V2EX
C
Check Point Blog
GbyAI
GbyAI
D
Docker
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
B
Blog RSS Feed
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
Netflix TechBlog - Medium
T
Troy Hunt's Blog
博客园 - Franky
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Microsoft Security Blog
Microsoft Security Blog
P
Privacy & Cybersecurity Law Blog
WordPress大学
WordPress大学
The Cloudflare Blog
S
SegmentFault 最新的问题
Latest news
Latest news
Microsoft Azure Blog
Microsoft Azure Blog
P
Proofpoint News Feed
I
InfoQ
博客园 - 【当耐特】
NISL@THU
NISL@THU
A
About on SuperTechFans
T
Tailwind CSS Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
The Hacker News
The Hacker News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Scott Helme
Scott Helme
雷峰网
雷峰网
C
CXSECURITY Database RSS Feed - CXSecurity.com
Security Latest
Security Latest
V
Vulnerabilities – Threatpost
Security Archives - TechRepublic
Security Archives - TechRepublic
A
Arctic Wolf
Hacker News: Ask HN
Hacker News: Ask HN
N
News and Events Feed by Topic
IT之家
IT之家
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
aimingoo的专栏
aimingoo的专栏
T
Threat Research - Cisco Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
SecWiki News
SecWiki News
大猫的无限游戏
大猫的无限游戏
S
Security Affairs
The Register - Security
The Register - Security
www.infosecurity-magazine.com
www.infosecurity-magazine.com
L
LINUX DO - 热门话题
T
Tor Project blog

Workflow SDK Documentation

Patterns for Defining Tools Human-in-the-Loop Building Durable AI Agents Queueing User Messages Resumable Streams Sleep, Suspense, and Scheduling Streaming Updates from Tools API Reference Workflow Globals Changelog Resilient run start Cookbook Building a World Deploying Astro Express Fastify Hono Getting Started NestJS Next.js Nitro Nuxt Python SvelteKit Vite corrupted-event-log fetch-in-workflow hook-conflict Errors node-js-module-in-workflow serialization-failed start-invalid-workflow-function step-not-registered timeout-in-workflow webhook-invalid-respond-with-value webhook-response-not-sent workflow-not-registered Errors & Retrying Hooks & Webhooks Idempotency Foundations Starting Workflows Streaming Versioning Workflows and Steps How the Directives Work Encryption Event Sourcing Framework Integrations Understanding Directives Migration Guides Migrating from AWS Step Functions Migrating from Inngest Migrating from Temporal Observability Testing Server-Based Testing createHook createWebhook defineHook FatalError fetch getStepMetadata getWorkflowMetadata getWritable workflow RetryableError sleep @workflow/vitest DurableAgent @workflow/ai WorkflowChatTransport getHookByToken getRun getWorld workflow/api resumeHook resumeWebhook Chat Session Modeling runtime-decryption-failed Upgrading Workflows abort-signal-timeout-in-workflow Cancellation How Cancellation Works Internal Serializable AbortController and AbortSignal Eager Processing of Steps & Incremental Event Replay TanStack Start Agent Cancellation Sequential & Parallel Execution Workflow Composition Local World | Workflow SDK Postgres World | Workflow SDK Vercel World | Workflow SDK Migrating from trigger.dev Secure Credential Handling Local World | Workflow SDK Postgres World | Workflow SDK Vercel World | Workflow SDK
Serialization
2026-05-31 · via Workflow SDK Documentation

Understand how workflow data is serialized and persisted across suspensions and resumptions.

All function arguments and return values passed between workflow and step functions must be serializable. Workflow SDK uses a custom serialization system built on top of devalue. This system supports standard JSON types, as well as a few additional popular Web API types.

The serialization system ensures that all data persists correctly across workflow suspensions and resumptions, enabling durable execution.

The following types can be serialized and passed through workflow functions:

Standard JSON Types:

  • string
  • number
  • boolean
  • null
  • Arrays of serializable values
  • Objects with string keys and serializable values

Extended Types:

  • undefined
  • bigint
  • ArrayBuffer
  • BigInt64Array, BigUint64Array
  • Date
  • Float32Array, Float64Array
  • Int8Array, Int16Array, Int32Array
  • Map<Serializable, Serializable>
  • RegExp
  • Set<Serializable>
  • URL
  • URLSearchParams
  • Uint8Array, Uint8ClampedArray, Uint16Array, Uint32Array

Notable:

These types have special handling and are explained in detail in the sections below.

  • Headers
  • Request
  • Response
  • ReadableStream<Serializable>
  • WritableStream<Serializable>

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.

Incorrect:

export async function updateUserWorkflow(userId: string) {
  "use workflow";

  let user = { id: userId, name: "John", email: "john@example.com" };
  await updateUserStep(user);

  // user.email is still "john@example.com"
  console.log(user.email); 
}

async function updateUserStep(user: { id: string; name: string; email: string }) {
  "use step";
  user.email = "newemail@example.com"; // Changes are lost
}

Correct - return the modified data:

export async function updateUserWorkflow(userId: string) {
  "use workflow";

  let user = { id: userId, name: "John", email: "john@example.com" };
  user = await updateUserStep(user); // Reassign the return value

  console.log(user.email); // "newemail@example.com"
}

async function updateUserStep(user: { id: string; name: string; email: string }) {
  "use step";
  user.email = "newemail@example.com";
  return user; 
}

Custom Classes:

  • Class instances that implement WORKFLOW_SERIALIZE and WORKFLOW_DESERIALIZE

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.

For complete information about using streams in workflows, including patterns for AI streaming, file processing, and progress updates, see the Streaming Guide.

The Web API Request and Response APIs are supported by the serialization system, and can be passed around between workflow and step functions similarly to other data types.

As a convenience, these two APIs are treated slightly differently when used within a workflow function: calling the text() / json() / arrayBuffer() instance methods is automatically treated as a step function invocation. This allows you to consume the body directly in the workflow context while maintaining proper serialization and caching.

For example, consider how receiving a webhook request provides the entire Request instance into the workflow context. You may consume the body of that request directly in the workflow, which will be cached as a step result for future resumptions of the workflow:

import { createWebhook } from "workflow";

export async function handleWebhookWorkflow() {
  "use workflow";

  const webhook = createWebhook();
  const request = await webhook;

  // The body of the request will only be consumed once
  const body = await request.json(); 

  // …
}

Using fetch in Workflows

Because Request and Response are serializable, Workflow SDK provides a fetch function that can be used directly in workflow functions:

import { fetch } from "workflow"; 

export async function apiWorkflow() {
  "use workflow";

  // fetch can be called directly in workflows
  const response = await fetch("https://api.example.com/data"); 
  const data = await response.json();

  return data;
}

The implementation is straightforward - fetch from workflow is a step function that wraps the standard fetch:

export async function fetch(...args: Parameters<typeof globalThis.fetch>) {
  "use step";
  return globalThis.fetch(...args);
}

This allows you to make HTTP requests directly in workflow functions while maintaining deterministic replay behavior through automatic caching.

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.

Basic Example

import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde"; 

class Point {
  constructor(
    public x: number,
    public y: number
  ) {}

  // Define how to serialize an instance to plain data
  static [WORKFLOW_SERIALIZE](instance: Point) { 
    return { x: instance.x, y: instance.y }; 
  } 

  // Define how to reconstruct an instance from plain data
  static [WORKFLOW_DESERIALIZE](data: { x: number; y: number }) { 
    return new Point(data.x, data.y); 
  } 
}

Once you've implemented these methods, instances of your class can be passed between workflow and step functions:

import { Point } from "./custom-class";

export async function geometryWorkflow() {
  "use workflow";

  const point = new Point(10, 20);
  // Point is serialized automatically
  const doubled = await doublePoint(point); 

  console.log(doubled.x, doubled.y); // 20, 40
  return doubled;
}

async function doublePoint(point: Point) {
  "use step";
  // Returns a new Point instance
  return new Point(point.x * 2, point.y * 2); 
}

How It Works

  1. WORKFLOW_SERIALIZE: A static method that receives a class instance and returns serializable data (primitives, plain objects, arrays, etc.)

  2. WORKFLOW_DESERIALIZE: A static method that receives the serialized data and returns a new class instance

  3. Automatic Registration: The SWC compiler plugin automatically detects classes that implement these symbols and registers them for serialization. Each class receives a deterministic classId derived from its file path and class name, and is registered into the global Symbol.for("workflow-class-registry") registry at build time — no manual registration step is required

Requirements

WORKFLOW_SERIALIZE and WORKFLOW_DESERIALIZE must be implemented as static methods on the class. Defining them as instance methods is not supported.

  • The data returned by WORKFLOW_SERIALIZE must itself be serializable (see Supported Serializable Types)
  • Both symbols must be implemented together - a class with only one will not be serializable

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:

  • No Node.js-specific APIs (like fs, path, crypto, etc.)
  • No non-deterministic operations (like Math.random() or Date.now())
  • No external network calls

Keep these methods simple and focused on data transformation only.

Instance Methods as Steps

In practice, many classes have methods that need Node.js APIs, perform network calls, or interact with databases — operations that are not allowed in the "use workflow" execution context. You can make these methods workflow-compatible by adding "use step" to them. 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.

This requires the class to implement WORKFLOW_SERIALIZE and WORKFLOW_DESERIALIZE, so that the instance can be passed to the step execution context.

import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde"; 
import { db } from "../lib/db";

class Order {
  constructor(
    public id: string,
    public items: Map<string, number>,
    public createdAt: Date
  ) {}

  // Custom serialization — data must be serializable types
  static [WORKFLOW_SERIALIZE](instance: Order) { 
    return { 
      id: instance.id, 
      items: instance.items, // Map is serializable
      createdAt: instance.createdAt, // Date is serializable
    }; 
  } 

  static [WORKFLOW_DESERIALIZE](data: { 
    id: string; 
    items: Map<string, number>; 
    createdAt: Date; 
  }) { 
    return new Order(data.id, data.items, data.createdAt); 
  } 

  // Methods without "use step" run in the workflow context
  // and must follow the same constraints as workflow functions
  total(): number {
    let sum = 0;
    for (const quantity of this.items.values()) {
      sum += quantity;
    }
    return sum;
  }

  // Instance methods with "use step" run as step functions
  // with full Node.js access — `this` is automatically serialized
  async save(): Promise<void> {
    "use step"; 
    await db.orders.insert({ 
      id: this.id, 
      items: Object.fromEntries(this.items), 
      createdAt: this.createdAt, 
    }); 
  }

  async sendConfirmation(email: string): Promise<string> {
    "use step"; 
    const res = await fetch("https://api.example.com/email", { 
      method: "POST", 
      body: JSON.stringify({ 
        to: email, 
        orderId: this.id, 
        itemCount: this.items.size, 
      }), 
    }); 
    const { messageId } = await res.json();
    return messageId;
  }
}

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:

export async function processOrderWorkflow(
  orderId: string,
  items: Map<string, number>,
  email: string
) {
  "use workflow";

  const order = new Order(orderId, items, new Date()); 

  // Runs in the workflow context — no "use step" needed
  const itemCount = order.total(); 

  // Each "use step" instance method call runs as a separate step
  await order.save(); 
  const messageId = await order.sendConfirmation(email); 

  return { orderId, itemCount, messageId };
}

Note that 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:

export class Order {
  // ...

  async addItem(name: string, quantity: number): Promise<Order> {
    "use step";
    this.items.set(name, quantity);
    return this; 
  }
}
export async function processOrderWorkflow() {
  "use workflow";

  let order = new Order(orderId, items, new Date());

  // Re-assign to capture the updated instance
  order = await order.addItem("Widget", 3); 
}