@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,533 @@
|
|
|
1
|
+
# hai-core — 详细用法
|
|
2
|
+
|
|
3
|
+
从 [SKILL.md](SKILL.md) 选择主题后读取对应小节。代码片段展示 API 用法;省略的业务变量、schema、依赖与 return 上下文需由应用补齐。
|
|
4
|
+
|
|
5
|
+
> `@h-ai/core` 是 hai-framework 的基础模块,提供配置管理、结构化日志、国际化、HaiResult 错误模型、Zod 校验错误本地化与模块生命周期工具。所有其他模块均依赖 core。
|
|
6
|
+
|
|
7
|
+
## 运行环境
|
|
8
|
+
|
|
9
|
+
**Node.js + 浏览器双端可用。** 构建工具自动选择正确入口(`package.json` exports 条件导出)。
|
|
10
|
+
|
|
11
|
+
| 能力 | Node.js | 浏览器 |
|
|
12
|
+
|------|---------|--------|
|
|
13
|
+
| `core.logger` | ✅ pino(结构化 JSON / pretty) | ✅ loglevel(DevTools console) |
|
|
14
|
+
| `core.config` | ✅ 完整(YAML 加载、watch、validate) | ⚠️ 占位 API;文件配置方法返回 `SERVICE_UNAVAILABLE` |
|
|
15
|
+
| `core.i18n` | ✅ | ✅ |
|
|
16
|
+
| `core.zodValidation` | ✅ | ✅ |
|
|
17
|
+
| `core.id` | ✅ | ✅ |
|
|
18
|
+
| 工具函数 | ✅ | ✅ |
|
|
19
|
+
|
|
20
|
+
## 适用场景
|
|
21
|
+
|
|
22
|
+
- 项目初始化与配置加载
|
|
23
|
+
- 使用 `core.logger` 记录日志
|
|
24
|
+
- 使用 `core.config` 读取/校验模块配置
|
|
25
|
+
- 使用 `core.i18n` 管理多语言
|
|
26
|
+
- 使用 `core.zodValidation` 将 Zod 默认英文错误映射为模块自己的 i18n 文案
|
|
27
|
+
- 使用 `ok()` / `err()` 构建 HaiResult 类型返回值
|
|
28
|
+
- 创建模块级 logger 或 i18n 消息获取器
|
|
29
|
+
|
|
30
|
+
## 使用步骤
|
|
31
|
+
|
|
32
|
+
### 1. 初始化
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { core } from '@h-ai/core'
|
|
36
|
+
|
|
37
|
+
// 自动加载 config/ 目录下的所有 _*.yml 和 *.yml
|
|
38
|
+
core.init({ configDir: './config' })
|
|
39
|
+
|
|
40
|
+
// 也可指定日志级别和配置监听
|
|
41
|
+
core.init({ configDir: './config', watchConfig: true })
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2. 使用功能
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// 日志
|
|
48
|
+
core.logger.info('Server started', { port: 3000 })
|
|
49
|
+
|
|
50
|
+
// 配置
|
|
51
|
+
const dbConfig = core.config.get('db')
|
|
52
|
+
|
|
53
|
+
// Zod 校验错误本地化
|
|
54
|
+
const formErrors = core.zodValidation.mapZodErrorToFormErrors(zodError, getMessage)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 核心 API
|
|
58
|
+
|
|
59
|
+
### 生命周期
|
|
60
|
+
|
|
61
|
+
| 方法 | 签名 | 说明 |
|
|
62
|
+
| ----------- | --------------------------------- | ----------------------------------------- |
|
|
63
|
+
| `core.init` | `(options?: CoreOptions) => void` | 初始化(同步,加载配置目录中的所有 YAML) |
|
|
64
|
+
|
|
65
|
+
> `CoreOptions`:`{ logging?: Partial<LoggingConfig>, configDir?: string, watchConfig?: boolean }`
|
|
66
|
+
>
|
|
67
|
+
> 注意:core 没有 `close()` 方法,初始化后持续可用。
|
|
68
|
+
|
|
69
|
+
### 配置管理 — `core.config`
|
|
70
|
+
|
|
71
|
+
> 浏览器端保留 `core.config` 对象用于类型/runtime 对齐,但没有文件系统:`get()` 返回 `undefined`,`has()` 返回 `false`,`keys()` 返回 `[]`;`load()` / `validate()` / `reload()` 返回 `HaiCommonError.SERVICE_UNAVAILABLE`;`watch()` 会立即回调该错误并返回 no-op 取消函数。
|
|
72
|
+
|
|
73
|
+
| 方法 | 签名 | 说明 |
|
|
74
|
+
| ------------ | -------------------------------------------------------------- | ------------------------------ |
|
|
75
|
+
| `get` | `<T>(name: string) => T \| undefined` | 获取模块配置段 |
|
|
76
|
+
| `getOrThrow` | `<T>(name: string) => T` | 获取配置(未加载时抛异常) |
|
|
77
|
+
| `load` | `<T>(name, filePath, schema?) => HaiResult<T>` | 手动加载配置文件 |
|
|
78
|
+
| `validate` | `<T>(name: string, schema: ZodType) => HaiResult<T>` | 用 Zod Schema 校验已加载的配置 |
|
|
79
|
+
| `has` | `(name: string) => boolean` | 检查配置段是否存在 |
|
|
80
|
+
| `reload` | `(name: string) => HaiResult<unknown>` | 重新从磁盘加载配置 |
|
|
81
|
+
| `clear` | `(name?: string) => void` | 清除配置缓存 |
|
|
82
|
+
| `keys` | `() => string[]` | 获取所有已加载的配置名称 |
|
|
83
|
+
| `watch` | `<T>(name, callback) => () => void` | 监听配置文件变更并自动重载 |
|
|
84
|
+
| `unwatch` | `(name?: string) => void` | 停止配置文件监听 |
|
|
85
|
+
| `isWatching` | `(name: string) => boolean` | 检查是否正在监听某个配置 |
|
|
86
|
+
|
|
87
|
+
**`watch()` 回调处理**:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// watch 回调签名:(config: T | null, error?: HaiError) => void
|
|
91
|
+
// - 成功重载时:传入新配置,error 为 undefined
|
|
92
|
+
// - 重载失败时:config 为 null,error 为错误详情(包含 code 和 message)
|
|
93
|
+
|
|
94
|
+
const unwatch = core.config.watch('db', (cfg, error) => {
|
|
95
|
+
if (error) {
|
|
96
|
+
// 处理重载失败(如文件解析错误),不应更新本地使用的配置
|
|
97
|
+
core.logger.error('Config reload failed', {
|
|
98
|
+
name: 'db',
|
|
99
|
+
code: error.code,
|
|
100
|
+
message: error.message,
|
|
101
|
+
})
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
// 使用新配置
|
|
105
|
+
// 注意:此时 core.config.get('db') 已经指向新数据
|
|
106
|
+
core.logger.info('Config updated', { name: 'db' }) // 不记录含密码的完整配置
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// 调用 unwatch() 停止监听
|
|
110
|
+
unwatch()
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
配置文件格式(YAML,支持约定式环境变量映射和显式插值):
|
|
114
|
+
|
|
115
|
+
- YAML 根、对象、数组和叶子节点均可映射为 `HAI_<配置名>_<YAML 路径>`;配置名和 key 转大写、层级以 `_` 分隔,camelCase 不拆词
|
|
116
|
+
- 例如 `core.config.load('ai', ...)` 加载的 `llm.apiKey` 对应 `HAI_AI_LLM_APIKEY`,`llm.models[0].temperature` 对应 `HAI_AI_LLM_MODELS_0_TEMPERATURE`
|
|
117
|
+
- 约定环境变量存在时整体替换对应节点并按 YAML 规则解析;例如 HAI_DB 可注入整段连接配置、HAI_REACH_PROVIDERS 可替换数组。父节点覆盖优先于子节点,覆盖后不再遍历其叶子变量;无父节点覆盖时,只遍历 YAML 已有路径
|
|
118
|
+
- 优先级为父节点约定变量 > 当前节点约定变量 > 显式 `${VAR}` > YAML 默认值;显式语法只用于指定特殊变量名
|
|
119
|
+
|
|
120
|
+
- `${VAR}` — 读取 `process.env.VAR`;缺失则返回 `HaiConfigError.CONFIG_ENV_VAR_MISSING` 错误
|
|
121
|
+
- `${VAR:default}` — 读取 `process.env.VAR`;缺失则使用默认值
|
|
122
|
+
- **类型还原**:整个值恰好是单个变量表达式时,结果还原为原生类型(number / boolean 等);混合文本始终为字符串
|
|
123
|
+
- **空值语义**:`${VAR:}` 或值为空字符串的 `${VAR}` 保持为 `''`,不会被 YAML 解析为 `null`;是否把空字符串视为未配置由模块 Schema 明确定义
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
# config/_core.yml
|
|
127
|
+
name: my-app
|
|
128
|
+
env: development
|
|
129
|
+
logging:
|
|
130
|
+
level: info
|
|
131
|
+
format: pretty
|
|
132
|
+
feature:
|
|
133
|
+
debug: false
|
|
134
|
+
maxRetries: 3
|
|
135
|
+
# 只有特殊变量名才需要显式语法:
|
|
136
|
+
url: http://${HOST}:${PORT} # → string(混合文本,不还原)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 日志 — `core.logger`
|
|
140
|
+
|
|
141
|
+
`core.logger` 既是默认 Logger 实例,也是日志管理的统一入口。
|
|
142
|
+
|
|
143
|
+
**日志记录**(实例方法):`trace` / `debug` / `info` / `warn` / `error` / `fatal`
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
core.logger.info('User created', { userId: '123' })
|
|
147
|
+
core.logger.error('Failed to connect', { error })
|
|
148
|
+
core.logger.debug('Processing item', { id, step: 'validation' })
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**管理方法**:
|
|
152
|
+
|
|
153
|
+
| 方法 | 签名 | 说明 |
|
|
154
|
+
| ----------- | -------------------------------------- | ------------------------ |
|
|
155
|
+
| `create` | `(options?: LoggerOptions) => Logger` | 创建新 Logger 实例 |
|
|
156
|
+
| `child` | `(context: Record<string, unknown>) => Logger` | 创建携带固定上下文的子 Logger |
|
|
157
|
+
| `configure` | `(config: Partial<LoggingConfig>) => void` | 配置全局日志选项(级别、格式等) |
|
|
158
|
+
| `setLevel` | `(level: LogLevel) => void` | 设置全局日志级别 |
|
|
159
|
+
| `getLevel` | `() => LogLevel` | 获取当前全局日志级别 |
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// 创建模块级 logger
|
|
163
|
+
const logger = core.logger.create({ name: 'my-module' })
|
|
164
|
+
logger.info('Module initialized')
|
|
165
|
+
|
|
166
|
+
// 创建携带固定上下文的子 logger(自动合并到每条日志)
|
|
167
|
+
const reqLogger = core.logger.child({ requestId: 'req-001' })
|
|
168
|
+
reqLogger.info('Request started') // 日志中自动包含 requestId
|
|
169
|
+
|
|
170
|
+
// 配置输出格式和级别
|
|
171
|
+
core.logger.configure({
|
|
172
|
+
level: 'warn',
|
|
173
|
+
format: 'json',
|
|
174
|
+
redact: ['password', 'token'], // 脱敏字段
|
|
175
|
+
}) // Node.js 支持
|
|
176
|
+
core.logger.setLevel('debug')
|
|
177
|
+
const level = core.logger.getLevel() // 'debug'
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**日志级别规范**:
|
|
181
|
+
|
|
182
|
+
| 级别 | 适用场景 |
|
|
183
|
+
| ------- | ---------------------------------- |
|
|
184
|
+
| `trace` | 循环内详细调试数据 |
|
|
185
|
+
| `debug` | 函数进入、参数概要、读操作 |
|
|
186
|
+
| `info` | 业务事件(初始化完成、写操作成功) |
|
|
187
|
+
| `warn` | 异常但可恢复(校验失败、重试) |
|
|
188
|
+
| `error` | 操作失败(需人工排查) |
|
|
189
|
+
| `fatal` | 致命错误(服务无法继续) |
|
|
190
|
+
|
|
191
|
+
### 国际化 — `core.i18n`
|
|
192
|
+
|
|
193
|
+
| 方法 | 签名 | 说明 |
|
|
194
|
+
| --------------------- | --------------------------------------------------------------------- | -------------- |
|
|
195
|
+
| `getGlobalLocale` | `() => string` | 获取当前语言 |
|
|
196
|
+
| `setGlobalLocale` | `(locale: string) => void` | 设置全局语言 |
|
|
197
|
+
| `createMessageGetter` | `(messages: Record<string, Record<string, string>>) => MessageGetter` | 创建消息获取器 |
|
|
198
|
+
|
|
199
|
+
创建模块 i18n:
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import enUS from '../messages/en-US.json'
|
|
203
|
+
import zhCN from '../messages/zh-CN.json'
|
|
204
|
+
|
|
205
|
+
const m = core.i18n.createMessageGetter({
|
|
206
|
+
'zh-CN': zhCN,
|
|
207
|
+
'en-US': enUS,
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
// 使用
|
|
211
|
+
m('user_created') // "用户已创建"
|
|
212
|
+
m('welcome', { params: { name: '张三' } }) // "欢迎,张三"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Zod 校验错误映射 — `core.zodValidation`
|
|
216
|
+
|
|
217
|
+
适用于 `@h-ai/kit`、`@h-ai/serv`、`@h-ai/api-client` 等需要把 Zod 默认英文错误
|
|
218
|
+
转换为模块自身 i18n 文案的场景。优先复用 `core.zodValidation`,不要在各模块重复维护
|
|
219
|
+
默认消息正则 / issue 本地化逻辑。
|
|
220
|
+
|
|
221
|
+
| 方法 | 签名 | 说明 |
|
|
222
|
+
|------|------|------|
|
|
223
|
+
| `createPrefixedZodMessageGetter` | `(prefix, getMessage) => ZodMessageGetter` | 按模块前缀自动派生 `serv_validation*` / `kit_validation*` 这类消息 key |
|
|
224
|
+
| `mapZodErrorToFormErrors` | `(error, getMessage) => ValidationFormError[]` | 一步完成提取 + 本地化 + 扁平化 |
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import type { ZodMessageGetter } from '@h-ai/core'
|
|
228
|
+
import { core } from '@h-ai/core'
|
|
229
|
+
import { z } from 'zod'
|
|
230
|
+
|
|
231
|
+
const LoginSchema = z.object({
|
|
232
|
+
email: z.string().email(),
|
|
233
|
+
password: z.string().min(8),
|
|
234
|
+
nickname: z.string().min(1, '请填写昵称'),
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
const getMessage: ZodMessageGetter = (key, params) => {
|
|
238
|
+
switch (key) {
|
|
239
|
+
case 'validationEmail':
|
|
240
|
+
return '请输入合法邮箱地址'
|
|
241
|
+
case 'validationStringMin':
|
|
242
|
+
return `至少输入 ${params?.min} 个字符`
|
|
243
|
+
default:
|
|
244
|
+
return '输入不合法'
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const result = LoginSchema.safeParse({
|
|
249
|
+
email: 'bad',
|
|
250
|
+
password: '123',
|
|
251
|
+
nickname: '',
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
if (!result.success) {
|
|
255
|
+
const getMessage = core.zodValidation.createPrefixedZodMessageGetter(
|
|
256
|
+
'serv',
|
|
257
|
+
(messageKey, params) => {
|
|
258
|
+
if (messageKey === 'serv_validationEmail')
|
|
259
|
+
return '请输入合法邮箱地址'
|
|
260
|
+
if (messageKey === 'serv_validationStringMin')
|
|
261
|
+
return `至少输入 ${params?.min} 个字符`
|
|
262
|
+
return '输入不合法'
|
|
263
|
+
},
|
|
264
|
+
)
|
|
265
|
+
const errors = core.zodValidation.mapZodErrorToFormErrors(result.error, getMessage)
|
|
266
|
+
|
|
267
|
+
errors[0]?.field // 'email'
|
|
268
|
+
errors[0]?.message // '请输入合法邮箱地址'
|
|
269
|
+
errors[2]?.message // '请填写昵称'(schema 自定义消息原样保留)
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### ID 生成 — `core.id`
|
|
274
|
+
|
|
275
|
+
| 方法 | 签名 | 说明 |
|
|
276
|
+
| -------------- | --------------------------------------- | ----------------------------- |
|
|
277
|
+
| `generate` | `(size?: number) => string` | nanoid(默认 21 字符) |
|
|
278
|
+
| `short` | `() => string` | 10 字符短 ID |
|
|
279
|
+
| `withPrefix` | `(prefix: string, length?: number) => string` | 带前缀的 nanoid |
|
|
280
|
+
| `trace` | `() => string` | `trace-` 前缀 ID |
|
|
281
|
+
| `request` | `() => string` | `req-` 前缀 ID |
|
|
282
|
+
| `uuid` | `() => string` | UUID v4 |
|
|
283
|
+
| `isValidUUID` | `(value: string) => boolean` | 校验 UUID 格式 |
|
|
284
|
+
| `isValidNanoId`| `(value: string, size?: number) => boolean` | 校验 nanoid 格式 |
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
const id = core.id.generate() // 'V1StGXR_Z5j3eK4uA9b8c'
|
|
288
|
+
const shortId = core.id.short() // 10 字符
|
|
289
|
+
const prefixed = core.id.withPrefix('order_') // 'order_V1StGXR...'
|
|
290
|
+
const traceId = core.id.trace() // 'trace-V1StGXR...'
|
|
291
|
+
const reqId = core.id.request() // 'req-V1StGXR...'
|
|
292
|
+
const uuid = core.id.uuid() // UUID v4
|
|
293
|
+
|
|
294
|
+
core.id.isValidUUID('f47ac10b-...') // true/false
|
|
295
|
+
core.id.isValidNanoId('abc123', 6) // true/false
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
#### UUID 生成规范(强制)
|
|
299
|
+
|
|
300
|
+
- 生成 UUID **一律用 `core.id.uuid()`**,禁止裸调 `crypto.randomUUID()`。
|
|
301
|
+
- 原因:`crypto.randomUUID` 仅在**安全上下文(HTTPS / localhost)**下可用,生产 http 环境会抛
|
|
302
|
+
`crypto.randomUUID is not a function`;`core.id.uuid()` 已做 `randomUUID → getRandomValues → Math.random` 三级兜底,Node.js 与浏览器通用。
|
|
303
|
+
- 需要不带横线的 compact 形式:`core.id.uuid().replaceAll('-', '')`。
|
|
304
|
+
- 代码评审与新建代码时以此为准,发现裸调 `crypto.randomUUID()` 一律改走 `core.id.uuid()`。
|
|
305
|
+
|
|
306
|
+
### 工具函数
|
|
307
|
+
|
|
308
|
+
#### 类型检查 — `core.typeUtils`
|
|
309
|
+
|
|
310
|
+
`isDefined` / `isObject` / `isFunction` / `isPromise` / `isString` / `isNumber` / `isBoolean` / `isArray`
|
|
311
|
+
|
|
312
|
+
#### 对象操作 — `core.object`
|
|
313
|
+
|
|
314
|
+
`deepClone` / `deepMerge` / `pick` / `omit` / `keys` / `values` / `entries` / `fromEntries`
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
core.object.deepMerge(a, b, c) // 递归合并(防原型污染)
|
|
318
|
+
core.object.pick(obj, ['a', 'b'])
|
|
319
|
+
core.object.omit(obj, ['c', 'd'])
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
#### 字符串操作 — `core.string`
|
|
323
|
+
|
|
324
|
+
`capitalize` / `kebabCase` / `camelCase` / `snakeCase` / `pascalCase` / `truncate` / `trim` / `isBlank` / `isNotBlank` / `padStart` / `padEnd` / `constantTimeEqual`
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
core.string.capitalize('hello') // 'Hello'
|
|
328
|
+
core.string.kebabCase('helloWorld') // 'hello-world'
|
|
329
|
+
core.string.constantTimeEqual(a, b) // 防时序攻击比较
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
#### 数组操作 — `core.array`
|
|
333
|
+
|
|
334
|
+
`unique` / `chunk` / `groupBy` / `first` / `last` / `flatten` / `compact` / `shuffle` / `intersection` / `difference`
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
core.array.unique([1, 1, 2]) // [1, 2]
|
|
338
|
+
core.array.chunk([1, 2, 3, 4], 2) // [[1,2], [3,4]]
|
|
339
|
+
core.array.groupBy(items, item => item.type) // Record<string, T[]>
|
|
340
|
+
core.array.intersection([1, 2], [2, 3]) // [2]
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
#### 异步操作 — `core.async`
|
|
344
|
+
|
|
345
|
+
`delay` / `withTimeout` / `retry` / `parallel` / `serial` / `debounce` / `throttle`
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
await core.async.delay(1000)
|
|
349
|
+
await core.async.withTimeout(fetch('/api'), 5000)
|
|
350
|
+
await core.async.retry(fn, { maxRetries: 3, delay: 1000 })
|
|
351
|
+
await core.async.parallel(items, processFn, 5) // 并发限制
|
|
352
|
+
const debouncedFn = core.async.debounce(fn, 300)
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
#### 时间操作 — `core.time`
|
|
356
|
+
|
|
357
|
+
`formatDate` / `timeAgo` / `now` / `nowSeconds` / `parseDate` / `isValidDate` / `addDays` / `addHours` / `startOfDay` / `endOfDay`
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
core.time.formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss')
|
|
361
|
+
core.time.timeAgo(new Date(Date.now() - 60000)) // '1 分钟前'
|
|
362
|
+
core.time.addDays(date, 7)
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### HaiResult 模型与错误处理
|
|
366
|
+
|
|
367
|
+
返回 `HaiResult<T>` 的操作须处理成功/失败分支;core 工具等直接返回值的 API 不套用此模式:
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import { ok, err, HaiCommonError } from '@h-ai/core'
|
|
371
|
+
|
|
372
|
+
// 成功
|
|
373
|
+
return ok(data)
|
|
374
|
+
|
|
375
|
+
// 失败
|
|
376
|
+
return err(HaiCommonError.NOT_FOUND, 'Resource not found')
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
**使用模式**:
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
const result = await someOperation()
|
|
383
|
+
|
|
384
|
+
// 模式1:直接检查
|
|
385
|
+
if (!result.success) {
|
|
386
|
+
core.logger.error('Operation failed', { error: result.error })
|
|
387
|
+
return result // 直接透传错误
|
|
388
|
+
}
|
|
389
|
+
const data = result.data
|
|
390
|
+
|
|
391
|
+
// 模式2:提前返回
|
|
392
|
+
if (!result.success)
|
|
393
|
+
return result
|
|
394
|
+
// 后续代码可安全使用 result.data
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## 错误码
|
|
398
|
+
|
|
399
|
+
Core 模块定义了两组标准错误码:
|
|
400
|
+
|
|
401
|
+
### `HaiCommonError`(通用错误)
|
|
402
|
+
|
|
403
|
+
| 错误码 | code | 说明 |
|
|
404
|
+
|--------|------|------|
|
|
405
|
+
| `HaiCommonError.NOT_INITIALIZED` | `hai:common:001` | 模块未初始化 |
|
|
406
|
+
| `HaiCommonError.INIT_FAILED` | `hai:common:002` | 初始化失败 |
|
|
407
|
+
| `HaiCommonError.INIT_IN_PROGRESS` | `hai:common:004` | 正在初始化中 |
|
|
408
|
+
| `HaiCommonError.UNAUTHORIZED` | `hai:common:100` | 未认证 |
|
|
409
|
+
| `HaiCommonError.FORBIDDEN` | `hai:common:101` | 无权限 |
|
|
410
|
+
| `HaiCommonError.TOKEN_EXPIRED` | `hai:common:102` | Token 已过期 |
|
|
411
|
+
| `HaiCommonError.TOKEN_INVALID` | `hai:common:103` | Token 无效 |
|
|
412
|
+
| `HaiCommonError.VALIDATION_ERROR` | `hai:common:200` | 校验失败 |
|
|
413
|
+
| `HaiCommonError.INVALID_REQUEST` | `hai:common:201` | 请求无效 |
|
|
414
|
+
| `HaiCommonError.PARAMETER_MISSING` | `hai:common:202` | 参数缺失 |
|
|
415
|
+
| `HaiCommonError.NOT_FOUND` | `hai:common:300` | 资源不存在 |
|
|
416
|
+
| `HaiCommonError.ALREADY_EXISTS` | `hai:common:301` | 资源已存在 |
|
|
417
|
+
| `HaiCommonError.CONFLICT` | `hai:common:302` | 冲突 |
|
|
418
|
+
| `HaiCommonError.API_ERROR` | `hai:common:400` | API 错误 |
|
|
419
|
+
| `HaiCommonError.NETWORK_ERROR` | `hai:common:401` | 网络错误 |
|
|
420
|
+
| `HaiCommonError.TIMEOUT` | `hai:common:402` | 超时 |
|
|
421
|
+
| `HaiCommonError.SERVICE_UNAVAILABLE` | `hai:common:403` | 服务不可用 |
|
|
422
|
+
| `HaiCommonError.INTERNAL_ERROR` | `hai:common:500` | 内部错误 |
|
|
423
|
+
| `HaiCommonError.DATABASE_ERROR` | `hai:common:501` | 数据库错误 |
|
|
424
|
+
| `HaiCommonError.UNKNOWN_ERROR` | `hai:common:599` | 未知错误 |
|
|
425
|
+
|
|
426
|
+
**使用示例**:
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
import { HaiCommonError, err } from '@h-ai/core'
|
|
430
|
+
|
|
431
|
+
// 创建错误实例
|
|
432
|
+
err(HaiCommonError.NOT_FOUND, 'User not found')
|
|
433
|
+
err(HaiCommonError.VALIDATION_ERROR, 'Invalid email', validationErrors)
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### `HaiConfigError`(配置错误)
|
|
437
|
+
|
|
438
|
+
| 错误码 | code | 说明 |
|
|
439
|
+
|--------|------|------|
|
|
440
|
+
| `HaiConfigError.CONFIG_FILE_NOT_FOUND` | `hai:core:010` | 配置文件不存在 |
|
|
441
|
+
| `HaiConfigError.CONFIG_PARSE_ERROR` | `hai:core:011` | YAML 解析失败 |
|
|
442
|
+
| `HaiConfigError.CONFIG_VALIDATION_ERROR` | `hai:core:012` | Schema 校验失败 |
|
|
443
|
+
| `HaiConfigError.CONFIG_ENV_VAR_MISSING` | `hai:core:013` | 必需环境变量缺失 |
|
|
444
|
+
| `HaiConfigError.CONFIG_NOT_LOADED` | `hai:core:014` | 配置未加载 |
|
|
445
|
+
|
|
446
|
+
**使用场景**:
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
import { core, HaiConfigError } from '@h-ai/core'
|
|
450
|
+
|
|
451
|
+
const result = core.config.load('db', './config/db.yml', schema)
|
|
452
|
+
if (!result.success) {
|
|
453
|
+
if (result.error.code === HaiConfigError.CONFIG_FILE_NOT_FOUND.code) {
|
|
454
|
+
core.logger.error('Config file missing')
|
|
455
|
+
}
|
|
456
|
+
else if (result.error.code === HaiConfigError.CONFIG_VALIDATION_ERROR.code) {
|
|
457
|
+
core.logger.error('Config validation failed', { issues: result.error.cause })
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
## 常见模式
|
|
463
|
+
|
|
464
|
+
### 服务端初始化(SvelteKit hooks.server.ts)
|
|
465
|
+
|
|
466
|
+
在应用已有的 init.ts 中执行 core.init({ configDir: './config' }),然后初始化依赖模块;hooks.server.ts 等待该启动流程完成后接入 kit.createHandle。完整依赖顺序与失败清理见 [hai-build](../hai-build/SKILL.md)。不要在一个文件里同时 import 和声明同名 initModules。
|
|
467
|
+
|
|
468
|
+
### 配置校验最佳实践
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
import { z } from 'zod'
|
|
472
|
+
|
|
473
|
+
const MyConfigSchema = z.object({
|
|
474
|
+
apiKey: z.string().min(1),
|
|
475
|
+
timeout: z.number().positive().default(5000),
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
// 模块初始化时校验
|
|
479
|
+
const result = core.config.validate('myModule', MyConfigSchema)
|
|
480
|
+
if (!result.success) {
|
|
481
|
+
core.logger.error('Invalid config', { error: result.error })
|
|
482
|
+
return result
|
|
483
|
+
}
|
|
484
|
+
const config = result.data // 类型安全
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
## 相关 Skills
|
|
488
|
+
|
|
489
|
+
- `hai-build`:项目整体架构与技能导航
|
|
490
|
+
- `hai-kit`:SvelteKit 集成层(Handle/Guard/Middleware)
|
|
491
|
+
- `hai-reldb` / `hai-cache` / `hai-iam`:使用 core.config 读取配置的下游模块
|
|
492
|
+
|
|
493
|
+
## 浏览器侧使用
|
|
494
|
+
|
|
495
|
+
> **禁止在应用中直接引入 `loglevel`。** `core.logger` 在浏览器端自动使用 loglevel 作为后端,API 与 Node.js 完全一致。
|
|
496
|
+
|
|
497
|
+
### 初始化
|
|
498
|
+
|
|
499
|
+
浏览器端无文件系统,不需要 `configDir`。在 `+layout.svelte` 或应用入口调用:
|
|
500
|
+
|
|
501
|
+
```typescript
|
|
502
|
+
import { core } from '@h-ai/core'
|
|
503
|
+
|
|
504
|
+
// 浏览器端:仅传入 logging 配置即可
|
|
505
|
+
core.init({ logging: { level: 'info' } })
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### 日志记录
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
// 与 Node.js 完全一致的 API
|
|
512
|
+
core.logger.info('Page loaded', { route: '/home' })
|
|
513
|
+
core.logger.error('API call failed', { url, status })
|
|
514
|
+
|
|
515
|
+
// 创建模块级 logger
|
|
516
|
+
const logger = core.logger.create({ name: 'my-page' })
|
|
517
|
+
logger.debug('Data fetched', { count: items.length })
|
|
518
|
+
|
|
519
|
+
// ❌ 禁止直接引用底层日志库
|
|
520
|
+
// import log from 'loglevel' ← 不要这样做
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### 浏览器侧限制
|
|
524
|
+
|
|
525
|
+
| 功能 | 行为 |
|
|
526
|
+
|------|------|
|
|
527
|
+
| `core.config` 文件配置 | 不支持;文件相关方法返回 `SERVICE_UNAVAILABLE`,配置应由服务端读取后下发 |
|
|
528
|
+
| `format: 'json' \| 'pretty'` | 被忽略(始终输出到 DevTools console) |
|
|
529
|
+
| 自定义 `redact` 配置 | 浏览器不使用该配置,但默认 sanitizeSensitiveFields 仍会清理上下文敏感字段;消息文本需调用方脱敏 |
|
|
530
|
+
| `fatal` 级别 | 映射为 `console.error`(带 `[FATAL]` 前缀) |
|
|
531
|
+
| context 输出 | JSON 字符串追加到消息末尾(非结构化对象) |
|
|
532
|
+
|
|
533
|
+
云资源输出格式与部署配置消费见 [hai-deploy](../hai-deploy/SKILL.md);这里的父节点覆盖规则同样适用。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hai-crypto
|
|
3
|
-
description: 使用 @h-ai/crypto
|
|
3
|
+
description: "使用 @h-ai/crypto 进行 SM2/SM3/SM4、密码哈希与传输加密。"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# hai-crypto
|
|
@@ -9,16 +9,14 @@ description: 使用 @h-ai/crypto 进行加密(非对称/哈希/对称)、密
|
|
|
9
9
|
|
|
10
10
|
| 项目 | 契约 |
|
|
11
11
|
| --- | --- |
|
|
12
|
-
| 能力 | 使用 @h-ai/crypto
|
|
13
|
-
| 适用场景 |
|
|
14
|
-
| 输入 |
|
|
15
|
-
| 输出 |
|
|
16
|
-
| 限制 |
|
|
12
|
+
| 能力 | 使用 @h-ai/crypto 进行 SM2/SM3/SM4、密码哈希与传输加密 |
|
|
13
|
+
| 适用场景 | 应用加密、验签、密码保存或传输保护 |
|
|
14
|
+
| 输入 | CryptoConfigInput、字节/文本、密钥和 transport 装配选项 |
|
|
15
|
+
| 输出 | 加解密/验签的 HaiResult;transport 工厂按公开类型使用 |
|
|
16
|
+
| 限制 | 先 init;password 仅服务端,浏览器仅支持部分能力。传输通过 serv/kit/api-client 顶层装配,不导入内部工厂。 |
|
|
17
17
|
|
|
18
18
|
> `@h-ai/crypto` 提供非对称加密、哈希、对称加密与密码哈希能力,支持 Node.js 与浏览器双端。
|
|
19
19
|
|
|
20
|
-
---
|
|
21
|
-
|
|
22
20
|
## 运行环境
|
|
23
21
|
|
|
24
22
|
**Node.js + 浏览器双端可用。** 浏览器端通过 `crypto.init()` 初始化(无需配置参数)。
|
|
@@ -33,8 +31,6 @@ description: 使用 @h-ai/crypto 进行加密(非对称/哈希/对称)、密
|
|
|
33
31
|
|
|
34
32
|
浏览器端主要用于 kit 传输加密场景(`kit.client.create({ transport: { crypto } })`),一般不需要直接调用 crypto API。
|
|
35
33
|
|
|
36
|
-
---
|
|
37
|
-
|
|
38
34
|
## 适用场景
|
|
39
35
|
|
|
40
36
|
- 非对称加密/解密、签名/验签、密钥生成
|
|
@@ -43,8 +39,6 @@ description: 使用 @h-ai/crypto 进行加密(非对称/哈希/对称)、密
|
|
|
43
39
|
- 密码存储与验证(加盐迭代哈希)
|
|
44
40
|
- 传输加密:为 serv / kit / api-client 提供统一协议、密钥协商和请求响应加解密
|
|
45
41
|
|
|
46
|
-
---
|
|
47
|
-
|
|
48
42
|
## 使用步骤
|
|
49
43
|
|
|
50
44
|
### 1. 初始化与关闭
|
|
@@ -58,8 +52,6 @@ await crypto.init()
|
|
|
58
52
|
await crypto.close()
|
|
59
53
|
```
|
|
60
54
|
|
|
61
|
-
---
|
|
62
|
-
|
|
63
55
|
## 核心 API
|
|
64
56
|
|
|
65
57
|
### 非对称加密 — `crypto.asymmetric`
|
|
@@ -221,8 +213,6 @@ const sharedServer = crypto.transport.createServer({
|
|
|
221
213
|
- kit:`kit.createHandle({ crypto: { crypto, transport: true } })` + `kit.client.create({ transport: { crypto } })`
|
|
222
214
|
- api-client:`apiClient.init({ transport: { crypto } })`
|
|
223
215
|
|
|
224
|
-
---
|
|
225
|
-
|
|
226
216
|
## 错误码 — `HaiCryptoError`
|
|
227
217
|
|
|
228
218
|
| 错误码 | code | 说明 |
|
|
@@ -240,8 +230,6 @@ const sharedServer = crypto.transport.createServer({
|
|
|
240
230
|
| `HaiCryptoError.HMAC_FAILED` | `hai:crypto:041` | HMAC 计算失败 |
|
|
241
231
|
| `HaiCryptoError.INVALID_IV` | `hai:crypto:060` | 无效 IV |
|
|
242
232
|
|
|
243
|
-
---
|
|
244
|
-
|
|
245
233
|
## 常见模式
|
|
246
234
|
|
|
247
235
|
### 数据加密存储
|
|
@@ -273,8 +261,6 @@ if (verifyResult.success && verifyResult.data) {
|
|
|
273
261
|
}
|
|
274
262
|
```
|
|
275
263
|
|
|
276
|
-
---
|
|
277
|
-
|
|
278
264
|
## 相关 Skills
|
|
279
265
|
|
|
280
266
|
- `hai-core`:配置与 HaiResult 模型
|