durable-routine
@asm/durable-routine 提供日历计算和可恢复的任务执行。宿主保存定义、安排投递,并在中断后重新投递;包不会启动轮询服务。
计算下次执行时刻
Section titled “计算下次执行时刻”import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";import { nextRoutineOccurrence, withTenantContext,} from "@asm/durable-routine";
const definition = { tenantId: "demo-tenant", scopeId: "demo-store", routineId: "inventory.refresh", profile: "inventory", schedule: { hour: 7, minute: 15, daysOfWeek: [1, 2, 3, 4, 5], timezone: "Asia/Tokyo", }, staggerWindowMs: 0,};const context = withTenantContext(definition, BACKGROUND_CONTEXT);const occurrence = nextRoutineOccurrence(definition, Date.now(), context);console.log(occurrence.scheduledAt, occurrence.fireAt, occurrence.runKey);scheduledAt 是日历上的名义时间,fireAt 是加入错峰偏移后的实际触发时间。宿主在 fireAt 投递,重试始终保留原 scheduledAt,不能用重试时的 Date.now() 替代。
小时为 0–23,分钟为 0–59,星期日为 0。daysOfWeek 和 daysOfMonth 同时存在时都必须匹配。夏令时跳过不存在的时刻,重复时刻只取第一次。默认错峰窗口为 30 分钟,偏移由租户、范围和 Routine 身份确定;设为 0 可关闭。
执行已注册的任务
Section titled “执行已注册的任务”接续上面的变量。宿主提供 redis、openHarness 和用 Durable defineTask 定义的 inventoryTask:
import { RedisRoutineLock, RoutineRunner, SqliteRoutineRunStore,} from "@asm/durable-routine";
const store = new SqliteRoutineRunStore("./routine-runs.sqlite");const runner = new RoutineRunner({ store, lock: new RedisRoutineLock(redis), openHarness,});try { const result = await runner.run({ ...definition, scheduledAt: occurrence.scheduledAt, task: inventoryTask, input: null, }, context); console.log(result.kind);} finally { await store.close();}运行记录可换成 MemoryRoutineRunStore 或 PostgresRoutineRunStore。PostgreSQL 版本需要先由迁移角色执行导出的 POSTGRES_ROUTINE_SCHEMA,再传入连接池。运行记录库和 Durable 任务存储是两份不同职责的数据。
openHarness 必须保证什么
Section titled “openHarness 必须保证什么”openHarness(run, context) 每次返回一个新的、暂停状态的 Harness,并重新注册任务定义。同一次运行必须重开同一份持久存储,且在 Harness 关闭前独占它。
例如两次不同日期的库存刷新使用不同运行键。它们可以并发,但不能仅凭“属于同一门店”就共用一个未加保护的 Durable 存储。PostgreSQL 可用 sessionKey: "routine:" + run.runKey 分开命名空间;同一运行重投时仍使用原键。
Runner 首次将会话、根任务和绑定关系写入同一次 Durable 提交。中断后重新读取绑定并等待原任务,不再新建根任务。Runner 会关闭它打开的 Harness;宿主关闭运行记录库、Redis 和数据库池。
处理结果与重试
Section titled “处理结果与重试”result.kind |
含义 |
|---|---|
succeeded |
Durable 任务已完成,结果在 run.outcome |
failed |
Durable 任务到达失败或中止的终态 |
skipped |
该次运行已是终态,无需再打开 Harness;检查 run.outcome |
locked |
同一运行仍持有 Redis 锁,保留投递以便稍后重试 |
基础设施异常会抛出,运行记录保留为 running。Redis 故障不会触发备用执行路径。RedisRoutineLock 接受 sendCommand(string[]) 适配器,默认 TTL 为 30 秒、命令超时为 5 秒。
检查点只保证已提交阶段的恢复。数据库之外的邮件、付款或其他外部副作用仍需宿主实现幂等请求。