# @krizaka/outbox 中文文档

规范地址：https://npmcn.com/package/%40krizaka/outbox/

版本：0\.1\.4
npm 元数据更新时间：2026-11-01T14:51:17.225Z
中文译文更新时间：2026-11-01T16:27:31.201Z
软件包许可：Apache-2\.0

本文由上游 README 机器翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40krizaka/outbox
原文对照：https://npmcn.com/package/%40krizaka/outbox/?lang=original
项目主页：https://github.com/krizaka/krizaka-ts/tree/main/packages/outbox
源码仓库：https://github.com/krizaka/krizaka-ts

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：117 kB，17 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=22
- 运行依赖：3 个
- peer 依赖：zod \>=3\.23，vitest \>=4
- 首次发布：2026-10-26T19:35:45.814Z
- 最新版本发布：2026-11-01T14:51:16.847Z
- 已发布版本数：6（近 12 个月 6 个）

## 中文简介

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

## 安装

```sh
npm install @krizaka/outbox
```

也可以使用：`pnpm add @krizaka/outbox`、`yarn add @krizaka/outbox`、`bun add @krizaka/outbox`

## README

# @krizaka /发件箱

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

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

```ts
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 是什么？

每个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 和提交记录判断。

