# @krizaka/rate-limit 中文文档

规范地址：https://npmcn.com/package/%40krizaka/rate-limit/

版本：0\.2\.2
npm 元数据更新时间：2026-11-01T14:49:54.383Z
中文译文更新时间：2026-11-01T16:36:04.197Z
软件包许可：Apache-2\.0

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

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

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

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

## 中文简介

每个Krizaka产品的每个操作限制：每个地址和每个主题的固定窗口，从管理设置中读取的限制，内存存储和PostgreSQL存储的原子upsert计数恰好在并发下，Retry-After的稳定429。N

## 安装

```sh
npm install @krizaka/rate-limit
```

也可以使用：`pnpm add @krizaka/rate-limit`、`yarn add @krizaka/rate-limit`、`bun add @krizaka/rate-limit`

## README

# @krizaka /限速

每个Krizaka产品的每个操作限制：每个地址和每个主题的固定窗口，从管理员设置中读取的限制，每个实例共享一个内存存储和一个PostgreSQL存储。它取代了 `rate-limit.ts` Yomizaka和Akinaza使用的是字节对字节，而PostgreSQL的一些变体使用的是字节对字节。

```ts
import { emitMigration } from "@krizaka/db";
import { createLimiter, dbLimitStore, rateLimitSchema, RateLimited } from "@krizaka/rate-limit";
import { limitsFromSettings, rateLimitSettings } from "@krizaka/rate-limit/settings";
import { clientIp } from "@krizaka/auth";

emitMigration(rateLimitSchema, 1, { tables: {} }); // commit the SQL once, in your migrations: table rate_limit.bucket

// the registry fragment: limits.signIn.perIp, limits.signIn.perSubject, limits.signIn.windowSeconds… the admin changes them without a redeploy
export const limitSettings = rateLimitSettings({ signIn: { perIp: 30, perSubject: 10, windowSeconds: 900 }, me: { perIp: 600, windowSeconds: 3600 } });

const limiter = createLimiter({
  store: dbLimitStore(query),                       // or memoryLimitStore() in one process and in tests
  clock: { now: () => new Date() },
  limits: limitsFromSettings((key) => settings.get(key)), // or a plain table: { signIn: { perIp: 30, perSubject: 10, windowSeconds: 900 } }
  onStoreError: (error, action) => log.error({ error, action }),
});

// in a route
await limiter.enforce("signIn", { ip: clientIp(req.headers, rules), subject: email }); // throws RateLimited: status 429, headers { "Retry-After" }
const verdict = await limiter.check("signIn", keys); // { ok: true } | { ok: false, retryAfterSeconds, scope: "ip" | "subject" }
await limiter.forget("signIn", keys); // a successful sign-in clears its failures
```

-   **副本的语义**，所以它们的测试没有改变：窗口从第一次命中开始并持续下去 `windowSeconds`;被拒绝的一击也被计算在内；地址首先被计算，拒绝到此为止；主语用小写进行比较，只有当动作有 `perSubject` 极限和1是给定的；一个未知的地址记在下面 `unknown`． 古老的呼唤 `limit("sign-in", req, email, [30, 900], [10, 900])` 是 `enforce("signIn", { ip, subject: email })` 与 `{ perIp: 30, perSubject: 10, windowSeconds: 900 }` 在设置中。
-   **限制器接受的是钥匙，而不是请求。** 提取客户端的地址 `clientIp` 的 `@krizaka/auth`;包不读取头文件。
-   **限制是设置。** `rateLimitSettings` （`@krizaka/rate-limit/settings`,需要 `@krizaka/settings` 和 `zod`)声明 `limits.<action>.perIp`， `.perSubject` 和 `.windowSeconds` 作为带有产品默认值的整数； `limitsFromSettings` 在每次检查时读回它们。操作id由字母和数字组成(`signIn`)，因为它位于设置键中。
-   **商店:** `memoryLimitStore({ maxKeys })` 保持副本的绑定（10,000个窗口；结束的窗口首先被删除，然后是一个共享溢出窗口，因此大量的键限制了自己，并且永远不会清除阻塞的调用者计数）。 `dbLimitStore(query, { schema })` 数量 **一个原子语句** （`INSERT … ON CONFLICT DO UPDATE … RETURNING`)，所以100个并行检查计数准确，每个实例看到相同的计数器；时间来自注入的时钟，而不是数据库的时钟。这张桌子是 `rate_limit.bucket (key, count, window_start, reset_at)`，每个键一行（偏离） `(key, window_start)` 作为主键：第一次点击开始窗口，两个并发的第一次点击将打开两行)。 `purge(now)` 删除结束的窗口：从产品的作业中调用它。
-   **失败的存储允许请求通过** （限位者不得将网站取下）并告知 `onStoreError`； `failOpen: false` 重新抛出。
-   **额外收费的** 在港口： `peek` （阅读不算数）， `set` （锁定）和 `reset`.
-   `limitStoreContract` (从 `@krizaka/rate-limit/testkit`)是每家商店都遵守的合同。

## 旋转栅门(`@krizaka/rate-limit/turnstile`）

Cloudflare Turnstile小部件在服务器向Cloudflare询问它生成的令牌之前不提供任何保护。 `createTurnstileVerifier({ secret, hostnames, now })` 返回 `verify({ token, remoteIp, action })` → `{ ok: true }` 或者 `{ ok: false, reason, errorCodes }`． 它 **未封闭的**没有秘密)`not-configured`)、没有或超大令牌(`missing-token`)，拒绝(`rejected`)，在另一个主机名或另一个表单上解决的令牌 `action`，重放令牌（一次性使用，记住Cloudflare的300秒），超时或无法访问的服务(`unavailable`)都拒绝。秘诀是 `ABUSE_CAPTCHA_SECRET_KEY` 与 `ABUSE_CAPTCHA_PROVIDER=turnstile` （提供者是一个值，不在浏览器中）；网站的关键是公众 `NEXT_PUBLIC_ABUSE_CAPTCHA_SITE_KEY`． 用4xx回复拒绝，什么都不发；记录 `reason` 只有（没有地址，没有留言）。规则 `turnstile-server-side` org注册表的。

## 常见问题

### @krizaka/rate-limit 是什么？

每个Krizaka产品的每个操作限制：每个地址和每个主题的固定窗口，从管理设置中读取的限制，内存存储和PostgreSQL存储的原子upsert计数恰好在并发下，Retry-After的稳定429。N

### 如何安装 @krizaka/rate-limit？

在项目目录运行 npm install @krizaka/rate-limit，或使用 pnpm add / yarn add / bun add。

### @krizaka/rate-limit 支持 TypeScript 吗？

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

### @krizaka/rate-limit 支持 ESM 还是 CommonJS？

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

### @krizaka/rate-limit 需要什么版本的 Node\.js？

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

### @krizaka/rate-limit 可以商用吗？

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

### @krizaka/rate-limit 还在维护吗？

最新版本 0\.2\.2 发布于 2026/11/01（1 天前）；至今共发布 7 个版本，其中过去 12 个月发布了 7 个；首次发布于 2026/10/27。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

