@h-ai/cli 0.1.0-alpha5
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/LICENSE +202 -0
- package/README.md +167 -0
- package/dist/chunk-2H7UOFLK.js +10 -0
- package/dist/chunk-2H7UOFLK.js.map +1 -0
- package/dist/chunk-LANIDJC7.js +659 -0
- package/dist/chunk-LANIDJC7.js.map +1 -0
- package/dist/chunk-YV7WPUI6.js +206 -0
- package/dist/chunk-YV7WPUI6.js.map +1 -0
- package/dist/config-templates-S5RI2MC6.js +5 -0
- package/dist/config-templates-S5RI2MC6.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1645 -0
- package/dist/index.js.map +1 -0
- package/dist/skill-templates-N5DU5FNX.js +5 -0
- package/dist/skill-templates-N5DU5FNX.js.map +1 -0
- package/dist/src-XJ6YXNAF.js +15030 -0
- package/dist/src-XJ6YXNAF.js.map +1 -0
- package/package.json +59 -0
- package/templates/README.md +126 -0
- package/templates/apps/admin/messages/en-US.json +33 -0
- package/templates/apps/admin/messages/zh-CN.json +33 -0
- package/templates/apps/admin/src/routes/+layout.svelte.hbs +30 -0
- package/templates/apps/admin/src/routes/+page.svelte.hbs +35 -0
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +44 -0
- package/templates/apps/admin/src/routes/admin/+page.svelte.hbs +45 -0
- package/templates/apps/admin/src/routes/admin/settings/+page.svelte.hbs +37 -0
- package/templates/apps/admin/src/routes/api/health/+server.ts +12 -0
- package/templates/apps/android-app/.env.hbs +3 -0
- package/templates/apps/android-app/capacitor.config.ts.hbs +12 -0
- package/templates/apps/android-app/messages/en-US.json +24 -0
- package/templates/apps/android-app/messages/zh-CN.json +24 -0
- package/templates/apps/android-app/src/lib/api.ts.hbs +30 -0
- package/templates/apps/android-app/src/lib/capacitor.ts +21 -0
- package/templates/apps/android-app/src/routes/+layout.svelte.hbs +42 -0
- package/templates/apps/android-app/src/routes/+layout.ts +7 -0
- package/templates/apps/android-app/src/routes/+page.svelte.hbs +27 -0
- package/templates/apps/api/src/routes/+layout.svelte +14 -0
- package/templates/apps/api/src/routes/+page.svelte +11 -0
- package/templates/apps/api/src/routes/api/v1/health/+server.ts +14 -0
- package/templates/apps/api/src/routes/api/v1/items/+server.ts +37 -0
- package/templates/apps/api/src/routes/api/v1/items/[id]/+server.ts +30 -0
- package/templates/apps/h5/messages/en-US.json +37 -0
- package/templates/apps/h5/messages/zh-CN.json +37 -0
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +57 -0
- package/templates/apps/h5/src/routes/+page.svelte.hbs +27 -0
- package/templates/apps/h5/src/routes/discover/+page.svelte.hbs +27 -0
- package/templates/apps/h5/src/routes/profile/+page.svelte.hbs +27 -0
- package/templates/apps/website/messages/en-US.json +32 -0
- package/templates/apps/website/messages/zh-CN.json +32 -0
- package/templates/apps/website/src/routes/+layout.svelte.hbs +66 -0
- package/templates/apps/website/src/routes/+page.svelte.hbs +33 -0
- package/templates/apps/website/src/routes/about/+page.svelte.hbs +27 -0
- package/templates/apps/website/src/routes/api/health/+server.ts +12 -0
- package/templates/apps/website/src/routes/contact/+page.svelte.hbs +59 -0
- package/templates/apps/website/src/routes/services/+page.svelte.hbs +63 -0
- package/templates/base/e2e/helpers.ts +49 -0
- package/templates/base/package.json.hbs +67 -0
- package/templates/base/playwright.config.ts +32 -0
- package/templates/base/src/app.css +2 -0
- package/templates/base/src/app.d.ts.hbs +18 -0
- package/templates/base/src/app.html.hbs +16 -0
- package/templates/base/src/hooks.server.ts.hbs +123 -0
- package/templates/base/src/lib/server/init.ts.hbs +190 -0
- package/templates/base/svelte.config.js.hbs +30 -0
- package/templates/base/tsconfig.json +8 -0
- package/templates/base/vite.config.ts.hbs +27 -0
- package/templates/features/ai/routes/api/chat/+server.ts +26 -0
- package/templates/features/datapipe/routes/api/datapipe/pipeline/+server.ts +41 -0
- package/templates/features/iam/routes-admin/(auth)/+layout.svelte.hbs +21 -0
- package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +98 -0
- package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +124 -0
- package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +98 -0
- package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +114 -0
- package/templates/features/iam/routes-shared/api/auth/login/+server.ts +32 -0
- package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +15 -0
- package/templates/features/iam/routes-shared/api/auth/me/+server.ts +12 -0
- package/templates/features/iam/routes-shared/api/auth/register/+server.ts +26 -0
- package/templates/features/payment/routes/api/v1/payment/create/+server.ts +30 -0
- package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +42 -0
- package/templates/features/payment/routes/api/v1/payment/query/[orderNo]/+server.ts +25 -0
- package/templates/features/storage/routes/api/upload/+server.ts +35 -0
- package/templates/features/vecdb/routes/api/vecdb/search/+server.ts +36 -0
- package/templates/i18n/project.inlang/settings.json +11 -0
- package/templates/skills/AGENTS.md +26 -0
- package/templates/skills/CLAUDE.md +12 -0
- package/templates/skills/copilot-instructions.md +61 -0
- package/templates/skills/hai-ai/SKILL.md +659 -0
- package/templates/skills/hai-api-client/SKILL.md +346 -0
- package/templates/skills/hai-app-create/SKILL.md +371 -0
- package/templates/skills/hai-app-review/SKILL.md +156 -0
- package/templates/skills/hai-app-tests/SKILL.md +468 -0
- package/templates/skills/hai-audit/SKILL.md +141 -0
- package/templates/skills/hai-build/SKILL.md +319 -0
- package/templates/skills/hai-cache/SKILL.md +184 -0
- package/templates/skills/hai-capacitor/SKILL.md +389 -0
- package/templates/skills/hai-core/SKILL.md +544 -0
- package/templates/skills/hai-crypto/SKILL.md +183 -0
- package/templates/skills/hai-datapipe/SKILL.md +214 -0
- package/templates/skills/hai-deploy/SKILL.md +210 -0
- package/templates/skills/hai-iam/SKILL.md +355 -0
- package/templates/skills/hai-kit/SKILL.md +319 -0
- package/templates/skills/hai-payment/SKILL.md +289 -0
- package/templates/skills/hai-reach/SKILL.md +325 -0
- package/templates/skills/hai-reldb/SKILL.md +459 -0
- package/templates/skills/hai-scheduler/SKILL.md +242 -0
- package/templates/skills/hai-storage/SKILL.md +230 -0
- package/templates/skills/hai-ui/SKILL.md +275 -0
- package/templates/skills/hai-vecdb/SKILL.md +273 -0
- package/templates/skills/opencode.json +11 -0
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hai-core
|
|
3
|
+
description: 使用 @h-ai/core 进行配置加载、日志记录、i18n 国际化、HaiResult 错误处理与模块生命周期管理;当需求涉及 core.init、core.logger、core.config、core.i18n、ok/err 或模块初始化模式时使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# hai-core
|
|
7
|
+
|
|
8
|
+
> `@h-ai/core` 是 hai-framework 的基础模块,提供配置管理、结构化日志、国际化、HaiResult 错误模型与模块生命周期工具。所有其他模块均依赖 core。
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 适用场景
|
|
13
|
+
|
|
14
|
+
- 项目初始化与配置加载
|
|
15
|
+
- 使用 `core.logger` 记录日志
|
|
16
|
+
- 使用 `core.config` 读取/校验模块配置
|
|
17
|
+
- 使用 `core.i18n` 管理多语言
|
|
18
|
+
- 使用 `ok()` / `err()` 构建 HaiResult 类型返回值
|
|
19
|
+
- 创建模块级 logger 或 i18n 消息获取器
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 使用步骤
|
|
24
|
+
|
|
25
|
+
### 1. 初始化
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { core } from '@h-ai/core'
|
|
29
|
+
|
|
30
|
+
// 自动加载 config/ 目录下的所有 _*.yml 和 *.yml
|
|
31
|
+
core.init({ configDir: './config' })
|
|
32
|
+
|
|
33
|
+
// 也可指定日志级别和配置监听
|
|
34
|
+
core.init({ configDir: './config', watchConfig: true })
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. 使用功能
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// 日志
|
|
41
|
+
core.logger.info('Server started', { port: 3000 })
|
|
42
|
+
|
|
43
|
+
// 配置
|
|
44
|
+
const dbConfig = core.config.get('db')
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 核心 API
|
|
50
|
+
|
|
51
|
+
### 生命周期
|
|
52
|
+
|
|
53
|
+
| 方法 | 签名 | 说明 |
|
|
54
|
+
| ----------- | --------------------------------- | ----------------------------------------- |
|
|
55
|
+
| `core.init` | `(options?: CoreOptions) => void` | 初始化(同步,加载配置目录中的所有 YAML) |
|
|
56
|
+
|
|
57
|
+
> `CoreOptions`:`{ logging?: Partial<LoggingConfig>, configDir?: string, watchConfig?: boolean }`
|
|
58
|
+
>
|
|
59
|
+
> 注意:core 没有 `close()` 方法,初始化后持续可用。
|
|
60
|
+
|
|
61
|
+
### 配置管理 — `core.config`
|
|
62
|
+
|
|
63
|
+
| 方法 | 签名 | 说明 |
|
|
64
|
+
| ------------ | -------------------------------------------------------------- | ------------------------------ |
|
|
65
|
+
| `get` | `<T>(name: string) => T \| undefined` | 获取模块配置段 |
|
|
66
|
+
| `getOrThrow` | `<T>(name: string) => T` | 获取配置(未加载时抛异常) |
|
|
67
|
+
| `load` | `<T>(name, filePath, schema?) => HaiResult<T>` | 手动加载配置文件 |
|
|
68
|
+
| `validate` | `<T>(name: string, schema: ZodType) => HaiResult<T>` | 用 Zod Schema 校验已加载的配置 |
|
|
69
|
+
| `has` | `(name: string) => boolean` | 检查配置段是否存在 |
|
|
70
|
+
| `reload` | `(name: string) => HaiResult<unknown>` | 重新从磁盘加载配置 |
|
|
71
|
+
| `clear` | `(name?: string) => void` | 清除配置缓存 |
|
|
72
|
+
| `keys` | `() => string[]` | 获取所有已加载的配置名称 |
|
|
73
|
+
| `watch` | `<T>(name, callback) => () => void` | 监听配置文件变更并自动重载 |
|
|
74
|
+
| `unwatch` | `(name?: string) => void` | 停止配置文件监听 |
|
|
75
|
+
| `isWatching` | `(name: string) => boolean` | 检查是否正在监听某个配置 |
|
|
76
|
+
|
|
77
|
+
**`watch()` 回调处理**:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// watch 回调签名:(config: T | null, error?: HaiError) => void
|
|
81
|
+
// - 成功重载时:传入新配置,error 为 undefined
|
|
82
|
+
// - 重载失败时:config 为 null,error 为错误详情(包含 code 和 message)
|
|
83
|
+
|
|
84
|
+
const unwatch = core.config.watch('db', (cfg, error) => {
|
|
85
|
+
if (error) {
|
|
86
|
+
// 处理重载失败(如文件解析错误),不应更新本地使用的配置
|
|
87
|
+
core.logger.error('Config reload failed', {
|
|
88
|
+
name: 'db',
|
|
89
|
+
code: error.code,
|
|
90
|
+
message: error.message,
|
|
91
|
+
})
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
// 使用新配置
|
|
95
|
+
// 注意:此时 core.config.get('db') 已经指向新数据
|
|
96
|
+
core.logger.info('Config updated', { db: cfg })
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
// 调用 unwatch() 停止监听
|
|
100
|
+
unwatch()
|
|
101
|
+
|
|
102
|
+
// 配置未加载时立即返回 NOT_LOADED 错误
|
|
103
|
+
core.config.watch('nonexistent', (_cfg, error) => {
|
|
104
|
+
// error.code === HaiConfigError.CONFIG_NOT_LOADED.code
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// 多个监听回调可在同一配置文件变更时同时触发
|
|
108
|
+
core.config.watch('app', cb1)
|
|
109
|
+
core.config.watch('app', cb2) // cb1 和 cb2 都会被调用
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**文件监听特性**:
|
|
113
|
+
|
|
114
|
+
- 使用 `fs.watch()` 实现,自动防抖(100ms 防抖避免快速重复保存触发多次回调)
|
|
115
|
+
- 同一配置名只会创建一个 watcher(多次 `watch()` 共享同一 watcher 实例)
|
|
116
|
+
- 当最后一个回调被移除时,watcher 自动关闭
|
|
117
|
+
- `unwatch()` 会关闭watcher,但缓存的配置数据保留
|
|
118
|
+
|
|
119
|
+
配置文件格式(YAML,支持环境变量插值):
|
|
120
|
+
|
|
121
|
+
- `${VAR}` — 读取 `process.env.VAR`;缺失则返回 `HaiConfigError.CONFIG_ENV_VAR_MISSING` 错误
|
|
122
|
+
- `${VAR:default}` — 读取 `process.env.VAR`;缺失则使用默认值
|
|
123
|
+
- **类型还原**:整个值恰好是单个变量表达式时,结果还原为原生类型(number / boolean 等);混合文本始终为字符串
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
# config/_core.yml
|
|
127
|
+
app:
|
|
128
|
+
name: ${HAI_APP_NAME:my-app} # → string
|
|
129
|
+
env: ${HAI_ENV:development} # → string
|
|
130
|
+
log:
|
|
131
|
+
level: ${HAI_LOG_LEVEL:info} # → string
|
|
132
|
+
format: ${HAI_LOG_FORMAT:pretty} # → string,pretty | json
|
|
133
|
+
feature:
|
|
134
|
+
debug: ${DEBUG:false} # → boolean false(类型还原)
|
|
135
|
+
maxRetries: ${MAX_RETRIES:3} # → number 3(类型还原)
|
|
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
|
+
### ID 生成 — `core.id`
|
|
216
|
+
|
|
217
|
+
| 方法 | 签名 | 说明 |
|
|
218
|
+
| -------------- | --------------------------------------- | ----------------------------- |
|
|
219
|
+
| `generate` | `(size?: number) => string` | nanoid(默认 21 字符) |
|
|
220
|
+
| `short` | `() => string` | 10 字符短 ID |
|
|
221
|
+
| `withPrefix` | `(prefix: string, length?: number) => string` | 带前缀的 nanoid |
|
|
222
|
+
| `trace` | `() => string` | `trace-` 前缀 ID |
|
|
223
|
+
| `request` | `() => string` | `req-` 前缀 ID |
|
|
224
|
+
| `uuid` | `() => string` | UUID v4 |
|
|
225
|
+
| `isValidUUID` | `(value: string) => boolean` | 校验 UUID 格式 |
|
|
226
|
+
| `isValidNanoId`| `(value: string, size?: number) => boolean` | 校验 nanoid 格式 |
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
const id = core.id.generate() // 'V1StGXR_Z5j3eK4uA9b8c'
|
|
230
|
+
const shortId = core.id.short() // 10 字符
|
|
231
|
+
const prefixed = core.id.withPrefix('order_') // 'order_V1StGXR...'
|
|
232
|
+
const traceId = core.id.trace() // 'trace-V1StGXR...'
|
|
233
|
+
const reqId = core.id.request() // 'req-V1StGXR...'
|
|
234
|
+
const uuid = core.id.uuid() // UUID v4
|
|
235
|
+
|
|
236
|
+
core.id.isValidUUID('f47ac10b-...') // true/false
|
|
237
|
+
core.id.isValidNanoId('abc123', 6) // true/false
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### 工具函数
|
|
241
|
+
|
|
242
|
+
#### 类型检查 — `core.typeUtils`
|
|
243
|
+
|
|
244
|
+
`isDefined` / `isObject` / `isFunction` / `isPromise` / `isString` / `isNumber` / `isBoolean` / `isArray`
|
|
245
|
+
|
|
246
|
+
#### 对象操作 — `core.object`
|
|
247
|
+
|
|
248
|
+
`deepClone` / `deepMerge` / `pick` / `omit` / `keys` / `values` / `entries` / `fromEntries`
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
core.object.deepMerge(a, b, c) // 递归合并(防原型污染)
|
|
252
|
+
core.object.pick(obj, ['a', 'b'])
|
|
253
|
+
core.object.omit(obj, ['c', 'd'])
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
#### 字符串操作 — `core.string`
|
|
257
|
+
|
|
258
|
+
`capitalize` / `kebabCase` / `camelCase` / `snakeCase` / `pascalCase` / `truncate` / `trim` / `isBlank` / `isNotBlank` / `padStart` / `padEnd` / `constantTimeEqual`
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
core.string.capitalize('hello') // 'Hello'
|
|
262
|
+
core.string.kebabCase('helloWorld') // 'hello-world'
|
|
263
|
+
core.string.constantTimeEqual(a, b) // 防时序攻击比较
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
#### 数组操作 — `core.array`
|
|
267
|
+
|
|
268
|
+
`unique` / `chunk` / `groupBy` / `first` / `last` / `flatten` / `compact` / `shuffle` / `intersection` / `difference`
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
core.array.unique([1, 1, 2]) // [1, 2]
|
|
272
|
+
core.array.chunk([1, 2, 3, 4], 2) // [[1,2], [3,4]]
|
|
273
|
+
core.array.groupBy(items, item => item.type) // Record<string, T[]>
|
|
274
|
+
core.array.intersection([1, 2], [2, 3]) // [2]
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
#### 异步操作 — `core.async`
|
|
278
|
+
|
|
279
|
+
`delay` / `withTimeout` / `retry` / `parallel` / `serial` / `debounce` / `throttle`
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
await core.async.delay(1000)
|
|
283
|
+
await core.async.withTimeout(fetch('/api'), 5000)
|
|
284
|
+
await core.async.retry(fn, { maxRetries: 3, delay: 1000 })
|
|
285
|
+
await core.async.parallel(items, processFn, 5) // 并发限制
|
|
286
|
+
const debouncedFn = core.async.debounce(fn, 300)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
#### 时间操作 — `core.time`
|
|
290
|
+
|
|
291
|
+
`formatDate` / `timeAgo` / `now` / `nowSeconds` / `parseDate` / `isValidDate` / `addDays` / `addHours` / `startOfDay` / `endOfDay`
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
core.time.formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss')
|
|
295
|
+
core.time.timeAgo(new Date(Date.now() - 60000)) // '1 分钟前'
|
|
296
|
+
core.time.addDays(date, 7)
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### HaiResult 模型与错误处理
|
|
300
|
+
|
|
301
|
+
所有 hai 模块操作返回 `HaiResult<T>` 类型,强制处理成功/失败分支:
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
import type { HaiResult, HaiErrorDef, HaiError } from '@h-ai/core'
|
|
305
|
+
import { err, ok } from '@h-ai/core'
|
|
306
|
+
|
|
307
|
+
// 成功
|
|
308
|
+
return ok(data)
|
|
309
|
+
|
|
310
|
+
// 失败:使用预定义错误定义创建错误实例
|
|
311
|
+
return err(HaiCommonError.NOT_FOUND, 'Resource not found')
|
|
312
|
+
|
|
313
|
+
// 带原始错误和建议的完整失败
|
|
314
|
+
return err(
|
|
315
|
+
HaiCommonError.INTERNAL_ERROR,
|
|
316
|
+
'Failed to process request',
|
|
317
|
+
originalError,
|
|
318
|
+
'Check database connection'
|
|
319
|
+
)
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**错误定义 vs 错误实例**:
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
// HaiErrorDef 是错误定义(包含错误码、HTTP 状态码、模块信息)
|
|
326
|
+
const errorDef: HaiErrorDef = {
|
|
327
|
+
code: 'hai:common:001', // system:module:code
|
|
328
|
+
httpStatus: 500, // HTTP 响应状态码
|
|
329
|
+
system: 'hai', // 系统标识
|
|
330
|
+
module: 'common', // 所属模块
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// HaiError 是具体错误实例(包含定义数据 + 运行时信息)
|
|
334
|
+
const errorInst: HaiError = {
|
|
335
|
+
...errorDef,
|
|
336
|
+
message: '具体错误消息', // i18n 翻译后的消息
|
|
337
|
+
cause?: originalError, // 原始错误
|
|
338
|
+
suggestion?: '用户可采取的行动', // 建议
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// err() 函数接受 HaiErrorDef,自动创建 HaiError 实例
|
|
342
|
+
err(errorDef, '消息文本', cause, '建议')
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**HaiResult 类型定义**:
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
type HaiResult<T>
|
|
349
|
+
= | { success: true, data: T }
|
|
350
|
+
| { success: false, error: HaiError }
|
|
351
|
+
code: string
|
|
352
|
+
message: string
|
|
353
|
+
httpStatus?: number
|
|
354
|
+
system?: string
|
|
355
|
+
module?: string
|
|
356
|
+
cause?: unknown
|
|
357
|
+
suggestion?: string
|
|
358
|
+
ext?: Record<string, unknown>
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**使用模式**:
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
const result = await someOperation()
|
|
366
|
+
|
|
367
|
+
// 模式1:直接检查
|
|
368
|
+
if (!result.success) {
|
|
369
|
+
core.logger.error('Operation failed', { error: result.error })
|
|
370
|
+
return result // 直接透传错误
|
|
371
|
+
}
|
|
372
|
+
const data = result.data
|
|
373
|
+
|
|
374
|
+
// 模式2:提前返回
|
|
375
|
+
if (!result.success)
|
|
376
|
+
return result
|
|
377
|
+
// 后续代码可安全使用 result.data
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### 模块工具 — `core.module`
|
|
381
|
+
|
|
382
|
+
| 方法 | 签名 | 说明 |
|
|
383
|
+
| ------------------------- | -------------------------------------------------------------------- | ---------------------------------------- |
|
|
384
|
+
| `createNotInitializedKit` | `<E>(code: E['code'], messageFn: () => string) => NotInitializedKit` | 创建未初始化代理(访问任何方法返回错误) |
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
// 在模块 main.ts 中使用
|
|
388
|
+
const notInitialized = core.module.createNotInitializedKit(
|
|
389
|
+
HaiReldbError.NOT_INITIALIZED,
|
|
390
|
+
() => reldbM('reldb_notInitialized'),
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
// 创建占位 Proxy(异步接口,默认)
|
|
394
|
+
let currentDdl: DdlOperations = notInitialized.proxy<DdlOperations>()
|
|
395
|
+
// 创建占位 Proxy(同步接口)
|
|
396
|
+
const currentHash: HashOperations = notInitialized.proxy<HashOperations>('sync')
|
|
397
|
+
|
|
398
|
+
export const myModule = {
|
|
399
|
+
async init(config) {
|
|
400
|
+
currentDdl = createDdlOperations(config)
|
|
401
|
+
return ok(undefined)
|
|
402
|
+
},
|
|
403
|
+
get ddl() {
|
|
404
|
+
return currentDdl
|
|
405
|
+
},
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## 错误码
|
|
410
|
+
|
|
411
|
+
Core 模块定义了两组标准错误码:
|
|
412
|
+
|
|
413
|
+
### `HaiCommonError`(通用错误)
|
|
414
|
+
|
|
415
|
+
| 错误码 | code | 说明 |
|
|
416
|
+
|--------|------|------|
|
|
417
|
+
| `HaiCommonError.NOT_INITIALIZED` | `hai:common:001` | 模块未初始化 |
|
|
418
|
+
| `HaiCommonError.INIT_FAILED` | `hai:common:002` | 初始化失败 |
|
|
419
|
+
| `HaiCommonError.INIT_IN_PROGRESS` | `hai:common:004` | 正在初始化中 |
|
|
420
|
+
| `HaiCommonError.UNAUTHORIZED` | `hai:common:100` | 未认证 |
|
|
421
|
+
| `HaiCommonError.FORBIDDEN` | `hai:common:101` | 无权限 |
|
|
422
|
+
| `HaiCommonError.TOKEN_EXPIRED` | `hai:common:102` | Token 已过期 |
|
|
423
|
+
| `HaiCommonError.TOKEN_INVALID` | `hai:common:103` | Token 无效 |
|
|
424
|
+
| `HaiCommonError.VALIDATION_ERROR` | `hai:common:200` | 校验失败 |
|
|
425
|
+
| `HaiCommonError.INVALID_REQUEST` | `hai:common:201` | 请求无效 |
|
|
426
|
+
| `HaiCommonError.PARAMETER_MISSING` | `hai:common:202` | 参数缺失 |
|
|
427
|
+
| `HaiCommonError.NOT_FOUND` | `hai:common:300` | 资源不存在 |
|
|
428
|
+
| `HaiCommonError.ALREADY_EXISTS` | `hai:common:301` | 资源已存在 |
|
|
429
|
+
| `HaiCommonError.CONFLICT` | `hai:common:302` | 冲突 |
|
|
430
|
+
| `HaiCommonError.API_ERROR` | `hai:common:400` | API 错误 |
|
|
431
|
+
| `HaiCommonError.NETWORK_ERROR` | `hai:common:401` | 网络错误 |
|
|
432
|
+
| `HaiCommonError.TIMEOUT` | `hai:common:402` | 超时 |
|
|
433
|
+
| `HaiCommonError.SERVICE_UNAVAILABLE` | `hai:common:403` | 服务不可用 |
|
|
434
|
+
| `HaiCommonError.INTERNAL_ERROR` | `hai:common:500` | 内部错误 |
|
|
435
|
+
| `HaiCommonError.DATABASE_ERROR` | `hai:common:501` | 数据库错误 |
|
|
436
|
+
| `HaiCommonError.UNKNOWN_ERROR` | `hai:common:599` | 未知错误 |
|
|
437
|
+
|
|
438
|
+
**使用示例**:
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
import { HaiCommonError, err } from '@h-ai/core'
|
|
442
|
+
|
|
443
|
+
// 创建错误实例
|
|
444
|
+
err(HaiCommonError.NOT_FOUND, 'User not found')
|
|
445
|
+
err(HaiCommonError.VALIDATION_ERROR, 'Invalid email', validationErrors)
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### `HaiConfigError`(配置错误)
|
|
449
|
+
|
|
450
|
+
| 错误码 | code | 说明 |
|
|
451
|
+
|--------|------|------|
|
|
452
|
+
| `HaiConfigError.CONFIG_FILE_NOT_FOUND` | `hai:core:010` | 配置文件不存在 |
|
|
453
|
+
| `HaiConfigError.CONFIG_PARSE_ERROR` | `hai:core:011` | YAML 解析失败 |
|
|
454
|
+
| `HaiConfigError.CONFIG_VALIDATION_ERROR` | `hai:core:012` | Schema 校验失败 |
|
|
455
|
+
| `HaiConfigError.CONFIG_ENV_VAR_MISSING` | `hai:core:013` | 必需环境变量缺失 |
|
|
456
|
+
| `HaiConfigError.CONFIG_NOT_LOADED` | `hai:core:014` | 配置未加载 |
|
|
457
|
+
|
|
458
|
+
**使用场景**:
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
import { core, HaiConfigError } from '@h-ai/core'
|
|
462
|
+
|
|
463
|
+
const result = core.config.load('db', './config/db.yml', schema)
|
|
464
|
+
if (!result.success) {
|
|
465
|
+
if (result.error.code === HaiConfigError.CONFIG_FILE_NOT_FOUND.code) {
|
|
466
|
+
core.logger.error('Config file missing')
|
|
467
|
+
}
|
|
468
|
+
else if (result.error.code === HaiConfigError.CONFIG_VALIDATION_ERROR.code) {
|
|
469
|
+
core.logger.error('Config validation failed', { issues: result.error.cause })
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### 自定义错误码(模块级)
|
|
475
|
+
|
|
476
|
+
各模块应定义自己的错误码常量,遵循格式 `hai:<module>:<code>`:
|
|
477
|
+
|
|
478
|
+
```typescript
|
|
479
|
+
// 示例:db 模块定义错误码
|
|
480
|
+
const DbErrorInfo = {
|
|
481
|
+
NOT_INITIALIZED: '001:500',
|
|
482
|
+
CONNECTION_FAILED: '010:500',
|
|
483
|
+
QUERY_FAILED: '011:500',
|
|
484
|
+
INVALID_SCHEMA: '012:400',
|
|
485
|
+
} as const satisfies ErrorInfo
|
|
486
|
+
|
|
487
|
+
export const DbError = error.buildHaiErrorsDef('db', DbErrorInfo)
|
|
488
|
+
|
|
489
|
+
// 使用
|
|
490
|
+
import { DbError, err } from '@h-ai/db'
|
|
491
|
+
|
|
492
|
+
err(DbError.CONNECTION_FAILED, 'Failed to connect to PostgreSQL')
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
## 常见模式
|
|
496
|
+
|
|
497
|
+
### 服务端初始化(SvelteKit hooks.server.ts)
|
|
498
|
+
|
|
499
|
+
```typescript
|
|
500
|
+
import { initModules } from '$lib/server/init'
|
|
501
|
+
|
|
502
|
+
// init.ts
|
|
503
|
+
import { core } from '@h-ai/core'
|
|
504
|
+
|
|
505
|
+
const appHandle = kit.createHandle({ /* ... */ })
|
|
506
|
+
|
|
507
|
+
export const handle = kit.sequence(appHandle)
|
|
508
|
+
|
|
509
|
+
let initialized = false
|
|
510
|
+
export async function initModules() {
|
|
511
|
+
if (initialized)
|
|
512
|
+
return
|
|
513
|
+
core.init({ configDir: './config' })
|
|
514
|
+
// ... 其他模块 init
|
|
515
|
+
initialized = true
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
### 配置校验最佳实践
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
import { z } from 'zod'
|
|
523
|
+
|
|
524
|
+
const MyConfigSchema = z.object({
|
|
525
|
+
apiKey: z.string().min(1),
|
|
526
|
+
timeout: z.number().positive().default(5000),
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
// 模块初始化时校验
|
|
530
|
+
const result = core.config.validate('myModule', MyConfigSchema)
|
|
531
|
+
if (!result.success) {
|
|
532
|
+
core.logger.error('Invalid config', { error: result.error })
|
|
533
|
+
return result
|
|
534
|
+
}
|
|
535
|
+
const config = result.data // 类型安全
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
---
|
|
539
|
+
|
|
540
|
+
## 相关 Skills
|
|
541
|
+
|
|
542
|
+
- `hai-build`:项目整体架构与技能导航
|
|
543
|
+
- `hai-kit`:SvelteKit 集成层(Handle/Guard/Middleware)
|
|
544
|
+
- `hai-reldb` / `hai-cache` / `hai-iam`:使用 core.config 读取配置的下游模块
|