mcpField
C
cloudflare-workflows

cloudflare-workflows

github.com/jezweb云与 DevOps1.1k341 次安装3 周前更新
代码仓库

关于 cloudflare-workflows

cloudflare-workflows 是一项在 Cloudflare Workers 上构建持久化工作流的技能,介绍步骤执行、重试、休眠、事件等待、测试及已记录问题的应对方法。

主要功能

  1. 01支持重试和超时的工作流步骤
  2. 02休眠与等待事件模式
  3. 03Vitest 测试指南
  4. 04重启期间的状态与副作用处理
  5. 05已记录平台问题的规避方法

适用场景

  1. 01实现可重试步骤组成的工作流
  2. 02暂停工作流并在事件到达后恢复
  3. 03使用 Vitest 测试工作流行为
  4. 04避免重启后重复执行副作用

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jezweb/claude-skills --skill cloudflare-workflows

安装后在 Claude Code 中输入/cloudflare-workflows确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

README.md
.claude-plugin/
plugin.json
references/
common-issues.md
workflow-patterns.md
rules/
cloudflare-workflows.md
templates/
basic-workflow.ts
scheduled-workflow.ts
worker-trigger.ts
workflow-with-events.ts
workflow-with-retries.ts

详细介绍

Cloudflare Workflows

状态:已可用于生产环境 ✅(自 2025 年 4 月起正式发布) 最后更新:2026-01-09 依赖项:cloudflare-worker-base(用于 Worker 设置) 最新版本:[email protected]、@cloudflare/[email protected]

近期更新(2025 年):

  • 2025 年 4 月:Workflows 正式发布——waitForEvent API、Vitest 测试、CPU 时间指标、4,500 个并发实例
  • 2025 年 10 月:实例创建速率提升 10 倍(每秒 100 个),并发数提升至 10,000
  • 2025 年限制:最多 1,024 个步骤,每步持久化状态 1MB(每个实例 100MB-1GB),事件载荷 1MB,CPU 时间最长 5 分钟
  • 测试:cloudflare:test 模块,包含 introspectWorkflowInstance、disableSleeps、mockStepResult、mockEvent 修饰符
  • 平台:等待中的实例不计入并发数,保留期限 3-30 天,子请求数 50-1,000

快速开始(5 分钟)

# 1. Scaffold project
npm create cloudflare@latest my-workflow -- --template cloudflare/workflows-starter --git --deploy false
cd my-workflow

# 2. Configure wrangler.jsonc
{
  "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)
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 */ });
  }
}

# 4. Deploy and test
npm run deploy
npx wrangler workflows instances list my-workflow

重要:继承 WorkflowEntrypoint,使用 step 方法实现 run(),并在 wrangler.jsonc 中配置绑定


已知问题预防

此技能可预防 Cloudflare Workflows 的 12 个已记录错误。

问题 #1:waitForEvent 超时后会跳过事件(本地开发环境)

错误:waitForEvent() 超时后发送的事件会被后续的 waitForEvent() 调用忽略 环境:仅限本地开发环境(wrangler dev)——生产环境中运行正常 来源:GitHub Issue #11740

原因:这是 miniflare 中的一个错误,已在生产环境中修复(2025 年 5 月),但尚未移植到本地模拟器。超时后,该实例的事件队列会损坏。

预防方法:

  • 在生产/预发布环境中测试 waitForEvent 超时场景,不要在本地开发环境中测试
  • 避免串联多个预期会超时的 waitForEvent() 调用

错误示例:

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)

状态:已知错误,miniflare 的修复尚未完成。


问题 #2:getPlatformProxy() 与 Workflow 绑定不兼容

错误:MiniflareCoreError [ERR_RUNTIME_FAILURE]: The Workers runtime failed to start 消息:Worker 的绑定引用了具有命名入口点的服务,但该服务没有此入口点 来源:GitHub Issue #9402

原因:wrangler 包中的 getPlatformProxy() 不支持 Workflow 绑定(与其处理 Durable Objects 的方式类似)。这会阻碍 Next.js 集成和本地 CLI 脚本运行。

预防方法:

  • 选项 1:使用 getPlatformProxy() 时注释掉 Workflow 绑定
  • 选项 2:创建不包含 workflows 的独立 wrangler.cli.jsonc,用于 CLI 脚本
  • 选项 3:直接通过已部署的 Worker 访问 Workflow 绑定,不要使用代理
// 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' });

状态:已知限制,计划修复(采用与 DO 类似的方式过滤 workflows)。


问题 #3:立即重定向后 Workflow 实例丢失(本地开发环境)

错误:已返回实例 ID,但查询时出现 instance.not_found 环境:仅限本地开发环境(wrangler dev)——生产环境中运行正常 来源:GitHub Issue #10806

原因:workflow.create() 后立即返回重定向,会导致请求在 Workflow 初始化完成前“软中止”(开发环境中为单线程执行)。

预防方法:使用 ctx.waitUntil() 确保 Workflow 初始化完成后再重定向:

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);
  }
};

状态:已在近期 wrangler 版本中修复(2025 年 9 月之后),但仍建议使用此变通方法以确保兼容性。


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

错误:[vitest-worker]: Timeout calling "resolveId" 环境:CI/CD 流水线(GitLab、GitHub Actions)——本地运行正常 来源:GitHub Issue #10600

原因:@cloudflare/vitest-pool-workers 在 CI 容器中存在资源限制问题,对工作流测试的影响比其他 worker 类型更大。

预防措施:

  1. 在 vitest 配置中增大 testTimeout:
    export default defineWorkersConfig({
      test: {
        testTimeout: 60_000 // Default: 5000ms
      }
    });
    
  2. 检查 CI 资源限制(CPU/内存)
  3. 如果不测试存储隔离,请使用 isolatedStorage: false
  4. 对于关键工作流,可以考虑针对已部署的实例进行测试,而不是使用 vitest

状态:已知问题,正在调查(内部:WOR-945)。


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

错误:调用 instance.restart() 或 instance.terminate() 时出现 Error: Not implemented yet 环境:仅限本地开发(wrangler dev)——生产环境运行正常 来源:GitHub Issue #11312

原因:miniflare 中尚未实现 Instance 管理 API。此外,即使工作流处于休眠状态,实例状态仍显示为 running。

预防措施:在本地开发支持上线之前,请在生产或预发布环境中测试实例生命周期管理(暂停/恢复/终止)。

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

状态:已知限制,本地开发支持暂无时间表。


问题 #6:I/O 必须在 step.do() 回调中执行

错误:"Cannot perform I/O on behalf of a different request" 来源:Cloudflare 运行时行为

原因:尝试从另一个请求处理程序中使用在某个请求上下文中创建的 I/O 对象。

预防措施:始终在 step.do() 回调中执行 I/O:

// ❌ 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();
});

问题 #7:NonRetryableError 在开发和生产环境中的行为不同

错误:NonRetryableError 使用空消息时,在开发模式下会触发重试,但在生产环境中运行正常 环境:开发环境特有的错误 来源:GitHub Issue #10113

原因:miniflare 与生产运行时对空错误消息的处理方式不同。

预防措施:始终为 NonRetryableError 提供消息:

// ❌ Retries in dev, exits in prod
throw new NonRetryableError('');

// ✅ Exits in both environments
throw new NonRetryableError('Validation failed');

状态:已知问题,已记录解决方法。


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

错误:在 step.do() 外声明的变量在休眠/挂起后会重置为初始值 来源:Cloudflare 工作流规则

原因:当引擎检测到没有待处理的工作时,工作流会进入休眠。休眠期间,所有内存状态都会丢失。

预防措施:仅使用 step.do() 返回的状态——其他所有内容都是临时的:

// ❌ 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
  }
}

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

错误:步骤不必要地重新运行,导致性能下降 来源:Cloudflare 工作流规则

原因:步骤名称充当缓存键。使用 Date.now()、Math.random() 或其他非确定性值,会导致每次运行都生成新的缓存键。

预防措施:使用静态、确定性的步骤名称:

// ❌ 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

问题 #10:在 step.do() 外使用 Promise.race/any 会导致不一致

错误:重启时解析的 Promise 不同,导致行为不一致 来源:Cloudflare 工作流规则

原因:步骤外的非确定性操作会在重启时再次运行,可能产生不同的结果。

预防措施:将所有非确定性逻辑放在 step.do() 内:

// ❌ 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

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

错误:工作流重启后出现重复日志、指标或操作 来源:Cloudflare Workflows 规则

原因:如果工作流在执行过程中重启,step.do() 外部的代码会执行多次。

预防方法:将日志记录、指标和其他副作用放入 step.do() 中:

// ❌ 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)
});

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

错误:步骤超时后出现重复扣款、重复数据库写入 来源:Cloudflare Workflows 规则

原因:步骤会单独重试。如果 API 调用成功,但步骤在返回前超时,重试时会再次调用 API。

预防方法:通过存在性检查来保护非幂等操作:

// ❌ 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 });
});

步骤方法

step.do() - 执行工作
step.do<T>(name: string, config?: WorkflowStepConfig, callback: () => Promise<T>): Promise<T>

参数:

  • name - 步骤名称(用于可观测性)
  • config(可选)- 重试配置(重试次数、超时、退避)
  • callback - 执行工作的异步函数

**返回值:**回调函数返回的值(必须可序列化)

示例:

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());
});

重要 - 序列化:

  • ✅ 允许:字符串、数字、布尔值、Array、Object、null
  • ❌ 禁止:Function、Symbol、循环引用、undefined
  • 如果返回值无法进行 JSON 序列化,则会抛出错误

step.sleep() - 相对时长休眠
step.sleep(name: string, duration: WorkflowDuration): Promise<void>

参数:

  • name - 步骤名称
  • duration - 数值(毫秒)或字符串:"second"、"minute"、"hour"、"day"、"week"、"month"、"year"(也接受复数形式)

示例:

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

**注意:**恢复执行的工作流优先于新实例。休眠不计入步骤限制。


step.sleepUntil() - 休眠至指定日期
step.sleepUntil(name: string, timestamp: Date | number): Promise<void>

参数:

  • name - 步骤名称
  • timestamp - Date 对象或 UNIX 时间戳(毫秒)

示例:

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() - 等待外部事件(2025 年 4 月 GA)
step.waitForEvent<T>(name: string, options: { type: string; timeout?: string | number }): Promise<T>

参数:

  • name - 步骤名称
  • options.type - 要匹配的事件类型
  • options.timeout(可选)- 最长等待时间(默认值:24 小时,最长:30 天)

**返回值:**通过 instance.sendEvent() 发送的事件负载

示例:

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');
    }
  }
};

超时处理:

try {
  const event = await step.waitForEvent('wait for user', { type: 'user-submitted', timeout: '10 minutes' });
} catch (error) {
  await step.do('send reminder', async () => { /* reminder */ });
}

WorkflowStepConfig

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
}

默认值:{ retries: { limit: 5, delay: 10000, backoff: 'exponential' }, timeout: '10 minutes' }

退避示例:

// 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 } }

错误处理

NonRetryableError

强制工作流立即失败,不进行重试:

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;
    });
  }
}

适用 NonRetryableError 的情况:

  • ✅ 身份验证/授权失败
  • ✅ 不会改变的无效输入
  • ✅ 资源不存在(404)
  • ✅ 验证错误
  • ❌ 网络故障(应该重试)
  • ❌ 速率限制(应该使用退避策略重试)
  • ❌ 暂时性服务中断(应该重试)

捕获错误以继续工作流

捕获可选步骤的错误,避免工作流失败:

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 */ });
  }
}

平稳降级:

let result;
try {
  result = await step.do('call primary API', async () => await callPrimaryAPI());
} catch {
  result = await step.do('call backup API', async () => await callBackupAPI());
}

触发工作流

配置绑定(wrangler.jsonc):

{
  "workflows": [{
    "name": "my-workflow",
    "binding": "MY_WORKFLOW",
    "class_name": "MyWorkflow",
    "script_name": "workflow-worker"  // If workflow in different Worker
  }]
}

从 Worker 触发:

const instance = await env.MY_WORKFLOW.create({ params: { userId: '123' } });
return Response.json({ id: instance.id, status: await instance.status() });

实例管理:

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();

状态持久化

工作流会自动持久化 step.do() 返回的状态:

✅ 可序列化:

  • 基本类型:string、number、boolean、null
  • 数组、对象、嵌套结构

❌ 不可序列化:

  • Function、Symbol、循环引用、undefined、类实例

示例:

// ✅ 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!

跨步骤访问状态:

const userData = await step.do('fetch user', async () => ({ id: 123, email: '[email protected]' }));
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}` }));

可观测性

内置指标(2025 年增强)

工作流会自动跟踪:

  • 实例状态:排队、运行中、已完成、出错、已暂停、等待中
  • 步骤执行情况:开始/结束时间、耗时、成功/失败
  • 重试历史:尝试次数、错误、延迟
  • 休眠状态:工作流将于何时唤醒
  • 输出:步骤和 run() 的返回值
  • CPU 时间(2025 年 4 月 GA):每个实例的活跃处理时间,可用于计费分析
在控制面板中查看指标

通过 Cloudflare 控制面板访问:

  1. Workers & Pages
  2. 选择你的工作流
  3. 查看实例和指标

指标包括:

  • 创建的实例总数
  • 成功率/错误率
  • 平均执行时间
  • 步骤级性能
  • CPU 时间用量(2025 年功能)
以编程方式访问
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 年):

// wrangler.jsonc
{ "limits": { "cpu_ms": 300000 } }  // 5 minutes max (default: 30 seconds)

限制(2025 年更新)

功能Workers FreeWorkers Paid
每个工作流的最大步骤数1,0241,024
每个步骤的最大状态1 MiB1 MiB
每个实例的最大状态100 MB1 GB
最大事件负载大小1 MiB1 MiB
最大 sleep/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 天
Workflow 名称长度上限64 个字符64 个字符
实例 ID 长度上限100 个字符100 个字符

重要说明:

  • step.sleep() 和 step.sleepUntil() 不计入 1,024 步骤的上限
  • 等待中的实例(休眠、重试或等待事件)不计入并发限制
  • 实例创建速率提升 10 倍(2025 年 10 月):每 10 秒 100 个 → 每秒 100 个
  • 并发数上限提高(2025 年 10 月):4,500 → 10,000 个并发实例
  • 状态持久化限制提高(2025 年):每个步骤从 128 KB 提高到 1 MiB,每个实例为 100 MB - 1 GB
  • 事件负载大小上限提高(2025 年):128 KB → 1 MiB
  • 可通过 wrangler.jsonc 配置 CPU 时间:{ "limits": { "cpu_ms": 300000 } }(最长 5 分钟)

定价

需要 Workers Paid 计划(每月 $5)

工作流执行:

  • 每月前 10,000,000 次步骤执行:免费
  • 超出后:每百万次步骤执行 $0.30

计为一次步骤执行的情况:

  • 每次 step.do() 调用
  • 每次步骤重试
  • step.sleep()、step.sleepUntil()、step.waitForEvent() 不计入

费用示例:

  • 包含 5 个步骤且无重试的工作流:5 次步骤执行
  • 包含 3 个步骤,其中 1 个步骤重试 2 次的工作流:5 次步骤执行(3 + 2)
  • 每月运行 10M 个简单工作流(每个 5 个步骤):((50M - 10M) / 1M) × $0.30 = 每月 $12

Vitest 测试(2025 年 4 月 GA)

工作流支持通过 cloudflare:test 模块进行完整的测试集成。

设置
npm install -D vitest@latest @cloudflare/vitest-pool-workers@latest

vitest.config.ts:

import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';
export default defineWorkersConfig({ test: { poolOptions: { workers: { miniflare: { bindings: { MY_WORKFLOW: { scriptName: 'workflow' } } } } } } });
自省 API
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
  }
});
测试修饰符
  • 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() 超时

官方文档:https://developers.cloudflare.com/workers/testing/vitest-integration/


相关文档


最后更新:2026-01-21 版本:2.0.0 变更:新增 12 项已记录的已知问题(TIER 1-2 研究发现):waitForEvent 超时错误、getPlatformProxy 故障、重定向导致实例丢失、Vitest CI 问题、本地开发限制、状态持久化规则、缓存注意事项和幂等模式 维护者:Jeremy Dawes | [email protected]

评论

支持 Markdown