@h-ai/cli 1.0.0-beta.2 → 1.0.0-beta.21

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.
Files changed (254) hide show
  1. package/README.en.md +74 -154
  2. package/README.ja.md +74 -154
  3. package/README.md +74 -154
  4. package/dist/{chunk-AKV2CQZV.js → chunk-2ZL5ORQ2.js} +316 -97
  5. package/dist/chunk-2ZL5ORQ2.js.map +1 -0
  6. package/dist/{chunk-EF7WOFWZ.js → chunk-L53P43B2.js} +1312 -163
  7. package/dist/chunk-L53P43B2.js.map +1 -0
  8. package/dist/cli-config-templates-LWMD2I6V.js +5 -0
  9. package/dist/{cli-config-templates-HHFHNXBN.js.map → cli-config-templates-LWMD2I6V.js.map} +1 -1
  10. package/dist/{cli-skill-templates-PN4FLGBC.js → cli-skill-templates-D6S2OQ6X.js} +3 -3
  11. package/dist/{cli-skill-templates-PN4FLGBC.js.map → cli-skill-templates-D6S2OQ6X.js.map} +1 -1
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.js +30118 -3253
  14. package/dist/index.js.map +1 -1
  15. package/dist/metafile-esm.json +1 -1
  16. package/package.json +22 -18
  17. package/templates/README.md +45 -30
  18. package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -10
  19. package/templates/apps/admin/src/routes/+page.svelte.hbs +4 -4
  20. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +17 -7
  21. package/templates/apps/admin/src/routes/admin/+page.svelte.hbs +26 -6
  22. package/templates/apps/admin/src/routes/admin/settings/+page.svelte.hbs +2 -2
  23. package/templates/apps/api/.dockerignore.hbs +10 -0
  24. package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +8 -0
  25. package/templates/apps/api/apps/{{projectName}}-contract/src/app-schemas.ts.hbs +6 -1
  26. package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +13 -5
  27. package/templates/apps/api/apps/{{projectName}}-contract/tests/contract.test.ts.hbs +13 -5
  28. package/templates/apps/api/apps/{{projectName}}-contract/tsup.config.ts.hbs +3 -1
  29. package/templates/apps/api/apps/{{projectName}}-serv/README.md.hbs +6 -2
  30. package/templates/apps/api/apps/{{projectName}}-serv/config/_serv.yml.hbs +24 -2
  31. package/templates/apps/api/apps/{{projectName}}-serv/package.json.hbs +4 -4
  32. package/templates/apps/api/apps/{{projectName}}-serv/src/app.ts.hbs +74 -24
  33. package/templates/apps/api/apps/{{projectName}}-serv/src/build-metadata.ts.hbs +0 -1
  34. package/templates/apps/api/apps/{{projectName}}-serv/src/lib/server/init.ts.hbs +153 -37
  35. package/templates/apps/api/apps/{{projectName}}-serv/src/server/procedures/index.ts.hbs +39 -13
  36. package/templates/apps/api/apps/{{projectName}}-serv/tests/service.test.ts.hbs +106 -33
  37. package/templates/apps/api/apps/{{projectName}}-serv/tsup.config.ts.hbs +3 -1
  38. package/templates/apps/api/apps/{{projectName}}-serv/vitest.config.ts.hbs +3 -1
  39. package/templates/apps/api/e2e/api-serv.spec.ts.hbs +20 -3
  40. package/templates/apps/api/e2e/capture-evidence.ts.hbs +4 -1
  41. package/templates/apps/api/eslint.config.js.hbs +3 -1
  42. package/templates/apps/api/images/serv.Dockerfile.hbs +15 -10
  43. package/templates/apps/api/playwright.config.ts.hbs +23 -19
  44. package/templates/apps/api/pnpm-workspace.yaml.hbs +27 -16
  45. package/templates/apps/fullstack/.dockerignore.hbs +10 -0
  46. package/templates/apps/fullstack/.gitignore.hbs +7 -2
  47. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +14 -12
  48. package/templates/apps/fullstack/apps/{{projectName}}-app/capacitor.config.ts.hbs +5 -3
  49. package/templates/apps/fullstack/apps/{{projectName}}-app/eslint.config.js.hbs +10 -2
  50. package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +1 -1
  51. package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +10 -2
  52. package/templates/apps/fullstack/apps/{{projectName}}-app/src/build-metadata.ts.hbs +0 -1
  53. package/templates/apps/fullstack/apps/{{projectName}}-app/src/lib/capacitor.ts.hbs +0 -1
  54. package/templates/apps/fullstack/apps/{{projectName}}-app/svelte.config.js.hbs +4 -1
  55. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +16 -4
  56. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +10 -8
  57. package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +3 -3
  58. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/App.svelte.hbs +8 -2
  59. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/build-metadata.ts.hbs +0 -1
  60. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +0 -2
  61. package/templates/apps/fullstack/apps/{{projectName}}-desktop/svelte.config.js.hbs +4 -1
  62. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +16 -4
  63. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +2 -0
  64. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +9 -9
  65. package/templates/apps/fullstack/apps/{{projectName}}-web/eslint.config.js.hbs +10 -2
  66. package/templates/apps/fullstack/apps/{{projectName}}-web/index.html.hbs +1 -0
  67. package/templates/apps/fullstack/apps/{{projectName}}-web/src/build-metadata.ts.hbs +0 -1
  68. package/templates/apps/fullstack/apps/{{projectName}}-web/src/main.ts.hbs +0 -2
  69. package/templates/apps/fullstack/apps/{{projectName}}-web/svelte.config.js.hbs +4 -1
  70. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +19 -5
  71. package/templates/apps/fullstack/docs/security-configuration.md.hbs +4 -1
  72. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +140 -28
  73. package/templates/apps/fullstack/e2e/session-close.spec.ts.hbs +19 -5
  74. package/templates/apps/fullstack/eslint.config.js.hbs +3 -1
  75. package/templates/apps/fullstack/images/serv.Dockerfile.hbs +19 -11
  76. package/templates/apps/fullstack/images/web.Dockerfile.hbs +17 -8
  77. package/templates/apps/fullstack/package.json.hbs +2 -1
  78. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +7 -5
  79. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-contract.ts.hbs +114 -23
  80. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +57 -11
  81. package/templates/apps/fullstack/packages/{{projectName}}-contract/tsup.config.ts.hbs +3 -1
  82. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +17 -7
  83. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +13 -7
  84. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +17 -7
  85. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +10 -4
  86. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +65 -30
  87. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/build-metadata.ts.hbs +0 -1
  88. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/index.ts.hbs +1 -2
  89. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +69 -21
  90. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/business-settings.ts.hbs +21 -5
  91. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +63 -11
  92. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/app-procedures.ts.hbs +6 -1
  93. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -1
  94. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +147 -33
  95. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/security-roles.ts.hbs +43 -12
  96. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +146 -30
  97. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/runtime.test.ts.hbs +33 -10
  98. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/security.test.ts.hbs +142 -26
  99. package/templates/apps/fullstack/packages/{{projectName}}-serv/tsup.config.ts.hbs +6 -2
  100. package/templates/apps/fullstack/packages/{{projectName}}-serv/vitest.config.ts.hbs +3 -1
  101. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +48 -66
  102. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/en-US.json.hbs +1 -0
  103. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +1 -0
  104. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/zh-CN.json.hbs +1 -0
  105. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +9 -2
  106. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/feedback-client.ts.hbs +0 -3
  107. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/sensitive-actions.svelte.ts.hbs +15 -3
  108. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +33 -7
  109. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/FeedbackEntry.svelte.hbs +16 -2
  110. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +104 -13
  111. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/feedback.svelte.hbs +37 -7
  112. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/roles.svelte.hbs +9 -3
  113. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +25 -3
  114. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +133 -19
  115. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/security/role-label.ts.hbs +14 -2
  116. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/guide-store.ts.hbs +66 -34
  117. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/management-store.svelte.ts.hbs +132 -30
  118. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/theme-store.svelte.ts.hbs +8 -2
  119. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +23 -5
  120. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/management-store.test.ts.hbs +143 -21
  121. package/templates/apps/fullstack/packages/{{projectName}}-shared/vitest.config.ts.hbs +7 -2
  122. package/templates/apps/fullstack/playwright.config.ts.hbs +25 -3
  123. package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +30 -14
  124. package/templates/apps/h5/src/routes/+layout.svelte.hbs +74 -30
  125. package/templates/apps/h5/src/routes/+page.svelte.hbs +2 -2
  126. package/templates/apps/h5/src/routes/discover/+page.svelte.hbs +2 -2
  127. package/templates/apps/h5/src/routes/profile/+page.svelte.hbs +2 -2
  128. package/templates/apps/mobile-app/capacitor.config.ts.hbs +5 -2
  129. package/templates/apps/mobile-app/messages/en-US.json +2 -2
  130. package/templates/apps/mobile-app/messages/ja-JP.json +2 -2
  131. package/templates/apps/mobile-app/messages/zh-CN.json +2 -2
  132. package/templates/apps/mobile-app/src/App.svelte.hbs +17 -5
  133. package/templates/apps/mobile-app/src/lib/api.ts.hbs +20 -6
  134. package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +8 -1
  135. package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +6 -2
  136. package/templates/apps/mobile-app/src/lib/pages/profile.svelte.hbs +13 -1
  137. package/templates/apps/mobile-app/src/lib/pages/tasks.svelte.hbs +5 -1
  138. package/templates/apps/website/scripts/static-website.mjs.hbs +19 -6
  139. package/templates/apps/website/src/routes/+layout.svelte.hbs +56 -29
  140. package/templates/apps/website/src/routes/+page.svelte.hbs +4 -4
  141. package/templates/apps/website/src/routes/about/+page.svelte.hbs +2 -2
  142. package/templates/apps/website/src/routes/contact/+page.svelte.hbs +2 -2
  143. package/templates/apps/website/src/routes/services/+page.svelte.hbs +20 -5
  144. package/templates/base/.dockerignore.hbs +10 -0
  145. package/templates/base/.gitignore.hbs +7 -2
  146. package/templates/base/e2e/app.spec.ts.hbs +130 -36
  147. package/templates/base/eslint-rules.js +44 -25
  148. package/templates/base/eslint.config.js.hbs +3 -1
  149. package/templates/base/images/web.Dockerfile.hbs +25 -14
  150. package/templates/base/package.json.hbs +13 -7
  151. package/templates/base/patches/daisyui-5.7.50.patch +8 -0
  152. package/templates/base/patches/deepmerge-4.3.1.patch +51 -0
  153. package/templates/base/playwright.config.ts +3 -1
  154. package/templates/base/pnpm-workspace.yaml.hbs +33 -16
  155. package/templates/base/src/app.d.ts.hbs +1 -1
  156. package/templates/base/src/app.html.hbs +1 -0
  157. package/templates/base/src/hooks.server.ts.hbs +44 -33
  158. package/templates/base/src/lib/build-metadata.ts.hbs +0 -1
  159. package/templates/base/src/lib/server/init.ts.hbs +146 -35
  160. package/templates/base/src/lib/server/kit-config.ts.hbs +7 -4
  161. package/templates/base/src/routes/+layout.server.ts.hbs +2 -2
  162. package/templates/base/svelte.config.js.hbs +10 -100
  163. package/templates/base/tests/scaffold.test.ts.hbs +12 -3
  164. package/templates/base/tsconfig.json.hbs +7 -4
  165. package/templates/base/vite.config.ts.hbs +108 -14
  166. package/templates/base/vitest.config.ts.hbs +6 -2
  167. package/templates/features/ai/routes/api/chat/+server.ts +3 -1
  168. package/templates/features/iam/routes-admin/(auth)/auth/forgot-password/+page.svelte.hbs +3 -3
  169. package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +3 -3
  170. package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +3 -3
  171. package/templates/features/iam/routes-admin/(auth)/auth/reset-password/+page.svelte.hbs +3 -3
  172. package/templates/features/iam/routes-h5/auth/forgot-password/+page.svelte.hbs +3 -3
  173. package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +3 -3
  174. package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +3 -3
  175. package/templates/features/iam/routes-h5/auth/reset-password/+page.svelte.hbs +3 -3
  176. package/templates/features/iam/routes-website/auth/forgot-password/+page.svelte.hbs +3 -3
  177. package/templates/features/iam/routes-website/auth/login/+page.svelte.hbs +3 -3
  178. package/templates/features/iam/routes-website/auth/register/+page.svelte.hbs +3 -3
  179. package/templates/features/iam/routes-website/auth/reset-password/+page.svelte.hbs +3 -3
  180. package/templates/features/payment/routes/api/v1/payment/create/+server.ts +14 -3
  181. package/templates/shared/browser-check/startup.js +115 -0
  182. package/templates/shared/browser-check/update.css +37 -0
  183. package/templates/shared/browser-check/update.html +19 -0
  184. package/templates/shared/browser-startup.spec.ts +23 -0
  185. package/templates/shared/build-metadata.ts +18 -4
  186. package/templates/shared/e2e-env.ts +22 -6
  187. package/templates/shared/native-name.d.mts +5 -0
  188. package/templates/shared/native-name.mjs +94 -0
  189. package/templates/shared/public-base-path.mjs +17 -4
  190. package/templates/shared/run-e2e.mjs +46 -12
  191. package/templates/shared/run-node.mjs +56 -0
  192. package/templates/shared/run-tauri.mjs +74 -0
  193. package/templates/shared/vite-runtime-config.ts +9 -3
  194. package/templates/skills/AGENTS.md +10 -73
  195. package/templates/skills/bridges/admin/AGENTS.md +9 -65
  196. package/templates/skills/bridges/api/AGENTS.md +8 -59
  197. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +9 -54
  198. package/templates/skills/bridges/h5/AGENTS.md +8 -63
  199. package/templates/skills/bridges/mobile-app/AGENTS.md +8 -62
  200. package/templates/skills/bridges/website/AGENTS.md +8 -63
  201. package/templates/skills/hai-ai/SKILL.md +6 -2
  202. package/templates/skills/hai-ai/acp.md +153 -0
  203. package/templates/skills/hai-ai/guide.md +27 -12
  204. package/templates/skills/hai-ai/reference.md +24 -15
  205. package/templates/skills/hai-ai/resources.md +69 -0
  206. package/templates/skills/hai-api-client/SKILL.md +1 -1
  207. package/templates/skills/hai-api-client/reference.md +3 -1
  208. package/templates/skills/hai-app-create/collaboration.md +7 -0
  209. package/templates/skills/hai-app-create/development.md +19 -10
  210. package/templates/skills/hai-app-create/project.md +33 -0
  211. package/templates/skills/hai-app-create/reference.md +10 -10
  212. package/templates/skills/hai-app-tests/reference.md +1 -1
  213. package/templates/skills/hai-app-tests/validation.md +1 -0
  214. package/templates/skills/hai-build/SKILL.md +19 -9
  215. package/templates/skills/hai-build/paths.md +4 -2
  216. package/templates/skills/hai-build/reference.md +26 -8
  217. package/templates/skills/hai-cache/reference.md +6 -2
  218. package/templates/skills/hai-capacitor/SKILL.md +1 -0
  219. package/templates/skills/hai-capacitor/reference.md +24 -0
  220. package/templates/skills/hai-ci/SKILL.md +3 -0
  221. package/templates/skills/hai-core/SKILL.md +9 -4
  222. package/templates/skills/hai-core/reference.md +40 -10
  223. package/templates/skills/hai-core/request-access.md +2 -2
  224. package/templates/skills/hai-crypto/SKILL.md +2 -2
  225. package/templates/skills/hai-crypto/reference.md +7 -3
  226. package/templates/skills/hai-deploy/SKILL.md +41 -5
  227. package/templates/skills/hai-deploy/middleware.md +26 -14
  228. package/templates/skills/hai-framework-sync/SKILL.md +13 -9
  229. package/templates/skills/hai-framework-sync/sync.md +2 -0
  230. package/templates/skills/hai-iam/reference.md +2 -2
  231. package/templates/skills/hai-kit/SKILL.md +1 -1
  232. package/templates/skills/hai-kit/reference.md +19 -5
  233. package/templates/skills/hai-payment/reference.md +41 -17
  234. package/templates/skills/hai-reach/SKILL.md +3 -1
  235. package/templates/skills/hai-reach/reference.md +29 -9
  236. package/templates/skills/hai-reldb/SKILL.md +1 -1
  237. package/templates/skills/hai-reldb/reference.md +9 -2
  238. package/templates/skills/hai-scheduler/reference.md +1 -1
  239. package/templates/skills/hai-serv/SKILL.md +2 -1
  240. package/templates/skills/hai-serv/reference.md +26 -15
  241. package/templates/skills/hai-storage/reference.md +23 -6
  242. package/templates/skills/hai-ui/SKILL.md +11 -6
  243. package/templates/skills/hai-ui/charts.md +51 -0
  244. package/templates/skills/hai-ui/guide.md +1 -1
  245. package/templates/skills/hai-ui/reference.md +58 -38
  246. package/templates/skills/hai-vecdb/reference.md +8 -3
  247. package/dist/chunk-AKV2CQZV.js.map +0 -1
  248. package/dist/chunk-EF7WOFWZ.js.map +0 -1
  249. package/dist/chunk-MUHRBJSW.js +0 -29272
  250. package/dist/chunk-MUHRBJSW.js.map +0 -1
  251. package/dist/cli-config-templates-HHFHNXBN.js +0 -5
  252. package/dist/src-TEBHR5I5.js +0 -5
  253. package/dist/src-TEBHR5I5.js.map +0 -1
  254. package/templates/apps/fullstack/playwright.session-close.config.ts.hbs +0 -18
@@ -6,13 +6,15 @@
6
6
 
7
7
  ## 运行环境
8
8
 
9
+ Provider 名称必须唯一,重名在连接前返回 CONFIG_ERROR。close 等待在途初始化并释放其 Provider,返回后不再迟到发布模块或定时器。
10
+
9
11
  > ⚠️ **服务端模块(Node.js only)。** 浏览器端不直接发送邮件/短信,而是通过 API 端点触发服务端 `reach.send()`。
10
12
 
11
13
  ## 依赖
12
14
 
13
15
  | 模块 | 用途 | 是否必需 | 初始化要求 |
14
16
  | --- | --- | --- | --- |
15
- | `@h-ai/reldb` | 数据库(发送日志与模板持久化) | DND delay 必需;其他场景可选 | 启用 delay 前先初始化,队列表不可用则 reach.init 返回 CONFIG_ERROR |
17
+ | `@h-ai/reldb` | 数据库(发送日志与模板持久化) | 模板与 DND delay 必需;其他即时发送可选 | 先初始化;非空配置模板保存或必需表准备失败则 reach.init 返回 CONFIG_ERROR 并关闭候选 Provider |
16
18
  | `@h-ai/cache` | 缓存(DND delay 策略分布式锁) | 可选 | 已初始化时自动启用分布式锁 |
17
19
 
18
20
  ## 适用场景
@@ -45,6 +47,10 @@ providers:
45
47
  accessKeyId: ${HAI_REACH_PROVIDERS_0_ACCESSKEYID:}
46
48
  accessKeySecret: ${HAI_REACH_PROVIDERS_0_ACCESSKEYSECRET:}
47
49
  signName: ${HAI_REACH_PROVIDERS_0_SIGNNAME:}
50
+ # 按需启用隔离网关
51
+ # gateway:
52
+ # url: https://sms-gateway.example.com/dx
53
+ # body: {}
48
54
  - name: webhook
49
55
  type: api
50
56
  url: ${HAI_REACH_WEBHOOK_URL:}
@@ -76,21 +82,27 @@ API Provider 只接受 HTTP(S) URL。回调地址若来自不可信输入,业
76
82
  ### 2. 初始化与关闭
77
83
 
78
84
  ```typescript
79
- import { cache } from '@h-ai/cache'
85
+ import { cache, CacheConfigSchema } from '@h-ai/cache'
80
86
  import { core } from '@h-ai/core'
81
- import { reach } from '@h-ai/reach'
82
- import { reldb } from '@h-ai/reldb'
87
+ import { reach, ReachConfigSchema } from '@h-ai/reach'
88
+ import { reldb, ReldbConfigSchema } from '@h-ai/reldb'
83
89
 
84
90
  // 当前配置启用了 delay:数据库和队列表为必需依赖。
85
- const database = await reldb.init(core.config.get('db'))
91
+ const databaseConfig = core.config.validate('db', ReldbConfigSchema)
92
+ if (!databaseConfig.success) throw new Error(databaseConfig.error.message)
93
+ const database = await reldb.init(databaseConfig.data)
86
94
  if (!database.success)
87
95
  throw new Error(database.error.message)
88
- const cached = await cache.init(core.config.get('cache')) // 可选,启用 flush 分布式锁
96
+ const cacheConfig = core.config.validate('cache', CacheConfigSchema)
97
+ if (!cacheConfig.success) throw new Error(cacheConfig.error.message)
98
+ const cached = await cache.init(cacheConfig.data) // 可选,启用 flush 分布式锁
89
99
  if (!cached.success)
90
100
  throw new Error(cached.error.message)
91
101
 
92
102
  // 再初始化触达模块(自动检测已初始化的 reldb/cache 单例)
93
- const initialized = await reach.init(core.config.get('reach'))
103
+ const reachConfig = core.config.validate('reach', ReachConfigSchema)
104
+ if (!reachConfig.success) throw new Error(reachConfig.error.message)
105
+ const initialized = await reach.init(reachConfig.data)
94
106
  if (!initialized.success)
95
107
  throw new Error(initialized.error.message)
96
108
  // ... 使用触达服务
@@ -157,13 +169,21 @@ await reach.send({
157
169
  })
158
170
  ```
159
171
 
172
+ ### 隔离网关与动态字段
173
+
174
+ 在 `aliyun-sms` 下配置 `gateway.url`,使用包含路径的完整 HTTPS 地址。受信内网确需明文时设置 `gateway.allowInsecureHttp: true`。请求使用 POST,阿里云签名仍基于 RPC 路径 `/` 和查询参数,网关路径、头和正文仅供网关使用。`gateway.headers` 取字符串键值,`gateway.body` 可设 JSON 对象、数组或原始文本,`body: {}` 表示空 JSON 对象。
175
+
176
+ 从服务端 `reach.init(config, { gatewayHooks: { sms: hook } })` 注入同步或异步函数。`sms` 对应已配置网关的 Provider 名称,`hook` 接收 `{ method, url, body, signal }`,其中 `body` 已序列化,`url` 含签名和敏感查询。返回 `{ headers, body }` 时,头按名称忽略大小写覆盖,正文整段替换,省略字段时沿用静态配置。`Host` 和 `Content-Length` 等由 HTTP 客户端管理。函数和请求共用 10 秒期限,拒绝重定向。`reach.config` 仅展示网关的地址和 HTTP 开关。
177
+
178
+ 验证网关转发时确认上游路径、方法和查询参数,HTTP 2xx 且阿里云 `Code: OK` 表示已受理,送达状态通过 `BizId` 查询。明文连接的访问日志应脱敏查询串,配置及测试均使用占位数据或受控的私有注入。
179
+
160
180
  ## 核心 API
161
181
 
162
182
  ### reach 对象
163
183
 
164
184
  | 方法 / 属性 | 签名 | 说明 |
165
185
  | --------------------- | -------------------------------------------------------- | ---------------------------------- |
166
- | `reach.init` | `(config: ReachConfigInput) => Promise<HaiResult<void>>` | 初始化(注册多个 Provider) |
186
+ | `reach.init` | `(config: ReachConfigInput, options?: ReachInitOptions) => Promise<HaiResult<void>>` | 初始化 Provider,可注入网关函数 |
167
187
  | `reach.send` | `(message: ReachMessage) => Promise<HaiResult<SendResult>>` | 发送消息(通过 provider 字段路由) |
168
188
  | `reach.queue.get` | `(queueId: string) => Promise<HaiResult<ReachQueueEntry \| null>>` | 不含正文/收件人的队列状态 |
169
189
  | `reach.queue.list` | `(options: ReachQueueListOptions) => Promise<HaiResult<ReachQueueEntry[]>>` | 可信服务端分页查询 dead/unknown,每页至多 100 条 |
@@ -234,7 +254,7 @@ interface ReachConfigInput {
234
254
 
235
255
  验证码和密码重置等有时效的通知应明确选择 DND 策略;可靠入队成功不代表用户已收到验证码。
236
256
 
237
- 队列 `created_at` / `updated_at` / `locked_until` / `next_attempt_at` 均为 Unix 毫秒 `BIGINT`(SQLite 为 64 位 INTEGER);仓库把 driver 的字符串/数字归一化为安全整数 number,并拒绝负数、小数和不安全整数。模板仓库的 TIMESTAMP 时间返回 Date。已有表须在部署时同步列结构,包含次数、退避策略和失败分类,模块不自动迁移旧表;跨库验收必须运行 PostgreSQL/MySQL 用例,SQLite 无法证明 32 位溢出已经消除。
257
+ 队列 `created_at` / `updated_at` / `locked_until` / `next_attempt_at` 均为 Unix 毫秒 `BIGINT`(SQLite 为 64 位 INTEGER);仓库把 driver 的字符串/数字归一化为安全整数 number,并拒绝负数、小数和不安全整数。模板仓库的 TIMESTAMP 时间返回 Date。初始化自动执行版本化迁移,包含次数、退避策略和失败分类;升级前先 dry-run、备份并停止旧写入。旧通知载荷退役会清空正文/变量并将旧待发项标记 discarded,回退镜像不能恢复这些数据;跨库验收必须运行 PostgreSQL/MySQL 用例,SQLite 无法证明 32 位溢出已经消除。
238
258
 
239
259
  ### 租约、接收端幂等与未知结果
240
260
 
@@ -41,4 +41,4 @@ description: "使用 @h-ai/reldb 访问 SQLite/PostgreSQL/MySQL,编写 SQL、C
41
41
 
42
42
  JSON 对象合并使用 RFC 7396 补丁,必须将返回的 SQL/params 放在同一次 UPDATE 内;禁止先读对象再覆盖整个列来模拟跨节点原子合并。路径函数的后端差异和完整可类型检查示例见 [reference.md](reference.md)。
43
43
 
44
- 框架模块维护自己的版本迁移,应用不直接 ALTER 框架表。schemaColumns 只读真实列;migrateSchema(...,{dryRun:true}) 不创建账本。生产先停旧写入并备份 DB/共享会话缓存;SQLite/PostgreSQL 事务,MySQL DDL 隐式提交须可重入,失败不推进版本且重试检查真实结构。回退恢复兼容 DB 与代码,不只换二进制。模块 migrateIam/migrateReach/migrateScheduler 分别预检,初始化执行受支持步骤。
44
+ 框架模块维护自己的版本迁移,应用不直接 ALTER 框架表。`reldb.schemaColumns(table, sql?)` 只读真实列;`reldb.migrateSchema(module, steps, { dryRun: true })` 不创建账本。生产先停旧写入并备份 DB/共享会话缓存;SQLite/PostgreSQL 事务,MySQL DDL 隐式提交须可重入,失败不推进版本且重试检查真实结构。回退恢复兼容 DB 与代码,不只换二进制。模块 migrateIam/migrateReach/migrateScheduler 分别预检,初始化执行受支持步骤。
@@ -62,9 +62,12 @@ PostgreSQL 空闲连接错误已监听,由池移除并在下一次查询替换
62
62
 
63
63
  ```typescript
64
64
  import { core } from '@h-ai/core'
65
- import { reldb } from '@h-ai/reldb'
65
+ import { reldb, ReldbConfigSchema } from '@h-ai/reldb'
66
66
 
67
- await reldb.init(core.config.get('db'))
67
+ const config = core.config.validate('db', ReldbConfigSchema)
68
+ if (!config.success) throw new Error(config.error.message)
69
+ const initialized = await reldb.init(config.data)
70
+ if (!initialized.success) throw new Error(initialized.error.message)
68
71
  // ... 使用数据库
69
72
  const closed = await reldb.close()
70
73
  if (!closed.success) throw new Error(closed.error.message)
@@ -338,6 +341,8 @@ class UserRepository extends BaseReldbCrudRepository<User> {
338
341
 
339
342
  ### 事务 — `reldb.tx`
340
343
 
344
+ 一个事务只允许一个结束操作。`commit()` 或 `rollback()` 开始后,新查询、重复提交和并发回滚立即返回 TRANSACTION_FAILED;连接只归还一次。调用方仍须等待首次结束结果,不能把并发结束当作故障恢复。
345
+
341
346
  ```typescript
342
347
  // 方式1:wrap(自动 commit/rollback)
343
348
  const result = await reldb.tx.wrap(async (tx) => {
@@ -495,6 +500,8 @@ const result = await reldb.tx.wrap(async (tx) => {
495
500
 
496
501
  ## 生产共享状态
497
502
 
503
+ 应用自己的表迁移使用 `reldb.schemaColumns(table, sql?)` 读取真实列,使用 `reldb.migrateSchema(module, steps, options?)` 执行版本步骤;两者属于已初始化实例,不另传 reldb 句柄。`dryRun: true` 不写账本,失败不推进版本;MySQL DDL 必须可重入。框架表由各模块迁移入口维护,应用不要直接修改。
504
+
498
505
  生产仅使用 PostgreSQL/MySQL;SQLite(含磁盘文件)只用于开发/明确测试,不能作为跨主机共享状态。 NODE_ENV 或 HAI_CORE_ENV 为 production 时在初始化成功前检查,只有 HAI_E2E=1/HAI_E2E_MOCK=1 明确测试豁免。不要将租约当作最终写入条件,外部未知效果须按稳定业务键核对。
499
506
 
500
507
  MySQL 空 url(含空环境变量占位)按未配置处理,使用分字段;非空仍严格校验。
@@ -175,7 +175,7 @@ await scheduler.init({
175
175
 
176
176
  可信服务端用 `execution.listLogFailures({ limit, offset })` 监控 pending(范围与 listUnknown 相同),用 `execution.repairLog(occurrenceId)` 从共享 journal 幂等补写;可跨节点、重启及并发调用。写入已提交但回执丢失时通过固定主键和完整内容核对去重。补写返回 `LOG_PERSIST_FAILED`(032)时继续保留 pending,并输出安全告警;全部数据库不可用且终态未提交时仍按 unknown 核对外部效果,不能假定存在可恢复结果。补写与日志保留均不调用业务 Hook。保留策略失败仅告警,不撤销已保存日志。`onTaskFinish` 是本地业务完成通知,其 unconfirmed 状态不是持久化确认;以 trigger 返回值及 execution.get 的 logPersistence 为准。
177
177
 
178
- 领取前竞争/跳过的 interrupted 仅为观测日志,没有可补写的终态;其写入未确认时返回 unconfirmed。JSON 大载荷保存完整结果和全部重试记录,避免 MySQL 的 255 字符截断;新 schema 不自动迁移旧日志/执行表。关闭会等待在途补写,先关闭 scheduler 再关闭数据库。
178
+ 领取前竞争/跳过的 interrupted 仅为观测日志,没有可补写的终态;其写入未确认时返回 unconfirmed。JSON 大载荷保存完整结果和全部重试记录,避免 MySQL 的 255 字符截断;初始化会自动执行版本化迁移,归档并复制旧日志;升级前先 dry-run、备份并停止旧写入,按模块 README 核对迁移影响。关闭会等待在途补写,先关闭 scheduler 再关闭数据库。
179
179
 
180
180
  ```ts
181
181
  const result = await scheduler.trigger('cleanup', { idempotencyKey: 'cleanup-2026-10-23' })
@@ -5,7 +5,7 @@ description: "为独立 API 服务装配 contract/procedures、认证守卫、No
5
5
 
6
6
  # hai-serv
7
7
 
8
- transport 仅支持 v2:SM2 发送方签名、独立密钥的 Encrypt-then-HMAC、HTTP 上下文/响应状态绑定及共享原子防重放。GET/HEAD、空 POST 与空响应也认证;无降级开关。TLS 信任边界、5 分钟窗口、nonce 持久化与实现示例见 [参考](reference.md)。
8
+ transport 仅支持 v2:SM2 发送方签名、独立密钥的 Encrypt-then-HMAC、HTTP 上下文/响应状态绑定及共享原子防重放。GET/HEAD、空 POST 与空响应也认证;无降级开关。TLS 信任边界、可配时间窗口、nonce 持久化与实现示例见 [参考](reference.md)。
9
9
 
10
10
  ## 能力契约
11
11
 
@@ -26,6 +26,7 @@ transport 仅支持 v2:SM2 发送方签名、独立密钥的 Encrypt-then-HMAC
26
26
  - AI 默认 feature 以可信 session.userId 绑定 objectId,所有读写和记忆按主体隔离;跨主体访问必须显式 authorizeObject,同名 sessionId 不代表同一用户。
27
27
  - 认证绑定 route 的 auth/permission/role;启动配置错误与正常请求领域错误分别处理。
28
28
  - 普通 procedure 的失败 HaiResult 默认通过 HTTP 200 返回,必须检查 success 与 error.code;error.httpStatus 是领域错误元数据。contract 输入校验失败为 HTTP 400,HTTP middleware、刷新 cookie 等响应另按自身契约处理,不能混为同一种状态映射。
29
+ - `http.apiPrefix` 默认 `/api/v1`,支持 `/somepath/api/v1` 等完整合法绝对路径;与前端 base、健康/文档及存储路径成套配置,网关保留 API 前缀。拒绝末尾斜杠、空路径段、路径穿越、查询和片段。
29
30
  - 浏览器 Cookie、CORS、apiPrefix、transport 须与 api-client 同步;语音 ticket 单独核验。
30
31
  - transport 服务端显式注入受管 serverKeyRing 和共享 keyStore;请求带协商 serverKeyId。私钥只从 Secret/私有环境加载,不放公开配置;轮换、重启与跨节点要求见 hai-crypto。
31
32
  - 缺失路由返回本地化 HaiResult JSON 404;加密传输保留该状态。默认 feature 可按需选用,contract 与挂载的 procedures 保持一致。
@@ -4,12 +4,16 @@
4
4
 
5
5
  > `@h-ai/serv` 是 hai-framework 的 API Service 运行时,对外暴露最小 HTTP App 抽象,将 `@h-ai/api-contract` 的领域 contract 挂载成跨端可访问的 HTTP API;Hono 是内部实现细节,应用代码不要直接 import 或暴露 Hono。
6
6
 
7
+ 命名空间的公开类型是根入口的 `ServFunctions`。上下文、工厂与泛型 contract 实现器均通过 `serv.xx` 使用;应用可导出方法或整个命名空间并生成声明,不要为类型推导改用私有构建路径或导入内部函数。
8
+
7
9
  ## 运行环境
8
10
 
9
11
  > ⚠️ **服务端模块(Node.js 或满足同等 Node API 依赖的宿主)。** 浏览器端通过 `@h-ai/api-client` 调用由本模块暴露的 HTTP API。
10
12
 
11
13
  > 生命周期说明:`@h-ai/serv` 是无状态 HTTP App 装配器,不提供 `init()` / `close()`;依赖模块(iam/storage/ai/crypto 等)由应用先初始化,并在 `serv.listen(..., { onClose })` 中反向释放。`serv.createApp()` 的启动期配置错误会 fail-fast 抛出。
12
14
 
15
+ 返回的监听句柄允许立即 `close()` 取消未完成绑定,重复关闭复用结果;真实监听错误仍会拒绝。调用 `handle.close()` 不代替宿主的业务模块清理。
16
+
13
17
  ## 适用场景
14
18
 
15
19
  - 将 `@h-ai/api-contract` contract 装配成最小 HTTP App 抽象
@@ -18,13 +22,15 @@
18
22
  - 使用 Node listener,或在满足包 Node 依赖的宿主/测试中注入标准 fetch handler
19
23
  - 自定义 `ServContext`(注入 session、tenant 等请求级数据)
20
24
  - 生成 OpenAPI 3.1 文档供外部工具消费
21
- - 通过 `config/_serv.yml` 管理 API 前缀、OpenAPI、docs、health、rpc 等 HTTP 挂载配置
25
+ - 通过 `config/_serv.yml` 管理 API 前缀、OpenAPI、docs、health 等 HTTP 挂载配置
22
26
  - 通过 `serv.createRuntimeSecurityPolicy(...)` 对生产 CORS Origin、原生 WebView Origin、Secure Cookie 与 API 文档暴露执行 fail-closed 策略
23
27
  - 启用传输加密:`serv.createApp({ transport: { crypto, serverKeyRing, keyStore } })`
24
28
  - 高级场景通过 `createApp({ middlewares })` 自定义 HTTP middleware,通过 `createContext` 注入请求级业务字段
25
29
  - 公开只读文件通过 `serv.storageAssets(...)` 统一处理安全 key、MIME 白名单、GET/HEAD、ETag 与缓存头
26
30
  - 为 Audio WebSocket 配置一次性 ticket 校验、IAM/模型/配额授权与并发释放钩子
27
31
 
32
+ 权限遵循 IAM/Kit 的精确值、全局 `*` 与冒号前缀 `users:*` 规则;声明多个权限时全部满足。返回的 listener `close()` 幂等并移除信号监听器,先关闭 Audio WebSocket,1 秒后终止未配合的音频连接;业务模块由宿主反序释放。
33
+
28
34
  ## 使用步骤
29
35
 
30
36
  ### 1. 安装依赖
@@ -63,6 +69,7 @@ const procedures = {
63
69
  const app = serv.createApp({
64
70
  contract,
65
71
  procedures,
72
+ iam,
66
73
  http: {
67
74
  apiPrefix: '/api/v1',
68
75
  openapi: { path: '/openapi.json' },
@@ -98,6 +105,7 @@ const app = serv.createApp({
98
105
  openapi: security.exposeApiDocs ? servConfig.http.openapi : false,
99
106
  docs: security.exposeApiDocs ? servConfig.http.docs : false,
100
107
  },
108
+ iam,
101
109
  refreshCookie: { secure: security.secureRefreshCookie },
102
110
  middlewares: [{
103
111
  middleware: serv.cors({
@@ -113,7 +121,7 @@ const app = serv.createApp({
113
121
  - 生产环境 `cors.origin` 禁止为空或 `*`,启动时 fail-fast。
114
122
  - 普通 Web Origin 仅接受 HTTP(S);原生 Origin 通过 `cors.nativeOrigins` 显式允许 HTTP(S)、Capacitor 或 Tauri。
115
123
  - Origin 按完整规范化值精确匹配,不使用前缀/后缀匹配。
116
- - 生产环境自动要求 Secure refresh cookie,并把 OpenAPI/docs 作为安全上限关闭。
124
+ - 生产环境默认给刷新 Cookie 加 `Secure`,并关闭 OpenAPI 和 docs。受控内网或 VPN 的 HTTP 入口可在后端进程设置 `HAI_SERV_REFRESH_COOKIE_SECURE=false`,重启并重新登录。HTTP 会明文传输刷新凭据,生产 CORS 仍须配置完整的实际 Origin。
117
125
 
118
126
  ### 2.1 Audio WebSocket 安全接入
119
127
 
@@ -235,7 +243,7 @@ export default { fetch: handler }
235
243
  import { core } from '@h-ai/core'
236
244
  import { serv, ServConfigSchema } from '@h-ai/serv'
237
245
 
238
- core.init({ configDir: './config' })
246
+ core.init({ configDir: './config', configSchemas: { serv: ServConfigSchema } })
239
247
 
240
248
  const validation = core.config.validate('serv', ServConfigSchema)
241
249
  if (!validation.success)
@@ -276,7 +284,6 @@ http:
276
284
  health:
277
285
  path: /health
278
286
  readyPath: /ready
279
- rpc: false
280
287
 
281
288
  transport:
282
289
  keyExchangePath: /_hai/key-exchange
@@ -288,7 +295,9 @@ transport:
288
295
  - /_hai/scalar.js
289
296
  ```
290
297
 
291
- 认证协议仅支持 v2:独立随机 SM4-CBC 密钥和 HMAC-SM3 密钥由 SM2 封装,先验签、再验证 MAC,最后解密 CBC。请求由客户端私钥签名,响应由所选服务端版本签名。认证字段包括 version、direction、serverKeyId、clientId、128 位随机 requestId(兼 nonce)、issuedAt、HTTP method、pathname + 原始 search 的 SM3 pathHash,以及响应 status。请求有效期 5 分钟,未来时钟偏差最多 30 秒;共享 registry 原子消费 (clientId, requestId),nonce 至少保留到 issuedAt + 6 分钟以覆盖节点偏差,存储故障拒绝请求。Redis 必须保留有效 nonce,使用 noeviction/可靠持久化策略;各节点时钟须同步。此能力防止同一传输消息重放,业务重试仍需自己的幂等键。
298
+ 认证协议仅支持 v2:独立随机 SM4-CBC 密钥和 HMAC-SM3 密钥由 SM2 封装,先验签、再验证 MAC,最后解密 CBC。请求由客户端私钥签名,响应由所选服务端版本签名。认证字段包括 version、direction、serverKeyId、clientId、128 位随机 requestId(兼 nonce)、issuedAt、HTTP method、pathname + 原始 search 的 SM3 pathHash,以及响应 status。请求默认有效期为 5 分钟,客户端时间最多可领先服务器 30 秒。服务端可用 `maxRequestAgeSeconds`(1~600)和 `maxFutureSkewSeconds`(0~300)调整,单位为整数秒。共享 registry 原子消费 (clientId, requestId),nonce 保留到 issuedAt + 11 分钟,覆盖最长可配窗口及节点时钟差,存储故障拒绝请求。Redis 必须保留有效 nonce,使用 noeviction/可靠持久化策略;各节点时钟须同步。此能力防止同一传输消息重放,业务重试仍需自己的幂等键。
299
+
300
+ 在 `_serv.yml` 的 `transport` 块配置时间窗口,将字段传给服务端 transport 装配。完成旧节点升级后等待 12 分钟,再把有效期调到 300 秒以上。校时提示与 `TransportTimeError.serverTime` 见 [Crypto 参考](../hai-crypto/reference.md)。
292
301
 
293
302
  空 POST 也发送空明文的认证封装;GET/HEAD 将封装放在 X-Hai-Transport 头,204/304/HEAD 等无正文响应也使用同名头承载认证封装。其它请求/响应在 JSON 正文承载封装并带 X-Encrypted: true。客户端必须关联原请求上下文和 HTTP status,缺标记、旧格式或认证失败一律 reject;明文接口由 Serv/Kit 路径规则 `encryption: disabled` 或宿主 `excludePaths` 显式允许;调用方同步选择普通请求,API Client/Kit 包装器使用自己的 `excludePaths`。CORS 须允许并暴露 X-Hai-Transport。握手公钥的真实性依赖 TLS,不替代 IAM 业务授权。
294
303
 
@@ -483,7 +492,7 @@ const app = serv.createApp({
483
492
  })
484
493
  ```
485
494
 
486
- 执行顺序为:`securityHeaders` → 内置 CORS 响应包装/握手和预检策略 → `transport`(若启用)/请求策略 → 普通 `middlewares` → health/refresh-cookie/OpenAPI/RPC/docs/oRPC routes。
495
+ 执行顺序为:`securityHeaders` → 内置 CORS 响应包装/握手和预检策略 → `transport`(若启用)/请求策略 → 普通 `middlewares` → health/refresh-cookie/OpenAPI/docs/oRPC routes。
487
496
 
488
497
  - 普通 `middlewares` 在解密和策略之后执行,提前响应仍受访问策略和响应加密约束。二进制端点显式加入 transport.excludePaths;内置 CORS 预检和密钥交换也执行策略。
489
498
  - 公开只读文件优先使用 `serv.storageAssets(...)`;应用仍须显式提供 key/MIME 白名单、缓存与跨域策略,框架不内置头像等业务概念。
@@ -549,9 +558,9 @@ HTTP 审计、限流、CORS、租户头校验等跨 route 能力继续放在
549
558
  | oRPC contract 输入 schema 校验失败(handler 之前) | 400 | 本地化失败体,`error.httpStatus=400` 与字段错误 |
550
559
  | handler 内 `serv.validateInputOrFail(...)` 失败 | 200 | 失败 HaiResult,`error.httpStatus=400` 与字段错误 |
551
560
 
552
- HTTP middleware、自定义 Response、refresh-cookie、OpenAPI/RPC 入口权限和 transport 协议错误分别按自身响应契约处理,可返回真实 HTTP 4xx/5xx。`@h-ai/api-client` 在真实 HTTP 401 或 HTTP 200 的有效失败 HaiResult(`success=false`、字符串 `error.code/message`、`error.httpStatus=401`)后自动刷新,成功后只重试一次;内置登录、注册和配置的刷新路径除外,403/500 不刷新。启用 transport 时刷新和重试复用加密链路,不能手写明文刷新请求。
561
+ HTTP middleware、自定义 Response、refresh-cookie、OpenAPI 入口权限和 transport 协议错误分别按自身响应契约处理,可返回真实 HTTP 4xx/5xx。`@h-ai/api-client` 在真实 HTTP 401 或 HTTP 200 的有效失败 HaiResult(`success=false`、字符串 `error.code/message`、`error.httpStatus=401`)后自动刷新,成功后只重试一次;内置登录、注册和配置的刷新路径除外,403/500 不刷新。启用 transport 时刷新和重试复用加密链路,不能手写明文刷新请求。
553
562
 
554
- SvelteKit 的 `kit.response.fromError` 根据 `error.httpStatus` 生成 HTTP Response;`kit.response.fromResult` 使用显式错误码映射,未命中默认 400。这些响应转换规则不能套用到 serv 普通 procedure。
563
+ SvelteKit 的 `kit.response.fromError` 根据归一化后的 `error.httpStatus` 生成 HTTP Response;`kit.response.fromResult` 优先使用显式错误码映射,再使用错误自身的 `httpStatus`,缺失或非法状态统一为 500。这些响应转换规则不能套用到 serv 普通 procedure。
555
564
 
556
565
  按三层处理:
557
566
 
@@ -580,11 +589,11 @@ const procedures = serv
580
589
  .build()
581
590
  ```
582
591
 
583
- > 如果错误来自下游模块(如 `iam` / `storage`)并且该模块已经返回 `HaiResult`,serv 默认**透传**它的 `error.message`。由于 `HaiError` 目前不携带 `messageKey/params`,serv 边界不能对任意下游错误再做通用重翻译;要做请求级 i18n,必须在创建该错误的模块/feature 里就拿到 locale。
592
+ > 下游 4xx 消息经脱敏后公开,5xx 和缺失或非法状态使用安全内部错误与 traceId,完整原因只记录服务端日志。`HaiError` 不携带 `messageKey/params`,领域消息的请求级 i18n 仍在创建消息的模块/feature 中接收 locale 后完成。自动审计使用同一归一化状态,失败不能按非法原始状态记为成功。
584
593
 
585
594
  ### `serv.parseRequestContext` — 默认 Context 解析器
586
595
 
587
- 默认忽略转发头,缺少运行时对端时 IP 为 undefined。XFF 从右向左停在首个不可信地址,忽略左侧;可信链无效/过长时保留 IP 未知,不能回退为代理身份。Fetch 使用 `app.fetch(request, { remoteAddress: platformPeerAddress })`,对端必须来自可信平台 API。自定义 context 传递完整 input,保留已解析的 ip;内部 RPC 使用连接对端,转发头不能放宽来源限制。
596
+ 默认忽略转发头,缺少运行时对端时 IP 为 undefined。XFF 从右向左停在首个不可信地址,忽略左侧;可信链无效/过长时保留 IP 未知,不能回退为代理身份。Fetch 使用 `app.fetch(request, { remoteAddress: platformPeerAddress })`,对端必须来自可信平台 API。自定义 context 传递完整 input,保留已解析的 ip。
588
597
 
589
598
  从 HTTP 请求头自动解析:
590
599
 
@@ -615,8 +624,7 @@ const spec = await serv.generateSpec(contract, {
615
624
  | `apiPrefix` | `'/api/v1'` | oRPC OpenAPI handler 挂载前缀 |
616
625
  | `health` | `{ path: '/health', readyPath: '/ready' }` | 健康与就绪检查端点 |
617
626
  | `openapi` | `false` | OpenAPI JSON endpoint,显式开启 |
618
- | `docs` | `false` | Scalar 文档页,显式开启;启用后自动挂载 `/_hai/scalar.js` 本地脚本路由 |
619
- | `rpc` | `false` | 内部 RPC endpoint,显式开启;`loopback` / `private-network` 基于真实连接 IP,网关场景用 `gateway-only` |
627
+ | `docs` | `false` | Scalar 文档页,显式开启;在文档目录下挂载 `_hai/scalar.js`,页面使用相对脚本和 spec 地址;认证仍在后端执行 |
620
628
  | `transport` | `undefined` | 顶层配置;启用后默认挂载 `{apiPrefix}/_hai/key-exchange` |
621
629
 
622
630
  ```ts
@@ -627,7 +635,6 @@ const http: ServHttpConfigInput = {
627
635
  openapi: { path: '/openapi.json' },
628
636
  docs: { path: '/docs' },
629
637
  health: { path: '/health', readyPath: '/ready' },
630
- rpc: false,
631
638
  }
632
639
  ```
633
640
 
@@ -738,6 +745,8 @@ route 实现器内置以下错误:
738
745
  原生客户端匹配 `nativeTokenTransport.isRequest` 后,登录/注册/刷新响应保留轮换后的
739
746
  `refreshToken`,刷新请求从 `{ refreshToken }` body 读取,且不读写浏览器 Cookie。
740
747
 
748
+ 刷新失败经过同一公开错误投影,并记录英文关联日志;仅 401 认证失效会清除浏览器刷新 Cookie,限流、5xx 和缺失或非法状态保留凭据供稍后重试。未捕获的刷新异常返回安全的 HTTP 500 JSON,不公开内部 cause/ext。
749
+
741
750
  若同时启用 `transport: { crypto, serverKeyRing, keyStore }`,`/auth/refresh` 也必须走加密链路;cookie-only 刷新请求允许空 body,服务端不要把 `Request.body === null` 误判为需要解密的请求体。
742
751
 
743
752
  ### 配置
@@ -751,7 +760,7 @@ const app = serv.createApp({
751
760
  refreshCookie: {
752
761
  // cookieName?: string 默认 'hai_refresh_token'
753
762
  // maxAge?: number 默认 30 * 24 * 3600(30 天)
754
- // secure?: boolean 默认 NODE_ENV 或 HAI_CORE_ENV 为 production 时开启;测试开关不放宽
763
+ // secure?: boolean 显式值优先于后端变量 HAI_SERV_REFRESH_COOKIE_SECURE
755
764
  // onRefresh?: (token) => ... 可选:覆盖默认的 iam.session.refresh
756
765
  nativeTokenTransport: {
757
766
  // 必须 fail-closed:同时校验平台标识与允许的原生 Origin。
@@ -774,9 +783,11 @@ const app = serv.createApp({
774
783
  | Path | `{apiPrefix}/auth/refresh`(最小范围) |
775
784
  | HttpOnly | ✓ |
776
785
  | SameSite | `Strict` |
777
- | Secure | 生产自动开启,`secure: false` 可在 HTTP 开发环境关闭 |
786
+ | Secure | 显式 `secure` 优先,然后读取 `HAI_SERV_REFRESH_COOKIE_SECURE` 的 `true` 或 `false`,其余值按环境默认 |
778
787
  | Max-Age | 30 天(可配置) |
779
788
 
789
+ 直接调用 `serv.createApp` 时,环境默认根据 `NODE_ENV` 或 `HAI_CORE_ENV` 是否为 `production` 判断。通过 `serv.createRuntimeSecurityPolicy` 赋值时,策略的默认值来自传入的 `environment`。测试开关不参与该判断。
790
+
780
791
  ## 测试
781
792
 
782
793
  ```bash
@@ -4,6 +4,8 @@
4
4
 
5
5
  ## 运行环境
6
6
 
7
+ Local 的 key 路径拒绝文件符号链接和目录 junction;每个路径段都保留 `.meta.json` / `.upload.tmp` 后缀及 `.hai-storage-meta` 目录,不区分大小写。先在该内部目录准备文件身份对应的代际元数据(含显式空默认值),再原子发布内容;准备失败不改内容,发布后的历史记录清理失败只记录诊断,不回滚其他写入。代际记录是唯一格式,不读取旧 `<key>.meta.json`;缺少当前记录时明确失败,旧数据须离线迁移或重新 `file.put()`。备份与迁移包含内容和内部记录,不混用旧程序写入。copy 默认继承源元数据;S3 copy 接收原始 key,内部逐段编码 Unicode、空格和 URL 分隔字符。浏览器根入口仅导出 Schema、类型和客户端辅助能力。
8
+
7
9
  > ⚠️ **服务端模块**。`storage.file` / `storage.dir` / `storage.presign` 均在 Node.js 端使用。浏览器端通过预签名 URL 或 API 端点间接访问存储(见下方「浏览器端文件上传」)。
8
10
 
9
11
  ## 适用场景
@@ -32,7 +34,7 @@ root: ./data/storage
32
34
  # bucket: my-bucket
33
35
  # createBucket: ${HAI_STORAGE_CREATEBUCKET:false}
34
36
  # forcePathStyle: false
35
- # prefix: ''
37
+ # prefix: "${HAI_STORAGE_PREFIX:}"
36
38
  # publicUrl: ''
37
39
  operationLog:
38
40
  read: false # file.get/head/exists, dir.list, presign.getUrl/publicUrl
@@ -42,7 +44,9 @@ operationLog:
42
44
  ```
43
45
 
44
46
  约定环境变量优先于 YAML,例如 `HAI_STORAGE_ROOT`、
45
- `HAI_STORAGE_ACCESSKEYID`、`HAI_STORAGE_SECRETACCESSKEY`。
47
+ `HAI_STORAGE_ACCESSKEYID`、`HAI_STORAGE_SECRETACCESSKEY`。注册了 `StorageConfigSchema` 的应用可直接设置 `HAI_STORAGE_PREFIX=my-app/uploads/`。调用方传相对 key,初始化探测与默认列表使用带尾斜杠的目录前缀,空前缀使用桶根。
48
+
49
+ 限制应用凭据时,将对象操作授权到该前缀下的对象 ARN,并用 `s3:prefix` 条件限制 `s3:ListBucket`。使用预先创建的桶并关闭 `createBucket`,以便初始化只执行带前缀的列表探测。
46
50
 
47
51
  MinIO、RustFS 使用同一个 S3 Provider:配置 `type: s3`、S3 API `endpoint`(通常为 9000 端口)和 `forcePathStyle: true`。`createBucket` 默认 false,需要预先创建 `bucket`;设为 true 后,`storage.init` 用 HeadBucket 检查,明确 404 时才创建,存在的桶及其中对象保留。权限、网络或创建失败均返回失败且不发布连接;凭据须有检查及创建桶的权限。不要新增 `type: minio` 或 `type: rustfs`。
48
52
 
@@ -53,9 +57,13 @@ MinIO、RustFS 使用同一个 S3 Provider:配置 `type: s3`、S3 API `endpoin
53
57
  ### 2. 初始化与关闭
54
58
 
55
59
  ```typescript
56
- import { storage } from '@h-ai/storage'
60
+ import { core } from '@h-ai/core'
61
+ import { storage, StorageConfigSchema } from '@h-ai/storage'
57
62
 
58
- await storage.init(core.config.get('storage'))
63
+ const config = core.config.validate('storage', StorageConfigSchema)
64
+ if (!config.success) throw new Error(config.error.message)
65
+ const initialized = await storage.init(config.data)
66
+ if (!initialized.success) throw new Error(initialized.error.message)
59
67
  // 使用后关闭
60
68
  const closeResult = await storage.close()
61
69
  if (!closeResult.success) {
@@ -190,10 +198,19 @@ if (!result.success) return result
190
198
 
191
199
  `checksum.generate(data, options?)` / `checksum.verify(data, expected, options?)` 默认 SHA-256、小写 hex,可选 sha384/sha512/sha1/md5、base64 编码与 signal;流仅消费一次,读取失败返回 HaiResult 错误,不匹配返回 false。MD5/SHA-1 不作安全认证,ETag 和 multipart 复合 checksum 不当作完整文件摘要。浏览器不导入 Node checksum;离线增量摘要需使用适用的浏览器实现,不把 SubtleCrypto.digest 当流式 API。
192
200
 
193
- ### 与 kit 集成(文件上传 API)
201
+ ### Serv / Fullstack 私有附件
202
+
203
+ Fullstack/API 项目先读取本工程已交付的 hai-serv/reference.md 中“扩展应用自有 contract + procedures”和“Feature Procedures”,结合 hai-api-contract、hai-api-client 扩展;保留其 contract/serv/shared 分层,不复制下方 SvelteKit 路由。
204
+
205
+ - 在 `packages/<name>-contract`(API 为 `apps/<name>-contract`)声明应用自己的附件 contract,在 serv 的 procedures 聚合入口挂载。当前生成的 Fullstack 客户端是共享 `managementApi`,需先将新领域纳入工程 contract;不能把文档中的默认 `apiClient.storage` 当作工程已经开放的接口。
206
+ - 自有 procedure 使用 `serv.implement(contract).context<ServContext>().route(...).auth().handle(...)`;owner 来自可信 `context.session.userId`,按 ID 操作还须查验业务记录归属、状态与租户。key 由服务端生成,列表也在服务端限定本人范围。
207
+ - 可复用 `createStorageProcedures({ storage, authorize })` 的已有签名/列表/元数据/删除操作,authorize 必须按可信 session 核对 operation 与全部 keys,拒绝空 prefix 与跨用户请求。只挂载实际需要的子路由,不能绕过应用附件记录、限额或审计;框架没有默认的 `attachments.*` 业务 API。
208
+ - FileUpload 注入应用 uploadHandler;S3 签名 URL 是短期访问凭据,不能当作持续会话授权。上传完成后仍验证大小、实际内容与对象状态;仍有效的 PUT URL 可以重写其目标,严格发布时使用 staging/final 对象隔离。需要 Local 或严格逐次鉴权的二进制流程时,通过 Serv 自有 middleware 接入 `storage.file.put/getStream`,显式认证并精确配置传输策略,不能把公开 `serv.storageAssets` 用于本人私有附件。
209
+
210
+ ### 与 kit 集成(SvelteKit 文件上传 API)
194
211
 
195
212
  ```typescript
196
- import { storage } from '$lib/server/init'
213
+ import { storage } from '#lib/server/init.js'
197
214
  // src/routes/api/files/upload/+server.ts
198
215
  import { kit } from '@h-ai/kit'
199
216
  import { z } from 'zod'
@@ -1,17 +1,17 @@
1
1
  ---
2
2
  name: hai-ui
3
- description: "使用 @h-ai/ui 构建 Svelte 5 表单、表格、弹窗、新手引导、帮助中心、主题与 AI 内容展示。"
3
+ description: "使用 @h-ai/ui 构建 Svelte 5 表单、表格、看板图表、弹窗、引导、代码工作区、主题与 AI 内容展示。"
4
4
  ---
5
5
 
6
6
  # hai-ui
7
7
 
8
8
  ## 能力契约
9
9
 
10
- 主题 DOM 与安全缓存复用 `@h-ai/ui/theme` 的 `applyTheme` / `getSavedTheme(fallbackTheme?)`;默认主题取应用配置,选择范围与已编译 CSS 主题一致。共享 theme.css 已包含 high-contrast 高对比及 formal 直角商务主题;新应用默认提供该选项,显式主题白名单尊重宿主配置。不能通过淡化正文/占位符抵消其对比度,验收实际文字、边框、焦点、缩放及偏好持久化。
10
+ 主题 DOM 与安全缓存复用 `@h-ai/ui/theme` 的 `applyTheme` / `getSavedTheme(fallbackTheme?)`;默认主题取应用配置,选择范围与已编译 CSS 主题一致。共享 theme.css 已包含 high-contrast 高对比、formal 直角商务及 eye-care 暖绿护眼主题;新应用默认提供该选项,显式主题白名单尊重宿主配置。不能通过淡化正文/占位符抵消其对比度,验收实际文字、边框、焦点、缩放及偏好持久化。
11
11
 
12
12
  | 项目 | 契约 |
13
13
  | --- | --- |
14
- | 能力 | 使用 @h-ai/ui 构建 Svelte 5 表单、表格、弹窗、移动界面、主题与 AI 内容展示 |
14
+ | 能力 | 使用 @h-ai/ui 构建 Svelte 5 表单、表格、弹窗、代码工作区、移动界面、主题与 AI 内容展示 |
15
15
  | 适用场景 | 已有 Svelte 项目中的组件选择与交互实现 |
16
16
  | 输入 | 现有 Svelte 页面、组件 Props、Tailwind 扫描路径与主题 |
17
17
  | 输出 | 复用原子/组合/场景组件的界面及交互验证 |
@@ -25,13 +25,15 @@ description: "使用 @h-ai/ui 构建 Svelte 5 表单、表格、弹窗、新手
25
25
 
26
26
  - 查询/登录采用原生表单 Enter 提交;提示使用 Button submitHint(仅 submit),不注册占用浏览器按键的全局监听。输入法确认不得触发搜索;保留 Tab、Enter/Space、Esc、菜单方向键和清晰焦点。弹窗默认聚焦首个可编辑项,危险确认优先聚焦取消;关闭恢复触发控件,忙碌/禁用时不得继续提交。
27
27
 
28
+ 轻量看板复用 MetricCard / ReportCard / DashboardGrid 和 Chart 的五种 preset;原生属性覆盖默认值。图形仅在客户端有数据时按类型加载,SSR 显示占位。更多 LayerChart 图形放入 ReportCard children,由宿主显式依赖并按需加载原生组件。示例与边界见[图表和看板](charts.md)。
29
+
28
30
  ## 使用路径
29
31
 
30
32
  Toast 支持 F8 聚焦/返回、Escape 关闭可关闭提示并恢复焦点;Modal 内也要验证键盘路径。`notifyError` 接收完整错误对象或安全本地化字符串;Promise 组件需抛异常时用 `new Error(result.error.message, { cause: result.error })` 保留安全业务原因和 traceId。普通 Error 和 5xx 使用兜底,不传未知 `err.message`。修改及交付遵循[沟通与同步维护](../hai-app-create/collaboration.md)。
31
33
 
32
34
  状态层可从轻量 `@h-ai/ui/i18n` 导入同一个 `authErrorMessage`,避免加载场景组件;管理台、移动/桌面示例与模板保持一致。
33
35
 
34
- 认证错误复用公开 `authErrorMessage(error)`(从 `@h-ai/ui/components/scenes/iam/index` 导入):凭据错误统一,不显示未知原文,保留有效 traceId 供排查图标展开;首次/到期改密、密码复用 034 和会话上限有明确指引。AuthPanel 与敏感操作确认已内置;自定义改密复用此映射。确认密码按原始字符串比较,不裁剪或 Unicode 归一化;字段错误、操作成功和认证错误均使用统一 Toast,字段保留定位与无障碍关联。
36
+ 认证错误复用公开 `authErrorMessage(error)`(从 `@h-ai/ui/components/scenes/iam/index` 导入):凭据错误统一,不显示未知原文,保留有效 traceId 供排查图标展开;首次/到期改密、密码复用 034 和会话上限有明确指引。AuthPanel 与敏感操作确认已内置;自定义改密复用此映射。确认密码按原始字符串比较,不裁剪或 Unicode 归一化;字段错误、操作成功和认证错误均使用统一 Toast,字段保留定位与无障碍关联。Kit 和 API Client 的已认证传输时间诊断会在认证提示中显示服务器 UTC 时间及校时建议。
35
37
 
36
38
  新页面或较大改版先使用 [hai-ui-design](../hai-ui-design/SKILL.md) 确定任务、布局、状态和组件映射;本 skill 负责正确实现。完成后按 [hai-ui-review](../hai-ui-review/SKILL.md) 查看真实页面,小范围修复沿用已有设计。
37
39
 
@@ -40,9 +42,10 @@ Toast 支持 F8 聚焦/返回、Escape 关闭可关闭提示并恢复焦点;Mo
40
42
  - 先检查组件导入、Tailwind @source 和主题 CSS,再选场景 → 组合 → 原子组件,缺能力才自定义。
41
43
  - 管理列表和工作台随可用宽度展开;flex/grid 子项设 min-width: 0,表格在自身容器滚动。阅读页与登录表单按内容约束宽度。实际检查 390、1440、1920、2560、3840px,避免宽屏仍挤在中央窄列,手机出现整页横向滚动。
42
44
  - 按安装版本的 exports 选择明确列出的 components barrel 入口(不开放任意内部文件);主题工具使用 `@h-ai/ui/theme`,避免仅为主题加载根入口的 AI/图表组件。禁止导入内部 src/dist;例如原子组件使用 `@h-ai/ui/components/primitives/index`。
43
- - 自动导入识别无分号、跨行及别名的运行时 import;已绑定的本地组件名称不会重复注入。
45
+ - 先执行 `vitePreprocess()` 或其他样式/模板语言转换,再执行 `autoImportHaiUi()`;自动导入读取整份 Svelte AST,不接受未转换的 SCSS/Pug,直接 markup API 保持同步。
46
+ - 自动导入仅针对模板 AST 中实际使用的组件,忽略注释/textarea/脚本字符串中的示例,并尊重脚本声明及 each/await/snippet 局部绑定;真实 import、别名与运行时绑定保持不变。根入口 `@h-ai/ui` 的 type-only 组件按需提升。外部同名类型冲突会构建报错并列出文件/组件名,应为类型设置别名或以其他本地名显式导入组件,不重写业务类型引用。
44
47
  - 应用只提供页面级文案;组件内部中英日文由 @h-ai/ui 维护。
45
- - `UserProfile` 用 `avatarAccept` / `avatarHint` 对齐业务的头像格式与大小限制;文件最终校验和上传由业务回调完成。
48
+ - `UserProfile` 用 `avatarAccept` / `avatarHint` 对齐业务的头像格式与大小限制;文件最终校验和上传由业务回调完成。`onsubmit` 必须返回 boolean,只有 `true` 退出编辑,`false` 或异常保留草稿与重试入口。
46
49
  - `BareInput type="file"` 通过 `onchange` 的 `currentTarget.files` 读取文件,不绑定文件路径 `value`;其他输入类型可 `bind:value`。
47
50
  - AI 文本视为不可信;Markdown/Mermaid 预览与允许执行 HTML/JS 的预览有不同边界。
48
51
 
@@ -60,6 +63,8 @@ AuthPanel 的 config.captcha.enabled 开启后,复用 createAuthActions 提供
60
63
 
61
64
  新手引导、首次 Cookie 提示、操作导览和帮助中心见 [guide.md](guide.md)。fullstack 默认在共享管理界面接入,新增业务只补内容与 data-guide 锚点。
62
65
 
66
+ 浏览源码、多文件标签、工作区搜索和选区操作见[代码工作区](reference.md#代码工作区)。只需提供目录列表和文件读取回调即可开始浏览,按需添加搜索和宿主面板。
67
+
63
68
  按任务读取 [reference.md](reference.md) 的对应小节;签名以当前源码或安装版本的公开类型为准,不默认加载全部资料。示例中的业务变量由应用提供。
64
69
 
65
70
  - 运行环境
@@ -0,0 +1,51 @@
1
+ # 图表与轻量看板
2
+
3
+ 业务负责查询、权限和聚合,UI 负责数值展示和交互。先选择 MetricCard / ReportCard / DashboardGrid,再选择统一 Chart 或原生 LayerChart。完整自助 BI、透视引擎与报表设计器不属于这些组件的职责。
4
+
5
+ ```svelte
6
+ <script lang="ts">
7
+ import { Chart, DashboardGrid, MetricCard, ReportCard } from '@h-ai/ui/components/scenes/charts/index'
8
+ const data = [{ month: 'Jan', revenue: 42, cost: 24 }, { month: 'Feb', revenue: 58, cost: 31 }]
9
+ </script>
10
+
11
+ <DashboardGrid label="核心指标" columns={4}>
12
+ <MetricCard title="收入" value="128,430" unit="CNY" trend={12.8} trendTone="success" trendLabel="较上月" />
13
+ <MetricCard title="响应耗时" value={42} unit="ms" trend={-8.2} trendTone="success" />
14
+ </DashboardGrid>
15
+ <ReportCard title="收入与成本">
16
+ <Chart preset="grouped-bar" {data} x="month" y={['revenue', 'cost']} />
17
+ </ReportCard>
18
+ ```
19
+
20
+ 示例文案由应用翻译模块提供。`value` 接受已格式化字符串或数值;缺失和非有限数显示“—”,零值正常显示。`trend={12.8}` 表示 +12.8%,`trendTone` 独立指定业务语义,不能把所有下降都标成坏结果。MetricCard children 可加入迷你趋势,不提供时不加载图形引擎。
21
+
22
+ DashboardGrid 在容器小于 40rem 时一列,40rem 起最多两列,64rem 起使用 columns 指定的 1–4 列。ReportCard 复用 Card,支持 actions、children、footer,以及 loading/empty 状态;状态期间不挂载图形。对长标题、长数值和窄容器检查溢出。
23
+
24
+ | preset | 图形 | 默认配置 |
25
+ | --- | --- | --- |
26
+ | grouped-bar | bar | seriesLayout='group'、坐标轴、网格、图例 |
27
+ | stacked-bar | bar | seriesLayout='stack'、坐标轴、网格、图例 |
28
+ | stacked-area | area | seriesLayout='stack'、坐标轴、网格、图例 |
29
+ | donut | pie | innerRadius=0.65、图例 |
30
+ | sparkline | line | 高度 64、无坐标轴/网格/图例/数据点 |
31
+
32
+ 显式 type 优先于 preset 图形类型,未提供两者时为 line。显式原生参数覆盖预设:例如 `legend={false}`、`innerRadius={0.8}`、`height={80}`。原有 type/data/x/y 调用与 LayerChart 参数、snippet 透传保持兼容。
33
+
34
+ Chart 在 SSR 中保留高度并显示加载占位,浏览器挂载且数据非空后动态加载所选 renderer;loading/empty 时不加载。快速切换类型时过期导入不替换新图形。高级图形同样建议使用宿主动态组件;仅把组件放在条件分支里不等于拆分静态 import。
35
+
36
+ ## 更多 LayerChart 图形
37
+
38
+ 不需要把所有 LayerChart 能力压进 ChartType。ReportCard children 可承载 ArcChart、径向图、树图、热图等原生组件。应用显式添加与当前 UI 兼容的 layerchart 依赖(本仓库当前验证 2.6.1),按安装版本 exports 选择真实入口;layerchart/svg 等单渲染层入口可减少不需要的渲染分支。
39
+
40
+ ```svelte
41
+ <script lang="ts">
42
+ import { ReportCard } from '@h-ai/ui/components/scenes/charts/index'
43
+ import { ArcChart } from 'layerchart/svg'
44
+ import 'layerchart/daisyui-5.css'
45
+ </script>
46
+ <ReportCard title="目标完成率">
47
+ <ArcChart data={[{ key: 'Target', value: 72 }]} value="value" maxValue={100} range={[-90, 90]} innerRadius={0.78} height={240} />
48
+ </ReportCard>
49
+ ```
50
+
51
+ 原生组件的类型、数据与生命周期遵循 LayerChart,UI 不转发或仿造整套上游 API。上例为静态使用片段;按需加载时将原生图形放进独立组件,由宿主使用动态 import。完整示例在 admin 图表展厅中,点击后才加载 ArcChart。
@@ -10,7 +10,7 @@
10
10
  <script lang='ts'>
11
11
  import { createGuide, GuideHost } from '@h-ai/ui/guide'
12
12
  import { createSvelteKitNavAdapter } from '@h-ai/kit/client'
13
- import * as m from '$lib/paraglide/messages.js'
13
+ import * as m from '#lib/paraglide/messages.js'
14
14
 
15
15
  const guide = createGuide({
16
16
  nav: createSvelteKitNavAdapter(),