@h-ai/cli 0.1.0-alpha.54 → 0.1.0-alpha.56
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 +4 -0
- package/dist/{chunk-FVDXJYMO.js → chunk-O3ANVICQ.js} +31 -2
- package/dist/chunk-O3ANVICQ.js.map +1 -0
- package/dist/cli-config-templates-OWBEGBZQ.js +5 -0
- package/dist/{cli-config-templates-WGN2I23J.js.map → cli-config-templates-OWBEGBZQ.js.map} +1 -1
- package/dist/index.js +3 -3
- package/dist/{src-6ZBWDBUB.js → src-TBIET7YU.js} +908 -127
- package/dist/src-TBIET7YU.js.map +1 -0
- package/package.json +7 -7
- package/templates/base/.gitignore.hbs +3 -0
- package/templates/base/package.json.hbs +1 -1
- package/templates/skills/AGENTS.md +8 -22
- package/templates/skills/CLAUDE.md +3 -16
- package/templates/skills/bridges/admin/AGENTS.md +3 -16
- package/templates/skills/bridges/admin/CLAUDE.md +3 -11
- package/templates/skills/bridges/admin/copilot-instructions.md +2 -39
- package/templates/skills/bridges/api/AGENTS.md +5 -18
- package/templates/skills/bridges/api/CLAUDE.md +3 -11
- package/templates/skills/bridges/api/copilot-instructions.md +3 -38
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +24 -59
- package/templates/skills/bridges/fullstack/CLAUDE.md.hbs +1 -15
- package/templates/skills/bridges/fullstack/copilot-instructions.md.hbs +5 -45
- package/templates/skills/bridges/h5/AGENTS.md +4 -17
- package/templates/skills/bridges/h5/CLAUDE.md +3 -11
- package/templates/skills/bridges/h5/copilot-instructions.md +2 -37
- package/templates/skills/bridges/mobile-app/AGENTS.md +5 -18
- package/templates/skills/bridges/mobile-app/CLAUDE.md +3 -11
- package/templates/skills/bridges/mobile-app/copilot-instructions.md +2 -38
- package/templates/skills/bridges/website/AGENTS.md +4 -17
- package/templates/skills/bridges/website/CLAUDE.md +3 -11
- package/templates/skills/bridges/website/copilot-instructions.md +2 -37
- package/templates/skills/copilot-instructions.md +2 -36
- package/templates/skills/hai-ai/SKILL.md +35 -389
- package/templates/skills/hai-ai/guide.md +383 -0
- package/templates/skills/hai-ai/reference.md +1 -1
- package/templates/skills/hai-api-client/SKILL.md +6 -6
- package/templates/skills/hai-api-contract/SKILL.md +10 -25
- package/templates/skills/hai-app-create/SKILL.md +29 -388
- package/templates/skills/hai-app-create/reference.md +350 -0
- package/templates/skills/hai-app-review/SKILL.md +9 -23
- package/templates/skills/hai-app-tests/SKILL.md +29 -494
- package/templates/skills/hai-app-tests/reference.md +442 -0
- package/templates/skills/hai-audit/SKILL.md +6 -6
- package/templates/skills/hai-build/SKILL.md +19 -338
- package/templates/skills/hai-build/reference.md +326 -0
- package/templates/skills/hai-cache/SKILL.md +27 -32
- package/templates/skills/hai-capacitor/SKILL.md +19 -366
- package/templates/skills/hai-capacitor/reference.md +352 -0
- package/templates/skills/hai-ci/SKILL.md +6 -21
- package/templates/skills/hai-core/SKILL.md +25 -566
- package/templates/skills/hai-core/reference.md +533 -0
- package/templates/skills/hai-crypto/SKILL.md +6 -20
- package/templates/skills/hai-datapipe/SKILL.md +9 -23
- package/templates/skills/hai-deploy/SKILL.md +63 -25
- package/templates/skills/hai-framework-sync/SKILL.md +8 -20
- package/templates/skills/hai-iam/SKILL.md +27 -371
- package/templates/skills/hai-iam/reference.md +343 -0
- package/templates/skills/hai-kit/SKILL.md +6 -6
- package/templates/skills/hai-payment/SKILL.md +6 -26
- package/templates/skills/hai-pr-review/SKILL.md +6 -22
- package/templates/skills/hai-reach/SKILL.md +19 -317
- package/templates/skills/hai-reach/reference.md +303 -0
- package/templates/skills/hai-reldb/SKILL.md +19 -472
- package/templates/skills/hai-reldb/reference.md +452 -0
- package/templates/skills/hai-scheduler/SKILL.md +6 -6
- package/templates/skills/hai-serv/SKILL.md +27 -759
- package/templates/skills/hai-serv/reference.md +736 -0
- package/templates/skills/hai-storage/SKILL.md +7 -25
- package/templates/skills/hai-ui/SKILL.md +29 -625
- package/templates/skills/hai-ui/reference.md +598 -0
- package/templates/skills/hai-vecdb/SKILL.md +18 -303
- package/templates/skills/hai-vecdb/reference.md +291 -0
- package/dist/chunk-FVDXJYMO.js.map +0 -1
- package/dist/cli-config-templates-WGN2I23J.js +0 -5
- package/dist/src-6ZBWDBUB.js.map +0 -1
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# hai-build — 详细用法
|
|
2
|
+
|
|
3
|
+
从 [SKILL.md](SKILL.md) 选择主题后读取对应小节。代码片段展示 API 用法;省略的业务变量、schema、依赖与 return 上下文需由应用补齐。
|
|
4
|
+
|
|
5
|
+
框架开发、CLI、服务端最低 Node.js 版本统一为 22.12.0;包与脚手架 engines、共享 node22 编译目标和 CI 最低版本必须同步。不再按 Node 20 编写兼容分支。
|
|
6
|
+
|
|
7
|
+
tsup 包的 Turbo `build` 依赖不缓存的 `build:clean`(仅清本包 dist),再执行构建或恢复缓存。不要移除此步骤:Turbo 缓存恢复不会自动移除另一模式的多余文件。
|
|
8
|
+
|
|
9
|
+
使用 Turbo 时,影响构建的环境变量必须进入任务 `env`,共享构建配置进入 `globalDependencies`。框架的 `HAI_DOCKER_PROD_BUILD=true` 关闭声明和 sourcemap,应与普通构建使用不同缓存;PUBLIC/VITE/TAURI 变量同样参与构建缓存。切换后验证实际产物并恢复普通开发构建。
|
|
10
|
+
|
|
11
|
+
> 本技能是 hai-framework 应用开发的起点。帮助理解项目结构、定位正确的技能、并遵循 **TDD 驱动的标准工作流**。支持 SSR Web、SPA、Android/iOS 原生 App 等多端构建。
|
|
12
|
+
|
|
13
|
+
## 适用场景
|
|
14
|
+
|
|
15
|
+
- 初次接触项目,需要了解整体架构与模块关系
|
|
16
|
+
- 不确定应使用哪个技能来完成任务
|
|
17
|
+
- 需要了解模块初始化顺序与依赖关系
|
|
18
|
+
- 需要执行跨模块操作或全局配置变更
|
|
19
|
+
- 多端构建切换(SSR ↔ SPA ↔ 原生 App)
|
|
20
|
+
|
|
21
|
+
## 项目架构
|
|
22
|
+
|
|
23
|
+
### 技术栈
|
|
24
|
+
|
|
25
|
+
| 层次 | 技术 |
|
|
26
|
+
| -------- | ------------------------------ |
|
|
27
|
+
| 框架 | SvelteKit 2 + Svelte 5 (Runes) |
|
|
28
|
+
| 样式 | TailwindCSS 4 + DaisyUI 5 |
|
|
29
|
+
| 语言 | TypeScript 5.7+ (strict) |
|
|
30
|
+
| 构建 | Vite + tsup |
|
|
31
|
+
| 包管理 | pnpm |
|
|
32
|
+
| 单元测试 | Vitest |
|
|
33
|
+
| E2E 测试 | Playwright |
|
|
34
|
+
| 原生 App | Capacitor 7 |
|
|
35
|
+
| 公共 API | oRPC contract + Hono + typed client |
|
|
36
|
+
| 认证 | Bearer access token + httpOnly refresh cookie(浏览器推荐) |
|
|
37
|
+
|
|
38
|
+
### 多端构建模式
|
|
39
|
+
|
|
40
|
+
| 模式 | Adapter | 认证方式 | 部署目标 |
|
|
41
|
+
| ------- | -------------- | --------------------- | ----------------------- |
|
|
42
|
+
| SSR Web | adapter-node | Bearer access token + Cookie | Node.js 服务器 |
|
|
43
|
+
| SPA | adapter-static | Bearer access token + httpOnly refresh cookie | CDN / 静态托管 |
|
|
44
|
+
| Android | adapter-static | Bearer access token + Capacitor secure storage | Capacitor → Android APK |
|
|
45
|
+
| iOS | adapter-static | Bearer access token + Capacitor secure storage | Capacitor → iOS IPA |
|
|
46
|
+
|
|
47
|
+
**Adapter 切换**:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// svelte.config.js — 使用 kit 提供的 createAdapter
|
|
51
|
+
import { createAdapter } from '@h-ai/kit/adapter'
|
|
52
|
+
|
|
53
|
+
const config = {
|
|
54
|
+
kit: {
|
|
55
|
+
adapter: createAdapter(), // 根据 VITE_ADAPTER 环境变量自动选择
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- `VITE_ADAPTER=node`(默认)→ adapter-node(SSR)
|
|
61
|
+
- `VITE_ADAPTER=static` → adapter-static(SPA / 原生 App)
|
|
62
|
+
|
|
63
|
+
### 目录结构
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
项目根/
|
|
67
|
+
config/ # 模块配置(YAML)
|
|
68
|
+
_core.yml # Core 配置(必须)
|
|
69
|
+
_db.yml # DB 配置(按需)
|
|
70
|
+
_cache.yml # Cache 配置(按需)
|
|
71
|
+
_iam.yml # IAM 配置(按需)
|
|
72
|
+
_storage.yml # Storage 配置(按需)
|
|
73
|
+
_ai.yml # AI 配置(按需)
|
|
74
|
+
src/
|
|
75
|
+
app.html # HTML 入口
|
|
76
|
+
app.d.ts # 全局类型声明
|
|
77
|
+
hooks.server.ts # 服务端 Hook(模块初始化 + 请求管道)
|
|
78
|
+
lib/
|
|
79
|
+
api.ts # @h-ai/api-client 初始化(SPA / 原生 App 使用)
|
|
80
|
+
capacitor.ts # Capacitor 初始化(原生 App)
|
|
81
|
+
server/
|
|
82
|
+
init.ts # 模块初始化入口(单例,SSR)
|
|
83
|
+
procedures/ # 应用私有 oRPC procedures(仅 API Service 场景)
|
|
84
|
+
paraglide/ # i18n 生成文件(禁止手动修改)
|
|
85
|
+
components/ # 应用组件
|
|
86
|
+
routes/
|
|
87
|
+
+layout.svelte # 根布局
|
|
88
|
+
+page.svelte # 首页
|
|
89
|
+
(auth)/ # 认证相关页面组
|
|
90
|
+
api/ # API 端点
|
|
91
|
+
v1/ # API v1 版本
|
|
92
|
+
payment/ # 支付端点(按需)
|
|
93
|
+
static/ # 静态资源
|
|
94
|
+
messages/ # i18n 翻译文件
|
|
95
|
+
capacitor.config.ts # Capacitor 配置(原生 App)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 模块依赖图
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
core(基础能力:配置、日志、i18n、HaiResult)
|
|
102
|
+
├── crypto(加密:SM2/SM3/SM4)
|
|
103
|
+
├── db(数据库:SQLite/PostgreSQL/MySQL)
|
|
104
|
+
├── cache(缓存:内存/Redis)
|
|
105
|
+
├── storage(存储:本地/S3)
|
|
106
|
+
├── ai(AI:LLM/MCP/Agent)
|
|
107
|
+
├── iam(身份管理)← 依赖 crypto + db + cache
|
|
108
|
+
├── payment(支付)← 依赖 db + crypto
|
|
109
|
+
├── api-contract(公共 HTTP API 契约)← 纯定义,依赖 core + zod + oRPC contract
|
|
110
|
+
├── serv(Hono + oRPC API Service)← 依赖 api-contract;features 按需依赖 iam/storage/ai
|
|
111
|
+
├── kit(SvelteKit 集成)← Hook / guard / validate / response,不定义公共 contract
|
|
112
|
+
├── api-client(typed API 客户端)← 依赖 api-contract + oRPC client
|
|
113
|
+
├── capacitor(原生能力)← 纯浏览器端
|
|
114
|
+
└── ui(UI 组件库)← 依赖 core
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 模块初始化顺序
|
|
118
|
+
|
|
119
|
+
#### SSR 模式(hooks.server.ts)
|
|
120
|
+
|
|
121
|
+
在 `src/lib/server/init.ts` 中按依赖顺序初始化:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { core } from '@h-ai/core'
|
|
125
|
+
import { cache, CacheConfigSchema } from '@h-ai/cache'
|
|
126
|
+
import { reldb, ReldbConfigSchema } from '@h-ai/reldb'
|
|
127
|
+
import { iam, IamConfigSchema } from '@h-ai/iam'
|
|
128
|
+
|
|
129
|
+
// 放入应用已有的启动函数;并发防护和关闭钩子复用当前模板。
|
|
130
|
+
core.init({ configDir: './config' })
|
|
131
|
+
const dbConfig = core.config.validate('db', ReldbConfigSchema)
|
|
132
|
+
if (!dbConfig.success) return dbConfig
|
|
133
|
+
const cacheConfig = core.config.validate('cache', CacheConfigSchema)
|
|
134
|
+
if (!cacheConfig.success) return cacheConfig
|
|
135
|
+
const iamConfig = core.config.validate('iam', IamConfigSchema)
|
|
136
|
+
if (!iamConfig.success) return iamConfig
|
|
137
|
+
const db = await reldb.init(dbConfig.data)
|
|
138
|
+
if (!db.success) return db
|
|
139
|
+
const cached = await cache.init(cacheConfig.data)
|
|
140
|
+
if (!cached.success) {
|
|
141
|
+
await reldb.close()
|
|
142
|
+
return cached
|
|
143
|
+
}
|
|
144
|
+
const identity = await iam.init(iamConfig.data)
|
|
145
|
+
if (!identity.success) {
|
|
146
|
+
await cache.close()
|
|
147
|
+
await reldb.close()
|
|
148
|
+
return identity
|
|
149
|
+
}
|
|
150
|
+
// 应用结束时 await iam.close()、cache.close()、reldb.close();
|
|
151
|
+
// 检查关闭结果;core 无 close,kit/api-contract/serv 不需要 init。
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### SPA / 原生 App 模式(src/lib/api.ts)
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { apiClient } from '@h-ai/api-client'
|
|
158
|
+
|
|
159
|
+
export async function initApi() {
|
|
160
|
+
return apiClient.init({
|
|
161
|
+
baseUrl: import.meta.env.VITE_API_BASE_URL,
|
|
162
|
+
auth: {}, // 浏览器默认 httpOnly cookie;原生 App 传入 Capacitor TokenStorage
|
|
163
|
+
timeout: 15_000,
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export { apiClient }
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### 原生 App 追加初始化(src/lib/capacitor.ts)
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { capacitor } from '@h-ai/capacitor'
|
|
174
|
+
|
|
175
|
+
export async function initCapacitor() {
|
|
176
|
+
const initialized = await capacitor.init()
|
|
177
|
+
if (!initialized.success) return initialized
|
|
178
|
+
return capacitor.statusBar.configure({ style: 'dark' })
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 配置文件格式
|
|
183
|
+
|
|
184
|
+
所有模块配置使用 YAML 保存默认值;`HAI_<配置名>_<YAML 路径>` 环境变量
|
|
185
|
+
自动覆盖对应叶子项,且优先级更高:
|
|
186
|
+
|
|
187
|
+
```yaml
|
|
188
|
+
# config/_core.yml
|
|
189
|
+
name: my-app
|
|
190
|
+
env: development
|
|
191
|
+
logging:
|
|
192
|
+
level: info
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
以上字段分别对应 `HAI_CORE_NAME`、`HAI_CORE_ENV`、
|
|
196
|
+
`HAI_CORE_LOGGING_LEVEL`。
|
|
197
|
+
|
|
198
|
+
## 技能导航
|
|
199
|
+
|
|
200
|
+
根据任务类型选择正确的技能:
|
|
201
|
+
|
|
202
|
+
### 模块使用(API 与集成)
|
|
203
|
+
|
|
204
|
+
| 任务 | 技能 | 触发关键词 |
|
|
205
|
+
| --------------------- | ---------------- | ------------------------------------------------- |
|
|
206
|
+
| 配置/日志/i18n/HaiResult | `hai-core` | core.init, core.logger, core.config, HaiResult |
|
|
207
|
+
| 数据库操作 | `hai-reldb` | reldb.init, SQL, CRUD, 事务, 分页, DDL |
|
|
208
|
+
| 缓存操作 | `hai-cache` | cache.init, cache.kv.get/set, TTL, Redis |
|
|
209
|
+
| 文件存储 | `hai-storage` | storage.init, 上传, 下载, S3, 本地存储 |
|
|
210
|
+
| 加密/签名/哈希 | `hai-crypto` | crypto.init, SM2, SM3, SM4, 加密, 签名 |
|
|
211
|
+
| 身份认证/授权 | `hai-iam` | iam.init, 登录, 注册, RBAC, Token, Bearer |
|
|
212
|
+
| AI/LLM/MCP | `hai-ai` | ai.init, LLM, MCP, Agent, 工具调用 |
|
|
213
|
+
| 公共 API 契约 | `hai-api-contract` | apiContract.create, contract, schema, oRPC, HaiResult |
|
|
214
|
+
| API Service 运行时 | `hai-serv` | serv.createApp, Hono, procedures, OpenAPI, docs, route guard |
|
|
215
|
+
| SvelteKit 集成 | `hai-kit` | kit.createHandle, guard, middleware, validate, response |
|
|
216
|
+
| UI 组件 | `hai-ui` | 表单, 按钮, 表格, Modal, Toast, 移动端组件 |
|
|
217
|
+
| typed API 客户端 | `hai-api-client` | apiClient.init, typed client, Bearer, 401 refresh, custom fetch |
|
|
218
|
+
| 原生 App 能力 | `hai-capacitor` | capacitor, 相机, 推送, 状态栏, 设备信息 |
|
|
219
|
+
| 支付 | `hai-payment` | payment, 微信支付, 支付宝, Stripe, 订单 |
|
|
220
|
+
|
|
221
|
+
### 开发流程
|
|
222
|
+
|
|
223
|
+
| 任务 | 技能 | 触发关键词 |
|
|
224
|
+
| ---------------------- | ---------------- | -------------------------------------- |
|
|
225
|
+
| 创建页面/组件/API/模型 | `hai-app-create` | 新建页面, 添加组件, API 端点, 数据模型 |
|
|
226
|
+
| 代码审查与规范化 | `hai-app-review` | 审查, review, 规范, 优化 |
|
|
227
|
+
| 编写/补充测试 | `hai-app-tests` | 测试, test, 单测, 覆盖率, TDD, E2E |
|
|
228
|
+
| CI/CD 与质量门禁 | `hai-ci` | CI, CD, GitHub Actions, workflow, secret scan |
|
|
229
|
+
| PR / Issue 交付审查 | `hai-pr-review` | PR, pull request, issue, CODEOWNERS, 自动 Review, AI Review |
|
|
230
|
+
| hai-framework 同步 | `hai-framework-sync` | hai-framework, skills 同步, framework:use:local, framework:watch |
|
|
231
|
+
|
|
232
|
+
## TDD 开发工作流(强制)
|
|
233
|
+
|
|
234
|
+
> 所有新功能开发必须遵循 TDD:**先写测试 → 确认失败 → 再实现 → 确认通过 → 重构**。
|
|
235
|
+
|
|
236
|
+
### 完整流程
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
需求分析 → 设计测试用例 → 编写测试(Red)→ 运行测试确认失败
|
|
240
|
+
→ 编写实现(Green)→ 运行测试确认通过
|
|
241
|
+
→ 重构优化(Refactor)→ 运行质量门禁
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### 按阶段使用的技能
|
|
245
|
+
|
|
246
|
+
| 阶段 | 主要技能 | 说明 |
|
|
247
|
+
| ------------------- | ---------------- | ------------------------------------------ |
|
|
248
|
+
| 需求分析 + 测试设计 | `hai-app-tests` | 拆分测试点,确定单元测试与 E2E 测试 |
|
|
249
|
+
| Red:编写测试 | `hai-app-tests` | 编写 Vitest 单元测试 + Playwright E2E 测试 |
|
|
250
|
+
| Green:编写实现 | `hai-app-create` | 按照创建规范编写服务/路由/组件 |
|
|
251
|
+
| Refactor:重构审查 | `hai-app-review` | 代码规范审查、分层检查、日志/i18n 合规 |
|
|
252
|
+
|
|
253
|
+
### 快速指令
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
# Red:运行测试确认新增目标用例因预期行为缺失而失败(既有回归保持通过)
|
|
257
|
+
pnpm --filter <app-name> test
|
|
258
|
+
pnpm --filter <app-name> test:e2e
|
|
259
|
+
|
|
260
|
+
# Green:运行测试确认全部通过
|
|
261
|
+
pnpm --filter <app-name> test
|
|
262
|
+
pnpm --filter <app-name> test:e2e
|
|
263
|
+
|
|
264
|
+
# Refactor:质量门禁
|
|
265
|
+
pnpm typecheck && pnpm lint && pnpm test
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## 统一编码规范
|
|
269
|
+
|
|
270
|
+
### 强制规则
|
|
271
|
+
|
|
272
|
+
- **禁止 `any`**:不确定类型用 `unknown`,在边界处做类型缩窄
|
|
273
|
+
- **禁止 `console.log`**:使用 `core.logger`(trace/debug/info/warn/error/fatal)
|
|
274
|
+
- **禁止硬编码密钥**:使用环境变量,新增变量需在 `.env` 中写入占位符
|
|
275
|
+
- **禁止硬编码用户文案**:所有用户可见文本必须使用 i18n key
|
|
276
|
+
- **禁止修改 `src/lib/paraglide`** 生成文件
|
|
277
|
+
|
|
278
|
+
### HaiResult 模式
|
|
279
|
+
|
|
280
|
+
返回 `HaiResult<T>` 的领域操作按以下模式处理;工具、工厂和流式 API 以签名为准:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
const result = await reldb.sql.query('SELECT * FROM users')
|
|
284
|
+
if (!result.success) {
|
|
285
|
+
// result.error: { code, message, details? }
|
|
286
|
+
core.logger.error('Query failed', { error: result.error })
|
|
287
|
+
return kit.response.internalError()
|
|
288
|
+
}
|
|
289
|
+
// result.data 可安全使用
|
|
290
|
+
return kit.response.ok(result.data)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### 质量门禁
|
|
294
|
+
|
|
295
|
+
每次变更后按顺序执行:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
pnpm typecheck # TypeScript 类型检查
|
|
299
|
+
pnpm lint # ESLint 检查
|
|
300
|
+
pnpm test # Vitest 单元测试
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## 标准工作流
|
|
304
|
+
|
|
305
|
+
### 变更前:需求分析 + 测试设计
|
|
306
|
+
|
|
307
|
+
1. 列出将修改的文件路径
|
|
308
|
+
2. 确认哪些类型/接口会变
|
|
309
|
+
3. 搜索确认引用关系(避免遗漏)
|
|
310
|
+
4. **设计测试用例清单**(单元测试 + E2E 测试)
|
|
311
|
+
|
|
312
|
+
### 变更中:TDD 三步走
|
|
313
|
+
|
|
314
|
+
1. **Red**:编写单元测试(Vitest)+ E2E 测试(Playwright),确认新增目标用例失败、既有回归通过
|
|
315
|
+
2. **Green**:按技能指引完成功能实现,逐步让测试通过
|
|
316
|
+
3. **Refactor**:审查代码规范、重构优化
|
|
317
|
+
|
|
318
|
+
### 变更后:验证
|
|
319
|
+
|
|
320
|
+
1. `pnpm typecheck` 通过
|
|
321
|
+
2. `pnpm lint` 通过
|
|
322
|
+
3. `pnpm test` 通过(单元测试)
|
|
323
|
+
4. `pnpm --filter <app-name> test:e2e` 通过(E2E 测试)
|
|
324
|
+
5. 搜索确认所有引用点已更新
|
|
325
|
+
|
|
326
|
+
原生交付必须单独执行 Tauri/Gradle/Xcode 打包,并在目标 OS/设备验证安装启动、登录/退出、重启会话、安全 TokenStore、真实 API 域名与覆盖升级。Web 构建/E2E 不代替原生验收。保存提交、包 hash、平台/工具链及逐项结果;缺平台写未验证,禁止静默 skip。hai-framework 仓库参考 `.github/workflows/native.yml` 与 desktop-app / mobile-app README 的「原生交付与验收」章节。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hai-cache
|
|
3
|
-
description: 使用 @h-ai/cache
|
|
3
|
+
description: "使用 @h-ai/cache 的 Memory/Redis 缓存、TTL、集合、排行榜和锁。"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# hai-cache
|
|
@@ -9,24 +9,20 @@ description: 使用 @h-ai/cache 进行内存或 Redis 缓存操作(kv/hash/lis
|
|
|
9
9
|
|
|
10
10
|
| 项目 | 契约 |
|
|
11
11
|
| --- | --- |
|
|
12
|
-
| 能力 | 使用 @h-ai/cache
|
|
13
|
-
| 适用场景 |
|
|
14
|
-
| 输入 |
|
|
15
|
-
| 输出 |
|
|
16
|
-
| 限制 |
|
|
12
|
+
| 能力 | 使用 @h-ai/cache 的 Memory/Redis 缓存、TTL、集合、排行榜和锁 |
|
|
13
|
+
| 适用场景 | 缓存读写、过期策略或共享资源互斥 |
|
|
14
|
+
| 输入 | CacheConfigInput、key/value、TTL、每次获锁的唯一 owner |
|
|
15
|
+
| 输出 | 缓存和锁操作的 HaiResult,获锁 data 为 boolean |
|
|
16
|
+
| 限制 | 仅服务端;Memory 不跨节点。release/extend 携带本次 owner,避免锁过期后误操作新持有者。 |
|
|
17
17
|
|
|
18
18
|
> `@h-ai/cache` 提供统一缓存接口,支持 Memory 与 Redis 后端,包含 KV / Hash / List / Set / ZSet / 分布式锁 六类操作。
|
|
19
19
|
|
|
20
20
|
内存 KV 在到期时间点即失效。整数计数器拒绝 null/boolean/array、非整数增量和安全整数溢出,失败不修改原值;Hash 的 `__proto__` / `constructor` 名称按普通字段保留。
|
|
21
21
|
|
|
22
|
-
---
|
|
23
|
-
|
|
24
22
|
## 运行环境
|
|
25
23
|
|
|
26
24
|
> ⚠️ **服务端模块(Node.js only)。** 浏览器端无需直接操作缓存,由服务端模块(如 IAM、Scheduler)内部使用。
|
|
27
25
|
|
|
28
|
-
---
|
|
29
|
-
|
|
30
26
|
## 适用场景
|
|
31
27
|
|
|
32
28
|
- 缓存热点数据减少数据库查询
|
|
@@ -36,8 +32,6 @@ description: 使用 @h-ai/cache 进行内存或 Redis 缓存操作(kv/hash/lis
|
|
|
36
32
|
- 排行榜(ZSet)与队列(List)
|
|
37
33
|
- 分布式锁(多节点部署互斥控制)
|
|
38
34
|
|
|
39
|
-
---
|
|
40
|
-
|
|
41
35
|
## 使用步骤
|
|
42
36
|
|
|
43
37
|
### 1. 配置
|
|
@@ -68,8 +62,6 @@ await cache.close()
|
|
|
68
62
|
|
|
69
63
|
`cache.config` 返回的是脱敏后的配置快照;Redis `password` / `url` 等敏感值不会原样暴露给日志或调试输出。
|
|
70
64
|
|
|
71
|
-
---
|
|
72
|
-
|
|
73
65
|
## 核心 API
|
|
74
66
|
|
|
75
67
|
### KV 操作(`cache.kv`)
|
|
@@ -116,32 +108,35 @@ await cache.zset.zadd('rank:daily', { member: 'u1', score: 100 })
|
|
|
116
108
|
| `extend` | 续期锁 TTL(支持 owner 验证) |
|
|
117
109
|
|
|
118
110
|
```typescript
|
|
119
|
-
//
|
|
120
|
-
const
|
|
111
|
+
// 每次尝试生成唯一 owner,后续释放/续期复用同一个值
|
|
112
|
+
const owner = core.id.generate()
|
|
113
|
+
const acquired = await cache.lock.acquire('my-lock', { ttl: 30, owner })
|
|
121
114
|
if (acquired.success && acquired.data) {
|
|
122
115
|
try {
|
|
123
|
-
//
|
|
116
|
+
// 长任务仅在仍持有锁时续期;失败或 data=false 时停止依赖锁的后续操作
|
|
117
|
+
const extended = await cache.lock.extend('my-lock', 60, owner)
|
|
118
|
+
if (!extended.success || !extended.data) {
|
|
119
|
+
logger.warn('Lock extension failed; protected work cancelled')
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
// 受保护的操作
|
|
123
|
+
}
|
|
124
124
|
}
|
|
125
125
|
finally {
|
|
126
|
-
await cache.lock.release('my-lock',
|
|
126
|
+
await cache.lock.release('my-lock', owner)
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
// 续期
|
|
131
|
-
await cache.lock.extend('my-lock', 60, 'node-1')
|
|
132
|
-
|
|
133
130
|
// 检查
|
|
134
131
|
const locked = await cache.lock.isLocked('my-lock')
|
|
135
132
|
```
|
|
136
133
|
|
|
137
134
|
**最佳实践:**
|
|
138
135
|
|
|
139
|
-
- `owner`
|
|
136
|
+
- `owner` 每次获锁尝试唯一,释放/续期复用该值;仅用 nodeId 会使同节点旧任务误释放过期后重新获得的锁
|
|
140
137
|
- 释放锁时传入 `owner` 防止误释放他人锁
|
|
141
138
|
- Memory 后端适合开发/测试;Redis 后端用 Lua 脚本保证 release/extend 原子性
|
|
142
139
|
|
|
143
|
-
---
|
|
144
|
-
|
|
145
140
|
## 错误码 — `HaiCacheError`
|
|
146
141
|
|
|
147
142
|
| 错误码 | code | 说明 |
|
|
@@ -156,11 +151,9 @@ const locked = await cache.lock.isLocked('my-lock')
|
|
|
156
151
|
| `HaiCacheError.UNSUPPORTED_TYPE` | `hai:cache:011` | 不支持的缓存类型 |
|
|
157
152
|
| `HaiCacheError.CONFIG_ERROR` | `hai:cache:012` | 配置错误 |
|
|
158
153
|
|
|
159
|
-
---
|
|
160
|
-
|
|
161
154
|
## 常见模式
|
|
162
155
|
|
|
163
|
-
###
|
|
156
|
+
### 缓存命中与回源(未缓存空值,不防穿透)
|
|
164
157
|
|
|
165
158
|
```typescript
|
|
166
159
|
async function getUserCached(userId: string) {
|
|
@@ -181,22 +174,24 @@ async function getUserCached(userId: string) {
|
|
|
181
174
|
|
|
182
175
|
```typescript
|
|
183
176
|
const lockKey = 'batch:import'
|
|
184
|
-
const
|
|
177
|
+
const owner = core.id.generate()
|
|
178
|
+
const acquired = await cache.lock.acquire(lockKey, { ttl: 60, owner })
|
|
185
179
|
if (acquired.success && acquired.data) {
|
|
186
180
|
try {
|
|
187
181
|
await runBatchImport()
|
|
188
182
|
}
|
|
189
183
|
finally {
|
|
190
|
-
await cache.lock.release(lockKey,
|
|
184
|
+
await cache.lock.release(lockKey, owner)
|
|
191
185
|
}
|
|
192
186
|
}
|
|
193
|
-
else {
|
|
187
|
+
else if (acquired.success) {
|
|
194
188
|
logger.info('Another node is running the import')
|
|
195
189
|
}
|
|
190
|
+
else {
|
|
191
|
+
logger.error('Lock acquisition failed', { code: acquired.error.code })
|
|
192
|
+
}
|
|
196
193
|
```
|
|
197
194
|
|
|
198
|
-
---
|
|
199
|
-
|
|
200
195
|
## 相关 Skills
|
|
201
196
|
|
|
202
197
|
- `hai-build`:模块初始化顺序(cache 在 db 之后、iam 之前)
|