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