@krizaka/outbox

中文文档
0.1.4发布于 2026/11/01Apache-2.0TypeScriptES Module构建来源已验证

每个Krizaka产品的事务发件箱: 在调用者的事务中写入的事件,在提交后至少由租用的工作人员交付一次,具有加倍的重试,死信和一个幂等的消费者助手。没有经纪人,没有数据库客户

中文使用文档

@krizaka /发件箱

每个Krizaka产品的事务处理箱。一个事件是 写在调用者的事务中,因此它恰好在工作提交时存在,并且是 在提交之后提交,至少一次,由工人租用一批。无代理:PostgreSQL和 FOR UPDATE SKIP LOCKED 在实例之间共享工作。它取代了Akinaza的 mail_outbox (其 flushOutbox 没有人打电话),分析人员将产品发送出去;它是的TypeScript版本 krizaka-messaging的 krizaka_outbox (信封是 id, type, occurredAt, payload)。

保证是 至少一次. 消费者在消息id上必须是幂等的。 在租赁有效期间,债权是排他性的。一个工人交付,然后崩溃之前 complete 让租约到期,并传递消息 再次。outbox从不承诺 “一次”。

import { emitMigration, transaction } from "@krizaka/db";
import { consumeOnce, createOutbox, outboxSchema, runWorker } from "@krizaka/outbox";
import { readOutboxSettings } from "@krizaka/outbox/settings";

emitMigration(outboxSchema, 1, { tables: {} }); // commit the SQL once, in your migrations: outbox.message and outbox.processed

const outbox = createOutbox({ clock: { now: () => new Date() }, settings: () => readOutboxSettings((key) => settings.get(key)) });

// 1. in the use case: the message commits or rolls back with the work
await transaction(connect, async (tx) => {
  const redemption = await coupons.redeem(tx, { code, accountId });
  await outbox.append(tx, { topic: "credits", type: "evt.coupon.redeemed.v1", payload: { redemptionId: redemption.id }, id: redemption.eventId });
});

// 2. a worker the product starts (Next.js instrumentation.ts); the package has no timer, you pass `sleep`
const stop = new AbortController();
void runWorker({
  outbox, query: pool.query.bind(pool) /* a pool-level Query */, owner: `${hostname}:${process.pid}`, topic: "credits", signal: stop.signal,
  sleep: (seconds) => new Promise((resolve) => setTimeout(resolve, seconds * 1000)),
  deliver: async (message) => { /* throw to retry */ },
});

// 3. a consumer with an effect in the database applies it once per message id, in the effect's transaction
await transaction(connect, (tx) => consumeOnce(tx, "credits", message.id, clock.now(), () => credits.grant(tx, …)));
  • append(tx, { topic, type, payload, id?, availableAt? }) 取交易的 Query 首先。一个给定的 id 使追加幂等 (同一事实两次是一个消息)。 type 是路由密钥, evt.<module>.<fact>.v1 像Java的。
  • claim(query, { owner, topic? }) 是 UPDATE … WHERE id IN (SELECT … FOR UPDATE SKIP LOCKED) 并设定租约。 complete 和 fail(error) 只为租约的所有者行事 (失去租约的工人不能覆盖新的持有者)。 fail 后退- availableAt = now + backoffSeconds × 2^(attempts−1)封顶和封顶后 maxAttempts 消息是 死了 (保留,连同它最后的错误; retryDead 赋予它另一种生命)。一个消息 最后 允许尝试的租约到期,没有答案也死了,没有第九次交付。
  • runWorker 循环 drainOnce (要求一批,按顺序交付,记录每个结果),通过休眠 sleep 你通过的时候一轮没有发现任何东西,结束的时候信号中止。运行 drainOnce 从一个调度程序,而不是如果你喜欢cron一个循环。
  • 设置 (@krizaka/outbox/settings,需要 @krizaka/settings 和 zod): outbox.batchSize (50,1-500), outbox.leaseSeconds (60,10 - 3600:比你最慢的交货时间长) outbox.maxAttempts 1-50(8日), outbox.backoffSeconds (30,1-3600), outbox.backoffMaxSeconds (3600), outbox.retentionDays (7)。 purge 删除早于保留时间的已发送和失效消息: 从产品的作业中调用它。
  • 表 (outboxSchema、Postgres模式 outbox): message (id, topic, event_type, payload, created_at, available_at, attempts, leased_until, lease_owner, delivered_at, dead_at, last_error)和 processed (使用者,message_id),其中 consumeOnce 使用: 标记和效果在同一个事务中提交,因此崩溃不会留下任何一个,并且重新交付会找到标记。
  • outboxStoreContract (从 @krizaka/outbox/testkit)是每个商店的合同; memoryOutboxStore() 用于单元测试。

版本与发布记录

@krizaka/outbox 共发布 6 个版本,首次发布于 2026/10/27;过去 12 个月发布了 6 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.1.4latest2026/11/011 天前
0.1.32026/10/304 天前
0.1.22026/10/295 天前
0.1.12026/10/277 天前
0.1.02026/10/277 天前
0.0.0-stage2026/10/277 天前

关于 @krizaka/outbox 的常见问题

@krizaka/outbox 是什么?

每个Krizaka产品的事务发件箱: 在调用者的事务中写入的事件,在提交后至少由租用的工作人员交付一次,具有加倍的重试,死信和一个幂等的消费者助手。没有经纪人,没有数据库客户

如何安装 @krizaka/outbox?

在项目目录运行 npm install @krizaka/outbox,或使用 pnpm add / yarn add / bun add。

@krizaka/outbox 支持 TypeScript 吗?

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

@krizaka/outbox 支持 ESM 还是 CommonJS?

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

@krizaka/outbox 需要什么版本的 Node.js?

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

@krizaka/outbox 可以商用吗?

@krizaka/outbox 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@krizaka/outbox 还在维护吗?

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