@corbits/cron

中文文档
0.2.2发布于 2026/10/20LGPL-2.1-onlyTypeScriptES Module近 30 天下载 3263

Cron schedules for an Interchange hub: grant-gated schedule routes plus a ticker that turns a due row into run-trigger mail

原始使用文档
文档目录13 节
  1. Why @corbits/cron?
  2. Install
  3. Quickstart
  4. Where it fits
  5. Reference
  6. createCronRoutes(deps)
  7. Routes
  8. createCronTicker(opts)
  9. runCronMigrations(dbConfig, { schema })
  10. Other exports
  11. Using with Interchange
  12. Upgrading from 0.1
  13. License

@corbits/cron

A Corbits hub module that wakes Interchange agents on five-field UTC cron schedules, mounted as @intx/hub-api routes on the hub (Interchange's multi-tenant control plane) and stored in its Postgres. A ticker mails each due schedule to its agent's current run, the running instance of the agent's workflow definition.

Schedules support create, list, pause, resume and delete. There is no edit: delete and recreate. An expression must be able to fire (no Feb 30), at most 256 characters and 64 comma clauses.

Why @corbits/cron?

  1. Schedules follow the agent, not the run. A schedule names an agent by its definition name, so it survives redeploys. It waits while the agent has no current run and stops for good when the agent is deleted.
  2. Mounts like any hub route. createCronRoutes returns a Hono<TenantEnv> sub-app. Every route runs through the hub's requireGrant, the check that the calling principal (a user or agent account) holds a grant (a permission on a resource).
  3. Safe with several hub replicas. Tickers claim due rows with FOR UPDATE SKIP LOCKED, so no schedule fires twice. Migrations are idempotent and run under an advisory lock on every boot.

Install

bun add @corbits/cron @intx/authz @intx/db @intx/hub-api drizzle-orm hono postgres

Runs on Node >= 24.

Quickstart

import { nextCronFireAfter } from "@corbits/cron";

console.log(nextCronFireAfter("0 9 * * *", new Date()));

Prints the next 09:00 UTC, the next time a schedule with that expression fires.

Where it fits

Interchange runs AI agents as principals with their own identity, permissions and credentials. Its hub holds tenants, principals, grants and workflow runs.

  • Runs in: the hub, as routes on its Hono app, a ticker in its process and a table in its Postgres.
  • Plugs into: @intx/hub-api routes and grants, and @intx/db (its DBConfig, its tenant table as FK target, and its workflow definitions and runs).
  • Pairs with: @corbits/webhooks, whose run-trigger deliverer sends the mail, and the other Corbits hub modules, @corbits/mailbox and @corbits/artifacts.

Reference

createCronRoutes(deps)

depsTypeWhat the host provides
dbCronDbThe hub's drizzle handle.
requireGrantRequireGrantFrom @intx/hub-api's createRequireGrant.

Routes

Paths are relative to where the host mounts the sub-app.

MethodPathGrantPurpose
GET/cron-schedule:* read{ schedules: [...] } for the caller's tenant.
POST/cron-schedule:* createCreate from { expression, definitionName, subject, body }. 201 { schedule }; 400 invalid_body, invalid_expression or unknown_definition.
POST/:id/pausecron-schedule:<id> manageStop the ticker firing it. { schedule } with enabled: false; 404 not_found.
POST/:id/resumecron-schedule:<id> manageFire again from the next matching minute after now. { schedule } with enabled: true; 404 not_found.
DELETE/:idcron-schedule:<id> manageDelete one schedule. { ok: true }; 404 not_found when it is not in the caller's tenant.

createCronTicker(opts)

Returns { start(), stop() }.

Delivery is at most once per due minute. Each tick first commits lastFiredAt on every due row it claims, then calls deliver outside the row lock. A process that dies mid-batch, or a deliver that throws, drops that minute's fire rather than repeating it; the schedule fires again at its next matching minute. Stop and waiting callbacks run after their change commits.

optsTypePurpose
dbCronDbThe hub's drizzle handle.
deliverDeliverCronMailSends one due schedule's mail. createRunTriggerCronDeliver(deliverer) builds it.
intervalMsnumber (optional)Poll period. Defaults to 60 000.
onTickError(error) => void (optional)A tick failed before delivering, such as a lost connection. The next tick retries.
onDeliveryError(error, schedule) => void (optional)One delivery failed.
onScheduleWaiting(schedule) => void (optional)A schedule started waiting for its agent's next run.
onScheduleStopped(schedule) => void (optional)A schedule stopped: reason is agent_deleted, or invalid_expression for a row that can never fire.

A deliverer throws an error carrying RUN_GRANTS_NOT_ROUTABLE or RUN_MAIL_NOT_ROUTABLE (checked with isUnroutableRunTrigger) when a run's address is dead. If that run's sidecar is already released, the ticker marks the run failed so the schedule waits for the agent's next run.

runCronMigrations(dbConfig, { schema })

From @corbits/cron/migrations. Takes the same arguments as @intx/db's runMigrations. schema holds the host's tenant table, which must exist first. The schedule table always lives in the cron schema. Call it on every boot.

Other exports

ExportUse
isValidCronExpression(expression)Syntax, range and cap check. POST / also requires a possible fire.
nextCronFireAfter(expression, after)The next matching UTC minute after after.
isUnroutableRunTrigger(error), RUN_GRANTS_NOT_ROUTABLE, RUN_MAIL_NOT_ROUTABLEThe dead-address error contract a deliverer follows.
CronRoutesDeps, CreateCronTickerOpts, CronTicker, CronDb, DeliverCronMail, RunTriggerDeliverer, UnroutableRunTriggerTypes for the above.

Using with Interchange

Run the migrations on every hub boot and build the routes:

import { timeWindowEvaluator } from "@intx/authz";
import { createDB, createGrantStore, runMigrations } from "@intx/db";
import { createRequireGrant } from "@intx/hub-api";
import { createCronRoutes } from "@corbits/cron";
import { runCronMigrations } from "@corbits/cron/migrations";

const dbConfig = {
  host: "localhost",
  port: 5432,
  user: "postgres",
  password: "postgres",
  database: "interchange",
};
await runMigrations(dbConfig, { schema: "public" });
await runCronMigrations(dbConfig, { schema: "public" });
const { db } = createDB(dbConfig);

export const cronRoutes = createCronRoutes({
  db,
  requireGrant: createRequireGrant({
    grantStore: createGrantStore(db),
    conditionRegistry: { time_window: timeWindowEvaluator },
  }),
});

Mount cronRoutes at /cron under /api/tenants/:tenantId, behind the hub's auth and tenant middleware. Grant each principal cron-schedule:* with read and create to list and create, and manage (on cron-schedule:* or one cron-schedule:<id>) to delete. POST /cron with { "expression": "0 9 * * *", "definitionName": "daily-report", "subject": "Run", "body": "go" } then mails the daily-report agent every day at 09:00 UTC.

Start one ticker per hub process with createCronTicker({ db, deliver: createRunTriggerCronDeliver(deliverer) }), where deliverer is the hub's run-trigger deliverer from @corbits/webhooks' createRunTriggerDeliverer. Call ticker.stop() on shutdown. At fire time the ticker mails <run id>@<tenant domain> for the agent's current run, so the agent's workflow needs a mail trigger. Replicas share the table safely.

Upgrading from 0.1

  • mountCron(app, opts) is replaced by app.route(path, createCronRoutes({ db, requireGrant })). Routes are no longer at /api/tenants/:tenantId/cron, but wherever the host mounts them.
  • requireTenantMember is gone. Every route checks a cron-schedule grant, so principals need those grants before calling it, or they get 403.
  • applyCronMigrations(url, { tenantSchema }) is now runCronMigrations(dbConfig, { schema }), imported from @corbits/cron/migrations. Pass the old tenantSchema value as schema.
  • cronScheduleTable, CRON_FIELD_RANGES, cronExpressionCanFire, isValidTimeZone, zonedParts, MAX_LOOKAHEAD_MINUTES, resolveLiveDeployment, definitionExists, failStaleAnchorRun and the types CronField, ZonedParts, LiveDeployment, MountCronOpts, RequireTenantMember are no longer exported.
  • @intx/db, @intx/hub-api, drizzle-orm, hono and postgres are now peers.
  • createCronTicker's intervalMs is optional and defaults to one minute.
  • Existing databases upgrade on first boot with no manual step. Every 0.1.0 schedule keeps its state and keeps firing.

License

LGPL-2.1-only. See LICENSE.

版本与发布记录

@corbits/cron 共发布 4 个版本,首次发布于 2026/09/25;过去 12 个月发布了 4 个版本。

版本发布日期距今
0.2.2latest2026/10/2021 天前
0.2.12026/10/011 个月前
0.2.02026/09/281 个月前
0.1.02026/09/251 个月前

关于 @corbits/cron 的常见问题

@corbits/cron 是什么?

Cron schedules for an Interchange hub: grant-gated schedule routes plus a ticker that turns a due row into run-trigger mail

如何安装 @corbits/cron?

在项目目录运行 npm install @corbits/cron,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:hono(^4.13.8)、@intx/db(^0.5.0)、postgres(^3.4.9)、drizzle-orm(^0.45.3)、@intx/hub-api(^0.5.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@corbits/cron 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@corbits/cron 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@corbits/cron 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=24。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@corbits/cron 可以商用吗?

@corbits/cron 使用 LGPL-2.1-only 许可。弱 Copyleft:作为库被调用时通常不要求开源你自己的代码,但修改库本身需以 LGPL 开源;前端打包合并后的适用边界存在不同理解。以上为概括,不构成法律意见,请以许可原文为准。

@corbits/cron 的下载量有多少?

根据 npm 官方下载统计,@corbits/cron 在 2026/10/11 – 2026/11/09 这 30 天内被下载 3,263 次,日均约 109 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@corbits/cron 还在维护吗?

最新版本 0.2.2 发布于 2026/10/20(21 天前);至今共发布 4 个版本,其中过去 12 个月发布了 4 个;首次发布于 2026/09/25。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。