cloudflare-workflows

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Cloudflare Workflows

Cloudflare Workflows

Status: Production Ready ✅ (GA since April 2025) Last Updated: 2026-01-09 Dependencies: cloudflare-worker-base (for Worker setup) Latest Versions: wrangler@4.58.0, @cloudflare/workers-types@4.20260109.0
Recent Updates (2025):
  • April 2025: Workflows GA release - waitForEvent API, Vitest testing, CPU time metrics, 4,500 concurrent instances
  • October 2025: Instance creation rate 10x faster (100/sec), concurrency increased to 10,000
  • 2025 Limits: Max steps 1,024, state persistence 1MB/step (100MB-1GB per instance), event payloads 1MB, CPU time 5 min max
  • Testing: cloudflare:test module with introspectWorkflowInstance, disableSleeps, mockStepResult, mockEvent modifiers
  • Platform: Waiting instances don't count toward concurrency, retention 3-30 days, subrequests 50-1,000

状态:已就绪可用于生产环境 ✅(2025年4月正式发布) 最后更新:2026-01-09 依赖项:cloudflare-worker-base(用于Worker设置) 最新版本:wrangler@4.58.0, @cloudflare/workers-types@4.20260109.0
2025年近期更新
  • 2025年4月:Workflows正式发布 - 新增waitForEvent API、Vitest测试、CPU时间指标、支持4500个并发实例
  • 2025年10月:实例创建速度提升10倍(每秒100个),并发上限提升至10000个
  • 2025年限制:最大步骤数1024,每步骤状态持久化1MB(每个实例100MB-1GB),事件负载1MB,最大CPU时间5分钟
  • 测试:cloudflare:test模块包含introspectWorkflowInstance、disableSleeps、mockStepResult、mockEvent等修饰器
  • 平台特性:等待状态的实例不计入并发数,状态保留3-30天,子请求数50-1000

Quick Start (5 Minutes)

快速开始(5分钟)

bash
undefined
bash
undefined

1. Scaffold project

1. 初始化项目

npm create cloudflare@latest my-workflow -- --template cloudflare/workflows-starter --git --deploy false cd my-workflow
npm create cloudflare@latest my-workflow -- --template cloudflare/workflows-starter --git --deploy false cd my-workflow

2. Configure wrangler.jsonc

2. 配置wrangler.jsonc

{ "name": "my-workflow", "main": "src/index.ts", "compatibility_date": "2025-11-25", "workflows": [{ "name": "my-workflow", "binding": "MY_WORKFLOW", "class_name": "MyWorkflow" }] }
{ "name": "my-workflow", "main": "src/index.ts", "compatibility_date": "2025-11-25", "workflows": [{ "name": "my-workflow", "binding": "MY_WORKFLOW", "class_name": "MyWorkflow" }] }

3. Create workflow (src/index.ts)

3. 创建工作流(src/index.ts)

import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> { async run(event: WorkflowEvent<Params>, step: WorkflowStep) { const result = await step.do('process', async () => { /* work / }); await step.sleep('wait', '1 hour'); await step.do('continue', async () => { / more work */ }); } }
import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> { async run(event: WorkflowEvent<Params>, step: WorkflowStep) { const result = await step.do('process', async () => { /* 业务逻辑 / }); await step.sleep('wait', '1 hour'); await step.do('continue', async () => { / 更多业务逻辑 */ }); } }

4. Deploy and test

4. 部署并测试

npm run deploy npx wrangler workflows instances list my-workflow

**CRITICAL**: Extends `WorkflowEntrypoint`, implements `run()` with `step` methods, bindings in wrangler.jsonc

---
npm run deploy npx wrangler workflows instances list my-workflow

**关键注意事项**:继承`WorkflowEntrypoint`,实现带有`step`方法的`run()`,在wrangler.jsonc中配置绑定

---

Known Issues Prevention

已知问题预防

This skill prevents 12 documented errors with Cloudflare Workflows.
本技能可预防Cloudflare Workflows的12种已记录错误。

Issue #1: waitForEvent Skips Events After Timeout in Local Dev

问题1:本地开发中waitForEvent超时后跳过事件

Error: Events sent after a
waitForEvent()
timeout are ignored in subsequent
waitForEvent()
calls Environment: Local development (
wrangler dev
) only - works correctly in production Source: GitHub Issue #11740
Why It Happens: Bug in miniflare that was fixed in production (May 2025) but not ported to local emulator. After a timeout, the event queue becomes corrupted for that instance.
Prevention:
  • Test waitForEvent timeout scenarios in production/staging, not local dev
  • Avoid chaining multiple
    waitForEvent()
    calls where timeouts are expected
Example of Bug:
typescript
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    for (let i = 0; i < 3; i++) {
      try {
        const evt = await step.waitForEvent(`wait-${i}`, {
          type: 'user-action',
          timeout: '5 seconds'
        });
        console.log(`Iteration ${i}: Received event`);
      } catch {
        console.log(`Iteration ${i}: Timeout`);
      }
    }
  }
}
// In wrangler dev:
// - Iteration 1: ✅ receives event
// - Iteration 2: ⏱️ times out (expected)
// - Iteration 3: ❌ does not receive event (BUG - event is sent but ignored)
Status: Known bug, fix pending for miniflare.

错误现象
waitForEvent()
超时后发送的事件会在后续
waitForEvent()
调用中被忽略 环境:仅本地开发环境(
wrangler dev
)- 生产环境正常 来源GitHub Issue #11740
原因:miniflare中的bug已在生产环境修复(2025年5月),但未移植到本地模拟器。超时后,该实例的事件队列会损坏。
预防方案
  • 在生产/预发布环境测试waitForEvent超时场景,不要在本地开发环境测试
  • 避免在预期会超时的场景中链式调用多个
    waitForEvent()
错误示例
typescript
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    for (let i = 0; i < 3; i++) {
      try {
        const evt = await step.waitForEvent(`wait-${i}`, {
          type: 'user-action',
          timeout: '5 seconds'
        });
        console.log(`${i}次循环:收到事件`);
      } catch {
        console.log(`${i}次循环:超时`);
      }
    }
  }
}
// 在wrangler dev中:
// - 第1次循环:✅ 收到事件
// - 第2次循环:⏱️ 超时(符合预期)
// - 第3次循环:❌ 未收到事件(BUG - 事件已发送但被忽略)
状态:已知bug,等待miniflare修复。

Issue #2: getPlatformProxy() Fails With Workflow Bindings

问题2:getPlatformProxy()与工作流绑定时失败

Error:
MiniflareCoreError [ERR_RUNTIME_FAILURE]: The Workers runtime failed to start
Message: Worker's binding refers to service with named entrypoint, but service has no such entrypoint Source: GitHub Issue #9402
Why It Happens:
getPlatformProxy()
from
wrangler
package doesn't support Workflow bindings (similar to how it handles Durable Objects). This blocks Next.js integration and local CLI scripts.
Prevention:
  • Option 1: Comment out workflow bindings when using
    getPlatformProxy()
  • Option 2: Create separate
    wrangler.cli.jsonc
    without workflows for CLI scripts
  • Option 3: Access workflow bindings directly via deployed worker, not proxy
typescript
// Workaround: Separate config for CLI scripts
// wrangler.cli.jsonc (no workflows)
{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2025-01-20"
  // workflows commented out
}

// Use in script:
import { getPlatformProxy } from 'wrangler';
const { env } = await getPlatformProxy({ configPath: './wrangler.cli.jsonc' });
Status: Known limitation, fix planned (filter workflows similar to DOs).

错误信息
MiniflareCoreError [ERR_RUNTIME_FAILURE]: The Workers runtime failed to start
提示信息:Worker的绑定引用了带有命名入口点的服务,但该服务没有此入口点 来源GitHub Issue #9402
原因:wrangler包中的
getPlatformProxy()
不支持工作流绑定(与Durable Objects的处理方式类似)。这会阻碍Next.js集成和本地CLI脚本。
预防方案
  • 方案1:使用
    getPlatformProxy()
    时注释掉工作流绑定
  • 方案2:创建单独的
    wrangler.cli.jsonc
    ,不含工作流配置供CLI脚本使用
  • 方案3:直接通过已部署的Worker访问工作流绑定,而非通过代理
typescript
// 临时解决方案:为CLI脚本使用单独配置
// wrangler.cli.jsonc(不含工作流)
{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2025-01-20"
  // 工作流配置已注释
}

// 在脚本中使用:
import { getPlatformProxy } from 'wrangler';
const { env } = await getPlatformProxy({ configPath: './wrangler.cli.jsonc' });
状态:已知限制,计划修复(与DOs类似过滤工作流)。

Issue #3: Workflow Instance Lost After Immediate Redirect (Local Dev)

问题3:本地开发中立即重定向后工作流实例丢失

Error: Instance ID returned but
instance.not_found
when queried Environment: Local development (
wrangler dev
) only - works correctly in production Source: GitHub Issue #10806
Why It Happens: Returning a redirect immediately after
workflow.create()
causes request to "soft abort" before workflow initialization completes (single-threaded execution in dev).
Prevention: Use
ctx.waitUntil()
to ensure workflow initialization completes before redirect:
typescript
export default {
  async fetch(req: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const workflow = await env.MY_WORKFLOW.create({ params: { userId: '123' } });

    // ✅ Ensure workflow initialization completes
    ctx.waitUntil(workflow.status());

    return Response.redirect('/dashboard', 302);
  }
};
Status: Fixed in recent wrangler versions (post-Sept 2025), but workaround still recommended for compatibility.

错误现象:返回实例ID,但查询时提示
instance.not_found
环境:仅本地开发环境(
wrangler dev
)- 生产环境正常 来源GitHub Issue #10806
原因:调用
workflow.create()
后立即返回重定向会导致请求在工作流初始化完成前"软中止"(开发环境为单线程执行)。
预防方案:使用
ctx.waitUntil()
确保工作流初始化完成后再重定向:
typescript
export default {
  async fetch(req: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const workflow = await env.MY_WORKFLOW.create({ params: { userId: '123' } });

    // ✅ 确保工作流初始化完成
    ctx.waitUntil(workflow.status());

    return Response.redirect('/dashboard', 302);
  }
};
状态:在近期wrangler版本(2025年9月后)已修复,但仍建议使用该兼容方案。

Issue #4: Vitest Tests Unreliable in CI Environments

问题4:CI环境中Vitest测试不可靠

Error:
[vitest-worker]: Timeout calling "resolveId"
Environment: CI/CD pipelines (GitLab, GitHub Actions) - works locally Source: GitHub Issue #10600
Why It Happens:
@cloudflare/vitest-pool-workers
has resource constraint issues in CI containers, affecting workflow tests more than other worker types.
Prevention:
  1. Increase
    testTimeout
    in vitest config:
    typescript
    export default defineWorkersConfig({
      test: {
        testTimeout: 60_000 // Default: 5000ms
      }
    });
  2. Check CI resource limits (CPU/memory)
  3. Use
    isolatedStorage: false
    if not testing storage isolation
  4. Consider testing against deployed instances instead of vitest for critical workflows
Status: Known issue, investigating (Internal: WOR-945).

错误信息
[vitest-worker]: Timeout calling "resolveId"
环境:CI/CD流水线(GitLab、GitHub Actions)- 本地环境正常 来源GitHub Issue #10600
原因
@cloudflare/vitest-pool-workers
在CI容器中存在资源限制问题,对工作流测试的影响大于其他Worker类型。
预防方案
  1. 在vitest配置中增加
    testTimeout
    typescript
    export default defineWorkersConfig({
      test: {
        testTimeout: 60_000 // 默认:5000ms
      }
    });
  2. 检查CI资源限制(CPU/内存)
  3. 若未测试存储隔离,使用
    isolatedStorage: false
  4. 对于关键工作流,考虑针对已部署实例测试而非使用vitest
状态:已知问题,正在调查(内部编号:WOR-945)。

Issue #5: Instance restart() and terminate() Not Implemented in Local Dev

问题5:本地开发中未实现instance restart()和terminate()

Error:
Error: Not implemented yet
when calling
instance.restart()
or
instance.terminate()
Environment: Local development (
wrangler dev
) only - works in production Source: GitHub Issue #11312
Why It Happens: Instance management APIs not yet implemented in miniflare. Additionally, instance status shows
running
even when workflow is sleeping.
Prevention: Test instance lifecycle management (pause/resume/terminate) in production or staging environment until local dev support is added.
typescript
const instance = await env.MY_WORKFLOW.get(instanceId);

// ❌ Fails in wrangler dev
await instance.restart();    // Error: Not implemented yet
await instance.terminate();  // Error: Not implemented yet

// ✅ Works in production
Status: Known limitation, no timeline for local dev support.

错误信息:调用
instance.restart()
instance.terminate()
时提示
Error: Not implemented yet
环境:仅本地开发环境(
wrangler dev
)- 生产环境正常 来源GitHub Issue #11312
原因:miniflare尚未实现实例管理API。此外,即使工作流处于睡眠状态,实例状态仍显示为
running
预防方案:在生产或预发布环境测试实例生命周期管理(暂停/恢复/终止),直到本地开发环境支持该功能。
typescript
const instance = await env.MY_WORKFLOW.get(instanceId);

// ❌ 在wrangler dev中失败
await instance.restart();    // 错误:Not implemented yet
await instance.terminate();  // 错误:Not implemented yet

// ✅ 在生产环境正常工作
状态:已知限制,暂无本地开发环境支持时间表。

Issue #6: I/O Must Be Inside step.do() Callbacks

问题6:I/O操作必须在step.do()回调内执行

Error:
"Cannot perform I/O on behalf of a different request"
Source: Cloudflare runtime behavior
Why It Happens: Trying to use I/O objects created in one request context from another request handler.
Prevention: Always perform I/O within
step.do()
callbacks:
typescript
// ❌ Bad - I/O outside step
const response = await fetch('https://api.example.com/data');
const data = await response.json();

await step.do('use data', async () => {
  return data;  // This will fail!
});

// ✅ Good - I/O inside step
const data = await step.do('fetch data', async () => {
  const response = await fetch('https://api.example.com/data');
  return await response.json();
});

错误信息
"Cannot perform I/O on behalf of a different request"
来源:Cloudflare运行时行为
原因:尝试在一个请求上下文中创建的I/O对象用于另一个请求处理程序。
预防方案:始终在
step.do()
回调内执行I/O操作:
typescript
// ❌ 错误示例 - I/O操作在step外执行
const response = await fetch('https://api.example.com/data');
const data = await response.json();

await step.do('use data', async () => {
  return data;  // 此操作会失败!
});

// ✅ 正确示例 - I/O操作在step内执行
const data = await step.do('fetch data', async () => {
  const response = await fetch('https://api.example.com/data');
  return await response.json();
});

Issue #7: NonRetryableError Behaves Differently in Dev vs Production

问题7:NonRetryableError在开发与生产环境行为不同

Error: NonRetryableError with empty message causes retries in dev mode but works correctly in production Environment: Development-specific bug Source: GitHub Issue #10113
Why It Happens: Empty error messages are handled differently between miniflare and production runtime.
Prevention: Always provide a message to NonRetryableError:
typescript
// ❌ Retries in dev, exits in prod
throw new NonRetryableError('');

// ✅ Exits in both environments
throw new NonRetryableError('Validation failed');
Status: Known issue, workaround documented.

错误现象:空消息的NonRetryableError在开发环境中会重试,但在生产环境中正常退出 环境:仅开发环境bug 来源GitHub Issue #10113
原因:miniflare与生产运行时对空错误消息的处理方式不同。
预防方案:始终为NonRetryableError提供错误消息:
typescript
// ❌ 开发环境重试,生产环境退出
throw new NonRetryableError('');

// ✅ 两种环境均正常退出
throw new NonRetryableError('验证失败');
状态:已知问题,已记录临时解决方案。

Issue #8: In-Memory State Lost on Hibernation

问题8:休眠时内存状态丢失

Error: Variables declared outside
step.do()
reset to initial values after sleep/hibernation Source: Cloudflare Workflows Rules
Why It Happens: Workflows hibernate when the engine detects no pending work. All in-memory state is lost during hibernation.
Prevention: Only use state returned from
step.do()
- everything else is ephemeral:
typescript
// ❌ BAD - In-memory variable lost on hibernation
let counter = 0;
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    counter = await step.do('increment', async () => counter + 1);
    await step.sleep('wait', '1 hour'); // ← Hibernates here, in-memory state lost
    console.log(counter); // ❌ Will be 0, not 1!
  }
}

// ✅ GOOD - State from step.do() return values persists
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    const counter = await step.do('increment', async () => 1);
    await step.sleep('wait', '1 hour');
    console.log(counter); // ✅ Still 1
  }
}

错误现象:在
step.do()
外声明的变量在睡眠/休眠后重置为初始值 来源Cloudflare Workflows规则
原因:当引擎检测到无待处理工作时,工作流会进入休眠状态。休眠期间所有内存状态都会丢失。
预防方案:仅使用
step.do()
返回的状态 - 其他所有状态都是临时的:
typescript
// ❌ 错误示例 - 内存变量在休眠时丢失
let counter = 0;
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    counter = await step.do('increment', async () => counter + 1);
    await step.sleep('wait', '1 hour'); // ← 此处进入休眠,内存状态丢失
    console.log(counter); // ❌ 结果为0,而非1!
  }
}

// ✅ 正确示例 - step.do()返回的状态会持久化
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    const counter = await step.do('increment', async () => 1);
    await step.sleep('wait', '1 hour');
    console.log(counter); // ✅ 结果仍为1
  }
}

Issue #9: Non-Deterministic Step Names Break Caching

问题9:非确定性步骤名称破坏缓存

Error: Steps re-run unnecessarily, performance degradation Source: Cloudflare Workflows Rules
Why It Happens: Step names act as cache keys. Using
Date.now()
,
Math.random()
, or other non-deterministic values causes new cache keys every run.
Prevention: Use static, deterministic step names:
typescript
// ❌ BAD - Non-deterministic step name
await step.do(`fetch-data-${Date.now()}`, async () => {
  return await fetchExpensiveData();
});
// Every execution creates new cache key → step always re-runs

// ✅ GOOD - Deterministic step name
await step.do('fetch-data', async () => {
  return await fetchExpensiveData();
});
// Same cache key → result reused on restart/retry

错误现象:步骤不必要地重新运行,导致性能下降 来源Cloudflare Workflows规则
原因:步骤名称作为缓存键。使用
Date.now()
Math.random()
或其他非确定性值会导致每次运行生成新的缓存键。
预防方案:使用静态、确定性的步骤名称:
typescript
// ❌ 错误示例 - 非确定性步骤名称
await step.do(`fetch-data-${Date.now()}`, async () => {
  return await fetchExpensiveData();
});
// 每次执行都会创建新的缓存键 → 步骤始终重新运行

// ✅ 正确示例 - 确定性步骤名称
await step.do('fetch-data', async () => {
  return await fetchExpensiveData();
});
// 使用相同的缓存键 → 重启/重试时复用结果

Issue #10: Promise.race/any Outside step.do() Causes Inconsistency

问题10:step.do()外的Promise.race/any导致不一致

Error: Different promises resolve on restart, inconsistent behavior Source: Cloudflare Workflows Rules
Why It Happens: Non-deterministic operations outside steps run again on restart, potentially with different results.
Prevention: Keep all non-deterministic logic inside
step.do()
:
typescript
// ❌ BAD - Race outside step
const fastest = await Promise.race([fetchA(), fetchB()]);
await step.do('use result', async () => fastest);
// On restart: race runs again, different promise might win

// ✅ GOOD - Race inside step
const fastest = await step.do('fetch fastest', async () => {
  return await Promise.race([fetchA(), fetchB()]);
});
// On restart: cached result used, consistent behavior

错误现象:重启后不同的Promise可能会resolve,导致行为不一致 来源Cloudflare Workflows规则
原因:步骤外的非确定性操作在重启时会再次运行,可能产生不同的结果。
预防方案:将所有非确定性逻辑放在
step.do()
内:
typescript
// ❌ 错误示例 - 竞争操作在step外执行
const fastest = await Promise.race([fetchA(), fetchB()]);
await step.do('use result', async () => fastest);
// 重启时:竞争操作再次运行,可能会有不同的Promise获胜

// ✅ 正确示例 - 竞争操作在step内执行
const fastest = await step.do('fetch fastest', async () => {
  return await Promise.race([fetchA(), fetchB()]);
});
// 重启时:使用缓存结果,行为一致

Issue #11: Side Effects Repeat on Restart

问题11:重启时副作用重复执行

Error: Duplicate logs, metrics, or operations after workflow restart Source: Cloudflare Workflows Rules
Why It Happens: Code outside
step.do()
executes multiple times if the workflow restarts mid-execution.
Prevention: Put logging, metrics, and other side effects inside
step.do()
:
typescript
// ❌ BAD - Side effect outside step
console.log('Workflow started'); // ← Logs multiple times on restart
await step.do('work', async () => { /* work */ });

// ✅ GOOD - Side effects inside step
await step.do('log start', async () => {
  console.log('Workflow started'); // ← Logs once (cached)
});

错误现象:工作流重启后出现重复日志、指标或操作 来源Cloudflare Workflows规则
原因:如果工作流在执行中途重启,
step.do()
外的代码会多次执行。
预防方案:将日志、指标和其他副作用放在
step.do()
内:
typescript
// ❌ 错误示例 - 副作用在step外执行
console.log('工作流已启动'); // ← 重启时会多次打印日志
await step.do('work', async () => { /* 业务逻辑 */ });

// ✅ 正确示例 - 副作用在step内执行
await step.do('log start', async () => {
  console.log('工作流已启动'); // ← 仅打印一次(已缓存)
});

Issue #12: Non-Idempotent Operations Can Repeat

问题12:非幂等操作可能重复执行

Error: Double charges, duplicate database writes after step timeout Source: Cloudflare Workflows Rules
Why It Happens: Steps retry individually. If an API call succeeds but the step times out before returning, the retry will call the API again.
Prevention: Guard non-idempotent operations with existence checks:
typescript
// ❌ BAD - Charge customer without check
await step.do('charge', async () => {
  return await stripe.charges.create({ amount: 1000, customer: customerId });
});
// If step times out after charge succeeds, retry charges AGAIN!

// ✅ GOOD - Check for existing charge first
await step.do('charge', async () => {
  const existing = await stripe.charges.list({ customer: customerId, limit: 1 });
  if (existing.data.length > 0) return existing.data[0]; // Idempotent
  return await stripe.charges.create({ amount: 1000, customer: customerId });
});

错误现象:步骤超时后出现重复收费、重复数据库写入等情况 来源Cloudflare Workflows规则
原因:步骤会单独重试。如果API调用成功但步骤在返回前超时,重试会再次调用该API。
预防方案:为非幂等操作添加存在性检查:
typescript
// ❌ 错误示例 - 未检查直接收费
await step.do('charge', async () => {
  return await stripe.charges.create({ amount: 1000, customer: customerId });
});
// 如果收费成功后步骤超时,重试会再次收费!

// ✅ 正确示例 - 先检查是否已存在收费记录
await step.do('charge', async () => {
  const existing = await stripe.charges.list({ customer: customerId, limit: 1 });
  if (existing.data.length > 0) return existing.data[0]; // 幂等操作
  return await stripe.charges.create({ amount: 1000, customer: customerId });
});

Step Methods

步骤方法

step.do() - Execute Work

step.do() - 执行业务逻辑

typescript
step.do<T>(name: string, config?: WorkflowStepConfig, callback: () => Promise<T>): Promise<T>
Parameters:
  • name
    - Step name (for observability)
  • config
    (optional) - Retry configuration (retries, timeout, backoff)
  • callback
    - Async function that does the work
Returns: Value from callback (must be serializable)
Example:
typescript
const result = await step.do('call API', { retries: { limit: 10, delay: '10s', backoff: 'exponential' }, timeout: '5 min' }, async () => {
  return await fetch('https://api.example.com/data').then(r => r.json());
});
CRITICAL - Serialization:
  • ✅ Allowed: string, number, boolean, Array, Object, null
  • ❌ Forbidden: Function, Symbol, circular references, undefined
  • Throws error if return value isn't JSON serializable

typescript
step.do<T>(name: string, config?: WorkflowStepConfig, callback: () => Promise<T>): Promise<T>
参数:
  • name
    - 步骤名称(用于可观测性)
  • config
    (可选)- 重试配置(retries、timeout、backoff)
  • callback
    - 执行业务逻辑的异步函数
返回值: 回调函数的返回值(必须可序列化)
示例:
typescript
const result = await step.do('call API', { retries: { limit: 10, delay: '10s', backoff: 'exponential' }, timeout: '5 min' }, async () => {
  return await fetch('https://api.example.com/data').then(r => r.json());
});
关键注意事项 - 序列化:
  • ✅ 允许类型:string、number、boolean、Array、Object、null
  • ❌ 禁止类型:Function、Symbol、循环引用、undefined
  • 如果返回值不可序列化为JSON,会抛出错误

step.sleep() - Relative Sleep

step.sleep() - 相对延迟

typescript
step.sleep(name: string, duration: WorkflowDuration): Promise<void>
Parameters:
  • name
    - Step name
  • duration
    - Number (ms) or string:
    "second"
    ,
    "minute"
    ,
    "hour"
    ,
    "day"
    ,
    "week"
    ,
    "month"
    ,
    "year"
    (plural forms accepted)
Examples:
typescript
await step.sleep('wait 5 minutes', '5 minutes');
await step.sleep('wait 1 hour', '1 hour');
await step.sleep('wait 2 days', '2 days');
await step.sleep('wait 30 seconds', 30000);  // milliseconds
Note: Resuming workflows take priority over new instances. Sleeps don't count toward step limits.

typescript
step.sleep(name: string, duration: WorkflowDuration): Promise<void>
参数:
  • name
    - 步骤名称
  • duration
    - 数字(毫秒)或字符串:
    "second"
    "minute"
    "hour"
    "day"
    "week"
    "month"
    "year"
    (支持复数形式)
示例:
typescript
await step.sleep('wait 5 minutes', '5 minutes');
await step.sleep('wait 1 hour', '1 hour');
await step.sleep('wait 2 days', '2 days');
await step.sleep('wait 30 seconds', 30000);  // 毫秒
注意:恢复工作流的优先级高于新实例。sleep操作不计入步骤限制。

step.sleepUntil() - Sleep to Specific Date

step.sleepUntil() - 延迟到指定时间

typescript
step.sleepUntil(name: string, timestamp: Date | number): Promise<void>
Parameters:
  • name
    - Step name
  • timestamp
    - Date object or UNIX timestamp (milliseconds)
Examples:
typescript
await step.sleepUntil('wait for launch', new Date('2025-12-25T00:00:00Z'));
await step.sleepUntil('wait until time', Date.parse('24 Oct 2024 13:00:00 UTC'));

typescript
step.sleepUntil(name: string, timestamp: Date | number): Promise<void>
参数:
  • name
    - 步骤名称
  • timestamp
    - Date对象或UNIX时间戳(毫秒)
示例:
typescript
await step.sleepUntil('wait for launch', new Date('2025-12-25T00:00:00Z'));
await step.sleepUntil('wait until time', Date.parse('24 Oct 2024 13:00:00 UTC'));

step.waitForEvent() - Wait for External Event (GA April 2025)

step.waitForEvent() - 等待外部事件(2025年4月正式发布)

typescript
step.waitForEvent<T>(name: string, options: { type: string; timeout?: string | number }): Promise<T>
Parameters:
  • name
    - Step name
  • options.type
    - Event type to match
  • options.timeout
    (optional) - Max wait time (default: 24 hours, max: 30 days)
Returns: Event payload sent via
instance.sendEvent()
Example:
typescript
export class PaymentWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    await step.do('create payment', async () => { /* Stripe API */ });

    const webhookData = await step.waitForEvent<StripeWebhook>(
      'wait for payment confirmation',
      { type: 'stripe-webhook', timeout: '1 hour' }
    );

    if (webhookData.status === 'succeeded') {
      await step.do('fulfill order', async () => { /* fulfill */ });
    }
  }
}

// Worker sends event to workflow
export default {
  async fetch(req: Request, env: Env): Promise<Response> {
    if (req.url.includes('/webhook/stripe')) {
      const instance = await env.PAYMENT_WORKFLOW.get(instanceId);
      await instance.sendEvent({ type: 'stripe-webhook', payload: await req.json() });
      return new Response('OK');
    }
  }
};
Timeout handling:
typescript
try {
  const event = await step.waitForEvent('wait for user', { type: 'user-submitted', timeout: '10 minutes' });
} catch (error) {
  await step.do('send reminder', async () => { /* reminder */ });
}

typescript
step.waitForEvent<T>(name: string, options: { type: string; timeout?: string | number }): Promise<T>
参数:
  • name
    - 步骤名称
  • options.type
    - 要匹配的事件类型
  • options.timeout
    (可选)- 最大等待时间(默认:24小时,最大值:30天)
返回值: 通过
instance.sendEvent()
发送的事件负载
示例:
typescript
export class PaymentWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    await step.do('create payment', async () => { /* Stripe API调用 */ });

    const webhookData = await step.waitForEvent<StripeWebhook>(
      'wait for payment confirmation',
      { type: 'stripe-webhook', timeout: '1 hour' }
    );

    if (webhookData.status === 'succeeded') {
      await step.do('fulfill order', async () => { /* 订单履约 */ });
    }
  }
}

// Worker向工作流发送事件
export default {
  async fetch(req: Request, env: Env): Promise<Response> {
    if (req.url.includes('/webhook/stripe')) {
      const instance = await env.PAYMENT_WORKFLOW.get(instanceId);
      await instance.sendEvent({ type: 'stripe-webhook', payload: await req.json() });
      return new Response('OK');
    }
  }
};
超时处理:
typescript
try {
  const event = await step.waitForEvent('wait for user', { type: 'user-submitted', timeout: '10 minutes' });
} catch (error) {
  await step.do('send reminder', async () => { /* 发送提醒 */ });
}

WorkflowStepConfig

WorkflowStepConfig

typescript
interface WorkflowStepConfig {
  retries?: {
    limit: number;          // Max attempts (Infinity allowed)
    delay: string | number; // Delay between retries
    backoff?: 'constant' | 'linear' | 'exponential';
  };
  timeout?: string | number; // Max time per attempt
}
Default:
{ retries: { limit: 5, delay: 10000, backoff: 'exponential' }, timeout: '10 minutes' }
Backoff Examples:
typescript
// Constant: 30s, 30s, 30s
{ retries: { limit: 3, delay: '30 seconds', backoff: 'constant' } }

// Linear: 1m, 2m, 3m, 4m, 5m
{ retries: { limit: 5, delay: '1 minute', backoff: 'linear' } }

// Exponential (recommended): 10s, 20s, 40s, 80s, 160s
{ retries: { limit: 10, delay: '10 seconds', backoff: 'exponential' }, timeout: '5 minutes' }

// Unlimited retries
{ retries: { limit: Infinity, delay: '1 minute', backoff: 'exponential' } }

// No retries
{ retries: { limit: 0 } }

typescript
interface WorkflowStepConfig {
  retries?: {
    limit: number;          // 最大尝试次数(允许Infinity)
    delay: string | number; // 重试间隔
    backoff?: 'constant' | 'linear' | 'exponential';
  };
  timeout?: string | number; // 每次尝试的最大时间
}
默认配置:
{ retries: { limit: 5, delay: 10000, backoff: 'exponential' }, timeout: '10 minutes' }
退避策略示例:
typescript
// 固定间隔:30s, 30s, 30s
{ retries: { limit: 3, delay: '30 seconds', backoff: 'constant' } }

// 线性增长:1m, 2m, 3m, 4m, 5m
{ retries: { limit: 5, delay: '1 minute', backoff: 'linear' } }

// 指数增长(推荐):10s, 20s, 40s, 80s, 160s
{ retries: { limit: 10, delay: '10 seconds', backoff: 'exponential' }, timeout: '5 minutes' }

// 无限重试
{ retries: { limit: Infinity, delay: '1 minute', backoff: 'exponential' } }

// 不重试
{ retries: { limit: 0 } }

Error Handling

错误处理

NonRetryableError

NonRetryableError

Force workflow to fail immediately without retrying:
typescript
import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';
import { NonRetryableError } from 'cloudflare:workflows';

export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    await step.do('validate input', async () => {
      if (!event.payload.userId) {
        throw new NonRetryableError('userId is required');
      }

      // Validate user exists
      const user = await this.env.DB.prepare(
        'SELECT * FROM users WHERE id = ?'
      ).bind(event.payload.userId).first();

      if (!user) {
        // Terminal error - retrying won't help
        throw new NonRetryableError('User not found');
      }

      return user;
    });
  }
}
When to use NonRetryableError:
  • ✅ Authentication/authorization failures
  • ✅ Invalid input that won't change
  • ✅ Resource doesn't exist (404)
  • ✅ Validation errors
  • ❌ Network failures (should retry)
  • ❌ Rate limits (should retry with backoff)
  • ❌ Temporary service outages (should retry)

强制工作流立即失败,不进行重试:
typescript
import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';
import { NonRetryableError } from 'cloudflare:workflows';

export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    await step.do('validate input', async () => {
      if (!event.payload.userId) {
        throw new NonRetryableError('userId是必填项');
      }

      // 验证用户是否存在
      const user = await this.env.DB.prepare(
        'SELECT * FROM users WHERE id = ?'
      ).bind(event.payload.userId).first();

      if (!user) {
        // 终端错误 - 重试无济于事
        throw new NonRetryableError('用户不存在');
      }

      return user;
    });
  }
}
NonRetryableError适用场景:
  • ✅ 认证/授权失败
  • ✅ 不会改变的无效输入
  • ✅ 资源不存在(404)
  • ✅ 验证错误
  • ❌ 网络故障(应重试)
  • ❌ 速率限制(应带退避策略重试)
  • ❌ 临时服务中断(应重试)

Catch Errors to Continue Workflow

捕获错误以继续工作流

Prevent workflow failure by catching optional step errors:
typescript
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    await step.do('process payment', async () => { /* critical */ });

    try {
      await step.do('send email', async () => { /* optional */ });
    } catch (error) {
      await step.do('log failure', async () => {
        await this.env.DB.prepare('INSERT INTO failed_emails VALUES (?, ?)').bind(event.payload.userId, error.message).run();
      });
    }

    await step.do('update status', async () => { /* continues */ });
  }
}
Graceful Degradation:
typescript
let result;
try {
  result = await step.do('call primary API', async () => await callPrimaryAPI());
} catch {
  result = await step.do('call backup API', async () => await callBackupAPI());
}

通过捕获可选步骤的错误来防止工作流失败:
typescript
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
    await step.do('process payment', async () => { /* 核心逻辑 */ });

    try {
      await step.do('send email', async () => { /* 可选逻辑 */ });
    } catch (error) {
      await step.do('log failure', async () => {
        await this.env.DB.prepare('INSERT INTO failed_emails VALUES (?, ?)').bind(event.payload.userId, error.message).run();
      });
    }

    await step.do('update status', async () => { /* 继续执行 */ });
  }
}
优雅降级:
typescript
let result;
try {
  result = await step.do('call primary API', async () => await callPrimaryAPI());
} catch {
  result = await step.do('call backup API', async () => await callBackupAPI());
}

Triggering Workflows

触发工作流

Configure binding (wrangler.jsonc):
jsonc
{
  "workflows": [{
    "name": "my-workflow",
    "binding": "MY_WORKFLOW",
    "class_name": "MyWorkflow",
    "script_name": "workflow-worker"  // If workflow in different Worker
  }]
}
Trigger from Worker:
typescript
const instance = await env.MY_WORKFLOW.create({ params: { userId: '123' } });
return Response.json({ id: instance.id, status: await instance.status() });
Instance Management:
typescript
const instance = await env.MY_WORKFLOW.get(instanceId);
const status = await instance.status();  // { status: 'running'|'complete'|'errored'|'queued', error, output }
await instance.sendEvent({ type: 'user-action', payload: { action: 'approved' } });
await instance.pause();
await instance.resume();
await instance.terminate();

配置绑定(wrangler.jsonc):
jsonc
{
  "workflows": [{
    "name": "my-workflow",
    "binding": "MY_WORKFLOW",
    "class_name": "MyWorkflow",
    "script_name": "workflow-worker"  // 如果工作流在不同的Worker中
  }]
}
从Worker触发:
typescript
const instance = await env.MY_WORKFLOW.create({ params: { userId: '123' } });
return Response.json({ id: instance.id, status: await instance.status() });
实例管理:
typescript
const instance = await env.MY_WORKFLOW.get(instanceId);
const status = await instance.status();  // { status: 'running'|'complete'|'errored'|'queued', error, output }
await instance.sendEvent({ type: 'user-action', payload: { action: 'approved' } });
await instance.pause();
await instance.resume();
await instance.terminate();

State Persistence

状态持久化

Workflows automatically persist state returned from
step.do()
:
✅ Serializable:
  • Primitives:
    string
    ,
    number
    ,
    boolean
    ,
    null
  • Arrays, Objects, Nested structures
❌ Non-Serializable:
  • Functions, Symbols, circular references, undefined, class instances
Example:
typescript
// ✅ Good
const result = await step.do('fetch data', async () => ({
  users: [{ id: 1, name: 'Alice' }],
  timestamp: Date.now(),
  metadata: null
}));

// ❌ Bad - function not serializable
const bad = await step.do('bad', async () => ({ data: [1, 2, 3], transform: (x) => x * 2 }));  // Throws error!
Access State Across Steps:
typescript
const userData = await step.do('fetch user', async () => ({ id: 123, email: 'user@example.com' }));
const orderData = await step.do('create order', async () => ({ userId: userData.id, orderId: 'ORD-456' }));
await step.do('send email', async () => sendEmail({ to: userData.email, subject: `Order ${orderData.orderId}` }));

工作流会自动持久化
step.do()
返回的状态:
✅ 可序列化类型:
  • 基本类型:
    string
    ,
    number
    ,
    boolean
    ,
    null
  • 数组、对象、嵌套结构
❌ 不可序列化类型:
  • 函数、Symbol、循环引用、undefined、类实例
示例:
typescript
// ✅ 正确示例
const result = await step.do('fetch data', async () => ({
  users: [{ id: 1, name: 'Alice' }],
  timestamp: Date.now(),
  metadata: null
}));

// ❌ 错误示例 - 函数不可序列化
const bad = await step.do('bad', async () => ({ data: [1, 2, 3], transform: (x) => x * 2 }));  // 会抛出错误!
跨步骤访问状态:
typescript
const userData = await step.do('fetch user', async () => ({ id: 123, email: 'user@example.com' }));
const orderData = await step.do('create order', async () => ({ userId: userData.id, orderId: 'ORD-456' }));
await step.do('send email', async () => sendEmail({ to: userData.email, subject: `Order ${orderData.orderId}` }));

Observability

可观测性

Built-in Metrics (Enhanced in 2025)

内置指标(2025年增强)

Workflows automatically track:
  • Instance status: queued, running, complete, errored, paused, waiting
  • Step execution: start/end times, duration, success/failure
  • Retry history: attempts, errors, delays
  • Sleep state: when workflow will wake up
  • Output: return values from steps and run()
  • CPU time (GA April 2025): Active processing time per instance for billing insights
工作流会自动跟踪:
  • 实例状态: 排队中、运行中、已完成、已错误、已暂停、等待中
  • 步骤执行: 开始/结束时间、耗时、成功/失败
  • 重试历史: 尝试次数、错误信息、延迟
  • 睡眠状态: 工作流唤醒时间
  • 输出: 步骤和run()的返回值
  • CPU时间(2025年4月正式发布): 每个实例的活跃处理时间,用于计费洞察

View Metrics in Dashboard

在控制台查看指标

Access via Cloudflare dashboard:
  1. Workers & Pages
  2. Select your workflow
  3. View instances and metrics
Metrics include:
  • Total instances created
  • Success/error rates
  • Average execution time
  • Step-level performance
  • CPU time consumption (2025 feature)
通过Cloudflare控制台访问:
  1. 进入Workers & Pages
  2. 选择你的工作流
  3. 查看实例和指标
指标包含:
  • 创建的实例总数
  • 成功/错误率
  • 平均执行时间
  • 步骤级性能
  • CPU时间消耗(2025年新增功能)

Programmatic Access

程序化访问

typescript
const instance = await env.MY_WORKFLOW.get(instanceId);
const status = await instance.status();

console.log(status);
// {
//   status: 'complete' | 'running' | 'errored' | 'queued' | 'waiting' | 'unknown',
//   error: string | null,
//   output: { userId: '123', status: 'processed' }
// }
CPU Time Configuration (2025):
jsonc
// wrangler.jsonc
{ "limits": { "cpu_ms": 300000 } }  // 5 minutes max (default: 30 seconds)

typescript
const instance = await env.MY_WORKFLOW.get(instanceId);
const status = await instance.status();

console.log(status);
// {
//   status: 'complete' | 'running' | 'errored' | 'queued' | 'waiting' | 'unknown',
//   error: string | null,
//   output: { userId: '123', status: 'processed' }
// }
CPU时间配置(2025年):
jsonc
// wrangler.jsonc
{ "limits": { "cpu_ms": 300000 } }  // 最大5分钟(默认:30秒)

Limits (Updated 2025)

限制(2025年更新)

FeatureWorkers FreeWorkers Paid
Max steps per workflow1,0241,024
Max state per step1 MiB1 MiB
Max state per instance100 MB1 GB
Max event payload size1 MiB1 MiB
Max sleep/sleepUntil duration365 days365 days
Max waitForEvent timeout365 days365 days
CPU time per step10 ms30 sec (default), 5 min (max)
Duration (wall clock) per stepUnlimitedUnlimited
Max workflow executions100,000/dayUnlimited
Concurrent instances2510,000 (Oct 2025, up from 4,500)
Instance creation rate100/second100/second (Oct 2025, 10x faster)
Max queued instances100,0001,000,000
Max subrequests per instance50/request1,000/request
Retention (completed state)3 days30 days
Max Workflow name length64 chars64 chars
Max instance ID length100 chars100 chars
CRITICAL Notes:
  • step.sleep()
    and
    step.sleepUntil()
    do NOT count toward 1,024 step limit
  • Waiting instances (sleeping, retrying, or waiting for events) do NOT count toward concurrency limits
  • Instance creation rate increased 10x (October 2025): 100 per 10 seconds → 100 per second
  • Max concurrency increased (October 2025): 4,500 → 10,000 concurrent instances
  • State persistence limits increased (2025): 128 KB → 1 MiB per step, 100 MB - 1 GB per instance
  • Event payload size increased (2025): 128 KB → 1 MiB
  • CPU time configurable via
    wrangler.jsonc
    :
    { "limits": { "cpu_ms": 300000 } }
    (5 min max)

特性Workers免费版Workers付费版
每个工作流的最大步骤数1,0241,024
每步骤的最大状态大小1 MiB1 MiB
每个实例的最大状态大小100 MB1 GB
最大事件负载大小1 MiB1 MiB
step.sleep()/step.sleepUntil()最大延迟时间365天365天
waitForEvent()最大超时时间365天365天
每步骤的CPU时间10 ms30秒(默认),5分钟(最大值)
每步骤的挂钟时间无限制无限制
每日最大工作流执行次数100,000无限制
并发实例数2510,000(2025年10月,从4,500提升)
实例创建速率100/秒100/秒(2025年10月,提升10倍)
最大排队实例数100,0001,000,000
每个实例的最大子请求数50/请求1,000/请求
已完成状态的保留时间3天30天
工作流名称最大长度64字符64字符
实例ID最大长度100字符100字符
关键注意事项:
  • step.sleep()
    step.sleepUntil()
    计入1024步骤限制
  • 等待状态的实例(睡眠、重试或等待事件)计入并发数限制
  • 实例创建速率在2025年10月提升10倍:从每10秒100个提升至每秒100个
  • 最大并发数在2025年10月提升:从4,500提升至10,000个并发实例
  • 状态持久化限制在2025年提升:每步骤从128 KB提升至1 MiB,每个实例从100 MB提升至1 GB
  • 事件负载大小在2025年提升:从128 KB提升至1 MiB
  • CPU时间可通过
    wrangler.jsonc
    配置:
    { "limits": { "cpu_ms": 300000 } }
    (最大5分钟)

Pricing

定价

Requires Workers Paid plan ($5/month)
Workflow Executions:
  • First 10,000,000 step executions/month: FREE
  • After that: $0.30 per million step executions
What counts as a step execution:
  • Each
    step.do()
    call
  • Each retry of a step
  • step.sleep()
    ,
    step.sleepUntil()
    ,
    step.waitForEvent()
    do NOT count
Cost examples:
  • Workflow with 5 steps, no retries: 5 step executions
  • Workflow with 3 steps, 1 step retries 2 times: 5 step executions (3 + 2)
  • 10M simple workflows/month (5 steps each): ((50M - 10M) / 1M) × $0.30 = $12/month
需要Workers付费计划(每月5美元)
工作流执行费用:
  • 每月前10,000,000次步骤执行:免费
  • 超出部分:每百万次步骤执行0.30美元
计入步骤执行的操作:
  • 每次
    step.do()
    调用
  • 步骤的每次重试
  • step.sleep()
    step.sleepUntil()
    step.waitForEvent()
    计入
费用示例:
  • 包含5个步骤、无重试的工作流:5次步骤执行
  • 包含3个步骤、其中1个步骤重试2次的工作流:5次步骤执行(3+2)
  • 每月1000万个简单工作流(每个5个步骤):((50,000,000 - 10,000,000) / 1,000,000) × 0.30 = 每月12美元

Vitest Testing (GA April 2025)

Vitest测试(2025年4月正式发布)

Workflows support full testing integration via
cloudflare:test
module.
工作流支持通过
cloudflare:test
模块进行完整测试集成。

Setup

安装配置

bash
npm install -D vitest@latest @cloudflare/vitest-pool-workers@latest
vitest.config.ts:
typescript
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';
export default defineWorkersConfig({ test: { poolOptions: { workers: { miniflare: { bindings: { MY_WORKFLOW: { scriptName: 'workflow' } } } } } } });
bash
npm install -D vitest@latest @cloudflare/vitest-pool-workers@latest
vitest.config.ts:
typescript
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';
export default defineWorkersConfig({ test: { poolOptions: { workers: { miniflare: { bindings: { MY_WORKFLOW: { scriptName: 'workflow' } } } } } });

Introspection API

内省API

typescript
import { env, introspectWorkflowInstance } from 'cloudflare:test';

it('should complete workflow', async () => {
  const instance = await introspectWorkflowInstance(env.MY_WORKFLOW, 'test-123');

  try {
    await instance.modify(async (m) => {
      await m.disableSleeps();  // Skip all sleeps
      await m.mockStepResult({ name: 'fetch data' }, { users: [{ id: 1 }] });  // Mock step result
      await m.mockEvent({ type: 'approval', payload: { approved: true } });  // Send mock event
      await m.mockStepError({ name: 'call API' }, new Error('Network timeout'), 1);  // Force error once
    });

    await env.MY_WORKFLOW.create({ id: 'test-123' });
    await expect(instance.waitForStatus('complete')).resolves.not.toThrow();
  } finally {
    await instance.dispose();  // Cleanup
  }
});
typescript
import { env, introspectWorkflowInstance } from 'cloudflare:test';

it('should complete workflow', async () => {
  const instance = await introspectWorkflowInstance(env.MY_WORKFLOW, 'test-123');

  try {
    await instance.modify(async (m) => {
      await m.disableSleeps();  // 跳过所有睡眠
      await m.mockStepResult({ name: 'fetch data' }, { users: [{ id: 1 }] });  // 模拟步骤结果
      await m.mockEvent({ type: 'approval', payload: { approved: true } });  // 发送模拟事件
      await m.mockStepError({ name: 'call API' }, new Error('Network timeout'), 1);  // 强制步骤报错一次
    });

    await env.MY_WORKFLOW.create({ id: 'test-123' });
    await expect(instance.waitForStatus('complete')).resolves.not.toThrow();
  } finally {
    await instance.dispose();  // 清理资源
  }
});

Test Modifiers

测试修饰器

  • disableSleeps(steps?)
    - Skip sleeps instantly
  • mockStepResult(step, result)
    - Mock step.do() result
  • mockStepError(step, error, times?)
    - Force step.do() to throw
  • mockEvent(event)
    - Send mock event to step.waitForEvent()
  • forceStepTimeout(step, times?)
    - Force step.do() timeout
  • forceEventTimeout(step)
    - Force step.waitForEvent() timeout

  • disableSleeps(steps?)
    - 立即跳过所有睡眠
  • mockStepResult(step, result)
    - 模拟step.do()的结果
  • mockStepError(step, error, times?)
    - 强制step.do()抛出错误
  • mockEvent(event)
    - 向step.waitForEvent()发送模拟事件
  • forceStepTimeout(step, times?)
    - 强制step.do()超时
  • forceEventTimeout(step)
    - 强制step.waitForEvent()超时

Related Documentation

相关文档


Last Updated: 2026-01-21 Version: 2.0.0 Changes: Added 12 documented Known Issues (TIER 1-2 research findings): waitForEvent timeout bug, getPlatformProxy failure, redirect instance loss, Vitest CI issues, local dev limitations, state persistence rules, caching gotchas, and idempotency patterns Maintainer: Jeremy Dawes | jeremy@jezweb.net

最后更新: 2026-01-21 版本: 2.0.0 变更: 新增12种已知问题(TIER 1-2研究成果):waitForEvent超时bug、getPlatformProxy失败、重定向实例丢失、Vitest CI问题、本地开发限制、状态持久化规则、缓存陷阱、幂等模式 维护者: Jeremy Dawes | jeremy@jezweb.net