@h-ai/cli 0.1.0-alpha7 → 0.1.0-alpha9
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/package.json +2 -2
- package/templates/base/package.json.hbs +21 -21
- package/templates/skills/hai-ai/SKILL.md +72 -10
- package/templates/skills/hai-api-client/SKILL.md +14 -5
- package/templates/skills/hai-audit/SKILL.md +4 -0
- package/templates/skills/hai-cache/SKILL.md +6 -0
- package/templates/skills/hai-capacitor/SKILL.md +6 -18
- package/templates/skills/hai-core/SKILL.md +57 -117
- package/templates/skills/hai-crypto/SKILL.md +15 -0
- package/templates/skills/hai-datapipe/SKILL.md +6 -0
- package/templates/skills/hai-deploy/SKILL.md +6 -0
- package/templates/skills/hai-iam/SKILL.md +8 -2
- package/templates/skills/hai-kit/SKILL.md +95 -60
- package/templates/skills/hai-payment/SKILL.md +11 -25
- package/templates/skills/hai-reach/SKILL.md +6 -16
- package/templates/skills/hai-reldb/SKILL.md +12 -6
- package/templates/skills/hai-scheduler/SKILL.md +8 -62
- package/templates/skills/hai-storage/SKILL.md +46 -0
- package/templates/skills/hai-ui/SKILL.md +9 -6
- package/templates/skills/hai-vecdb/SKILL.md +6 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-ai/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.0-
|
|
4
|
+
"version": "0.1.0-alpha9",
|
|
5
5
|
"description": "Hai Framework CLI for project scaffolding and code generation.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"keywords": [
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"ora": "^9.1.0",
|
|
35
35
|
"prompts": "^2.4.2",
|
|
36
36
|
"yaml": "^2.7.0",
|
|
37
|
-
"@h-ai/core": "0.1.0-
|
|
37
|
+
"@h-ai/core": "0.1.0-alpha9"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/fs-extra": "^11.0.4",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "{{projectName}}",
|
|
3
|
-
"version": "0.1.0-
|
|
3
|
+
"version": "0.1.0-alpha9",
|
|
4
4
|
"private": true,
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -14,26 +14,26 @@
|
|
|
14
14
|
"test:e2e": "playwright test"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@h-ai/core": "^0.1.0-
|
|
18
|
-
"@h-ai/kit": "^0.1.0-
|
|
19
|
-
"@h-ai/ui": "^0.1.0-
|
|
20
|
-
"@h-ai/reldb": "^0.1.0-
|
|
21
|
-
"@h-ai/cache": "^0.1.0-
|
|
22
|
-
"@h-ai/iam": "^0.1.0-
|
|
23
|
-
"@h-ai/crypto": "^0.1.0-
|
|
24
|
-
"@h-ai/storage": "^0.1.0-
|
|
25
|
-
"@h-ai/ai": "^0.1.0-
|
|
26
|
-
"@h-ai/audit": "^0.1.0-
|
|
27
|
-
"@h-ai/reach": "^0.1.0-
|
|
28
|
-
"@h-ai/payment": "^0.1.0-
|
|
29
|
-
"@h-ai/vecdb": "^0.1.0-
|
|
30
|
-
"@h-ai/datapipe": "^0.1.0-
|
|
31
|
-
"@h-ai/scheduler": "^0.1.0-
|
|
32
|
-
"@h-ai/deploy": "^0.1.0-
|
|
33
|
-
"@h-ai/api-client": "^0.1.0-
|
|
34
|
-
"@h-ai/capacitor": "^0.1.0-
|
|
35
|
-
"@h-ai/api-client": "^0.1.0-
|
|
36
|
-
"@h-ai/capacitor": "^0.1.0-
|
|
17
|
+
"@h-ai/core": "^0.1.0-alpha9",
|
|
18
|
+
"@h-ai/kit": "^0.1.0-alpha9"{{#if hasUi}},
|
|
19
|
+
"@h-ai/ui": "^0.1.0-alpha9"{{/if}}{{#if features.db}},
|
|
20
|
+
"@h-ai/reldb": "^0.1.0-alpha9"{{/if}}{{#if features.cache}},
|
|
21
|
+
"@h-ai/cache": "^0.1.0-alpha9"{{/if}}{{#if features.iam}},
|
|
22
|
+
"@h-ai/iam": "^0.1.0-alpha9"{{/if}}{{#if features.crypto}},
|
|
23
|
+
"@h-ai/crypto": "^0.1.0-alpha9"{{/if}}{{#if features.storage}},
|
|
24
|
+
"@h-ai/storage": "^0.1.0-alpha9"{{/if}}{{#if features.ai}},
|
|
25
|
+
"@h-ai/ai": "^0.1.0-alpha9"{{/if}}{{#if features.audit}},
|
|
26
|
+
"@h-ai/audit": "^0.1.0-alpha9"{{/if}}{{#if features.reach}},
|
|
27
|
+
"@h-ai/reach": "^0.1.0-alpha9"{{/if}}{{#if features.payment}},
|
|
28
|
+
"@h-ai/payment": "^0.1.0-alpha9"{{/if}}{{#if features.vecdb}},
|
|
29
|
+
"@h-ai/vecdb": "^0.1.0-alpha9"{{/if}}{{#if features.datapipe}},
|
|
30
|
+
"@h-ai/datapipe": "^0.1.0-alpha9"{{/if}}{{#if features.scheduler}},
|
|
31
|
+
"@h-ai/scheduler": "^0.1.0-alpha9"{{/if}}{{#if features.deploy}},
|
|
32
|
+
"@h-ai/deploy": "^0.1.0-alpha9"{{/if}}{{#if features.api-client}},
|
|
33
|
+
"@h-ai/api-client": "^0.1.0-alpha9"{{/if}}{{#if features.capacitor}},
|
|
34
|
+
"@h-ai/capacitor": "^0.1.0-alpha9"{{/if}}{{#if isCapacitorApp}},
|
|
35
|
+
"@h-ai/api-client": "^0.1.0-alpha9",
|
|
36
|
+
"@h-ai/capacitor": "^0.1.0-alpha9"{{/if}},
|
|
37
37
|
"zod": "^3.23.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
@@ -9,6 +9,21 @@ description: 使用 @h-ai/ai 进行 LLM 调用(OpenAI 兼容)、MCP 服务
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## 运行环境
|
|
13
|
+
|
|
14
|
+
| 能力 | Node.js | 浏览器 |
|
|
15
|
+
|------|---------|--------|
|
|
16
|
+
| `ai.llm` LLM 调用 | ✅ | ❌(通过 API 端点代理) |
|
|
17
|
+
| `ai.mcp` MCP 服务器 | ✅ | ❌ |
|
|
18
|
+
| `ai.tools` 工具定义 | ✅ | ✅(纯函数,无需 init) |
|
|
19
|
+
| `ai.stream` 流处理 | ✅ | ✅(纯函数,无需 init) |
|
|
20
|
+
| `ai.memory` / `ai.conversation` | ✅ | ❌(通过 API 查询) |
|
|
21
|
+
| `ai.knowledge` 知识库 | ✅ | ❌(通过 API 查询) |
|
|
22
|
+
|
|
23
|
+
浏览器端消费 AI 能力的标准模式:通过 `api.stream()` 或 `apiFetch` 调用服务端 API 端点。
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
12
27
|
## 依赖
|
|
13
28
|
|
|
14
29
|
| 模块 | 用途 | 是否必需 | 初始化要求 |
|
|
@@ -368,16 +383,6 @@ if (result.success) {
|
|
|
368
383
|
| `renameSession` | `(sessionId, title) => Promise<HaiResult<void>>` | 重命名会话 |
|
|
369
384
|
| `removeSession` | `(sessionId) => Promise<HaiResult<void>>` | 删除会话 |
|
|
370
385
|
|
|
371
|
-
**ContextManagerOptions**(嵌套子模块配置):
|
|
372
|
-
- `scope?: InteractionScope` — 交互作用域
|
|
373
|
-
- `systemPrompt?: string` — 系统提示词
|
|
374
|
-
- `model?: string` / `temperature?: number` — LLM 参数覆盖
|
|
375
|
-
- `compress?: CompressOptions & { auto?: boolean }` — 压缩配置(maxTokens/strategy/preserveLastN/auto 等)
|
|
376
|
-
- `memory?: { enable?, enableExtract?, topK?, maxTokens?, position? }` — 记忆配置
|
|
377
|
-
- `rag?: { enable?, sources?, topK?, minScore?, enableRerank? }` — RAG 配置
|
|
378
|
-
- `reasoning?: { enable?, strategy?, maxRounds? }` — 推理配置
|
|
379
|
-
- `tools?: ToolRegistryOperations` — 工具注册表
|
|
380
|
-
|
|
381
386
|
**ContextManager 方法**:
|
|
382
387
|
- `addMessage(msg)` — 追加消息(自动压缩)
|
|
383
388
|
- `getMessages()` / `getTokenUsage()` / `getSummaries()` — 查询状态
|
|
@@ -649,6 +654,63 @@ async function manualChat(userInput: string) {
|
|
|
649
654
|
|
|
650
655
|
---
|
|
651
656
|
|
|
657
|
+
## 浏览器端使用
|
|
658
|
+
|
|
659
|
+
### 流式对话(SSE)
|
|
660
|
+
|
|
661
|
+
浏览器通过 `api.stream()` 消费服务端流式 API:
|
|
662
|
+
|
|
663
|
+
```typescript
|
|
664
|
+
import { api } from '@h-ai/api-client'
|
|
665
|
+
|
|
666
|
+
const controller = new AbortController()
|
|
667
|
+
|
|
668
|
+
for await (const chunk of api.stream('/api/v1/ai/chat', {
|
|
669
|
+
conversationId: 'conv-1',
|
|
670
|
+
content: '你好',
|
|
671
|
+
}, { signal: controller.signal })) {
|
|
672
|
+
// 每个 chunk 是 SSE data: 行的文本内容
|
|
673
|
+
appendToUI(chunk)
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// 主动停止
|
|
677
|
+
controller.abort()
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
或使用 `apiFetch`(SSR SvelteKit 同源场景):
|
|
681
|
+
|
|
682
|
+
```typescript
|
|
683
|
+
import { apiFetch } from '$lib/utils/api'
|
|
684
|
+
|
|
685
|
+
const res = await apiFetch('/api/v1/ai/chat', {
|
|
686
|
+
method: 'POST',
|
|
687
|
+
headers: { 'Content-Type': 'application/json' },
|
|
688
|
+
body: JSON.stringify({ conversationId, content: userInput }),
|
|
689
|
+
})
|
|
690
|
+
|
|
691
|
+
// 手动消费 SSE 流
|
|
692
|
+
const reader = res.body!.getReader()
|
|
693
|
+
const decoder = new TextDecoder()
|
|
694
|
+
while (true) {
|
|
695
|
+
const { done, value } = await reader.read()
|
|
696
|
+
if (done) break
|
|
697
|
+
appendToUI(decoder.decode(value))
|
|
698
|
+
}
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
### 浏览器端使用 ai.stream(处理流式 chunk)
|
|
702
|
+
|
|
703
|
+
`ai.stream` 是纯函数,浏览器端可直接使用:
|
|
704
|
+
|
|
705
|
+
```typescript
|
|
706
|
+
import { ai } from '@h-ai/ai'
|
|
707
|
+
|
|
708
|
+
const processor = ai.stream.createProcessor()
|
|
709
|
+
// 将服务端返回的 SSE chunk 逐条 process,提取 delta
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
---
|
|
713
|
+
|
|
652
714
|
## 相关 Skills
|
|
653
715
|
|
|
654
716
|
- `hai-build`:模块初始化顺序
|
|
@@ -9,6 +9,20 @@ description: 使用 @h-ai/api-client 单例模式构建多端共用的纯 TypeSc
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## 运行环境
|
|
13
|
+
|
|
14
|
+
**浏览器端专用。** 服务端 `+page.server.ts` / `+server.ts` 应直接调用模块 API(如 `iam.auth.login()`),不经过 HTTP 自环。
|
|
15
|
+
|
|
16
|
+
### 与 kit.client 的分工
|
|
17
|
+
|
|
18
|
+
| 场景 | 推荐 | 原因 |
|
|
19
|
+
|------|------|------|
|
|
20
|
+
| SSR SvelteKit 应用(同源请求) | `kit.client.create().apiFetch`(见 hai-kit SKILL) | 自动 CSRF、传输加密、同源 Cookie 透传 |
|
|
21
|
+
| SPA / 原生 App(跨域请求) | **本模块** `api.init()` + `api.call()` | 完整 Token 管理、401 自动刷新、独立 baseUrl |
|
|
22
|
+
| 契约调用(类型安全,跨域) | **本模块** `api.call(endpoint, input)` | EndpointDef 双向 Zod 校验 |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
12
26
|
## 适用场景
|
|
13
27
|
|
|
14
28
|
- 浏览器端/App 端统一 API 请求
|
|
@@ -247,11 +261,6 @@ const chat = await api.call(aiEndpoints.sendMessage, {
|
|
|
247
261
|
| `ai` | `@h-ai/ai/api` | `aiEndpoints` | chat, chatStream, sendMessage |
|
|
248
262
|
| `payment` | `@h-ai/payment/api` | `paymentEndpoints` | createOrder, queryOrder, … |
|
|
249
263
|
|
|
250
|
-
### 新模块接入契约
|
|
251
|
-
|
|
252
|
-
在模块 `src/api/` 下创建 Schema + Contract,并在 `package.json` 中声明 `"./api"` 子路径导出。
|
|
253
|
-
|
|
254
|
-
---
|
|
255
264
|
|
|
256
265
|
## 常见模式
|
|
257
266
|
|
|
@@ -9,6 +9,12 @@ description: 使用 @h-ai/capacitor 桥接 Capacitor 原生能力(Token 安全
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## 运行环境
|
|
13
|
+
|
|
14
|
+
> **浏览器端 / 原生 App 专用。** 在 Capacitor 原生环境(Android/iOS)中提供完整能力,纯 Web 环境下部分功能退化(如 Preferences 退化为 localStorage)。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
12
18
|
## 适用场景
|
|
13
19
|
|
|
14
20
|
- Android/iOS 原生应用开发(SvelteKit + Capacitor)
|
|
@@ -20,24 +26,6 @@ description: 使用 @h-ai/capacitor 桥接 Capacitor 原生能力(Token 安全
|
|
|
20
26
|
|
|
21
27
|
---
|
|
22
28
|
|
|
23
|
-
## 模块结构
|
|
24
|
-
|
|
25
|
-
```
|
|
26
|
-
packages/capacitor/src/
|
|
27
|
-
index.ts # export * 聚合
|
|
28
|
-
capacitor-main.ts # 服务对象(export const capacitor)
|
|
29
|
-
capacitor-types.ts # 公共类型(CapacitorFunctions / CapacitorError / 业务类型)
|
|
30
|
-
capacitor-config.ts # 错误码(HaiCapacitorError as const)
|
|
31
|
-
capacitor-i18n.ts # i18n 消息获取器
|
|
32
|
-
capacitor-token-storage.ts # Token 存储(TokenStorage 实现 + safe* 工具)
|
|
33
|
-
capacitor-device.ts # 设备信息
|
|
34
|
-
capacitor-camera.ts # 相机/相册
|
|
35
|
-
capacitor-push.ts # 推送通知
|
|
36
|
-
capacitor-status-bar.ts # 状态栏
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
---
|
|
40
|
-
|
|
41
29
|
## 使用步骤
|
|
42
30
|
|
|
43
31
|
### 1. 初始化与关闭
|
|
@@ -9,6 +9,20 @@ description: 使用 @h-ai/core 进行配置加载、日志记录、i18n 国际
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## 运行环境
|
|
13
|
+
|
|
14
|
+
**Node.js + 浏览器双端可用。** 构建工具自动选择正确入口(`package.json` exports 条件导出)。
|
|
15
|
+
|
|
16
|
+
| 能力 | Node.js | 浏览器 |
|
|
17
|
+
|------|---------|--------|
|
|
18
|
+
| `core.logger` | ✅ pino(结构化 JSON / pretty) | ✅ loglevel(DevTools console) |
|
|
19
|
+
| `core.config` | ✅ 完整(YAML 加载、watch、validate) | ⚠️ 仅支持 `core.init({ ... })` 传入配置 |
|
|
20
|
+
| `core.i18n` | ✅ | ✅ |
|
|
21
|
+
| `core.id` | ✅ | ✅ |
|
|
22
|
+
| 工具函数 | ✅ | ✅ |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
12
26
|
## 适用场景
|
|
13
27
|
|
|
14
28
|
- 项目初始化与配置加载
|
|
@@ -98,24 +112,8 @@ const unwatch = core.config.watch('db', (cfg, error) => {
|
|
|
98
112
|
|
|
99
113
|
// 调用 unwatch() 停止监听
|
|
100
114
|
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
115
|
```
|
|
111
116
|
|
|
112
|
-
**文件监听特性**:
|
|
113
|
-
|
|
114
|
-
- 使用 `fs.watch()` 实现,自动防抖(100ms 防抖避免快速重复保存触发多次回调)
|
|
115
|
-
- 同一配置名只会创建一个 watcher(多次 `watch()` 共享同一 watcher 实例)
|
|
116
|
-
- 当最后一个回调被移除时,watcher 自动关闭
|
|
117
|
-
- `unwatch()` 会关闭watcher,但缓存的配置数据保留
|
|
118
|
-
|
|
119
117
|
配置文件格式(YAML,支持环境变量插值):
|
|
120
118
|
|
|
121
119
|
- `${VAR}` — 读取 `process.env.VAR`;缺失则返回 `HaiConfigError.CONFIG_ENV_VAR_MISSING` 错误
|
|
@@ -301,62 +299,13 @@ core.time.addDays(date, 7)
|
|
|
301
299
|
所有 hai 模块操作返回 `HaiResult<T>` 类型,强制处理成功/失败分支:
|
|
302
300
|
|
|
303
301
|
```typescript
|
|
304
|
-
import
|
|
305
|
-
import { err, ok } from '@h-ai/core'
|
|
302
|
+
import { ok, err, HaiCommonError } from '@h-ai/core'
|
|
306
303
|
|
|
307
304
|
// 成功
|
|
308
305
|
return ok(data)
|
|
309
306
|
|
|
310
|
-
//
|
|
307
|
+
// 失败
|
|
311
308
|
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
309
|
```
|
|
361
310
|
|
|
362
311
|
**使用模式**:
|
|
@@ -377,35 +326,6 @@ if (!result.success)
|
|
|
377
326
|
// 后续代码可安全使用 result.data
|
|
378
327
|
```
|
|
379
328
|
|
|
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
329
|
## 错误码
|
|
410
330
|
|
|
411
331
|
Core 模块定义了两组标准错误码:
|
|
@@ -471,27 +391,6 @@ if (!result.success) {
|
|
|
471
391
|
}
|
|
472
392
|
```
|
|
473
393
|
|
|
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
394
|
## 常见模式
|
|
496
395
|
|
|
497
396
|
### 服务端初始化(SvelteKit hooks.server.ts)
|
|
@@ -542,3 +441,44 @@ const config = result.data // 类型安全
|
|
|
542
441
|
- `hai-build`:项目整体架构与技能导航
|
|
543
442
|
- `hai-kit`:SvelteKit 集成层(Handle/Guard/Middleware)
|
|
544
443
|
- `hai-reldb` / `hai-cache` / `hai-iam`:使用 core.config 读取配置的下游模块
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## 浏览器侧使用
|
|
448
|
+
|
|
449
|
+
> **禁止在应用中直接引入 `loglevel`。** `core.logger` 在浏览器端自动使用 loglevel 作为后端,API 与 Node.js 完全一致。
|
|
450
|
+
|
|
451
|
+
### 初始化
|
|
452
|
+
|
|
453
|
+
浏览器端无文件系统,不需要 `configDir`。在 `+layout.svelte` 或应用入口调用:
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
import { core } from '@h-ai/core'
|
|
457
|
+
|
|
458
|
+
// 浏览器端:仅传入 logging 配置即可
|
|
459
|
+
core.init({ logging: { level: 'info' } })
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### 日志记录
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
// 与 Node.js 完全一致的 API
|
|
466
|
+
core.logger.info('Page loaded', { route: '/home' })
|
|
467
|
+
core.logger.error('API call failed', { url, status })
|
|
468
|
+
|
|
469
|
+
// 创建模块级 logger
|
|
470
|
+
const logger = core.logger.create({ name: 'my-page' })
|
|
471
|
+
logger.debug('Data fetched', { count: items.length })
|
|
472
|
+
|
|
473
|
+
// ❌ 禁止直接引用底层日志库
|
|
474
|
+
// import log from 'loglevel' ← 不要这样做
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### 浏览器侧限制
|
|
478
|
+
|
|
479
|
+
| 功能 | 行为 |
|
|
480
|
+
|------|------|
|
|
481
|
+
| `format: 'json' \| 'pretty'` | 被忽略(始终输出到 DevTools console) |
|
|
482
|
+
| `redact` 字段脱敏 | 不生效(需手动脱敏) |
|
|
483
|
+
| `fatal` 级别 | 映射为 `console.error`(带 `[FATAL]` 前缀) |
|
|
484
|
+
| context 输出 | JSON 字符串追加到消息末尾(非结构化对象) |
|
|
@@ -9,6 +9,21 @@ description: 使用 @h-ai/crypto 进行加密(非对称/哈希/对称)与密
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## 运行环境
|
|
13
|
+
|
|
14
|
+
**Node.js + 浏览器双端可用。** 浏览器端通过 `crypto.init()` 初始化(无需配置参数)。
|
|
15
|
+
|
|
16
|
+
| 能力 | Node.js | 浏览器 | 说明 |
|
|
17
|
+
|------|---------|--------|------|
|
|
18
|
+
| `crypto.asymmetric` 非对称加密 | ✅ | ✅ | SM2 加解密、签名验签 |
|
|
19
|
+
| `crypto.hash` 哈希 | ✅ | ✅ | SM3 哈希、HMAC |
|
|
20
|
+
| `crypto.symmetric` 对称加密 | ✅ | ✅ | SM4 ECB/CBC |
|
|
21
|
+
| `crypto.password` 密码哈希 | ✅ | ❌ | 依赖 Node.js crypto(服务端专用) |
|
|
22
|
+
|
|
23
|
+
浏览器端主要用于 kit 传输加密场景(`kit.client.create({ transport: { crypto } })`),一般不需要直接调用 crypto API。
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
12
27
|
## 适用场景
|
|
13
28
|
|
|
14
29
|
- 非对称加密/解密、签名/验签、密钥生成
|
|
@@ -9,6 +9,12 @@ description: 使用 @h-ai/iam 进行身份认证(密码/OTP/LDAP/API Key)、
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## 运行环境
|
|
13
|
+
|
|
14
|
+
> ⚠️ **服务端模块(Node.js only)。** 浏览器端通过 `api.call(iamEndpoints.xxx)` 或 `apiFetch` 调用 IAM API 端点(参见 hai-api-client / hai-kit SKILL)。`@h-ai/iam/api` 子路径导出的 Zod Schema 和 EndpointDef 可在浏览器端使用。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
12
18
|
## 依赖
|
|
13
19
|
|
|
14
20
|
| 模块 | 用途 | 是否必需 | 初始化要求 |
|
|
@@ -243,7 +249,7 @@ const result = await api.call(iamEndpoints.login, { username, password })
|
|
|
243
249
|
| `HaiIamError.PASSWORD_EXPIRED` | `hai:iam:007` | 密码已过期 |
|
|
244
250
|
| `HaiIamError.PASSWORD_POLICY_VIOLATION` | `hai:iam:008` | 密码不符合策略 |
|
|
245
251
|
| `HaiIamError.OTP_INVALID` | `hai:iam:009` | 验证码无效 |
|
|
246
|
-
| `HaiIamError.
|
|
252
|
+
| `HaiIamError.NOT_INITIALIZED` | `hai:iam:010` | 未初始化 |
|
|
247
253
|
| `HaiIamError.OTP_RESEND_TOO_FAST` | `hai:iam:011` | 发送过于频繁 |
|
|
248
254
|
| `HaiIamError.LOGIN_DISABLED` | `hai:iam:012` | 登录方式已禁用 |
|
|
249
255
|
| `HaiIamError.REGISTER_DISABLED` | `hai:iam:013` | 注册已禁用 |
|
|
@@ -255,6 +261,7 @@ const result = await api.call(iamEndpoints.login, { username, password })
|
|
|
255
261
|
| `HaiIamError.RESET_TOKEN_INVALID` | `hai:iam:019` | 重置令牌无效 |
|
|
256
262
|
| `HaiIamError.RESET_TOKEN_EXPIRED` | `hai:iam:020` | 重置令牌已过期 |
|
|
257
263
|
| `HaiIamError.RESET_TOKEN_MAX_ATTEMPTS` | `hai:iam:021` | 重置验证次数超限 |
|
|
264
|
+
| `HaiIamError.OTP_EXPIRED` | `hai:iam:022` | 验证码已过期 |
|
|
258
265
|
| `HaiIamError.SESSION_NOT_FOUND` | `hai:iam:101` | 会话不存在 |
|
|
259
266
|
| `HaiIamError.SESSION_EXPIRED` | `hai:iam:102` | 会话已过期 |
|
|
260
267
|
| `HaiIamError.SESSION_INVALID` | `hai:iam:103` | 会话无效 |
|
|
@@ -276,7 +283,6 @@ const result = await api.call(iamEndpoints.login, { username, password })
|
|
|
276
283
|
| `HaiIamError.FORBIDDEN` | `hai:iam:501` | 禁止访问 |
|
|
277
284
|
| `HaiIamError.INVALID_ARGUMENT` | `hai:iam:502` | 参数无效 |
|
|
278
285
|
| `HaiIamError.CONFIG_ERROR` | `hai:iam:901` | 配置错误 |
|
|
279
|
-
| `HaiIamError.NOT_INITIALIZED` | `hai:iam:910` | 未初始化 |
|
|
280
286
|
| `HaiIamError.INTERNAL_ERROR` | `hai:iam:999` | 内部错误 |
|
|
281
287
|
|
|
282
288
|
---
|
|
@@ -23,6 +23,9 @@ description: 使用 @h-ai/kit 构建 SvelteKit 请求管道与 API 安全边界
|
|
|
23
23
|
- `kit.guard.*`(`require` / `check`)
|
|
24
24
|
- `kit.handler` / `kit.response` / `kit.validate`
|
|
25
25
|
- `kit.fromContract(endpoint, fn)` — 契约到 SvelteKit handler
|
|
26
|
+
- `kit.client.create()` — 浏览器端统一 API 客户端(`apiFetch`)
|
|
27
|
+
- `kit.auth.createTokenStore()` — 浏览器端 Token 存储
|
|
28
|
+
- `kit.auth.createHandleFetch()` — `hooks.client.ts` 自动附加 Token
|
|
26
29
|
- Bearer Token / `Authorization` header
|
|
27
30
|
- CORS 配置(含 Capacitor Origin 预设)
|
|
28
31
|
- 传输加密(key exchange / `X-Client-Id`)
|
|
@@ -61,12 +64,6 @@ export const handle = kit.createHandle({
|
|
|
61
64
|
})
|
|
62
65
|
```
|
|
63
66
|
|
|
64
|
-
Bearer Token 解析流程:
|
|
65
|
-
|
|
66
|
-
1. 检查 `Authorization: Bearer <token>` header
|
|
67
|
-
2. 若无 header,回退到 `event.cookies.get('hai_access_token')`(SSR 透传场景)
|
|
68
|
-
3. Token 经 `auth.verifyToken` 校验后挂载到 `event.locals.session`
|
|
69
|
-
|
|
70
67
|
### 2) API 端点使用 `kit.handler`
|
|
71
68
|
|
|
72
69
|
```typescript
|
|
@@ -169,38 +166,10 @@ const myEndpoint = defineEndpoint({
|
|
|
169
166
|
| `VALIDATION_ERROR` | 422 | 数据校验失败 |
|
|
170
167
|
| `INTERNAL_ERROR` | 500 | 未处理异常 |
|
|
171
168
|
|
|
172
|
-
### 传输加密常见错误(i18n key)
|
|
173
|
-
|
|
174
|
-
| key | 含义 |
|
|
175
|
-
| -------------------------------- | ---------------------------------- |
|
|
176
|
-
| `kit_transportClientIdRequired` | 缺少 `X-Client-Id`(强制加密模式) |
|
|
177
|
-
| `kit_transportClientKeyNotFound` | 客户端密钥不存在或失效 |
|
|
178
|
-
| `kit_transportInvalidPayload` | 加密载荷格式非法 |
|
|
179
|
-
| `kit_transportDecryptFailed` | 请求体解密失败 |
|
|
180
|
-
| `kit_transportKeyExchangeFailed` | 密钥交换失败 |
|
|
181
|
-
|
|
182
169
|
## API 契约范式(端到端类型安全)
|
|
183
170
|
|
|
184
171
|
> `kit.fromContract` 和 `api.call` 共同消费模块 `api/` 下的 `EndpointDef`,实现服务端↔客户端的全链路类型安全 + 运行时 Zod 校验。
|
|
185
172
|
|
|
186
|
-
### 契约架构
|
|
187
|
-
|
|
188
|
-
```
|
|
189
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
190
|
-
│ @h-ai/xx/api │
|
|
191
|
-
│ xx-api-schemas.ts ← Zod Schema(唯一真相源) │
|
|
192
|
-
│ xx-api-contract.ts ← xxEndpoints(method + path + schema)│
|
|
193
|
-
└─────────────┬──────────────────────────┬────────────────────┘
|
|
194
|
-
│ │
|
|
195
|
-
┌────────▼────────┐ ┌─────────▼─────────┐
|
|
196
|
-
│ 客户端(浏览器)│ │ 服务端(SvelteKit)│
|
|
197
|
-
│ api.call(ep, i) │ │ kit.fromContract │
|
|
198
|
-
│ @h-ai/api-client│ │ @h-ai/kit │
|
|
199
|
-
└────────┬────────┘ └─────────┬─────────┘
|
|
200
|
-
│ HTTP(类型安全) │
|
|
201
|
-
└──────────────────────────┘
|
|
202
|
-
```
|
|
203
|
-
|
|
204
173
|
### 服务端流程(本模块责任)
|
|
205
174
|
|
|
206
175
|
1. 从 `@h-ai/xx/api` 导入 `xxEndpoints`
|
|
@@ -249,33 +218,8 @@ export const POST = kit.fromContract(paymentEndpoints.createOrder, async (input,
|
|
|
249
218
|
})
|
|
250
219
|
```
|
|
251
220
|
|
|
252
|
-
### 已有契约模块一览
|
|
253
|
-
|
|
254
|
-
| 模块 | 导入路径 | 端点对象 | 典型端点 |
|
|
255
|
-
| ---------- | -------------------- | -------------------- | ------------------------------- |
|
|
256
|
-
| `storage` | `@h-ai/storage/api` | `storageEndpoints` | presignUpload, listFiles, … |
|
|
257
|
-
| `iam` | `@h-ai/iam/api` | `iamEndpoints` | login, logout, currentUser, … |
|
|
258
|
-
| `ai` | `@h-ai/ai/api` | `aiEndpoints` | chat, chatStream, sendMessage |
|
|
259
|
-
| `payment` | `@h-ai/payment/api` | `paymentEndpoints` | createOrder, queryOrder, … |
|
|
260
|
-
|
|
261
|
-
### 新模块接入契约
|
|
262
|
-
|
|
263
|
-
在模块 `src/api/` 下创建 Schema + Contract,并在 `package.json` 中声明 `"./api"` 子路径导出。
|
|
264
|
-
|
|
265
221
|
## 常见模式
|
|
266
222
|
|
|
267
|
-
### Bearer Token 认证流程
|
|
268
|
-
|
|
269
|
-
```
|
|
270
|
-
客户端 → Authorization: Bearer <accessToken> → kit.createHandle 解析
|
|
271
|
-
↓
|
|
272
|
-
auth.verifyToken(token) → locals.session
|
|
273
|
-
↓
|
|
274
|
-
protectedPaths 守卫检查 → 通过则继续
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
SSR 场景下,浏览器通过 `hai_access_token` httpOnly Cookie 透传 Token,Handle 自动回退读取。
|
|
278
|
-
|
|
279
223
|
### 契约模式(推荐新项目使用)
|
|
280
224
|
|
|
281
225
|
1. 在模块的 `api/` 目录定义 `EndpointDef`(Zod schema)
|
|
@@ -314,6 +258,97 @@ const config = {
|
|
|
314
258
|
|
|
315
259
|
- `hai-core`:全局配置、日志、HaiResult 基础能力
|
|
316
260
|
- `hai-iam`:Token 认证、角色权限模型
|
|
317
|
-
- `hai-api-client
|
|
261
|
+
- `hai-api-client`:客户端契约调用(SPA / 原生 App 场景)
|
|
318
262
|
- `hai-crypto`:传输加密与密钥管理能力
|
|
319
263
|
- `hai-capacitor`:原生 App CORS 场景
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## 浏览器侧使用
|
|
268
|
+
|
|
269
|
+
### 创建统一 API 客户端 — `kit.client`
|
|
270
|
+
|
|
271
|
+
> **所有浏览器端 API 请求必须通过 `apiFetch`,禁止直接 `fetch()`。**
|
|
272
|
+
|
|
273
|
+
在 `src/lib/utils/api.ts` 中一次性创建,全应用共享:
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
// src/lib/utils/api.ts
|
|
277
|
+
import { kit } from '@h-ai/kit'
|
|
278
|
+
|
|
279
|
+
const client = kit.client.create({
|
|
280
|
+
auth: true, // 自动从 localStorage 注入 Bearer Token
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
export const { apiFetch } = client
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
如需传输加密,传入 crypto:
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
import { crypto } from '@h-ai/crypto'
|
|
290
|
+
import { kit } from '@h-ai/kit'
|
|
291
|
+
|
|
292
|
+
if (typeof window !== 'undefined') {
|
|
293
|
+
crypto.init() // 浏览器端初始化加密模块
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const client = kit.client.create({
|
|
297
|
+
transport: { crypto }, // 透明完成密钥交换 + 请求/响应体加解密
|
|
298
|
+
auth: true,
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
export const { apiFetch } = client
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
`apiFetch` 自动处理:
|
|
305
|
+
- **CSRF**:写方法(POST/PUT/DELETE)自动读取 `hai_csrf` Cookie 并设置 `X-CSRF-Token`
|
|
306
|
+
- **Bearer Token**:自动从 localStorage 读取并注入 `Authorization` header
|
|
307
|
+
- **传输加密**:可选,首次写请求时自动密钥交换
|
|
308
|
+
|
|
309
|
+
使用示例:
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
// ✅ 正确方式
|
|
313
|
+
import { apiFetch } from '$lib/utils/api'
|
|
314
|
+
|
|
315
|
+
const res = await apiFetch('/api/users', {
|
|
316
|
+
method: 'POST',
|
|
317
|
+
headers: { 'Content-Type': 'application/json' },
|
|
318
|
+
body: JSON.stringify(data),
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
// ❌ 禁止在组件中直接 fetch
|
|
322
|
+
// const res = await fetch('/api/users', { ... })
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Token 存储 — `kit.auth.createTokenStore`
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
import { kit } from '@h-ai/kit'
|
|
329
|
+
|
|
330
|
+
// 创建 localStorage 存储(Web 端默认)
|
|
331
|
+
const tokenStore = kit.auth.createTokenStore('hai_access_token')
|
|
332
|
+
tokenStore.set(tokens.accessToken) // 登录后保存
|
|
333
|
+
tokenStore.get() // string | null
|
|
334
|
+
tokenStore.clear() // 登出时清除
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### hooks.client.ts 标准配置 — `kit.auth.createHandleFetch`
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
// src/hooks.client.ts
|
|
341
|
+
import { kit } from '@h-ai/kit'
|
|
342
|
+
|
|
343
|
+
// 自动为同源请求附加 Authorization: Bearer <token>
|
|
344
|
+
export const handleFetch = kit.auth.createHandleFetch()
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### 选择指南:kit.client vs api-client
|
|
348
|
+
|
|
349
|
+
| 场景 | 推荐 | 原因 |
|
|
350
|
+
|------|------|------|
|
|
351
|
+
| SSR SvelteKit 应用(同源请求) | `kit.client.create().apiFetch` | 自动 CSRF、同源 Cookie 透传、传输加密 |
|
|
352
|
+
| SPA / 原生 App(跨域请求) | `api.init()` + `api.call()` | 完整 Token 管理、401 自动刷新 |
|
|
353
|
+
| 服务端 `+page.server.ts` | 直接调用模块 API | 无需 HTTP 自环 |
|
|
354
|
+
| 契约调用(类型安全) | `api.call(endpoint, input)` | EndpointDef 双向 Zod 校验 |
|
|
@@ -9,6 +9,12 @@ description: 使用 @h-ai/payment 接入微信支付、支付宝、Stripe 统一
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## 运行环境
|
|
13
|
+
|
|
14
|
+
> **服务端 + 浏览器分工模块。** `payment.createOrder` / `payment.handleNotify` / `payment.refund` 等操作在 Node.js 端执行。浏览器端使用 `invokePayment()`(从 `@h-ai/payment/client` 导入)调起支付。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
12
18
|
## 依赖
|
|
13
19
|
|
|
14
20
|
| 模块 | 用途 | 是否必需 | 初始化要求 |
|
|
@@ -189,25 +195,14 @@ const payResult = await invokePayment(orderResult.data)
|
|
|
189
195
|
|
|
190
196
|
## Provider 模式
|
|
191
197
|
|
|
192
|
-
### PaymentProvider 接口
|
|
193
|
-
|
|
194
|
-
```typescript
|
|
195
|
-
interface PaymentProvider {
|
|
196
|
-
readonly name: string
|
|
197
|
-
createOrder: (input: CreateOrderInput) => Promise<HaiResult<PaymentOrder>>
|
|
198
|
-
handleNotify: (request: PaymentNotifyRequest) => Promise<HaiResult<PaymentNotifyResult>>
|
|
199
|
-
queryOrder: (outTradeNo: string) => Promise<HaiResult<OrderStatus>>
|
|
200
|
-
refund: (input: RefundInput) => Promise<HaiResult<RefundResult>>
|
|
201
|
-
closeOrder: (outTradeNo: string) => Promise<HaiResult<void>>
|
|
202
|
-
}
|
|
203
|
-
```
|
|
204
|
-
|
|
205
198
|
内置 Provider:
|
|
206
199
|
|
|
207
|
-
- **wechat** — 微信支付 API v3
|
|
208
|
-
- **alipay** —
|
|
200
|
+
- **wechat** — 微信支付 API v3
|
|
201
|
+
- **alipay** — 支付宝开放平台
|
|
209
202
|
- **stripe** — Stripe Checkout Session 模式
|
|
210
203
|
|
|
204
|
+
自定义 Provider 通过 `payment.registerProvider(provider)` 注册。
|
|
205
|
+
|
|
211
206
|
### API 契约
|
|
212
207
|
|
|
213
208
|
```typescript
|
|
@@ -267,16 +262,7 @@ export const POST = kit.handler(async ({ request, locals }) => {
|
|
|
267
262
|
|
|
268
263
|
## 审计日志
|
|
269
264
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
| 操作 | `action` | `resource` | `resourceId` | `details` |
|
|
273
|
-
| --- | --- | --- | --- | --- |
|
|
274
|
-
| 创建订单 | `create_order` | `payment` | `orderNo` | `{ provider, amount, tradeType }` |
|
|
275
|
-
| 支付回调 | `payment_notify` | `payment` | `orderNo` | `{ provider, transactionId, status, amount }` |
|
|
276
|
-
| 退款 | `refund` | `payment` | `orderNo` | `{ provider, refundNo, amount }` |
|
|
277
|
-
| 关闭订单 | `close_order` | `payment` | `orderNo` | `{ provider }` |
|
|
278
|
-
|
|
279
|
-
> `queryOrder` 为只读操作,不写审计日志。
|
|
265
|
+
关键支付操作(创建订单、回调、退款、关闭订单)成功后自动写入审计日志(依赖 `@h-ai/audit`),无需额外配置。`queryOrder` 为只读操作,不写审计日志。
|
|
280
266
|
|
|
281
267
|
---
|
|
282
268
|
|
|
@@ -9,6 +9,12 @@ description: 使用 @h-ai/reach 进行邮件、短信和 API 回调发送;当
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## 运行环境
|
|
13
|
+
|
|
14
|
+
> ⚠️ **服务端模块(Node.js only)。** 浏览器端不直接发送邮件/短信,而是通过 API 端点触发服务端 `reach.send()`。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
12
18
|
## 依赖
|
|
13
19
|
|
|
14
20
|
| 模块 | 用途 | 是否必需 | 初始化要求 |
|
|
@@ -300,22 +306,6 @@ if (!result.success) {
|
|
|
300
306
|
}
|
|
301
307
|
```
|
|
302
308
|
|
|
303
|
-
### 分布式锁(DND flush 保护)
|
|
304
|
-
|
|
305
|
-
> 多节点部署时,DND(delay 策略)的 pending 消息 flush 通过 `@h-ai/cache` 分布式锁保护,确保同一时刻只有一个节点执行 flush。
|
|
306
|
-
|
|
307
|
-
```typescript
|
|
308
|
-
import { cache } from '@h-ai/cache'
|
|
309
|
-
|
|
310
|
-
// 初始化 cache 后,reach 自动使用分布式锁保护 flush 操作
|
|
311
|
-
await cache.init({ type: 'redis', host: 'localhost', port: 6379 })
|
|
312
|
-
await reach.init({ /* ... */ })
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
- 锁键:`reach:flush-pending`,TTL 60 秒
|
|
316
|
-
- 若 cache 未初始化,分布式锁自动禁用,不影响单节点运行
|
|
317
|
-
- 使用稳定的进程级 owner 标识,防止误释放他人锁
|
|
318
|
-
|
|
319
309
|
---
|
|
320
310
|
|
|
321
311
|
## 相关 Skills
|
|
@@ -9,6 +9,12 @@ description: 使用 @h-ai/reldb 进行 SQLite/PostgreSQL/MySQL 的初始化、SQ
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## 运行环境
|
|
13
|
+
|
|
14
|
+
> ⚠️ **服务端模块(Node.js only)。** 浏览器端请通过 API 端点间接访问数据库(参见 hai-kit 契约模式 `kit.fromContract`)。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
12
18
|
## 适用场景
|
|
13
19
|
|
|
14
20
|
- 新增或修改数据库访问逻辑(SQL/DDL/CRUD/事务)
|
|
@@ -310,7 +316,7 @@ if (txResult.success) {
|
|
|
310
316
|
}
|
|
311
317
|
```
|
|
312
318
|
|
|
313
|
-
|
|
319
|
+
**事务内可用操作**:
|
|
314
320
|
|
|
315
321
|
- `tx.query / tx.get / tx.execute / tx.batch / tx.queryPage`
|
|
316
322
|
- `tx.crud.table(config)` — 事务内的 CRUD 仓库
|
|
@@ -392,11 +398,11 @@ await reldb.sql.execute(`UPDATE users SET profile = ${sql} WHERE id = ?`, [...pa
|
|
|
392
398
|
| `HaiReldbError.DUPLICATE_ENTRY` | `hai:reldb:007` | 重复条目 |
|
|
393
399
|
| `HaiReldbError.DEADLOCK` | `hai:reldb:008` | 死锁 |
|
|
394
400
|
| `HaiReldbError.TIMEOUT` | `hai:reldb:009` | 超时 |
|
|
395
|
-
| `HaiReldbError.
|
|
396
|
-
| `HaiReldbError.
|
|
397
|
-
| `HaiReldbError.
|
|
398
|
-
| `HaiReldbError.
|
|
399
|
-
| `HaiReldbError.
|
|
401
|
+
| `HaiReldbError.NOT_INITIALIZED` | `hai:reldb:010` | 数据库未初始化 |
|
|
402
|
+
| `HaiReldbError.POOL_EXHAUSTED` | `hai:reldb:011` | 连接池耗尽 |
|
|
403
|
+
| `HaiReldbError.UNSUPPORTED_TYPE` | `hai:reldb:012` | 不支持的数据库类型 |
|
|
404
|
+
| `HaiReldbError.CONFIG_ERROR` | `hai:reldb:013` | 配置错误 |
|
|
405
|
+
| `HaiReldbError.DDL_FAILED` | `hai:reldb:020` | DDL 操作失败 |
|
|
400
406
|
|
|
401
407
|
---
|
|
402
408
|
|
|
@@ -7,6 +7,10 @@ description: 使用 @h-ai/scheduler 进行统一定时任务管理、任务持
|
|
|
7
7
|
|
|
8
8
|
> `@h-ai/scheduler` 使用统一任务模型管理 API / JS / Hook 三类执行路径,支持持久化、手工触发来源记录、分布式锁与全局生命周期回调。
|
|
9
9
|
|
|
10
|
+
## 运行环境
|
|
11
|
+
|
|
12
|
+
> ⚠️ **服务端模块(Node.js only)。** 浏览器端通过 API 端点触发 `scheduler.trigger()` 或查询任务日志。
|
|
13
|
+
|
|
10
14
|
## 依赖
|
|
11
15
|
|
|
12
16
|
| 模块 | 用途 | 是否必需 | 初始化要求 |
|
|
@@ -20,6 +24,7 @@ description: 使用 @h-ai/scheduler 进行统一定时任务管理、任务持
|
|
|
20
24
|
|
|
21
25
|
```ts
|
|
22
26
|
import { cache } from '@h-ai/cache'
|
|
27
|
+
import { core } from '@h-ai/core'
|
|
23
28
|
import { reldb } from '@h-ai/reldb'
|
|
24
29
|
import { scheduler } from '@h-ai/scheduler'
|
|
25
30
|
|
|
@@ -32,13 +37,13 @@ await scheduler.init({
|
|
|
32
37
|
retentionDays: 30,
|
|
33
38
|
hooks: {
|
|
34
39
|
onTaskStart(event) {
|
|
35
|
-
|
|
40
|
+
core.logger.info('Task started', { taskId: event.task.id, trigger: event.trigger })
|
|
36
41
|
},
|
|
37
42
|
onTaskInterrupted(event) {
|
|
38
|
-
|
|
43
|
+
core.logger.warn('Task interrupted', { taskId: event.task.id, reason: event.reason })
|
|
39
44
|
},
|
|
40
45
|
onTaskFinish(event) {
|
|
41
|
-
|
|
46
|
+
core.logger.info('Task finished', { status: event.log.status })
|
|
42
47
|
},
|
|
43
48
|
},
|
|
44
49
|
})
|
|
@@ -106,65 +111,6 @@ const logs = await scheduler.getLogs({
|
|
|
106
111
|
})
|
|
107
112
|
```
|
|
108
113
|
|
|
109
|
-
## 核心类型
|
|
110
|
-
|
|
111
|
-
### TaskDefinition
|
|
112
|
-
|
|
113
|
-
```ts
|
|
114
|
-
interface TaskDefinition {
|
|
115
|
-
id: string
|
|
116
|
-
name: string
|
|
117
|
-
description?: string
|
|
118
|
-
cron: string
|
|
119
|
-
enabled?: boolean
|
|
120
|
-
deleteAfterRun?: boolean
|
|
121
|
-
retry?: {
|
|
122
|
-
maxAttempts: number
|
|
123
|
-
backoffMs?: number[]
|
|
124
|
-
}
|
|
125
|
-
params?: Record<string, unknown>
|
|
126
|
-
handler?: ApiTaskConfig | JsTaskConfig
|
|
127
|
-
}
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### Handler 配置
|
|
131
|
-
|
|
132
|
-
```ts
|
|
133
|
-
interface ApiTaskConfig {
|
|
134
|
-
kind: 'api'
|
|
135
|
-
url: string
|
|
136
|
-
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
|
|
137
|
-
headers?: Record<string, string>
|
|
138
|
-
body?: unknown
|
|
139
|
-
timeout?: number
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
interface JsTaskConfig {
|
|
143
|
-
kind: 'js'
|
|
144
|
-
code: string
|
|
145
|
-
timeout?: number
|
|
146
|
-
}
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
### TaskExecutionLog
|
|
150
|
-
|
|
151
|
-
```ts
|
|
152
|
-
interface TaskExecutionLog {
|
|
153
|
-
id: number
|
|
154
|
-
taskId: string
|
|
155
|
-
taskName: string
|
|
156
|
-
taskType: 'api' | 'js' | 'hook'
|
|
157
|
-
triggerType: 'scheduled' | 'manual'
|
|
158
|
-
triggerSource: string | null
|
|
159
|
-
status: 'success' | 'failed' | 'interrupted'
|
|
160
|
-
result: string | null
|
|
161
|
-
error: string | null
|
|
162
|
-
startedAt: number
|
|
163
|
-
finishedAt: number
|
|
164
|
-
duration: number
|
|
165
|
-
}
|
|
166
|
-
```
|
|
167
|
-
|
|
168
114
|
## 核心 API
|
|
169
115
|
|
|
170
116
|
| 方法 / 属性 | 签名 | 说明 |
|
|
@@ -9,6 +9,12 @@ description: 使用 @h-ai/storage 进行文件存储操作(本地/S3),包
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## 运行环境
|
|
13
|
+
|
|
14
|
+
> ⚠️ **服务端模块**。`storage.file` / `storage.dir` / `storage.presign` 均在 Node.js 端使用。浏览器端通过预签名 URL 或 API 端点间接访问存储(见下方「浏览器端文件上传」)。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
12
18
|
## 适用场景
|
|
13
19
|
|
|
14
20
|
- 文件上传与下载
|
|
@@ -222,6 +228,46 @@ if (result.success) {
|
|
|
222
228
|
|
|
223
229
|
---
|
|
224
230
|
|
|
231
|
+
## 浏览器端文件上传(预签名 URL 模式)
|
|
232
|
+
|
|
233
|
+
浏览器端无法直接使用 `storage` 模块,应通过 API 获取预签名 URL 后直传:
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
import { apiFetch } from '$lib/utils/api'
|
|
237
|
+
|
|
238
|
+
// 1. 向服务端请求预签名上传 URL
|
|
239
|
+
const res = await apiFetch('/api/storage/presign/upload', {
|
|
240
|
+
method: 'POST',
|
|
241
|
+
headers: { 'Content-Type': 'application/json' },
|
|
242
|
+
body: JSON.stringify({ key: `uploads/${file.name}`, contentType: file.type }),
|
|
243
|
+
})
|
|
244
|
+
const { url } = await res.json()
|
|
245
|
+
|
|
246
|
+
// 2. 浏览器直传到存储(不经过服务端,大文件友好)
|
|
247
|
+
await fetch(url, {
|
|
248
|
+
method: 'PUT',
|
|
249
|
+
body: file,
|
|
250
|
+
headers: { 'Content-Type': file.type },
|
|
251
|
+
})
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
也可使用契约调用(需配合 `@h-ai/api-client`):
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
import { api } from '@h-ai/api-client'
|
|
258
|
+
import { storageEndpoints } from '@h-ai/storage/api'
|
|
259
|
+
|
|
260
|
+
const result = await api.call(storageEndpoints.presignUpload, {
|
|
261
|
+
key: `avatars/${userId}.png`,
|
|
262
|
+
contentType: 'image/png',
|
|
263
|
+
})
|
|
264
|
+
if (result.success) {
|
|
265
|
+
await fetch(result.data.url, { method: 'PUT', body: file })
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
225
271
|
## 相关 Skills
|
|
226
272
|
|
|
227
273
|
- `hai-build`:模块初始化顺序
|
|
@@ -9,6 +9,12 @@ description: 使用 @h-ai/ui 构建多端应用界面,包含三层组件架构
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## 运行环境
|
|
13
|
+
|
|
14
|
+
> **浏览器端专用。** Svelte 组件在浏览器中渲染,服务端无需引用。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
12
18
|
## 适用场景
|
|
13
19
|
|
|
14
20
|
- 构建管理后台页面(表单、表格、弹窗、导航等)
|
|
@@ -257,10 +263,9 @@ const theme = getCurrentTheme()
|
|
|
257
263
|
|
|
258
264
|
1. **Svelte 5 Runes**:使用 `$state`、`$derived`、`$effect`
|
|
259
265
|
2. **Snippet 插槽**:使用 `{#snippet name()}...{/snippet}` 语法
|
|
260
|
-
3.
|
|
261
|
-
4.
|
|
262
|
-
5.
|
|
263
|
-
6. **移动端样式**:务必引入 `design-tokens.css` + `mobile.css`,使用 SafeArea 包裹原生 App 页面
|
|
266
|
+
3. **自动导入例外**:`toast`、类型导入、`Range`/`FileList` 必须显式 `import`
|
|
267
|
+
4. **Combobox 统一单选/多选**:`MultiSelect` 已删除
|
|
268
|
+
5. **移动端样式**:务必引入 `design-tokens.css` + `mobile.css`,使用 SafeArea 包裹原生 App 页面
|
|
264
269
|
|
|
265
270
|
---
|
|
266
271
|
|
|
@@ -271,5 +276,3 @@ const theme = getCurrentTheme()
|
|
|
271
276
|
- `hai-iam`:IAM 模块 API(与 LoginForm/RegisterForm 配合)
|
|
272
277
|
- `hai-capacitor`:原生 App 开发(与 SafeArea/AppBar 配合)
|
|
273
278
|
- `hai-api-client`:客户端数据获取
|
|
274
|
-
|
|
275
|
-
- `hai-capacitor`:原生 App 开发(与 SafeArea/AppBar 配合)
|