@h-ai/cli 0.1.0-alpha.18 → 0.1.0-alpha.19

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@h-ai/cli",
3
3
  "type": "module",
4
- "version": "0.1.0-alpha.18",
4
+ "version": "0.1.0-alpha.19",
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-alpha.18"
37
+ "@h-ai/core": "0.1.0-alpha.19"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/fs-extra": "^11.0.4",
@@ -15,7 +15,7 @@ const API_BASE = (import.meta.env.PUBLIC_API_BASE as string | undefined) ?? 'htt
15
15
  * 初始化 API 客户端
16
16
  *
17
17
  * 应在应用启动时调用一次。
18
- * - Token 存储使用 Capacitor Preferences(原生安全存储)
18
+ * - Token 存储使用 Capacitor 原生安全存储插件(仅原生环境启用)
19
19
  * - 自动附加 Bearer Token、401 自动刷新重试
20
20
  */
21
21
  export async function initApi() {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "{{projectName}}",
3
- "version": "0.1.0-alpha.18",
3
+ "version": "0.1.0-alpha.19",
4
4
  "private": true,
5
5
  "description": "{{#if_eq appType 'admin'}}Enterprise management system built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'website'}}Enterprise website built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'h5'}}Mobile H5 app built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'api'}}REST API service built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'android-app'}}Android app built with hai Agent Framework{{/if_eq}}",
6
6
  "keywords": ["hai", "sveltekit", "svelte5", "{{appType}}"],
@@ -22,35 +22,36 @@
22
22
  "clean": "rimraf .svelte-kit build .turbo"
23
23
  },
24
24
  "dependencies": {
25
- "@h-ai/core": "^0.1.0-alpha.18",
26
- "@h-ai/kit": "^0.1.0-alpha.18"{{#if hasUi}},
27
- "@h-ai/ui": "^0.1.0-alpha.18"{{/if}}{{#if features.db}},
28
- "@h-ai/reldb": "^0.1.0-alpha.18"{{/if}}{{#if features.cache}},
29
- "@h-ai/cache": "^0.1.0-alpha.18"{{/if}}{{#if features.iam}},
30
- "@h-ai/iam": "^0.1.0-alpha.18"{{/if}}{{#if features.crypto}},
31
- "@h-ai/crypto": "^0.1.0-alpha.18"{{/if}}{{#if features.storage}},
32
- "@h-ai/storage": "^0.1.0-alpha.18"{{/if}}{{#if features.ai}},
33
- "@h-ai/ai": "^0.1.0-alpha.18"{{/if}}{{#if features.audit}},
34
- "@h-ai/audit": "^0.1.0-alpha.18"{{/if}}{{#if features.reach}},
35
- "@h-ai/reach": "^0.1.0-alpha.18"{{/if}}{{#if features.payment}},
36
- "@h-ai/payment": "^0.1.0-alpha.18"{{/if}}{{#if features.vecdb}},
37
- "@h-ai/vecdb": "^0.1.0-alpha.18"{{/if}}{{#if features.datapipe}},
38
- "@h-ai/datapipe": "^0.1.0-alpha.18"{{/if}}{{#if features.scheduler}},
39
- "@h-ai/scheduler": "^0.1.0-alpha.18"{{/if}}{{#if features.deploy}},
40
- "@h-ai/deploy": "^0.1.0-alpha.18"{{/if}}{{#if features.api-client}},
41
- "@h-ai/api-client": "^0.1.0-alpha.18"{{/if}}{{#if features.capacitor}},
42
- "@h-ai/capacitor": "^0.1.0-alpha.18"{{/if}}{{#if isCapacitorApp}}{{#unless features.api-client}},
43
- "@h-ai/api-client": "^0.1.0-alpha.18"{{/unless}}{{#unless features.capacitor}},
44
- "@h-ai/capacitor": "^0.1.0-alpha.18"{{/unless}}{{/if}},
25
+ "@h-ai/core": "^0.1.0-alpha.19",
26
+ "@h-ai/kit": "^0.1.0-alpha.19"{{#if hasUi}},
27
+ "@h-ai/ui": "^0.1.0-alpha.19"{{/if}}{{#if features.db}},
28
+ "@h-ai/reldb": "^0.1.0-alpha.19"{{/if}}{{#if features.cache}},
29
+ "@h-ai/cache": "^0.1.0-alpha.19"{{/if}}{{#if features.iam}},
30
+ "@h-ai/iam": "^0.1.0-alpha.19"{{/if}}{{#if features.crypto}},
31
+ "@h-ai/crypto": "^0.1.0-alpha.19"{{/if}}{{#if features.storage}},
32
+ "@h-ai/storage": "^0.1.0-alpha.19"{{/if}}{{#if features.ai}},
33
+ "@h-ai/ai": "^0.1.0-alpha.19"{{/if}}{{#if features.audit}},
34
+ "@h-ai/audit": "^0.1.0-alpha.19"{{/if}}{{#if features.reach}},
35
+ "@h-ai/reach": "^0.1.0-alpha.19"{{/if}}{{#if features.payment}},
36
+ "@h-ai/payment": "^0.1.0-alpha.19"{{/if}}{{#if features.vecdb}},
37
+ "@h-ai/vecdb": "^0.1.0-alpha.19"{{/if}}{{#if features.datapipe}},
38
+ "@h-ai/datapipe": "^0.1.0-alpha.19"{{/if}}{{#if features.scheduler}},
39
+ "@h-ai/scheduler": "^0.1.0-alpha.19"{{/if}}{{#if features.deploy}},
40
+ "@h-ai/deploy": "^0.1.0-alpha.19"{{/if}}{{#if features.api-client}},
41
+ "@h-ai/api-client": "^0.1.0-alpha.19"{{/if}}{{#if features.capacitor}},
42
+ "@h-ai/capacitor": "^0.1.0-alpha.19"{{/if}}{{#if isCapacitorApp}}{{#unless features.api-client}},
43
+ "@h-ai/api-client": "^0.1.0-alpha.19"{{/unless}}{{#unless features.capacitor}},
44
+ "@h-ai/capacitor": "^0.1.0-alpha.19"{{/unless}}{{/if}},
45
45
  "zod": "^4.4.3"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@antfu/eslint-config": "^9.0.0",
49
49
  "@playwright/test": "^1.60.0",
50
50
  {{#if isCapacitorApp}}
51
- "@capacitor/cli": "^8.2.0",
52
- "@capacitor/core": "^8.2.0",
53
- "@capacitor/android": "^8.2.0",
51
+ "@aparajita/capacitor-secure-storage": "^8.0.0",
52
+ "@capacitor/cli": "^8.3.4",
53
+ "@capacitor/core": "^8.3.4",
54
+ "@capacitor/android": "^8.3.4",
54
55
  "@capacitor/preferences": "^8.0.1",
55
56
  "@sveltejs/adapter-static": "^3.0.10",
56
57
  {{else}}
@@ -7,18 +7,26 @@ import { payment } from '@h-ai/payment'
7
7
 
8
8
  export const POST: RequestHandler = async ({ request, locals }) => {
9
9
  const body = await request.json()
10
- const { provider, tradeType, outTradeNo, totalAmount, subject, notifyUrl } = body
10
+ const { provider, tradeType, outTradeNo, totalAmount, subject, notifyUrl, userId, metadata } = body
11
11
 
12
12
  if (!provider || !outTradeNo || !totalAmount || !subject) {
13
13
  return kit.response.badRequest('Missing required fields: provider, outTradeNo, totalAmount, subject')
14
14
  }
15
15
 
16
- const result = await payment.createOrder({
17
- provider,
18
- tradeType: tradeType ?? 'JSAPI',
19
- outTradeNo,
20
- totalAmount,
21
- subject,
16
+ const normalizedTradeType = tradeType === 'h5'
17
+ || tradeType === 'app'
18
+ || tradeType === 'native'
19
+ || tradeType === 'mini_program'
20
+ ? tradeType
21
+ : 'jsapi'
22
+
23
+ const result = await payment.createOrder(provider, {
24
+ orderNo: outTradeNo,
25
+ amount: Number(totalAmount),
26
+ description: subject,
27
+ tradeType: normalizedTradeType,
28
+ userId,
29
+ metadata,
22
30
  notifyUrl: notifyUrl ?? `${new URL(request.url).origin}/api/v1/payment/notify/${provider}`,
23
31
  })
24
32
 
@@ -21,8 +21,7 @@ export const POST: RequestHandler = async ({ request, params }) => {
21
21
  headers[key] = value
22
22
  })
23
23
 
24
- const result = await payment.handleNotify({
25
- provider,
24
+ const result = await payment.handleNotify(provider, {
26
25
  headers,
27
26
  body,
28
27
  })
@@ -122,6 +122,8 @@ ai.init(core.config.get('ai'))
122
122
  ai.close()
123
123
  ```
124
124
 
125
+ `ai.config` 返回的是脱敏后的配置快照;`apiKey`、`privateKey`、`baseUrl` 等敏感字段会自动隐藏,URL 类字段只掩码内嵌凭证。
126
+
125
127
  **特殊说明**:`ai.tools` 和 `ai.stream` 无需 init 即可使用;`ai.llm` 和 `ai.mcp` 操作需要先 init。
126
128
 
127
129
  ---
@@ -48,6 +48,8 @@ await apiClient.init({
48
48
  })
49
49
  ```
50
50
 
51
+ > `createCapacitorTokenStorage()` 仅在 Android/iOS 原生环境 + `@aparajita/capacitor-secure-storage` 已安装时持久化 token;纯 Web 不会回退到 `localStorage`。
52
+
51
53
  SSR / Node.js 测试场景请显式传入内存存储:
52
54
 
53
55
  ```ts
@@ -84,6 +86,8 @@ const client = apiClient.create(contract)
84
86
  await client.init({ baseUrl: 'https://api.example.com/api/v1' })
85
87
  ```
86
88
 
89
+ `client.config` / `apiClient.config` 返回的是脱敏后的运行配置快照;`baseUrl` 的内嵌凭证与 `headers.authorization` 等敏感值会自动隐藏。
90
+
87
91
  ### 4. 关闭
88
92
 
89
93
  ```ts
@@ -52,7 +52,10 @@ await audit.init({
52
52
  ### 3. 关闭
53
53
 
54
54
  ```ts
55
- await audit.close()
55
+ const closeResult = await audit.close()
56
+ if (!closeResult.success) {
57
+ throw new Error(closeResult.error.message)
58
+ }
56
59
  ```
57
60
 
58
61
  ## 核心 API
@@ -62,7 +65,7 @@ await audit.close()
62
65
  | 方法 | 签名 | 说明 |
63
66
  | --------------------- | ---------------------------------------------------------------- | ---------- |
64
67
  | `audit.init` | `(config: AuditInitConfig) => Promise<HaiResult<void>>` | 初始化模块 |
65
- | `audit.close` | `() => Promise<void>` | 关闭模块 |
68
+ | `audit.close` | `() => Promise<HaiResult<void>>` | 关闭模块 |
66
69
  | `audit.isInitialized` | `boolean` | 初始化状态 |
67
70
 
68
71
  ### 日志操作
@@ -86,6 +89,8 @@ await audit.close()
86
89
  | `audit.helper.passwordResetComplete(userId, ip?, ua?)` | 密码重置完成 |
87
90
  | `audit.helper.crud(input)` | CRUD 操作 |
88
91
 
92
+ > `audit.log()` / `audit.helper.*()` 中的 `details` 仅会自动脱敏凭证类字段(如 `password` / `token` / `secret`);邮箱、手机号默认保留原值。
93
+
89
94
  ## 错误码
90
95
 
91
96
  | 错误码 | code | 说明 |
@@ -51,6 +51,8 @@ await cache.init(core.config.get('cache'))
51
51
  await cache.close()
52
52
  ```
53
53
 
54
+ `cache.config` 返回的是脱敏后的配置快照;Redis `password` / `url` 等敏感值不会原样暴露给日志或调试输出。
55
+
54
56
  ---
55
57
 
56
58
  ## 核心 API
@@ -5,20 +5,20 @@ description: 使用 @h-ai/capacitor 桥接 Capacitor 原生能力(Token 安全
5
5
 
6
6
  # hai-capacitor
7
7
 
8
- > `@h-ai/capacitor` 是 hai-framework 的 Capacitor 原生桥接模块,封装常用原生能力为统一 API,返回 `HaiResult<T>`。与 `@h-ai/api-client` 配合实现 App 端 Token 安全存储。
8
+ > `@h-ai/capacitor` 是 hai-framework 的 Capacitor 原生桥接模块,封装常用原生能力为统一 API,返回 `HaiResult<T>`。与 `@h-ai/api-client` 配合时,Token 仅存于原生安全存储,不回退到 Web `localStorage`。
9
9
 
10
10
  ---
11
11
 
12
12
  ## 运行环境
13
13
 
14
- > **浏览器端 / 原生 App 专用。** 在 Capacitor 原生环境(Android/iOS)中提供完整能力,纯 Web 环境下部分功能退化(如 Preferences 退化为 localStorage)。
14
+ > **浏览器端 / 原生 App 专用。** 在 Capacitor 原生环境(Android/iOS)中提供完整能力;`createCapacitorTokenStorage()` 仅在原生环境持久化 token,纯 Web 不做不安全回退。`capacitor.preferences` 仍可用于普通偏好数据。
15
15
 
16
16
  ---
17
17
 
18
18
  ## 适用场景
19
19
 
20
20
  - Android/iOS 原生应用开发(SvelteKit + Capacitor)
21
- - Token 安全存储(Capacitor Preferences → SharedPreferences / UserDefaults)
21
+ - Token 安全存储(`@aparajita/capacitor-secure-storage`)
22
22
  - 设备信息获取(平台、型号、版本)
23
23
  - 推送通知注册与监听(FCM / APNs)
24
24
  - 原生相机拍照 / 相册选取
@@ -64,13 +64,13 @@ await apiClient.init({
64
64
  })
65
65
  ```
66
66
 
67
- `createCapacitorTokenStorage()` 返回 `TokenStorage` 实例(兼容 `@h-ai/api-client`),底层使用 `@capacitor/preferences`:
67
+ `createCapacitorTokenStorage()` 返回 `TokenStorage` 实例(兼容 `@h-ai/api-client`),底层使用 `@aparajita/capacitor-secure-storage`:
68
68
 
69
- - Android → SharedPreferences(应用沙盒)
70
- - iOS → UserDefaults
71
- - Web → localStorage(退化,建议仅原生端使用)
69
+ - Android → Android KeyStore + 加密 SharedPreferences
70
+ - iOS → Keychain
71
+ - Web → **不回退到 localStorage**;`get*()` 返回 `null`,`set*()` / `clear()` 为 no-op
72
72
 
73
- 所有方法内置 try-catch,Preferences 异常时 get 返回 `null`、set/clear 静默失败并记录日志。
73
+ 所有方法内置 try-catch;原生安全存储异常时 get 返回 `null`、set/clear 静默失败并记录日志。
74
74
 
75
75
  #### Preferences 子操作
76
76
 
@@ -208,6 +208,7 @@ await capacitor.statusBar.show()
208
208
  | `HaiCapacitorError.PREFERENCES_SET_FAILED` | `hai:capacitor:012` | Preferences 写入失败 |
209
209
  | `HaiCapacitorError.PREFERENCES_REMOVE_FAILED`| `hai:capacitor:013` | Preferences 删除失败 |
210
210
  | `HaiCapacitorError.DEVICE_INFO_FAILED` | `hai:capacitor:020` | 获取设备信息失败 |
211
+ | `HaiCapacitorError.APP_VERSION_FAILED` | `hai:capacitor:021` | 获取应用版本失败 |
211
212
  | `HaiCapacitorError.PUSH_REGISTER_FAILED` | `hai:capacitor:030` | 推送注册失败 |
212
213
  | `HaiCapacitorError.PUSH_LISTEN_FAILED` | `hai:capacitor:031` | 推送监听失败 |
213
214
  | `HaiCapacitorError.CAMERA_FAILED` | `hai:capacitor:040` | 拍照/相册失败 |
@@ -359,7 +360,8 @@ async function captureAndUpload() {
359
360
  | 插件 | 类型 | 用于 |
360
361
  | --- | --- | --- |
361
362
  | `@capacitor/core` | peerDependency(必需) | 核心运行时 |
362
- | `@capacitor/preferences` | peerDependency(必需) | Token 存储 |
363
+ | `@aparajita/capacitor-secure-storage` | 可选 peerDependency(原生 token 存储必需) | `createCapacitorTokenStorage()` |
364
+ | `@capacitor/preferences` | peerDependency(必需) | `capacitor.preferences.*` 普通偏好数据 |
363
365
  | `@capacitor/device` | 可选 | `getDeviceInfo()` |
364
366
  | `@capacitor/app` | 可选 | `getAppVersion()` |
365
367
  | `@capacitor/push-notifications` | 可选 | `registerPush()` / `listenPush()` |
@@ -16,7 +16,7 @@ description: 使用 @h-ai/core 进行配置加载、日志记录、i18n 国际
16
16
  | 能力 | Node.js | 浏览器 |
17
17
  |------|---------|--------|
18
18
  | `core.logger` | ✅ pino(结构化 JSON / pretty) | ✅ loglevel(DevTools console) |
19
- | `core.config` | ✅ 完整(YAML 加载、watch、validate) | ⚠️ 仅支持 `core.init({ ... })` 传入配置 |
19
+ | `core.config` | ✅ 完整(YAML 加载、watch、validate) | ⚠️ 占位 API;文件配置方法返回 `SERVICE_UNAVAILABLE` |
20
20
  | `core.i18n` | ✅ | ✅ |
21
21
  | `core.zodValidation` | ✅ | ✅ |
22
22
  | `core.id` | ✅ | ✅ |
@@ -79,6 +79,8 @@ const formErrors = core.zodValidation.mapZodErrorToFormErrors(zodError, getMessa
79
79
 
80
80
  ### 配置管理 — `core.config`
81
81
 
82
+ > 浏览器端保留 `core.config` 对象用于类型/runtime 对齐,但没有文件系统:`get()` 返回 `undefined`,`has()` 返回 `false`,`keys()` 返回 `[]`;`load()` / `validate()` / `reload()` 返回 `HaiCommonError.SERVICE_UNAVAILABLE`;`watch()` 会立即回调该错误并返回 no-op 取消函数。
83
+
82
84
  | 方法 | 签名 | 说明 |
83
85
  | ------------ | -------------------------------------------------------------- | ------------------------------ |
84
86
  | `get` | `<T>(name: string) => T \| undefined` | 获取模块配置段 |
@@ -541,6 +543,7 @@ logger.debug('Data fetched', { count: items.length })
541
543
 
542
544
  | 功能 | 行为 |
543
545
  |------|------|
546
+ | `core.config` 文件配置 | 不支持;文件相关方法返回 `SERVICE_UNAVAILABLE`,配置应由服务端读取后下发 |
544
547
  | `format: 'json' \| 'pretty'` | 被忽略(始终输出到 DevTools console) |
545
548
  | `redact` 字段脱敏 | 不生效(需手动脱敏) |
546
549
  | `fatal` 级别 | 映射为 `console.error`(带 `[FATAL]` 前缀) |
@@ -106,8 +106,8 @@ const valid = crypto.hash.verify('hello', hash.data!)
106
106
  | --------------- | ----------------------------------------------- | --------------------------- |
107
107
  | `generateKey` | `() => string` | 生成 128-bit 密钥(32 hex) |
108
108
  | `generateIV` | `() => string` | 生成随机 IV(32 hex) |
109
- | `encrypt` | `(data, key, options?) => HaiResult<string>` | 加密 |
110
- | `decrypt` | `(ciphertext, key, options?) => HaiResult<string>` | 解密 |
109
+ | `encrypt` | `(data, key, options?) => HaiResult<SymmetricEncryptedPayload>` | 加密(返回结构化密文) |
110
+ | `decrypt` | `(payload, key) => HaiResult<string>` | 解密结构化密文 |
111
111
  | `encryptWithIV` | `(data, key) => HaiResult<EncryptWithIVResult>` | CBC 模式加密(自动生成 IV) |
112
112
  | `decryptWithIV` | `(ciphertext, key, iv) => HaiResult<string>` | CBC 模式解密 |
113
113
  | `deriveKey` | `(password, salt) => string` | 从密码和盐值派生密钥 |
@@ -116,13 +116,24 @@ const valid = crypto.hash.verify('hello', hash.data!)
116
116
 
117
117
  ```typescript
118
118
  const key = crypto.symmetric.generateKey()
119
- const result = crypto.symmetric.encryptWithIV('明文数据', key)
120
- if (result.success) {
121
- const decrypted = crypto.symmetric.decryptWithIV(result.data.ciphertext, key, result.data.iv)
119
+
120
+ // 默认安全用法:CBC + 自动 IV,返回 { mode, ciphertext, iv, encoding }
121
+ const encrypted = crypto.symmetric.encrypt('明文数据', key)
122
+ if (encrypted.success) {
123
+ const decrypted = crypto.symmetric.decrypt(encrypted.data, key)
124
+ }
125
+
126
+ // 需要单独存储 IV 时使用结构化结果
127
+ const withIV = crypto.symmetric.encryptWithIV('明文数据', key)
128
+ if (withIV.success) {
129
+ const decrypted = crypto.symmetric.decryptWithIV(withIV.data.ciphertext, key, withIV.data.iv)
122
130
  }
123
131
  ```
124
132
 
125
- **SymmetricOptions**:`{ mode?: 'ecb' | 'cbc', iv?: string, inputEncoding?: 'utf8' | 'hex', outputFormat?: 'hex' | 'base64' }`
133
+ **SymmetricEncryptOptions**:`{ mode?: 'ecb' | 'cbc', iv?: string, outputFormat?: 'hex' | 'base64' }`
134
+ **SymmetricEncryptedPayload**:`{ mode: 'ecb' | 'cbc', ciphertext: string, iv?: string, encoding: 'hex' | 'base64' }`
135
+
136
+ > `encrypt()` 默认使用 CBC 并自动生成 IV。ECB 会泄漏明文结构,只有底层协议明确要求时才显式 `{ mode: 'ecb' }`。
126
137
 
127
138
  ### 密码哈希 — `crypto.password`
128
139
 
@@ -143,7 +154,7 @@ if (hashed.success) {
143
154
 
144
155
  ### 传输加密 — `crypto.transport`
145
156
 
146
- 所有传输加密工厂必须从 `crypto.transport` 访问,不从子目录导入内部工厂。
157
+ 传输加密主工厂必须从 `crypto.transport` 访问;共享 `keyStore` provider 请从 `@h-ai/crypto` 根入口导入,不从子目录内部路径导入。
147
158
 
148
159
  | 方法 | 签名 | 说明 |
149
160
  | --- | --- | --- |
@@ -151,6 +162,14 @@ if (hashed.success) {
151
162
  | `createClient` | `({ keyExchangeUrl, fetch? }) => TransportClient` | 创建客户端 encryptedFetch 会话 |
152
163
  | `protocol` | `TRANSPORT_PROTOCOL` | 协议常量:`X-Client-Id`、`X-Encrypted`、默认协商路径 |
153
164
 
165
+ 共享 `keyStore` provider(从 `@h-ai/crypto` 根入口导入):
166
+
167
+ | 工厂 | 签名 | 说明 |
168
+ | --- | --- | --- |
169
+ | `createInMemoryKeyStore` | `(maxClients?) => TransportKeyStore` | 默认 FIFO 内存实现 |
170
+ | `createRedisTransportKeyStore` | `({ cache, ttlSeconds? }) => TransportKeyStore` | 基于 `@h-ai/cache` 的共享实现,通常配 Redis |
171
+ | `createReldbTransportKeyStore` | `({ reldb, ttlSeconds? }) => TransportKeyStore` | 基于 `@h-ai/reldb` 的共享实现,自动建表 |
172
+
154
173
  ```typescript
155
174
  const server = crypto.transport.createServer({ maxClients: 10000 })
156
175
  if (!server.success)
@@ -166,6 +185,17 @@ const resp = await client.encryptedFetch('https://api.example.com/api/v1/echo',
166
185
  })
167
186
  ```
168
187
 
188
+ ```typescript
189
+ import { createRedisTransportKeyStore, crypto } from '@h-ai/crypto'
190
+ import { cache } from '@h-ai/cache'
191
+
192
+ await cache.init({ type: 'redis', host: '127.0.0.1', port: 6379 })
193
+
194
+ const sharedServer = crypto.transport.createServer({
195
+ keyStore: createRedisTransportKeyStore({ cache, ttlSeconds: 3600 }),
196
+ })
197
+ ```
198
+
169
199
  使用流程:
170
200
  1. 先 `await crypto.init()`。
171
201
  2. 服务端 `createServer()` 后暴露一个 POST 密钥协商端点:接收 `clientPublicKey`,返回 `serverPublicKey + clientId`。
@@ -174,6 +204,8 @@ const resp = await client.encryptedFetch('https://api.example.com/api/v1/echo',
174
204
  5. 服务端先 `decryptRequest()`,业务处理后再 `encryptResponse(clientId, data)`。
175
205
  6. 客户端收到 `X-Encrypted: true` 的响应后自动解密;`destroy()` 可清空当前会话。
176
206
 
207
+ `encryptedFetch()` 与原生 fetch 一样会 reject:网络错误、密钥协商失败、请求加密失败或响应解密失败都应在调用方按 fetch 错误处理策略捕获。
208
+
177
209
  常规应用优先使用上层封装:
178
210
  - serv:`serv.createApp({ transport: { crypto } })`
179
211
  - kit:`kit.createHandle({ crypto: { crypto, transport: true } })` + `kit.client.create({ transport: { crypto } })`
@@ -210,9 +242,11 @@ const encrypted = crypto.symmetric.encrypt(sensitiveData, key)
210
242
  // 存入数据库
211
243
 
212
244
  // 读取时解密
213
- const decrypted = crypto.symmetric.decrypt(storedData, key)
245
+ const decrypted = crypto.symmetric.decrypt(storedPayload, key)
214
246
  ```
215
247
 
248
+ > 默认密文是结构化对象,必须完整存储 `mode` / `ciphertext` / `iv` / `encoding` 字段;如果数据库字段已拆分 IV 与密文,请改用 `encryptWithIV()` / `decryptWithIV()`。
249
+
216
250
  ### 密码存储与验证
217
251
 
218
252
  ```typescript
@@ -83,6 +83,8 @@ await iam.init({
83
83
  })
84
84
  ```
85
85
 
86
+ `iam.config` 返回的是脱敏后的配置快照;LDAP `url` / `bindPassword` 等敏感值会自动隐藏。
87
+
86
88
  初始化时自动创建数据库表(5 张):`hai_iam_users`、`hai_iam_roles`、`hai_iam_permissions`、`hai_iam_role_permissions`、`hai_iam_user_roles`。
87
89
 
88
90
  Token 和 OTP 存储在 cache 中(不落库)。refreshToken 使用独立的 cache key 存储。
@@ -58,10 +58,12 @@ kit 不再维护本地传输加密实现;服务端和客户端都统一委托
58
58
 
59
59
  ```ts
60
60
  // hooks.server.ts
61
- import { crypto } from '@h-ai/crypto'
61
+ import { cache } from '@h-ai/cache'
62
+ import { createRedisTransportKeyStore, crypto } from '@h-ai/crypto'
62
63
  import { kit } from '@h-ai/kit'
63
64
 
64
65
  await crypto.init()
66
+ await cache.init({ type: 'redis', host: '127.0.0.1', port: 6379 })
65
67
 
66
68
  export const handle = kit.createHandle({
67
69
  auth: {
@@ -71,7 +73,10 @@ export const handle = kit.createHandle({
71
73
  },
72
74
  crypto: {
73
75
  crypto,
74
- transport: { requireEncryption: true },
76
+ transport: {
77
+ requireEncryption: true,
78
+ keyStore: createRedisTransportKeyStore({ cache, ttlSeconds: 3600 }),
79
+ },
75
80
  },
76
81
  })
77
82
  ```
@@ -100,6 +105,8 @@ export const { apiFetch } = kit.client.create({ auth: true })
100
105
 
101
106
  默认协商端点:`/api/_hai/key-exchange`。如 `keyExchangePath` 自定义,浏览器端同步设置 `keyExchangeUrl`。
102
107
 
108
+ 多节点部署时,可在 `kit.createHandle({ crypto: { transport } })` 的运行时对象里直接传 `keyStore`;推荐使用 `@h-ai/crypto` 根入口导出的 `createRedisTransportKeyStore()` / `createReldbTransportKeyStore()`。这类对象依赖不要写进 `_kit.yml`。
109
+
103
110
  transport 默认保护同源 `/api/*` endpoint 与 SvelteKit `__data.json` 页面数据请求;页面文档、静态资源与 `multipart/form-data` 上传请求保持明文透传。
104
111
 
105
112
  安全策略默认 fail-closed:`requireEncryption: true` 时,受保护路径缺少 `X-Client-Id` 必须返回 400;服务端 transport 管理器不可用、响应体无法加密或超过单次加密上限时返回错误,禁止明文业务响应回退。`requireEncryption: false` 只适合迁移期灰度。
@@ -58,6 +58,7 @@ alipay:
58
58
  stripe:
59
59
  secretKey: ${HAI_PAYMENT_STRIPE_SECRET_KEY}
60
60
  webhookSecret: ${HAI_PAYMENT_STRIPE_WEBHOOK_SECRET}
61
+ webhookToleranceSeconds: 300 # 可选,默认 300 秒
61
62
  ```
62
63
 
63
64
  ### 2. 初始化(服务端)
@@ -101,6 +102,8 @@ if (result.success) {
101
102
  // result.data: PaymentOrder { provider, tradeType, clientParams, prepayId? }
102
103
  // clientParams 包含调起支付所需参数
103
104
  }
105
+
106
+ // amount / totalAmount 必须是正整数(单位:分)
104
107
  ```
105
108
 
106
109
  ### 4. 处理支付回调
@@ -262,6 +265,8 @@ export const POST = kit.handler(async ({ request, locals }) => {
262
265
 
263
266
  关键支付操作(创建订单、回调、退款、关闭订单)成功后自动写入审计日志(依赖 `@h-ai/audit`),无需额外配置。`queryOrder` 为只读操作,不写审计日志。
264
267
 
268
+ 内置 Provider 的 HTTP 请求默认带 15 秒超时;Stripe 可通过 `webhookToleranceSeconds` 调整 webhook 时间戳容忍窗口。
269
+
265
270
  ---
266
271
 
267
272
  ## 相关 Skills
@@ -96,6 +96,8 @@ await reach.init(core.config.get('reach'))
96
96
  await reach.close()
97
97
  ```
98
98
 
99
+ `reach.config` 返回的是脱敏后的配置快照;`api.url` / `endpoint`、授权头、SMTP `pass`、短信密钥等敏感值会自动隐藏。
100
+
99
101
  ### 3. 保存模板(模板绑定 Provider)
100
102
 
101
103
  ```typescript
@@ -159,7 +161,7 @@ await reach.send({
159
161
  | `reach.init` | `(config: ReachConfigInput) => Promise<HaiResult<void>>` | 初始化(注册多个 Provider) |
160
162
  | `reach.send` | `(message: ReachMessage) => Promise<HaiResult<SendResult>>` | 发送消息(通过 provider 字段路由) |
161
163
  | `reach.template` | `ReachTemplateRegistry` | 模板注册表 |
162
- | `reach.config` | `ReachConfig \| null` | 当前配置 |
164
+ | `reach.config` | `ReachConfig \| null` | 当前脱敏配置快照 |
163
165
  | `reach.isInitialized` | `boolean` | 是否已初始化 |
164
166
  | `reach.close` | `() => Promise<void>` | 关闭所有连接 |
165
167
 
@@ -51,6 +51,8 @@ await reldb.init(core.config.get('db'))
51
51
  await reldb.close()
52
52
  ```
53
53
 
54
+ `reldb.config` 返回的是脱敏后的配置快照;数据库密码与 URL 内嵌凭证会自动隐藏。
55
+
54
56
  ### 3. 选择操作接口
55
57
 
56
58
  | 接口 | 用途 | 入口 |
@@ -80,6 +80,8 @@ await scheduler.register({
80
80
  })
81
81
  ```
82
82
 
83
+ > ⚠️ **安全警示**:`kind: 'js'` 仅允许受信任的服务端代码。当前实现使用 Node.js `vm` 便捷执行,不是安全沙箱;禁止把用户、租户或运营后台自由输入的 JS 字符串直接注册为任务。需要可配置执行逻辑时,优先改用 `kind: 'api'` 或 `hooks.onTaskExecute`。
84
+
83
85
  ### 3. 使用全局 execute hook 处理无 handler 任务
84
86
 
85
87
  ```ts
@@ -271,6 +271,7 @@ const app = serv.createApp({
271
271
  crypto,
272
272
  // keyExchangePath 默认 '/_hai/key-exchange'
273
273
  // maxClients: 10000,
274
+ // keyStore: createRedisTransportKeyStore({ cache, ttlSeconds: 3600 }),
274
275
  },
275
276
  })
276
277
  ```
@@ -298,6 +299,8 @@ await apiClient.init({
298
299
 
299
300
  > 不要导入或暴露子目录内部 transport 工厂;公共装配点只有 `serv.createApp({ transport: { crypto } })`。
300
301
 
302
+ 多节点部署时,直接在 `serv.createApp({ transport })` 的运行时对象里注入 `keyStore` 即可;推荐从 `@h-ai/crypto` 根入口导入 `createRedisTransportKeyStore()` / `createReldbTransportKeyStore()`。`keyStore` 不属于 `_serv.yml` 配置项,配置文件仍只保留静态路径和白名单。
303
+
301
304
  **上下文工厂优先级**(`context.session` 填充来源):
302
305
 
303
306
  1. 显式 `createContext`(高级场景:多租户、额外字段)
@@ -52,9 +52,14 @@ import { storage } from '@h-ai/storage'
52
52
 
53
53
  await storage.init(core.config.get('storage'))
54
54
  // 使用后关闭
55
- await storage.close()
55
+ const closeResult = await storage.close()
56
+ if (!closeResult.success) {
57
+ throw new Error(closeResult.error.message)
58
+ }
56
59
  ```
57
60
 
61
+ > `storage.config` 返回的是**脱敏配置快照**;S3 的 `accessKeyId` / `secretAccessKey` 等敏感字段会被替换为 `[REDACTED]`。
62
+
58
63
  ---
59
64
 
60
65
  ## 核心 API
@@ -106,6 +111,8 @@ const downloadUrl = await storage.presign.getUrl('uploads/doc.pdf', {
106
111
  })
107
112
  ```
108
113
 
114
+ > `storage.presign.putUrl()` 当前不支持 `maxSize` 约束参数;如需大小限制,请在应用层上传前校验,或在服务端落库/回调处二次校验。
115
+
109
116
  ---
110
117
 
111
118
  ## 错误码 — `HaiStorageError`
@@ -174,6 +181,8 @@ export const POST = kit.handler(async ({ request, locals }) => {
174
181
 
175
182
  > 说明:`@h-ai/storage` 只提供存储能力,不强制要求 `/api/storage` 路由命名。应用可按业务语义自定义 API 路径。
176
183
 
184
+ > Local Provider 的 `storage.dir.list()` 会递归扫描匹配前缀下的文件,仅支持 `maxKeys` 截断,不支持 `continuationToken` 真分页;大目录建议缩小 `prefix` 或改用 S3 Provider。
185
+
177
186
  ### 头像上传
178
187
 
179
188
  ```typescript
@@ -55,9 +55,14 @@ import { vecdb } from '@h-ai/vecdb'
55
55
 
56
56
  await vecdb.init(core.config.get('vecdb'))
57
57
  // ... 使用向量数据库
58
- await vecdb.close()
58
+ const closeResult = await vecdb.close()
59
+ if (!closeResult.success) {
60
+ throw new Error(closeResult.error.message)
61
+ }
59
62
  ```
60
63
 
64
+ > `vecdb.config` 返回的是**脱敏配置快照**;连接字符串中的用户名/密码、独立 `password` 字段和 `apiKey` 会被替换为 `[REDACTED]`。
65
+
61
66
  ### 3. 选择操作接口
62
67
 
63
68
  | 接口 | 用途 | 入口 |
@@ -144,6 +149,8 @@ await vecdb.vector.upsert('docs', [
144
149
  await vecdb.vector.delete('docs', ['doc-1', 'doc-2'])
145
150
  ```
146
151
 
152
+ > 空批量 `insert/upsert/delete` 会被视为 no-op;写入与搜索会校验向量维度,不匹配时返回 `HaiVecdbError.DIMENSION_MISMATCH`。Qdrant `collection.exists()` 仅在 404 时返回 `false`,网络异常会返回查询错误而不是误判不存在。
153
+
147
154
  **VectorDocument**:
148
155
 
149
156
  ```typescript