@krizaka/credits

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

每个Krizaka产品都有一个信用分类账:每天和每月限额到期的赠款,购买,借记,退款,持有。扩展;平衡是推导出来的。同时消费永远不会为负,也不会花费两次。没有数据库客户机,没有

中文使用文档

@krizaka /学分

每个Krizaka产品都有一个信用分类账。 它取代了三种型号:Yomizaka的 audit_credits 和 free_audit_used_at 列在 accounts (reserveAudit / releaseAudit / refundFailedAudit),以及Java工具包 credit_wallet / credit_ledger_entry / credit_hold (krizaka-billing,后面跟着他们的名字和事件)。优惠券模块(#46)通过它授予上限积分。

分类帐只是附注,余额是派生出来的。 没有什么东西能储存可以漂移的平衡:罐子剩下的是它的线条之和。数据库触发器拒绝任何 UPDATE 或者 DELETE 一行的。积分是整数,而不是浮点数。

import { emitMigration, transaction } from "@krizaka/db";
import { createCredits, creditsSchema } from "@krizaka/credits";
import { creditsSettings, readCreditsSettings } from "@krizaka/credits/settings";

emitMigration(creditsSchema, 1, { tables: { owner: "accounts" } }); // commit the SQL once, in your migrations

const credits = createCredits({ clock, settings: () => readCreditsSettings((key) => settings.get(key)), outbox });

await transaction(connect, async (tx) => {
  // a coupon's free credits: 20 audits, at most 5 a day, then stop; they lapse in 30 days
  await credits.grant(tx, { accountId, amount: 20, capPerDay: 5, expiresAt, onExhausted: "stop", source: `coupon:${redemptionId}`, idempotencyKey: `redeem-${redemptionId}` });
  // credits bought (the payment webhook, in the event's transaction)
  await credits.purchase(tx, { accountId, amount: 10, source: `purchase:${paymentRef}`, idempotencyKey: `pay-${paymentRef}` });
});

// spending: it refuses with a typed code, writes nothing when it refuses, and a replay of the key spends nothing more
const { draws } = await credits.consume(tx, { accountId, amount: 1, reason: `audit:${auditId}`, idempotencyKey: `audit-${auditId}` });
await credits.refund(tx, { accountId, consumed: `audit-${auditId}`, reason: "the run failed" }); // back to the same pots, once

该模型

  • 两种锅。 一、 授予 锅是免费使用的(优惠券,津贴,演示):它总是 到期,可能会被封顶 每天 和 每个月,并说明用完后会发生什么: stop 或者 fallback_paid. 一个 购买 Pot不会过期,也不会封顶。
  • consume(tx, { accountId, amount, reason, idempotencyKey }) 从授予的POTS中提取 第一个到期,然后从购买的积分中扣除。没有什么是片面的:它涵盖了全部或拒绝。
    • 越过锅盖: CREDITS_CAP_DAY / CREDITS_CAP_MONTH (合理使用限制:购买的积分不绕过它;另一个被授予的锅仍然可以服务);
    • 一个 stop 空锅: CREDITS_EXHAUSTED,即使存在购买积分;
    • 一个 fallback_paid 锅是空的:它移动到购买的积分,然后回答 PAYWALL;
    • 没有学分: PAYWALL. 拒绝是 CreditsErrorS和a code 和HTTP status 路线可以回答(402 / 429)。
  • 成立。 hold 为正在进行的工作保留积分(它计入上限和余额,并在之后到期) credits.holdTtlSeconds); settle(holdId, { amount? }) 花费工作的成本(最多是保留),并释放其余部分; release 释放一切。一旦每个;重播回答第一个结果。
  • refund 将一次消费(或固定持有)所花的钱返还给相同的锅。 expire(grantId) 注销剩下的钱(一张作废的优惠券:已经消费的积分永远不会收回); expireDue 在一个工作中扫掉掉了的锅。 adjust 是管理员的签名更正,从不低于零。
  • balance 是 { total, granted, purchased, held, available, grants[] }派生的; history 先写最新的行。
  • 幂等性。 每次写信都需要一把钥匙;操作的行是 <kind>.<key>:<n> (在数据库中唯一)。密钥在每个分类账中是唯一的,而不是每个帐户:使用id(付款ref,作业id)。已使用的密钥 另一个账户 被拒绝了(CREDITS_KEY_REUSED). 键不带冒号。
  • 并发性。 每个作家都会 pg_advisory_xact_lock 首先在帐户上,然后读取并决定:最后一个信用的并发消费永远不会为负,上限在负载下保持,并且多次发送的一个键花费一次(所有在PostgreSQL上证明,在循环中)。
  • 日期和月份 在管理员的IANA区域中被PostgreSQL删除(credits.timeZone、默认 America/Toronto),所以夏令时是数据库自己的时区数据;“今天”来自注入时钟。
  • 事件 (通过 @krizaka/outbox,在调用者的事务中,从事实派生的id,因此每个都宣布一次): evt.credit.granted.v1 和 evt.credit.exhausted.v1的名字 krizaka-billing-api,每个都有一个JSON模式(CREDIT_EVENT_SCHEMAS)。
  • 设置 (@krizaka/credits/settings): credits.timeZone (在运行时的IANA区域中进行选择)和 credits.holdTtlSeconds (900年,30 - 86400)。
  • 表 (creditsSchema、Postgres模式 credits): grant, entry, hold. 它引用的一个产品表是所有者(owner(帐户表)。有信用的账户是 匿名,从未删除它是一种会计记录,就像发票一样。
  • creditsLedgerContract (@krizaka/credits/testkit)是每个商店的合同; memoryCreditStore() 是用于单元测试的(它没有事务,所以 lock 不做任何事情:并发性是在PostgreSQL上证明的)。

采用它(Yomizaka)

audit_credits 变成购买的锅(purchase 每个账户的余额,键 import-<accountId>),免费审核是一项授予的工作 onExhausted: "fallback_paid", reserveAudit 一个 consume (一个 PAYWALL 已经是Yomizaka的402), releaseAudit 和 refundFailedAudit 一个 refund 消费键的。

版本与发布记录

@krizaka/credits 共发布 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/credits 的常见问题

@krizaka/credits 是什么?

每个Krizaka产品都有一个信用分类账:每天和每月限额到期的赠款,购买,借记,退款,持有。扩展;平衡是推导出来的。同时消费永远不会为负,也不会花费两次。没有数据库客户机,没有

如何安装 @krizaka/credits?

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

@krizaka/credits 支持 TypeScript 吗?

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

@krizaka/credits 支持 ESM 还是 CommonJS?

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

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

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

@krizaka/credits 可以商用吗?

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

@krizaka/credits 还在维护吗?

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