客户端和服务端工作流共享同一种 JSON 格式和同一个解释器 @malla/engine。唯一不同的是运行每个步骤的适配器。工作流是解释执行的,从不编译:发布时存储一个不可变的版本,由一个 runner 为所有站点提供服务。
三种适配器
浏览器已发布站点和画布预览。sleep 是一个定时器;waitForEvent 不可用。
同步在 runner 内联运行,用于端点和短任务。在进程内重试;持久化步骤会抛出 DurableRequired。
持久化Cloudflare Workflows(SiteWorkflow):sleep、waitForEvent、带退避的重试、重放。
ts
interface StepAdapter {
do<T>(name: string, retry: RetryPolicy | undefined, fn: () => Promise<T>): Promise<T>
sleep(name: string, duration: string): Promise<void>
waitForEvent(name: string, opts: { type: string; timeout?: string }): Promise<unknown>
}
步骤格式
ts
interface WorkflowStep {
id: string
action: string // core.<name> or <pluginId>.<name>
params?: Record<string, unknown> // values may be Bindings at any depth
branches?: Record<string, WorkflowStep[]> // then/else, body, try/catch, cases, lanes a…h
disabled?: boolean
retry?: { limit: number; delay?: string; backoff?: 'constant' | 'linear' | 'exponential' }
}
每个步骤的结果存储在 steps[id] 中。控制流把步骤列表嵌套在 branches 中。当且仅当服务端工作流包含一个已启用的 core.sleep 或 core.waitForEvent 时,它才是持久化的。
重放规则
Cloudflare Workflows 会重新执行代码,并为已完成的步骤返回缓存的结果,因此:
- 步骤名称是确定性的路径
<branch>/<loop index>/<step>,这样循环和分支能正确重放;并行通道会加上<name>/<lane>/<step>。 - 步骤之外的一切都是确定性的;
fn.now()在步骤内部运行。 - 工作流版本固定在运行参数中,因此进行中的运行绝不会看到之后的编辑。
- 保存在历史中的步骤结果,每个最多 16 KB。
Runner 接线
text
apps/runner (Worker)
├─ POST /run/:siteId/:workflowId test and editor runs (api)
├─ ALL /api/* endpoints + webhooks (sites)
├─ Workflows binding → SiteWorkflow durable runs
├─ Durable Objects → SiteScheduler (cron alarms), RunStream (live test runs),
│ SiteDatabase (tables, visitors)
└─ Worker Loader → runJs isolates (no bindings, no secrets)
已发布的服务端工作流连同其 HTTP 路由和定时计划,存放在 KV 的 wf:<environment>:<siteId> 下。每次发布都会重新安排站点的定时任务。
限制
| 客户端 | 服务端 | |
|---|---|---|
| 每次运行的步骤数 | 200 | 1,000,按套餐可能更低 |
| 嵌套深度 | 8 | 8 |
| 循环次数 | 1,000 | 1,000 |
| 每步重试次数 | — | 5 |
| 同步运行超时 | — | 30 秒 |
| 并行通道 / switch 分支 | 8 / 32 | 8 / 32 |