unified-keys 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -51,8 +51,8 @@ const result = await verifyKey(
51
51
  ### `client.createKey({ appId, prefix?, credits?, maxUses?, meta?, externalId?, expiresAt?, count? })`
52
52
  创建 key。返回 `{ keys: [{ key, keyId }] }`,`key` 是完整明文。`appId` 首次自动注册。
53
53
 
54
- ### `verifyKey({ baseUrl }, { key, decrement?, context? })`
55
- 公开验证(无需 rootKey)。`decrement: true` 扣减额度并写 verify log;`false` 只查。
54
+ ### `verifyKey({ key, decrement?, autoDelete?, context? })`
55
+ 公开验证(无需 rootKey,baseUrl 内置默认)。`decrement: true` 扣减 + 写 verify log;`false` 只查。`autoDelete: true` 扣减后额度用尽(remaining=0)自动删 key(一次性 key,避免 DB 堆积)。SDK 前置 `isValidKeyFormat` 校验,格式不对直接返回不查 DB。
56
56
  返回 `{ valid, found, keyId, remaining, maxUses, meta, externalId, appId, enabled, expiresAt, createdAt }`。
57
57
 
58
58
  ### `client.listKeys({ appId, limit?, offset? })`
package/SKILL.md CHANGED
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: unified-keys
3
- description: 当项目需要管理 API key(创建、验证扣减额度、查询使用记录、管理端显示明文)时使用。自建通用 key 服务 keys.932324.xyz,明文存储 + 完整使用记录,npm 包 unified-keys。
3
+ description: 当项目需要管理 API key(创建、验证扣减额度、查询使用记录、管理端显示明文、用尽自动删除)时使用。自建通用 key 服务 keys.932324.xyz,明文存储 + 完整使用记录,npm 包 unified-keys。
4
4
  ---
5
5
 
6
6
  # unified-keys
7
7
 
8
- 自建通用 API key 管理 SDK(替代 Unkey)。**明文存储 + 明文返回 + 完整使用记录**。服务跑在 Cloudflare Worker(`keys.932324.xyz`),数据存 D1。
8
+ 自建通用 API key 管理 SDK(替代 Unkey)。**明文存储 + 明文返回 + 完整使用记录 + 用尽自动删除**。服务跑在 Cloudflare Worker(`keys.932324.xyz`),数据存 D1。
9
9
 
10
10
  ## 何时用
11
11
 
@@ -13,6 +13,7 @@ description: 当项目需要管理 API key(创建、验证扣减额度、查
13
13
  - 需要 verify key + 原子扣减额度(credits)
14
14
  - 想看每个 key 的使用历史(谁、何时、扣了几次、退了几次)
15
15
  - 想在管理端显示**完整 key 明文**(Unkey 做不到,只给前缀)
16
+ - 一次性 key 用完自动删(避免 DB 堆积无用 key)
16
17
  - 多项目共用一个 key 服务(appId 命名空间隔离)
17
18
 
18
19
  ## 安装
@@ -22,24 +23,15 @@ pnpm add unified-keys
22
23
  # 或 npm i unified-keys
23
24
  ```
24
25
 
25
- ## 服务端点
26
-
27
- - **公开 verify**:`POST https://keys.932324.xyz/v1/keys/verify`(无需鉴权,任何持有 key 的人可调)
28
- - **管理 API**:`https://keys.932324.xyz/v1/*`(需 `Authorization: Bearer <ROOT_KEY>`)
29
- - 健康检查:`GET https://keys.932324.xyz/v1/health`
30
-
31
- ROOT_KEY 联系服务管理员获取。
32
-
33
26
  ## 核心 API
34
27
 
28
+ **baseUrl 内置默认**(`https://keys.932324.xyz`),无需传。
29
+
35
30
  ```ts
36
- import { createClient, verifyKey } from "unified-keys";
31
+ import { createClient, isValidKeyFormat, verifyKey } from "unified-keys";
37
32
 
38
- // 管理客户端(需 rootKey
39
- const client = createClient({
40
- baseUrl: "https://keys.932324.xyz",
41
- rootKey: process.env.KEYS_ROOT_KEY,
42
- });
33
+ // 管理客户端(需 rootKey;baseUrl 内置)
34
+ const client = createClient({ rootKey: process.env.KEYS_ROOT_KEY });
43
35
 
44
36
  // 创建 key(明文返回;appId = 项目 slug,首次自动注册)
45
37
  const { keys } = await client.createKey({
@@ -50,13 +42,15 @@ const { keys } = await client.createKey({
50
42
  meta: { plan: "pro" }, // 任意元数据(可选)
51
43
  count: 1, // 批量创建数
52
44
  });
53
- // keys[0].key = 完整明文(仅创建时与 list/get 返回),keys[0].keyId
45
+ // keys[0].key = 完整明文 `<prefix>_<8位base62>`
54
46
 
55
47
  // 验证 + 扣减(用户持有明文 key,服务端验证)
56
- const result = await verifyKey(
57
- { baseUrl: "https://keys.932324.xyz" },
58
- { key: userKey, decrement: true, context: { username: "alice", ip: "1.2.3.4" } }
59
- );
48
+ const result = await verifyKey({
49
+ key: userKey,
50
+ decrement: true, // 扣减(默认 true)
51
+ autoDelete: true, // 扣减后用尽(remaining=0)自动删 key(一次性 key 用)
52
+ context: { username: "alice", ip: "1.2.3.4" }, // 写 usage_log
53
+ });
60
54
  // { valid, found, keyId, remaining, maxUses, meta, externalId, appId, enabled, expiresAt, createdAt }
61
55
  // found=false: key 不存在;valid=false found=true: 存在但禁用/过期/额度耗尽
62
56
 
@@ -68,7 +62,7 @@ const { key, usageLogs } = await client.getKey(keyId);
68
62
 
69
63
  // 查使用记录(核心特性)
70
64
  const { logs } = await client.getKeyUsageLogs(keyId, { limit: 50 });
71
- // logs: [{ event: "verify"|"refund"|"create"|"delete"|"update", creditsBefore, creditsAfter, context, createdAt }]
65
+ // logs: [{ event: "verify"|"refund"|"create"|"delete"|"update"|"disable", creditsBefore, creditsAfter, context, createdAt }]
72
66
 
73
67
  // 调整额度(refund 恢复)
74
68
  await client.updateCredits(keyId, {
@@ -83,35 +77,52 @@ await client.updateKey(keyId, { meta: { plan: "enterprise" }, enabled: false });
83
77
  // 删除
84
78
  await client.deleteKey(keyId);
85
79
  await client.deleteKeys([keyId1, keyId2]);
80
+
81
+ // 手输校验(isValidKeyFormat 库内置,与 verify 前置校验同源)
82
+ if (!isValidKeyFormat(inputKey)) return invalid();
86
83
  ```
87
84
 
88
85
  ## 特性(区别于 Unkey)
89
86
 
90
87
  | | unified-keys | Unkey |
91
88
  |---|---|---|
92
- | 明文显示 | ✅ 列表/详情返回完整明文 | ❌ 只给 start 前缀 |
89
+ | 明文显示 | ✅ 列表/详情返回完整明文 | ❌ 只给前缀 |
93
90
  | 使用记录 | ✅ 每次 verify/refund/create/delete 写 usage_log | ❌ 只给当前 remaining |
94
91
  | verify 延迟 | <5ms(D1 binding 零网络跳) | 50-100ms(公网) |
92
+ | 用尽自动删 | ✅ verify `autoDelete` 选项 | ❌ |
93
+ | 前置格式校验 | ✅ SDK `isValidKeyFormat`,格式不对不查 DB | ❌ |
95
94
  | 自托管 | ✅ 自己的 D1 | ❌ SaaS |
96
95
  | 原子扣减 | ✅ `UPDATE ... WHERE credits>0 RETURNING` | ✅ |
97
96
 
97
+ ## verify 选项
98
+
99
+ | 选项 | 默认 | 说明 |
100
+ |---|---|---|
101
+ | `decrement` | true | true 扣减 + 写 verify log;false 只查 |
102
+ | `autoDelete` | false | 扣减后 remaining=0 自动删 key(一次性 key,避免 DB 堆积无用 key) |
103
+ | `context` | - | 写入 usage_log({username, ip, source, ...}) |
104
+
105
+ **SDK 前置校验**:verify 前先 `isValidKeyFormat(key)`,格式不对直接返回 `{valid:false, found:false}`,不查 DB(省时间)。应用也可单独用 `isValidKeyFormat` 做手输校验。
106
+
98
107
  ## event 类型(usage_log)
99
108
 
100
109
  - `verify`:verify 扣减(含 creditsBefore/After + context)
101
110
  - `refund`:updateCredits increment 恢复额度
102
- - `create` / `delete` / `update` / `disable`
111
+ - `create` / `delete`(含 autoDelete 触发的 exhausted_autodelete)/ `update` / `disable`
103
112
 
104
113
  ## 集成示例
105
114
 
106
- ### Next.js Route Handler(服务端 verify 用户持有 key
115
+ ### Next.js Route Handler(服务端 verify + 一次性 key 用尽自动删)
107
116
  ```ts
108
117
  import { verifyKey } from "unified-keys";
109
118
  export async function POST(req: Request) {
110
119
  const { key } = await req.json();
111
- const r = await verifyKey(
112
- { baseUrl: "https://keys.932324.xyz" },
113
- { key, decrement: true, context: { ip: req.headers.get("x-forwarded-for") } }
114
- );
120
+ const r = await verifyKey({
121
+ key,
122
+ decrement: true,
123
+ autoDelete: true, // 用完自动删
124
+ context: { ip: req.headers.get("x-forwarded-for") },
125
+ });
115
126
  if (!r.valid) return Response.json({ error: "invalid key" }, { status: 403 });
116
127
  // ... 授权逻辑
117
128
  }
@@ -120,10 +131,7 @@ export async function POST(req: Request) {
120
131
  ### Next.js Server Component(只读查询,不扣减)
121
132
  ```ts
122
133
  import { verifyKey } from "unified-keys";
123
- const info = await verifyKey(
124
- { baseUrl: "https://keys.932324.xyz" },
125
- { key: params.key, decrement: false }
126
- );
134
+ const info = await verifyKey({ key: params.key, decrement: false });
127
135
  if (!info.found || !info.valid) notFound();
128
136
  // info.remaining / info.meta / info.expiresAt
129
137
  ```
@@ -131,16 +139,21 @@ if (!info.found || !info.valid) notFound();
131
139
  ### Cloudflare Worker(管理端)
132
140
  ```ts
133
141
  import { createClient } from "unified-keys";
134
- const client = createClient({
135
- baseUrl: c.env.KEY_SERVICE_URL,
136
- rootKey: c.env.KEY_SERVICE_ROOT_KEY,
137
- });
142
+ const client = createClient({ rootKey: c.env.KEY_SERVICE_ROOT_KEY });
138
143
  await client.createKey({ appId: "my-app", credits: 1 });
139
144
  ```
140
145
 
146
+ ## 服务端点
147
+
148
+ - **公开 verify**:`POST https://keys.932324.xyz/v1/keys/verify`(无需鉴权,任何持有 key 的人可调)
149
+ - **管理 API**:`https://keys.932324.xyz/v1/*`(需 `Authorization: Bearer <ROOT_KEY>`)
150
+ - 健康检查:`GET https://keys.932324.xyz/v1/health`
151
+
152
+ ROOT_KEY 联系服务管理员获取。
153
+
141
154
  ## 实现位置(本项目内)
142
155
 
143
156
  - 服务端:`apps/key-service`(Hono Worker → keys.932324.xyz)
144
- - SDK:`packages/keys`(发布名 unified-keys
157
+ - SDK:`packages/keys`(npm `unified-keys`)
145
158
  - schema:`packages/db/src/schemas/key-service.ts`(key_apps / key_records / key_usage_logs)
146
- - 已迁移:github-inviter / user-dashboard(shadcn-proxy / notify-me 仍用 Unkey,待后续)
159
+ - 4 应用全迁移:github-inviter / user-dashboard / notify-me 完整迁移;shadcn-proxy 新用新服务 + Unkey 格式路由 fallback(旧 key 保持可用)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unified-keys",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "自建通用 API key 管理 SDK(明文存储 + 明文返回 + 完整使用记录),调用 keys.932324.xyz 服务",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
package/src/client.ts CHANGED
@@ -20,6 +20,8 @@ import type {
20
20
  VerifyResult,
21
21
  } from "./types";
22
22
 
23
+ const DEFAULT_BASE_URL = "https://keys.932324.xyz";
24
+
23
25
  const KEY_FORMAT = /^[a-zA-Z0-9_-]{8,128}$/;
24
26
 
25
27
  /**
@@ -31,7 +33,7 @@ export function isValidKeyFormat(key: string): boolean {
31
33
  }
32
34
 
33
35
  export function createClient(config: KeyServiceClientConfig): KeyServiceClient {
34
- const baseUrl = config.baseUrl.replace(/\/$/, "");
36
+ const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
35
37
  const rootKey = config.rootKey;
36
38
 
37
39
  async function request<T>(
@@ -118,15 +120,12 @@ export function createClient(config: KeyServiceClientConfig): KeyServiceClient {
118
120
  }
119
121
 
120
122
  /**
121
- * 公开 verify 端点,无需 rootKey
123
+ * 公开 verify 端点,无需 rootKey,baseUrl 内置默认。
122
124
  * 适合「用户持有明文 key、服务端验证并扣减」的场景。
123
125
  */
124
- export async function verifyKey(
125
- config: { baseUrl: string },
126
- input: VerifyInput
127
- ): Promise<VerifyResult> {
126
+ export async function verifyKey(input: VerifyInput): Promise<VerifyResult> {
128
127
  if (!isValidKeyFormat(input.key)) return { valid: false, found: false };
129
- const baseUrl = config.baseUrl.replace(/\/$/, "");
128
+ const baseUrl = DEFAULT_BASE_URL;
130
129
  const res = await fetch(`${baseUrl}/v1/keys/verify`, {
131
130
  method: "POST",
132
131
  headers: { "Content-Type": "application/json" },
package/src/types.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  /** unified-keys SDK 类型定义 */
2
2
 
3
3
  export interface KeyServiceClientConfig {
4
- /** 服务端点,如 https://keys.932324.xyz */
5
- baseUrl: string;
4
+ /**
5
+ * 服务端点,默认 https://keys.932324.xyz(自建服务固定,通常无需传)
6
+ */
7
+ baseUrl?: string;
6
8
  /** 管理 API 需要;verify 端点不需要 */
7
9
  rootKey?: string;
8
10
  }
@@ -122,6 +124,8 @@ export interface GetKeyResult {
122
124
  }
123
125
 
124
126
  export interface VerifyInput {
127
+ /** 扣减后额度用尽(remaining=0)时自动删除 key,避免 DB 堆积无用 key */
128
+ autoDelete?: boolean;
125
129
  /** 上下文,写入 usage_log(如 {username, source, ip}) */
126
130
  context?: Record<string, unknown>;
127
131
  /** 默认 true;false 时只查不扣(写 verify log 但不扣减) */