@krizaka/rate-limit

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

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

中文使用文档

@krizaka /限速

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

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 共发布 7 个版本,首次发布于 2026/10/27;过去 12 个月发布了 7 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.2.2latest2026/11/011 天前
0.2.12026/10/304 天前
0.2.02026/10/295 天前
0.1.22026/10/295 天前
0.1.12026/10/277 天前
0.1.02026/10/277 天前

关于 @krizaka/rate-limit 的常见问题

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