@h-ai/cli 0.1.0-alpha9 → 1.0.0-beta.2

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 (438) hide show
  1. package/README.en.md +229 -0
  2. package/README.ja.md +229 -0
  3. package/README.md +175 -113
  4. package/dist/chunk-AKV2CQZV.js +1497 -0
  5. package/dist/chunk-AKV2CQZV.js.map +1 -0
  6. package/dist/chunk-EF7WOFWZ.js +7557 -0
  7. package/dist/chunk-EF7WOFWZ.js.map +1 -0
  8. package/dist/chunk-MUHRBJSW.js +29272 -0
  9. package/dist/chunk-MUHRBJSW.js.map +1 -0
  10. package/dist/chunk-ZSDEVXZ7.js +38 -0
  11. package/dist/{chunk-2H7UOFLK.js.map → chunk-ZSDEVXZ7.js.map} +1 -1
  12. package/dist/cli-config-templates-HHFHNXBN.js +5 -0
  13. package/dist/cli-config-templates-HHFHNXBN.js.map +1 -0
  14. package/dist/cli-skill-templates-PN4FLGBC.js +5 -0
  15. package/dist/cli-skill-templates-PN4FLGBC.js.map +1 -0
  16. package/dist/index.js +4444 -554
  17. package/dist/index.js.map +1 -1
  18. package/dist/metafile-esm.json +1 -0
  19. package/dist/src-TEBHR5I5.js +5 -0
  20. package/dist/src-TEBHR5I5.js.map +1 -0
  21. package/package.json +35 -15
  22. package/templates/README.md +56 -114
  23. package/templates/apps/admin/messages/en-US.json +7 -0
  24. package/templates/apps/admin/messages/ja-JP.json +40 -0
  25. package/templates/apps/admin/messages/zh-CN.json +7 -0
  26. package/templates/apps/admin/src/routes/+layout.svelte.hbs +65 -8
  27. package/templates/apps/admin/src/routes/+page.svelte.hbs +35 -14
  28. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +54 -22
  29. package/templates/apps/admin/src/routes/admin/+page.svelte.hbs +33 -24
  30. package/templates/apps/admin/src/routes/admin/settings/+page.svelte.hbs +16 -13
  31. package/templates/apps/api/.dockerignore.hbs +25 -0
  32. package/templates/apps/api/.gitignore.hbs +22 -0
  33. package/templates/apps/api/.npmrc.hbs +1 -0
  34. package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +39 -0
  35. package/templates/apps/api/apps/{{projectName}}-contract/package.json.hbs +41 -0
  36. package/templates/apps/api/apps/{{projectName}}-contract/src/app-contract.ts.hbs +27 -0
  37. package/templates/apps/api/apps/{{projectName}}-contract/src/app-schemas.ts.hbs +28 -0
  38. package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +5 -0
  39. package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +16 -0
  40. package/templates/apps/api/apps/{{projectName}}-contract/tests/contract.test.ts.hbs +18 -0
  41. package/templates/apps/api/apps/{{projectName}}-contract/tsconfig.json.hbs +12 -0
  42. package/templates/apps/api/apps/{{projectName}}-contract/tsup.config.ts.hbs +11 -0
  43. package/templates/apps/api/apps/{{projectName}}-contract/vitest.config.ts.hbs +9 -0
  44. package/templates/apps/api/apps/{{projectName}}-serv/README.md.hbs +60 -0
  45. package/templates/apps/api/apps/{{projectName}}-serv/config/_core.yml.hbs +17 -0
  46. package/templates/apps/api/apps/{{projectName}}-serv/config/_serv.yml.hbs +54 -0
  47. package/templates/apps/api/apps/{{projectName}}-serv/data/.gitkeep.hbs +1 -0
  48. package/templates/apps/api/apps/{{projectName}}-serv/package.json.hbs +66 -0
  49. package/templates/apps/api/apps/{{projectName}}-serv/src/app.ts.hbs +119 -0
  50. package/templates/apps/api/apps/{{projectName}}-serv/src/build-metadata.ts.hbs +7 -0
  51. package/templates/apps/api/apps/{{projectName}}-serv/src/index.ts.hbs +41 -0
  52. package/templates/apps/api/apps/{{projectName}}-serv/src/lib/server/init.ts.hbs +165 -0
  53. package/templates/apps/api/apps/{{projectName}}-serv/src/server/procedures/app-procedures.ts.hbs +57 -0
  54. package/templates/apps/api/apps/{{projectName}}-serv/src/server/procedures/index.ts.hbs +41 -0
  55. package/templates/apps/api/apps/{{projectName}}-serv/tests/service.test.ts.hbs +176 -0
  56. package/templates/apps/api/apps/{{projectName}}-serv/tsconfig.json.hbs +16 -0
  57. package/templates/apps/api/apps/{{projectName}}-serv/tsup.config.ts.hbs +20 -0
  58. package/templates/apps/api/apps/{{projectName}}-serv/vitest.config.ts.hbs +12 -0
  59. package/templates/apps/api/e2e/api-serv.spec.ts.hbs +113 -0
  60. package/templates/apps/api/e2e/capture-evidence.ts.hbs +23 -0
  61. package/templates/apps/api/eslint.config.js.hbs +30 -0
  62. package/templates/apps/api/hai.json.hbs +4 -0
  63. package/templates/apps/api/images/serv.Dockerfile.hbs +37 -0
  64. package/templates/apps/api/package.json.hbs +53 -0
  65. package/templates/apps/api/playwright.config.ts.hbs +67 -0
  66. package/templates/apps/api/pnpm-workspace.yaml.hbs +40 -0
  67. package/templates/apps/api/tsconfig.base.json +14 -0
  68. package/templates/apps/fullstack/.dockerignore.hbs +25 -0
  69. package/templates/apps/fullstack/.gitignore.hbs +38 -0
  70. package/templates/apps/fullstack/.npmrc.hbs +1 -0
  71. package/templates/apps/fullstack/apps/{{projectName}}-app/.env.example.hbs +10 -0
  72. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +67 -0
  73. package/templates/apps/fullstack/apps/{{projectName}}-app/capacitor.config.ts.hbs +20 -0
  74. package/templates/apps/fullstack/apps/{{projectName}}-app/eslint.config.js.hbs +20 -0
  75. package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +22 -0
  76. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/en-US.json.hbs +7 -0
  77. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/ja-JP.json.hbs +7 -0
  78. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/zh-CN.json.hbs +7 -0
  79. package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +86 -0
  80. package/templates/apps/fullstack/apps/{{projectName}}-app/project.inlang/settings.json.hbs +17 -0
  81. package/templates/apps/fullstack/apps/{{projectName}}-app/public/runtime-config.js.hbs +4 -0
  82. package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +59 -0
  83. package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.css.hbs +20 -0
  84. package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.d.ts.hbs +8 -0
  85. package/templates/apps/fullstack/apps/{{projectName}}-app/src/build-metadata.ts.hbs +9 -0
  86. package/templates/apps/fullstack/apps/{{projectName}}-app/src/lib/capacitor.ts.hbs +19 -0
  87. package/templates/apps/fullstack/apps/{{projectName}}-app/src/main.ts.hbs +21 -0
  88. package/templates/apps/fullstack/apps/{{projectName}}-app/svelte.config.js.hbs +16 -0
  89. package/templates/apps/fullstack/apps/{{projectName}}-app/tsconfig.json.hbs +23 -0
  90. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +74 -0
  91. package/templates/apps/fullstack/apps/{{projectName}}-desktop/.env.example.hbs +6 -0
  92. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +64 -0
  93. package/templates/apps/fullstack/apps/{{projectName}}-desktop/eslint.config.js.hbs +20 -0
  94. package/templates/apps/fullstack/apps/{{projectName}}-desktop/index.html.hbs +20 -0
  95. package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/en-US.json.hbs +7 -0
  96. package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/ja-JP.json.hbs +7 -0
  97. package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/zh-CN.json.hbs +7 -0
  98. package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +66 -0
  99. package/templates/apps/fullstack/apps/{{projectName}}-desktop/project.inlang/settings.json.hbs +17 -0
  100. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/App.svelte.hbs +25 -0
  101. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/app.css.hbs +18 -0
  102. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/app.d.ts.hbs +8 -0
  103. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/build-metadata.ts.hbs +9 -0
  104. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/main.ts.hbs +20 -0
  105. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/Cargo.toml.hbs +33 -0
  106. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/build.rs.hbs +6 -0
  107. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/capabilities/default.json.hbs +16 -0
  108. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/128x128.png +0 -0
  109. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/128x128@2x.png +0 -0
  110. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/32x32.png +0 -0
  111. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/icon.icns +0 -0
  112. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/icon.ico +0 -0
  113. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/lib.rs.hbs +91 -0
  114. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/main.rs.hbs +8 -0
  115. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +46 -0
  116. package/templates/apps/fullstack/apps/{{projectName}}-desktop/svelte.config.js.hbs +14 -0
  117. package/templates/apps/fullstack/apps/{{projectName}}-desktop/tsconfig.json.hbs +22 -0
  118. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +89 -0
  119. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +25 -0
  120. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/en-US.json.hbs +12 -0
  121. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/ja-JP.json.hbs +12 -0
  122. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/zh-CN.json.hbs +12 -0
  123. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/project.inlang/settings.json.hbs +17 -0
  124. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +54 -0
  125. package/templates/apps/fullstack/apps/{{projectName}}-web/eslint.config.js.hbs +20 -0
  126. package/templates/apps/fullstack/apps/{{projectName}}-web/index.html.hbs +21 -0
  127. package/templates/apps/fullstack/apps/{{projectName}}-web/messages/en-US.json.hbs +7 -0
  128. package/templates/apps/fullstack/apps/{{projectName}}-web/messages/ja-JP.json.hbs +7 -0
  129. package/templates/apps/fullstack/apps/{{projectName}}-web/messages/zh-CN.json.hbs +7 -0
  130. package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +53 -0
  131. package/templates/apps/fullstack/apps/{{projectName}}-web/project.inlang/settings.json.hbs +17 -0
  132. package/templates/apps/fullstack/apps/{{projectName}}-web/public/runtime-config.js.hbs +1 -0
  133. package/templates/apps/fullstack/apps/{{projectName}}-web/src/App.svelte.hbs +18 -0
  134. package/templates/apps/fullstack/apps/{{projectName}}-web/src/app.css.hbs +18 -0
  135. package/templates/apps/fullstack/apps/{{projectName}}-web/src/app.d.ts.hbs +8 -0
  136. package/templates/apps/fullstack/apps/{{projectName}}-web/src/build-metadata.ts.hbs +9 -0
  137. package/templates/apps/fullstack/apps/{{projectName}}-web/src/main.ts.hbs +14 -0
  138. package/templates/apps/fullstack/apps/{{projectName}}-web/svelte.config.js.hbs +16 -0
  139. package/templates/apps/fullstack/apps/{{projectName}}-web/tsconfig.json.hbs +23 -0
  140. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +76 -0
  141. package/templates/apps/fullstack/docs/security-configuration.md.hbs +349 -0
  142. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +988 -0
  143. package/templates/apps/fullstack/e2e/session-close.spec.ts.hbs +119 -0
  144. package/templates/apps/fullstack/eslint.config.js.hbs +41 -0
  145. package/templates/apps/fullstack/hai.json.hbs +11 -0
  146. package/templates/apps/fullstack/images/serv.Dockerfile.hbs +38 -0
  147. package/templates/apps/fullstack/images/web/19-runtime-config.envsh.hbs +5 -0
  148. package/templates/apps/fullstack/images/web/nginx.conf.hbs +24 -0
  149. package/templates/apps/fullstack/images/web/runtime-config.js.template.hbs +3 -0
  150. package/templates/apps/fullstack/images/web.Dockerfile.hbs +37 -0
  151. package/templates/apps/fullstack/package.json.hbs +61 -0
  152. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +40 -0
  153. package/templates/apps/fullstack/packages/{{projectName}}-contract/package.json.hbs +30 -0
  154. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-contract.ts.hbs +78 -0
  155. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +134 -0
  156. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/app-contract.ts.hbs +20 -0
  157. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/app-schemas.ts.hbs +35 -0
  158. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +7 -0
  159. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +23 -0
  160. package/templates/apps/fullstack/packages/{{projectName}}-contract/tests/contract.test.ts.hbs +31 -0
  161. package/templates/apps/fullstack/packages/{{projectName}}-contract/tsconfig.json.hbs +9 -0
  162. package/templates/apps/fullstack/packages/{{projectName}}-contract/tsup.config.ts.hbs +9 -0
  163. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +84 -0
  164. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_ai.yml.hbs +1 -0
  165. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_audit.yml.hbs +1 -0
  166. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_cache.yml.hbs +1 -0
  167. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +22 -0
  168. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_db.yml.hbs +1 -0
  169. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_feedback.yml.hbs +1 -0
  170. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_iam.yml.hbs +1 -0
  171. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_payment.yml.hbs +1 -0
  172. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +60 -0
  173. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_scheduler.yml.hbs +1 -0
  174. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +64 -0
  175. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_storage.yml.hbs +1 -0
  176. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_vecdb.yml.hbs +1 -0
  177. package/templates/apps/fullstack/packages/{{projectName}}-serv/data/.gitkeep.hbs +1 -0
  178. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +62 -0
  179. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +115 -0
  180. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/build-metadata.ts.hbs +7 -0
  181. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/index.ts.hbs +40 -0
  182. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +294 -0
  183. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/audit-jobs.ts.hbs +28 -0
  184. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/business-settings.ts.hbs +50 -0
  185. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/messages.ts.hbs +50 -0
  186. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +106 -0
  187. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/app-procedures.ts.hbs +49 -0
  188. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +34 -0
  189. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +159 -0
  190. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/security-roles.ts.hbs +73 -0
  191. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +505 -0
  192. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/runtime.test.ts.hbs +72 -0
  193. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/security.test.ts.hbs +183 -0
  194. package/templates/apps/fullstack/packages/{{projectName}}-serv/tsconfig.json.hbs +10 -0
  195. package/templates/apps/fullstack/packages/{{projectName}}-serv/tsup.config.ts.hbs +18 -0
  196. package/templates/apps/fullstack/packages/{{projectName}}-serv/vitest.config.ts.hbs +12 -0
  197. package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +10 -0
  198. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +97 -0
  199. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/en-US.json.hbs +164 -0
  200. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +164 -0
  201. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/zh-CN.json.hbs +164 -0
  202. package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +55 -0
  203. package/templates/apps/fullstack/packages/{{projectName}}-shared/project.inlang/settings.json.hbs +11 -0
  204. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/index.ts.hbs +7 -0
  205. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +94 -0
  206. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/feedback-client.ts.hbs +36 -0
  207. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/sensitive-actions.svelte.ts.hbs +16 -0
  208. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/session-recovery.svelte.ts.hbs +4 -0
  209. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +122 -0
  210. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +90 -0
  211. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/FeedbackEntry.svelte.hbs +86 -0
  212. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +94 -0
  213. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/configuration.svelte.hbs +25 -0
  214. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/feedback.svelte.hbs +157 -0
  215. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/roles.svelte.hbs +77 -0
  216. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +44 -0
  217. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +216 -0
  218. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/security/role-label.ts.hbs +12 -0
  219. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/guide-store.ts.hbs +55 -0
  220. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/management-store.svelte.ts.hbs +614 -0
  221. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/theme-store.svelte.ts.hbs +23 -0
  222. package/templates/apps/fullstack/packages/{{projectName}}-shared/svelte.config.js.hbs +1 -0
  223. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +150 -0
  224. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/management-store.test.ts.hbs +253 -0
  225. package/templates/apps/fullstack/packages/{{projectName}}-shared/tsconfig.json.hbs +17 -0
  226. package/templates/apps/fullstack/packages/{{projectName}}-shared/vitest.config.ts.hbs +7 -0
  227. package/templates/apps/fullstack/playwright.config.ts.hbs +114 -0
  228. package/templates/apps/fullstack/playwright.session-close.config.ts.hbs +18 -0
  229. package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +38 -0
  230. package/templates/apps/fullstack/tsconfig.base.json +14 -0
  231. package/templates/apps/h5/messages/en-US.json +3 -0
  232. package/templates/apps/h5/messages/ja-JP.json +40 -0
  233. package/templates/apps/h5/messages/zh-CN.json +3 -0
  234. package/templates/apps/h5/src/routes/+layout.svelte.hbs +85 -30
  235. package/templates/apps/h5/src/routes/+page.svelte.hbs +20 -8
  236. package/templates/apps/h5/src/routes/discover/+page.svelte.hbs +17 -6
  237. package/templates/apps/h5/src/routes/profile/+page.svelte.hbs +17 -6
  238. package/templates/apps/mobile-app/.env.hbs +3 -0
  239. package/templates/apps/{android-app → mobile-app}/capacitor.config.ts.hbs +2 -2
  240. package/templates/apps/mobile-app/index.html.hbs +14 -0
  241. package/templates/apps/mobile-app/messages/en-US.json +36 -0
  242. package/templates/apps/mobile-app/messages/ja-JP.json +36 -0
  243. package/templates/apps/mobile-app/messages/zh-CN.json +36 -0
  244. package/templates/apps/mobile-app/public/runtime-config.js +1 -0
  245. package/templates/apps/mobile-app/src/App.svelte.hbs +153 -0
  246. package/templates/apps/mobile-app/src/lib/api.ts.hbs +213 -0
  247. package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +14 -0
  248. package/templates/apps/{android-app → mobile-app}/src/lib/capacitor.ts +6 -7
  249. package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +58 -0
  250. package/templates/apps/mobile-app/src/lib/pages/profile.svelte.hbs +13 -0
  251. package/templates/apps/mobile-app/src/lib/pages/tasks.svelte.hbs +16 -0
  252. package/templates/apps/mobile-app/src/main.ts.hbs +11 -0
  253. package/templates/apps/website/messages/en-US.json +4 -2
  254. package/templates/apps/website/messages/ja-JP.json +34 -0
  255. package/templates/apps/website/messages/zh-CN.json +4 -2
  256. package/templates/apps/website/scripts/static-website.mjs.hbs +67 -0
  257. package/templates/apps/website/src/routes/+layout.svelte.hbs +77 -26
  258. package/templates/apps/website/src/routes/+layout.ts +3 -0
  259. package/templates/apps/website/src/routes/+page.svelte.hbs +31 -12
  260. package/templates/apps/website/src/routes/about/+page.svelte.hbs +11 -6
  261. package/templates/apps/website/src/routes/api/health/+server.ts +3 -0
  262. package/templates/apps/website/src/routes/contact/+page.svelte.hbs +44 -34
  263. package/templates/apps/website/src/routes/services/+page.svelte.hbs +27 -40
  264. package/templates/base/.dockerignore.hbs +27 -0
  265. package/templates/base/.gitignore.hbs +41 -0
  266. package/templates/base/.npmrc.hbs +1 -0
  267. package/templates/base/e2e/app.spec.ts.hbs +213 -0
  268. package/templates/base/e2e/helpers.ts +1 -1
  269. package/templates/base/eslint-rules.js +45 -0
  270. package/templates/base/eslint.config.js.hbs +34 -0
  271. package/templates/base/hai.json.hbs +4 -0
  272. package/templates/base/images/web/nginx.conf.hbs +24 -0
  273. package/templates/base/images/web.Dockerfile.hbs +72 -0
  274. package/templates/base/package.json.hbs +125 -49
  275. package/templates/base/playwright.config.ts +44 -6
  276. package/templates/base/pnpm-workspace.yaml.hbs +40 -0
  277. package/templates/base/src/app.css.hbs +40 -0
  278. package/templates/base/src/app.d.ts.hbs +14 -1
  279. package/templates/base/src/app.html.hbs +13 -0
  280. package/templates/base/src/hooks.server.ts.hbs +115 -43
  281. package/templates/base/src/lib/build-metadata.ts.hbs +7 -0
  282. package/templates/base/src/lib/server/init.ts.hbs +125 -152
  283. package/templates/base/src/lib/server/kit-config.ts.hbs +40 -0
  284. package/templates/base/src/routes/+layout.server.ts.hbs +35 -0
  285. package/templates/base/static/favicon.png +0 -0
  286. package/templates/base/svelte.config.js.hbs +84 -5
  287. package/templates/base/tests/scaffold.test.ts.hbs +102 -0
  288. package/templates/base/tsconfig.json.hbs +50 -0
  289. package/templates/base/vite.config.ts.hbs +117 -12
  290. package/templates/base/vitest.config.ts.hbs +19 -0
  291. package/templates/features/ai/routes/api/chat/+server.ts +6 -7
  292. package/templates/features/iam/routes-admin/(auth)/+layout.svelte.hbs +3 -17
  293. package/templates/features/iam/routes-admin/(auth)/auth/forgot-password/+page.svelte.hbs +10 -0
  294. package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +9 -96
  295. package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +9 -122
  296. package/templates/features/iam/routes-admin/(auth)/auth/reset-password/+page.svelte.hbs +10 -0
  297. package/templates/features/iam/routes-h5/auth/forgot-password/+page.svelte.hbs +10 -0
  298. package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +9 -96
  299. package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +9 -112
  300. package/templates/features/iam/routes-h5/auth/reset-password/+page.svelte.hbs +10 -0
  301. package/templates/features/iam/routes-shared/api/auth/captcha/+server.ts.hbs +4 -0
  302. package/templates/features/iam/routes-shared/api/auth/config/+server.ts +4 -0
  303. package/templates/features/iam/routes-shared/api/auth/login/+server.ts +1 -29
  304. package/templates/features/iam/routes-shared/api/auth/login/ldap/+server.ts +4 -0
  305. package/templates/features/iam/routes-shared/api/auth/login/otp/+server.ts +4 -0
  306. package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +13 -6
  307. package/templates/features/iam/routes-shared/api/auth/otp/send/+server.ts +4 -0
  308. package/templates/features/iam/routes-shared/api/auth/password/renew/+server.ts +5 -0
  309. package/templates/features/iam/routes-shared/api/auth/password-reset/confirm/+server.ts +4 -0
  310. package/templates/features/iam/routes-shared/api/auth/password-reset/request/+server.ts +4 -0
  311. package/templates/features/iam/routes-shared/api/auth/reauthenticate/+server.ts +5 -0
  312. package/templates/features/iam/routes-shared/api/auth/register/+server.ts +1 -23
  313. package/templates/features/iam/routes-shared/api/auth/session/close/+server.ts.hbs +4 -0
  314. package/templates/features/iam/routes-website/auth/forgot-password/+page.svelte.hbs +10 -0
  315. package/templates/features/iam/routes-website/auth/login/+page.svelte.hbs +11 -0
  316. package/templates/features/iam/routes-website/auth/register/+page.svelte.hbs +11 -0
  317. package/templates/features/iam/routes-website/auth/reset-password/+page.svelte.hbs +10 -0
  318. package/templates/features/payment/routes/api/v1/payment/create/+server.ts +25 -23
  319. package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +5 -2
  320. package/templates/features/storage/routes/api/upload/+server.ts +10 -8
  321. package/templates/i18n/project.inlang/settings.json +2 -2
  322. package/templates/shared/.gitattributes +11 -0
  323. package/templates/shared/build-metadata.ts +42 -0
  324. package/templates/shared/container-runtime-config.sh +30 -0
  325. package/templates/shared/e2e-env.ts +98 -0
  326. package/templates/shared/hash-router.svelte.ts +83 -0
  327. package/templates/shared/public-base-path.mjs +19 -0
  328. package/templates/shared/run-e2e.mjs +104 -0
  329. package/templates/shared/vite-runtime-config.ts +56 -0
  330. package/templates/skills/AGENTS.md +75 -16
  331. package/templates/skills/CLAUDE.md +0 -11
  332. package/templates/skills/bridges/admin/AGENTS.md +89 -0
  333. package/templates/skills/bridges/api/AGENTS.md +83 -0
  334. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +102 -0
  335. package/templates/skills/bridges/h5/AGENTS.md +88 -0
  336. package/templates/skills/bridges/mobile-app/AGENTS.md +100 -0
  337. package/templates/skills/bridges/website/AGENTS.md +88 -0
  338. package/templates/skills/copilot-instructions.md +3 -59
  339. package/templates/skills/hai-ai/SKILL.md +44 -715
  340. package/templates/skills/hai-ai/guide.md +457 -0
  341. package/templates/skills/hai-ai/reference.md +292 -0
  342. package/templates/skills/hai-api-client/SKILL.md +26 -339
  343. package/templates/skills/hai-api-client/reference.md +180 -0
  344. package/templates/skills/hai-api-contract/SKILL.md +36 -0
  345. package/templates/skills/hai-api-contract/reference.md +280 -0
  346. package/templates/skills/hai-app-create/SKILL.md +38 -351
  347. package/templates/skills/hai-app-create/collaboration.md +52 -0
  348. package/templates/skills/hai-app-create/development.md +243 -0
  349. package/templates/skills/hai-app-create/reference.md +376 -0
  350. package/templates/skills/hai-app-review/SKILL.md +57 -126
  351. package/templates/skills/hai-app-review/differential.md +34 -0
  352. package/templates/skills/hai-app-tests/SKILL.md +34 -454
  353. package/templates/skills/hai-app-tests/reference.md +462 -0
  354. package/templates/skills/hai-app-tests/validation.md +42 -0
  355. package/templates/skills/hai-audit/SKILL.md +37 -122
  356. package/templates/skills/hai-audit/reference.md +184 -0
  357. package/templates/skills/hai-build/SKILL.md +62 -315
  358. package/templates/skills/hai-build/paths.md +30 -0
  359. package/templates/skills/hai-build/reference.md +353 -0
  360. package/templates/skills/hai-cache/SKILL.md +26 -175
  361. package/templates/skills/hai-cache/reference.md +229 -0
  362. package/templates/skills/hai-capacitor/SKILL.md +24 -364
  363. package/templates/skills/hai-capacitor/reference.md +358 -0
  364. package/templates/skills/hai-change-plan/SKILL.md +46 -0
  365. package/templates/skills/hai-ci/SKILL.md +114 -0
  366. package/templates/skills/hai-core/SKILL.md +31 -469
  367. package/templates/skills/hai-core/reference.md +557 -0
  368. package/templates/skills/hai-core/request-access.md +65 -0
  369. package/templates/skills/hai-crypto/SKILL.md +29 -180
  370. package/templates/skills/hai-crypto/reference.md +317 -0
  371. package/templates/skills/hai-datapipe/SKILL.md +24 -207
  372. package/templates/skills/hai-datapipe/reference.md +204 -0
  373. package/templates/skills/hai-deploy/SKILL.md +53 -178
  374. package/templates/skills/hai-deploy/credentials.md +19 -0
  375. package/templates/skills/hai-deploy/middleware.md +129 -0
  376. package/templates/skills/hai-feedback/SKILL.md +85 -0
  377. package/templates/skills/hai-framework-sync/SKILL.md +105 -0
  378. package/templates/skills/hai-framework-sync/sync.md +49 -0
  379. package/templates/skills/hai-iam/SKILL.md +53 -325
  380. package/templates/skills/hai-iam/reference.md +535 -0
  381. package/templates/skills/hai-kit/SKILL.md +33 -329
  382. package/templates/skills/hai-kit/reference.md +202 -0
  383. package/templates/skills/hai-payment/SKILL.md +28 -262
  384. package/templates/skills/hai-payment/reference.md +295 -0
  385. package/templates/skills/hai-pr-review/SKILL.md +41 -0
  386. package/templates/skills/hai-reach/SKILL.md +28 -300
  387. package/templates/skills/hai-reach/reference.md +363 -0
  388. package/templates/skills/hai-reldb/SKILL.md +28 -449
  389. package/templates/skills/hai-reldb/reference.md +500 -0
  390. package/templates/skills/hai-scheduler/SKILL.md +26 -174
  391. package/templates/skills/hai-scheduler/reference.md +216 -0
  392. package/templates/skills/hai-serv/SKILL.md +60 -0
  393. package/templates/skills/hai-serv/reference.md +792 -0
  394. package/templates/skills/hai-spec-implement/SKILL.md +50 -0
  395. package/templates/skills/hai-spec-implement/changes.md +67 -0
  396. package/templates/skills/hai-spec-implement/evaluation.md +18 -0
  397. package/templates/skills/hai-spec-implement/handoff.md +47 -0
  398. package/templates/skills/hai-spec-implement/templates.md +117 -0
  399. package/templates/skills/hai-spec-implement/workflow.md +56 -0
  400. package/templates/skills/hai-spec-review/SKILL.md +57 -0
  401. package/templates/skills/hai-spec-review/checklist.md +71 -0
  402. package/templates/skills/hai-storage/SKILL.md +31 -262
  403. package/templates/skills/hai-storage/reference.md +284 -0
  404. package/templates/skills/hai-ui/SKILL.md +62 -238
  405. package/templates/skills/hai-ui/guide.md +81 -0
  406. package/templates/skills/hai-ui/reference.md +705 -0
  407. package/templates/skills/hai-ui-design/SKILL.md +31 -0
  408. package/templates/skills/hai-ui-design/patterns.md +31 -0
  409. package/templates/skills/hai-ui-review/SKILL.md +32 -0
  410. package/templates/skills/hai-ui-review/checklist.md +29 -0
  411. package/templates/skills/hai-vecdb/SKILL.md +25 -267
  412. package/templates/skills/hai-vecdb/reference.md +325 -0
  413. package/templates/skills/opencode.json +0 -5
  414. package/dist/chunk-2H7UOFLK.js +0 -10
  415. package/dist/chunk-LANIDJC7.js +0 -659
  416. package/dist/chunk-LANIDJC7.js.map +0 -1
  417. package/dist/chunk-YV7WPUI6.js +0 -206
  418. package/dist/chunk-YV7WPUI6.js.map +0 -1
  419. package/dist/config-templates-S5RI2MC6.js +0 -5
  420. package/dist/config-templates-S5RI2MC6.js.map +0 -1
  421. package/dist/skill-templates-N5DU5FNX.js +0 -5
  422. package/dist/skill-templates-N5DU5FNX.js.map +0 -1
  423. package/dist/src-XJ6YXNAF.js +0 -15030
  424. package/dist/src-XJ6YXNAF.js.map +0 -1
  425. package/templates/apps/android-app/.env.hbs +0 -3
  426. package/templates/apps/android-app/messages/en-US.json +0 -24
  427. package/templates/apps/android-app/messages/zh-CN.json +0 -24
  428. package/templates/apps/android-app/src/lib/api.ts.hbs +0 -30
  429. package/templates/apps/android-app/src/routes/+layout.svelte.hbs +0 -42
  430. package/templates/apps/android-app/src/routes/+layout.ts +0 -7
  431. package/templates/apps/android-app/src/routes/+page.svelte.hbs +0 -27
  432. package/templates/apps/api/src/routes/+layout.svelte +0 -14
  433. package/templates/apps/api/src/routes/+page.svelte +0 -11
  434. package/templates/apps/api/src/routes/api/v1/health/+server.ts +0 -14
  435. package/templates/apps/api/src/routes/api/v1/items/+server.ts +0 -37
  436. package/templates/apps/api/src/routes/api/v1/items/[id]/+server.ts +0 -30
  437. package/templates/base/src/app.css +0 -2
  438. package/templates/base/tsconfig.json +0 -8
@@ -0,0 +1,792 @@
1
+ # hai-serv — 详细用法
2
+
3
+ 从 [SKILL.md](SKILL.md) 选择主题后读取对应小节。代码片段展示 API 用法;省略的业务变量、schema、依赖与 return 上下文需由应用补齐。
4
+
5
+ > `@h-ai/serv` 是 hai-framework 的 API Service 运行时,对外暴露最小 HTTP App 抽象,将 `@h-ai/api-contract` 的领域 contract 挂载成跨端可访问的 HTTP API;Hono 是内部实现细节,应用代码不要直接 import 或暴露 Hono。
6
+
7
+ ## 运行环境
8
+
9
+ > ⚠️ **服务端模块(Node.js 或满足同等 Node API 依赖的宿主)。** 浏览器端通过 `@h-ai/api-client` 调用由本模块暴露的 HTTP API。
10
+
11
+ > 生命周期说明:`@h-ai/serv` 是无状态 HTTP App 装配器,不提供 `init()` / `close()`;依赖模块(iam/storage/ai/crypto 等)由应用先初始化,并在 `serv.listen(..., { onClose })` 中反向释放。`serv.createApp()` 的启动期配置错误会 fail-fast 抛出。
12
+
13
+ ## 适用场景
14
+
15
+ - 将 `@h-ai/api-contract` contract 装配成最小 HTTP App 抽象
16
+ - 配置 `/health`、`/ready`、`/openapi.json`、`/docs` 等系统端点
17
+ - 在 procedure route 上添加 `.auth()`、`.permission()` 或 `.role()` guard
18
+ - 使用 Node listener,或在满足包 Node 依赖的宿主/测试中注入标准 fetch handler
19
+ - 自定义 `ServContext`(注入 session、tenant 等请求级数据)
20
+ - 生成 OpenAPI 3.1 文档供外部工具消费
21
+ - 通过 `config/_serv.yml` 管理 API 前缀、OpenAPI、docs、health、rpc 等 HTTP 挂载配置
22
+ - 通过 `serv.createRuntimeSecurityPolicy(...)` 对生产 CORS Origin、原生 WebView Origin、Secure Cookie 与 API 文档暴露执行 fail-closed 策略
23
+ - 启用传输加密:`serv.createApp({ transport: { crypto, serverKeyRing, keyStore } })`
24
+ - 高级场景通过 `createApp({ middlewares })` 自定义 HTTP middleware,通过 `createContext` 注入请求级业务字段
25
+ - 公开只读文件通过 `serv.storageAssets(...)` 统一处理安全 key、MIME 白名单、GET/HEAD、ETag 与缓存头
26
+ - 为 Audio WebSocket 配置一次性 ticket 校验、IAM/模型/配额授权与并发释放钩子
27
+
28
+ ## 使用步骤
29
+
30
+ ### 1. 安装依赖
31
+
32
+ ```typescript
33
+ // @h-ai/serv 依赖 @h-ai/api-contract 提供 contract 定义
34
+ // 业务 procedure 依赖对应领域模块(iam/storage/ai)
35
+ ```
36
+
37
+ ### 2. 创建并启动 app
38
+
39
+ ```typescript
40
+ import { ai } from '@h-ai/ai'
41
+ import { apiContract } from '@h-ai/api-contract'
42
+ import { iam } from '@h-ai/iam'
43
+ import { serv } from '@h-ai/serv'
44
+ import { createAiProcedures } from '@h-ai/serv/features/ai'
45
+ import { createIamProcedures } from '@h-ai/serv/features/iam'
46
+ import { createStorageProcedures } from '@h-ai/serv/features/storage'
47
+ import { storage } from '@h-ai/storage'
48
+
49
+ const contract = apiContract.create({ iam: apiContract.iam, storage: apiContract.storage, ai: apiContract.ai })
50
+
51
+ // 装配 procedures(每个 feature 对应 contract 中的一个领域)
52
+ const procedures = {
53
+ iam: createIamProcedures({ iam }),
54
+ storage: createStorageProcedures({
55
+ storage,
56
+ authorize: ({ context, keys }) => keys.every(key =>
57
+ key.startsWith(`users/${encodeURIComponent(context.session.userId)}/`)),
58
+ }),
59
+ ai: createAiProcedures({ ai }),
60
+ }
61
+
62
+ // 创建 HTTP App 抽象;不要在应用导出类型里暴露 Hono
63
+ const app = serv.createApp({
64
+ contract,
65
+ procedures,
66
+ http: {
67
+ apiPrefix: '/api/v1',
68
+ openapi: { path: '/openapi.json' },
69
+ docs: { path: '/docs' },
70
+ health: { path: '/health', readyPath: '/ready' },
71
+ },
72
+ })
73
+
74
+ // Node.js 启动(host/port 来自 _serv.yml 的 server;HOST/PORT 环境变量高于配置文件;onClose 自动监听 SIGINT/SIGTERM)
75
+ const { server } = core.config.getOrThrow<ServConfig>('serv')
76
+ serv.listen(app, {
77
+ host: server.host,
78
+ port: server.port,
79
+ onListening: info => logger.info('API service listening', { port: info.port }),
80
+ onClose: closeApp, // 业务模块关闭函数(ai/storage/iam/reldb 等反向释放)
81
+ })
82
+ ```
83
+
84
+ 生产部署先从 `_core.yml.env` 与 `_serv.yml.cors` 创建安全策略;不要在业务应用重复实现 Origin 解析:
85
+
86
+ ```typescript
87
+ const security = serv.createRuntimeSecurityPolicy({
88
+ environment: coreConfig.env,
89
+ corsOrigin: servConfig.cors.origin,
90
+ nativeOrigins: servConfig.cors.nativeOrigins,
91
+ })
92
+
93
+ const app = serv.createApp({
94
+ contract,
95
+ procedures,
96
+ http: {
97
+ ...servConfig.http,
98
+ openapi: security.exposeApiDocs ? servConfig.http.openapi : false,
99
+ docs: security.exposeApiDocs ? servConfig.http.docs : false,
100
+ },
101
+ refreshCookie: { secure: security.secureRefreshCookie },
102
+ middlewares: [{
103
+ middleware: serv.cors({
104
+ origin: security.allowOrigin,
105
+ credentials: servConfig.cors.credentials,
106
+ allowedHeaders: [...servConfig.cors.allowedHeaders],
107
+ exposedHeaders: [...servConfig.cors.exposedHeaders],
108
+ }),
109
+ }],
110
+ })
111
+ ```
112
+
113
+ - 生产环境 `cors.origin` 禁止为空或 `*`,启动时 fail-fast。
114
+ - 普通 Web Origin 仅接受 HTTP(S);原生 Origin 通过 `cors.nativeOrigins` 显式允许 HTTP(S)、Capacitor 或 Tauri。
115
+ - Origin 按完整规范化值精确匹配,不使用前缀/后缀匹配。
116
+ - 生产环境自动要求 Secure refresh cookie,并把 OpenAPI/docs 作为安全上限关闭。
117
+
118
+ ### 2.1 Audio WebSocket 安全接入
119
+
120
+ ```typescript
121
+ const app = serv.createApp({
122
+ contract,
123
+ procedures,
124
+ iam,
125
+ audio: {
126
+ ai,
127
+ verifyTicket: consumeAudioTicket, // 校验用途/时效并原子消费,返回 { session, grant? }(推荐 iam.ticket.consume)
128
+ authorize: (session, request, grant) => authorizeAudioRequest(session, request, grant),
129
+ onSessionEnd: (session, request) => releaseAudioConcurrency(session.userId, request.operation),
130
+ },
131
+ })
132
+ ```
133
+
134
+ - ticket 由已登录 HTTP 请求签发(推荐 `iam.ticket.issue`),短期有效且只能消费一次;连接建立即鉴权(独立预鉴权超时,默认 5s),未鉴权不处理任何业务帧;普通 IAM access token 禁止进入 WebSocket URL。
135
+ - `verifyTicket` 返回 `HaiResult<AudioTicketVerification>`(`{ session, grant? }`);`grant` 承载票据绑定的操作/模型/会话,供 `authorize` 交叉校验 `start` 未越权。
136
+ - 每帧经运行时 Zod 校验并按严格状态机接受(识别只收音频、合成只收文本、`start`/`done` 各一次、`segmentId` 会话内唯一);输入队列、消息积压、发送缓冲均有上限(`preAuthTimeoutMs`/`maxPendingMessages`/`maxSendBufferBytes` 等可调),超限或取消时以领域错误关闭并级联中止上游。
137
+ - `authorize` 返回的 `AuthorizedAudioRequest` 是唯一会传给 `ai.audio` 的模型、音色与格式配置;未提供时客户端的付费参数会被忽略(若票据 `grant.model` 存在则采用之)。
138
+ - IAM 权限、Persona、套餐配额和并发计数属于应用策略;框架提供校验与结束钩子,不硬编码业务权限名或存储。
139
+
140
+ ### 3. 扩展应用自有 contract + procedures
141
+
142
+ `apiContract.create({...})` 接受任意 key——除了框架提供的 `iam/storage/ai`,应用可以挂入自己的领域 contract,
143
+ 客户端通过 `client.<key>.<procedure>(...)` 调用,类型完全推导自同一份 contract。
144
+
145
+ ```typescript
146
+ // apps/api-service-contract/src/app-contract.ts
147
+ import { apiContract } from '@h-ai/api-contract'
148
+ import { z } from 'zod'
149
+
150
+ const InfoOutput = apiContract.haiResultSchema(z.object({ name: z.string(), version: z.string() }))
151
+ const EchoInput = z.object({ message: z.string().min(1).max(2000) })
152
+ const EchoOutput = apiContract.haiResultSchema(z.object({ message: z.string(), userId: z.string() }))
153
+
154
+ export const appContract = {
155
+ info: apiContract.route({ method: 'POST', path: '/app/info', operationId: 'app.info', tags: ['app'] })
156
+ .output(InfoOutput),
157
+ echo: apiContract.route({ method: 'POST', path: '/app/echo', operationId: 'app.echo', tags: ['app'] })
158
+ .input(EchoInput)
159
+ .output(EchoOutput),
160
+ }
161
+ ```
162
+
163
+ ```typescript
164
+ // src/server/procedures/app-procedures.ts
165
+ import type { ServContext } from '@h-ai/serv'
166
+ import { appContract } from '@h-ai/api-service-contract'
167
+ import { ok } from '@h-ai/core'
168
+ import { serv } from '@h-ai/serv'
169
+
170
+ export function createAppProcedures(deps: { name: string, version: string }) {
171
+ return serv
172
+ .implement(appContract)
173
+ .context<ServContext>()
174
+ // 公开 procedure:route 名只出现一次,handler 直接返回 HaiResult。
175
+ .route('info', () => ok({ name: deps.name, version: deps.version }))
176
+ // 鉴权 procedure:guard 通过后 context.session 自动收窄为非空。
177
+ .route('echo')
178
+ .auth()
179
+ .handle(({ input, context }) => ok({
180
+ message: input.message,
181
+ userId: context.session.userId,
182
+ }))
183
+ .build()
184
+ }
185
+ ```
186
+
187
+ 把自有 contract / procedures 与框架的 iam/storage/ai 平铺合并:
188
+
189
+ ```typescript
190
+ const contract = apiContract.create({
191
+ iam: apiContract.iam,
192
+ storage: apiContract.storage,
193
+ ai: apiContract.ai,
194
+ app: appContract, // ← 自有契约
195
+ })
196
+
197
+ const procedures = {
198
+ iam: createIamProcedures({ iam }),
199
+ storage: createStorageProcedures({
200
+ storage,
201
+ authorize: ({ context, keys }) => keys.every(key =>
202
+ key.startsWith(`users/${encodeURIComponent(context.session.userId)}/`)),
203
+ }),
204
+ ai: createAiProcedures({ ai }),
205
+ app: createAppProcedures({ name: 'demo', version: '1.0.0' }),
206
+ }
207
+
208
+ const app = serv.createApp({ contract, procedures, http, iam })
209
+ ```
210
+
211
+ 注意要点:
212
+
213
+ - **无 input 的 POST 过程**:业务层无需 input,但 transport 仍发送空明文的认证封装。GET/HEAD 使用 X-Hai-Transport 头;空请求与响应不存在未认证旁路。
214
+ - **认证授权写在 route 链上**:公开过程使用 `.route(path, handler)`;需要认证时使用 `.route(path).auth().handle(handler)`。
215
+ - **客户端导入相同 contract**:把服务端与客户端共享的 contract 独立成 package(如 `@h-ai/api-service-contract`),禁止从 app 源码目录跨应用 import。
216
+
217
+ ### 5. 标准 fetch 签名适配
218
+
219
+ toFetch 只适配调用签名;包根仍静态依赖 Node API,未经实际验证不能据此宣称 Workers/Deno/纯边缘部署兼容。
220
+
221
+ ```typescript
222
+ import { serv } from '@h-ai/serv'
223
+
224
+ // toFetch 返回标准 Fetch handler
225
+ const handler = serv.toFetch(app)
226
+
227
+ export default { fetch: handler }
228
+ ```
229
+
230
+ ### 6. 通过 `config/_serv.yml` 管理 HTTP 配置
231
+
232
+ `@h-ai/serv` 不自己扫描 YAML;推荐由 `@h-ai/core` 统一加载,再用 `ServConfigSchema` 校验:
233
+
234
+ ```typescript
235
+ import { core } from '@h-ai/core'
236
+ import { serv, ServConfigSchema } from '@h-ai/serv'
237
+
238
+ core.init({ configDir: './config' })
239
+
240
+ const validation = core.config.validate('serv', ServConfigSchema)
241
+ if (!validation.success)
242
+ throw new Error(validation.error.message)
243
+
244
+ const servConfig = core.config.getOrThrow<import('@h-ai/serv').ServConfig>('serv')
245
+
246
+ const app = serv.createApp({
247
+ contract,
248
+ procedures,
249
+ http: servConfig.http,
250
+ transport: servConfig.transport === false
251
+ ? undefined
252
+ : {
253
+ crypto,
254
+ serverKeyRing,
255
+ keyStore,
256
+ keyExchangePath: servConfig.transport.keyExchangePath,
257
+ excludePaths: [...servConfig.transport.excludePaths],
258
+ },
259
+ })
260
+ ```
261
+
262
+ ```yaml
263
+ # config/_serv.yml
264
+ server:
265
+ host: 127.0.0.1
266
+ port: 3000
267
+
268
+ http:
269
+ trustedProxies: auto # 普通宿主忽略转发头,受管私有 Compose 自动采用容器网段
270
+ access: false # 按路径/IP/时间限制见 hai-core/request-access.md
271
+ apiPrefix: /api/v1
272
+ openapi:
273
+ path: /openapi.json
274
+ docs:
275
+ path: /docs
276
+ health:
277
+ path: /health
278
+ readyPath: /ready
279
+ rpc: false
280
+
281
+ transport:
282
+ keyExchangePath: /_hai/key-exchange
283
+ excludePaths:
284
+ - /health
285
+ - /ready
286
+ - /openapi.json
287
+ - /docs
288
+ - /_hai/scalar.js
289
+ ```
290
+
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/可靠持久化策略;各节点时钟须同步。此能力防止同一传输消息重放,业务重试仍需自己的幂等键。
292
+
293
+ 空 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
+
295
+ ## 核心 API
296
+
297
+ ### `serv.createApp(options)` — 创建 HTTP App 抽象
298
+
299
+ ```ts
300
+ import type { CreateServAppOptions, ServContext } from '@h-ai/serv'
301
+ import { apiContract } from '@h-ai/api-contract'
302
+ import { serv } from '@h-ai/serv'
303
+
304
+ // 最小服务使用默认健康检查;业务路由由真实 contract/procedures 扩展。
305
+ const contract = apiContract.create({})
306
+ const procedures = serv.implement(contract).context<ServContext>().build()
307
+ const options: CreateServAppOptions<typeof contract, typeof procedures> = {
308
+ contract,
309
+ procedures,
310
+ http: { apiPrefix: '/api/v1' },
311
+ }
312
+ const app = serv.createApp(options)
313
+ ```
314
+
315
+ ### `apiContract.route` / `serv.implement` — contract 与运行时分层
316
+
317
+ 应用代码不要直接 `import { oc } from '@orpc/contract'` 或 `import { implement } from '@orpc/server'`。
318
+ contract 路由统一用 `@h-ai/api-contract` 定义,procedure 运行时统一用 `@h-ai/serv` 实现:
319
+
320
+ ```typescript
321
+ import { apiContract } from '@h-ai/api-contract'
322
+ import { serv } from '@h-ai/serv'
323
+
324
+ // 等价于 oc.route(...),但不让应用感知 @orpc/contract
325
+ const route = apiContract.route({ method: 'POST', path: '/x', operationId: 'x', tags: ['x'] })
326
+
327
+ const procedures = serv
328
+ .implement(appContract)
329
+ .context<ServContext>()
330
+ .route('info', infoHandler)
331
+ .route('echo')
332
+ .auth()
333
+ .handle(echoHandler)
334
+ .build()
335
+ ```
336
+
337
+ ### 传输加密
338
+
339
+ serv 不暴露本地传输加密工厂;内部统一调用 `crypto.transport.createServer({ serverKeyRing, keyStore })`。客户端用 `@h-ai/api-client` 的 `transport` 配置自动协商。
340
+
341
+ 业务容量统一配置 transport.maxPlaintextBytes:UTF-8 JSON 明文字节,默认 1 MiB,有效整数范围 1 KiB~64 MiB,请求和响应共用。客户端从握手获取服务端预算,可选本地上限取较小值;resolveTransportBodyLimits(limit, { requiredPlaintextBytes }) 合并模块需求:保留显式上限,不足时报配置冲突;省略时按需要推导,无模块需要仍为 1 MiB。Serv/Kit 的 auto 规范化为 undefined。密文预算为两倍明文加 32 KiB。握手 JSON 16 KiB、公钥 4 KiB、认证头 16 KiB、路径 8 KiB、认证及防重放保持独立。原流超限/取消立即停止、取消并释放 reader;请求 413、响应返回加密的 500 容量错误,客户端识别 TransportBodyTooLargeError / hai:crypto:070。 通过 _serv.yml 的 transport.maxPlaintextBytes 或对应 HAI_*_TRANSPORT_MAXPLAINTEXTBYTES 配置并转发到服务端 transport;重建正文保留 signal 并删除旧长度/编码头。
342
+
343
+ ```typescript
344
+ import { crypto } from '@h-ai/crypto'
345
+ import { serv } from '@h-ai/serv'
346
+
347
+ await crypto.init()
348
+
349
+ const app = serv.createApp({
350
+ contract,
351
+ procedures,
352
+ http: { apiPrefix: '/api/v1' },
353
+ transport: {
354
+ crypto,
355
+ serverKeyRing,
356
+ // keyExchangePath 默认 '/_hai/key-exchange'
357
+ keyStore, // 已连接的共享 Redis/数据库 registry。
358
+ },
359
+ })
360
+ ```
361
+
362
+ 客户端:
363
+
364
+ ```typescript
365
+ await apiClient.init({
366
+ baseUrl: 'https://api.example.com/api/v1',
367
+ transport: { crypto },
368
+ })
369
+ ```
370
+
371
+ 若服务端通过 `config/_serv.yml` 自定义了 `transport.keyExchangePath`,客户端也必须传入同一路径:
372
+
373
+ ```typescript
374
+ await apiClient.init({
375
+ baseUrl: 'https://api.example.com/api/v1',
376
+ transport: {
377
+ crypto,
378
+ keyExchangePath: '/custom/key-exchange',
379
+ },
380
+ })
381
+ ```
382
+
383
+ > 不要导入或暴露子目录内部 transport 工厂;公共装配点只有 `serv.createApp({ transport: { crypto, serverKeyRing, keyStore } })`。
384
+
385
+ 启用 transport 必须显式注入 `serverKeyRing` 与 `keyStore`;多节点使用相同受管 ring 和共享 Redis/数据库 registry,仅共享客户端公钥不足以跨节点解密。通过根入口 `parseTransportServerKeyRing(process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING)` 解析并检查 HaiResult,私钥不放 `_serv.yml`、PUBLIC_/VITE_、日志或响应。请求带协商 serverKeyId,未知/撤销 ID 拒绝。轮换先分发新旧 ring,再切 activeKeyId 并滚动重建;删除旧版本后客户端须 destroy 并重协商,不自动重放非幂等业务。默认 YAML maxClients 已删除;开发/测试显式 `createInMemoryKeyStore(capacity)`。无需 sticky session,详见 hai-crypto 的密钥轮换说明。
386
+
387
+ **上下文工厂优先级**(`context.session` 填充来源):
388
+
389
+ 1. 显式 `createContext`(高级场景:多租户、额外字段)
390
+ 2. `verifyToken`(逃脱口:使用非 @h-ai/iam 的认证服务)
391
+ 3. `iam.auth.verifyToken`(推荐:传入 `iam` 后自动启用)
392
+ 4. 默认 `parseRequestContext`:仅解析请求元数据,`session` 为 `undefined`
393
+
394
+ > ⚠️ `verifyToken` 会在每次请求调用(不缓存);verifyToken 失败或抛错统一收敛为 `session=undefined`,由 route 的 `.auth()` / `.permission()` / `.role()` 返回失败 HaiResult,`error.httpStatus=401`;普通 procedure 的 HTTP 响应默认仍为 200。
395
+
396
+ ### 链式 procedure guard
397
+
398
+ | 链式调用 | 说明 |
399
+ | ---------------------------------------------------- | ---------------------------------------------------------------------------------------- |
400
+ | `.route(path, handler)` | 注册公开 procedure;path/input/output 从 contract 推导 |
401
+ | `.route(path).auth().handle(handler)` | 要求 session;通过后 `context.session` 自动收窄 |
402
+ | `.permission(permission)` | 隐含认证并检查权限,支持 `serv.WILDCARD_PERMISSION` |
403
+ | `.role(role)` | 隐含认证并检查角色,支持 `serv.WILDCARD_ROLE` |
404
+ | `.build()` | contract 全部 procedures 实现后构建 router |
405
+ | `serv.validateInputOrFail(zodSchema, input, locale)` | 在 procedure 内执行 Zod 二次校验,失败时返回本地化 `HaiResult` + `ValidationFormError[]` |
406
+ | `serv.resolveRequestLocale(headers)` | 从 `x-hai-locale` / `Accept-Language` 解析并规范化 locale |
407
+ | `serv.m(key, { locale, params })` | 读取 serv 自身 i18n 消息(请求级本地化) |
408
+
409
+ ```typescript
410
+ import { serv } from '@h-ai/serv'
411
+
412
+ const procedures = serv
413
+ .implement(appContract)
414
+ .context<ServContext>()
415
+ .route('info', infoHandler)
416
+ .route('users.update')
417
+ .permission('user.write')
418
+ .role('admin')
419
+ .handle(updateUserHandler)
420
+ .build()
421
+ ```
422
+
423
+ - `.permission()` / `.role()` 隐含 `.auth()`;未登录返回 `error.httpStatus=401` 的失败 HaiResult,授权不足为 403。普通 procedure 的 HTTP 响应默认仍为 200,必须检查 `success` 与 `error.code`。
424
+ - 同一路由声明多个 permission/role 时必须全部满足。
425
+ - handler 的未处理异常由 router 统一转换成 INTERNAL_ERROR HaiResult。
426
+ - 重复/未知路径会被拒绝;遗漏 contract procedure 时 `.build()` 在类型上不可用,运行时也会校验。
427
+
428
+ ### 高级:自定义 pipeline(HTTP middleware + context + route guard)
429
+
430
+ `@h-ai/serv` 的 pipeline 分三层:
431
+
432
+ 1. **HTTP middleware 层**:通过 `serv.createApp({ middlewares })` 注入 HTTP middleware
433
+ 2. **context 层**:通过 `verifyToken` / `createContext` / `serv.buildAuthContextFactory()` 自定义请求上下文
434
+ 3. **route guard 层**:通过 `.auth()` / `.permission()` / `.role()` 声明认证授权
435
+
436
+ #### HTTP middleware
437
+
438
+ 适用于请求日志、trace、限流、CORS、租户头校验等 **HTTP 层** 横切逻辑,也可挂载不适合进入 JSON/oRPC contract 的 WebSocket、文件和二进制端点。常见 CORS 场景可直接复用 `serv.cors(...)`:
439
+
440
+ ```typescript
441
+ import type { ServMiddleware } from '@h-ai/serv'
442
+ import { serv } from '@h-ai/serv'
443
+
444
+ const requestMetrics: ServMiddleware = async (c, next) => {
445
+ const startedAt = Date.now()
446
+ await next()
447
+ c.header('x-response-time-ms', String(Date.now() - startedAt))
448
+ }
449
+
450
+ const app = serv.createApp({
451
+ contract,
452
+ procedures,
453
+ http: { apiPrefix: '/api/v1' },
454
+ middlewares: [
455
+ {
456
+ middleware: serv.cors({
457
+ origin: origin => origin === 'https://app.example.com',
458
+ credentials: true,
459
+ exposedHeaders: ['X-Encrypted', 'X-Request-Id'],
460
+ }),
461
+ },
462
+ { middleware: requestMetrics },
463
+ {
464
+ path: '/api/v1/*',
465
+ middleware: async (c, next) => {
466
+ if (!c.req.header('x-tenant-id'))
467
+ return c.text('Missing x-tenant-id', 400)
468
+ await next()
469
+ },
470
+ },
471
+ {
472
+ path: '/assets/*',
473
+ middleware: serv.storageAssets({
474
+ storage,
475
+ pathPrefix: '/assets/',
476
+ keyPattern: /^public\/[\w-]+\.png$/,
477
+ allowedContentTypes: ['image/png'],
478
+ cacheControl: 'public, max-age=31536000, immutable',
479
+ crossOriginResourcePolicy: 'cross-origin',
480
+ }),
481
+ },
482
+ ],
483
+ })
484
+ ```
485
+
486
+ 执行顺序为:`securityHeaders` → 内置 CORS 响应包装/握手和预检策略 → `transport`(若启用)/请求策略 → 普通 `middlewares` → health/refresh-cookie/OpenAPI/RPC/docs/oRPC routes。
487
+
488
+ - 普通 `middlewares` 在解密和策略之后执行,提前响应仍受访问策略和响应加密约束。二进制端点显式加入 transport.excludePaths;内置 CORS 预检和密钥交换也执行策略。
489
+ - 公开只读文件优先使用 `serv.storageAssets(...)`;应用仍须显式提供 key/MIME 白名单、缓存与跨域策略,框架不内置头像等业务概念。
490
+ - 私有资源、Range 下载或动态授权继续使用自定义 middleware/procedure。
491
+ - 若需要读取解密后的业务 body,请改用 context / procedure 层扩展。
492
+ - 浏览器若需读取自定义响应头(例如 transport 的 `X-Encrypted`),记得通过 `serv.cors({ exposedHeaders: [...] })` 显式暴露。
493
+
494
+ #### Context pipeline
495
+
496
+ 如果想复用“Bearer token → session”这段逻辑,再追加租户/工作区等字段,优先:
497
+
498
+ ```typescript
499
+ const baseContext = serv.buildAuthContextFactory(token => iam.auth.verifyToken(token))
500
+
501
+ const app = serv.createApp({
502
+ contract,
503
+ procedures,
504
+ createContext: async (input) => {
505
+ const context = await baseContext(input)
506
+ return {
507
+ ...context,
508
+ tenantId: input.request.headers.get('x-tenant-id') ?? null,
509
+ }
510
+ },
511
+ })
512
+ ```
513
+
514
+ 选择建议:
515
+
516
+ - 只换 token 校验实现:`verifyToken`
517
+ - 在默认认证上下文之上追加字段:`buildAuthContextFactory(...) + createContext`
518
+ - 完全接管上下文构造:`createContext`
519
+
520
+ #### Route guard
521
+
522
+ 认证、权限与角色约束直接写在目标 route 上,不嵌套高阶 handler:
523
+
524
+ ```typescript
525
+ const procedures = serv
526
+ .implement(widgetContract)
527
+ .context<ServContext>()
528
+ .route('widgets.list', ({ input }) => widgetService.list(input))
529
+ .route('widgets.create')
530
+ .permission('widgets.write')
531
+ .handle(({ input, context }) =>
532
+ widgetService.create(context.session.userId, input))
533
+ .build()
534
+ ```
535
+
536
+ HTTP 审计、限流、CORS、租户头校验等跨 route 能力继续放在
537
+ `createApp({ middlewares })`;请求级业务字段通过 `createContext` 注入。
538
+ 不要为认证授权重新嵌套多层高阶 handler。
539
+
540
+ ### 正常业务请求中的错误与 i18n
541
+
542
+ 先区分 HTTP 状态与 HaiResult 中的 `error.httpStatus`;普通 procedure 返回的领域错误元数据不会自动改写 HTTP 状态,当前默认装配如下(显式自定义响应状态或 middleware 按实际入口契约验证):
543
+
544
+ | 失败来源 | HTTP 响应 | 结果判定 |
545
+ | -------------------------------------------------- | --------- | ------------------------------------------------------------------------- |
546
+ | procedure `.auth()` / `.permission()` / `.role()` | 200 | `success=false`;`error.httpStatus` 为 401 / 403 |
547
+ | handler 返回 `err(...)` 或下游模块失败 HaiResult | 200 | 检查 `success`、`error.code`,例如数据库查询错误的 `error.httpStatus=500` |
548
+ | handler 未处理异常被 route 实现器捕获 | 200 | INTERNAL_ERROR 失败 HaiResult,`error.httpStatus=500` |
549
+ | oRPC contract 输入 schema 校验失败(handler 之前) | 400 | 本地化失败体,`error.httpStatus=400` 与字段错误 |
550
+ | handler 内 `serv.validateInputOrFail(...)` 失败 | 200 | 失败 HaiResult,`error.httpStatus=400` 与字段错误 |
551
+
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 时刷新和重试复用加密链路,不能手写明文刷新请求。
553
+
554
+ SvelteKit 的 `kit.response.fromError` 根据 `error.httpStatus` 生成 HTTP Response;`kit.response.fromResult` 使用显式错误码映射,未命中默认 400。这些响应转换规则不能套用到 serv 普通 procedure。
555
+
556
+ 按三层处理:
557
+
558
+ 1. **oRPC contract 输入校验**:进入 handler 前自动发生;`serv.createApp()` 会把默认英文 Zod 错误改写为本地化 `errors[]`。
559
+ 2. **handler 内二次校验**:对跨字段规则、数据库回读对象等适合 Zod 表达的场景,使用 `serv.validateInputOrFail(zodSchema, input, context.locale)`;简单业务规则直接 `err(...)` 即可。
560
+ 3. **业务/领域错误**:不要 throw 预期失败;直接返回 `err(...)`,并在**创建错误消息的那一层**用对应模块的 i18n getter 按 `context.locale` 出消息。
561
+
562
+ ```typescript
563
+ import { serv } from '@h-ai/serv'
564
+ import { z } from 'zod'
565
+
566
+ const Schema = z.object({ title: z.string().min(1) })
567
+
568
+ const procedures = serv
569
+ .implement(widgetContract)
570
+ .context<ServContext>()
571
+ .route('widgets.create')
572
+ .auth()
573
+ .handle(({ input, context }) => {
574
+ const validated = serv.validateInputOrFail(Schema, input, context.locale)
575
+ if (!validated.success)
576
+ return validated
577
+
578
+ return widgetService.create(context.session.userId, validated.data)
579
+ })
580
+ .build()
581
+ ```
582
+
583
+ > 如果错误来自下游模块(如 `iam` / `storage`)并且该模块已经返回 `HaiResult`,serv 默认**透传**它的 `error.message`。由于 `HaiError` 目前不携带 `messageKey/params`,serv 边界不能对任意下游错误再做通用重翻译;要做请求级 i18n,必须在创建该错误的模块/feature 里就拿到 locale。
584
+
585
+ ### `serv.parseRequestContext` — 默认 Context 解析器
586
+
587
+ 默认忽略转发头,缺少运行时对端时 IP 为 undefined。XFF 从右向左停在首个不可信地址,忽略左侧;可信链无效/过长时保留 IP 未知,不能回退为代理身份。Fetch 使用 `app.fetch(request, { remoteAddress: platformPeerAddress })`,对端必须来自可信平台 API。自定义 context 传递完整 input,保留已解析的 ip;内部 RPC 使用连接对端,转发头不能放宽来源限制。
588
+
589
+ 从 HTTP 请求头自动解析:
590
+
591
+ | 字段 | 来源 |
592
+ | ------------- | --------------------------------------------------------------------------------------------------------- |
593
+ | `accessToken` | `Authorization: Bearer <token>` |
594
+ | `requestId` | `x-request-id` 或自动生成 UUID |
595
+ | `ip` | Node socket / Fetch 显式 remoteAddress;仅 http.trustedProxies 中的代理可提供 XFF(从右向左)或 X-Real-IP |
596
+ | `locale` | `accept-language` |
597
+ | `userAgent` | `user-agent` |
598
+
599
+ ### `serv.generateSpec(contract, options)` — 生成 OpenAPI 文档
600
+
601
+ ```typescript
602
+ const spec = await serv.generateSpec(contract, {
603
+ title: 'My API',
604
+ version: '1.0.0',
605
+ apiPrefix: '/api/v1',
606
+ description: '接口说明',
607
+ })
608
+ // spec 为 OpenAPI 3.1 Document 对象
609
+ ```
610
+
611
+ ## HTTP 配置(`ServHttpConfigInput`)
612
+
613
+ | 字段 | 默认值 | 说明 |
614
+ | ----------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
615
+ | `apiPrefix` | `'/api/v1'` | oRPC OpenAPI handler 挂载前缀 |
616
+ | `health` | `{ path: '/health', readyPath: '/ready' }` | 健康与就绪检查端点 |
617
+ | `openapi` | `false` | OpenAPI JSON endpoint,显式开启 |
618
+ | `docs` | `false` | Scalar 文档页,显式开启;启用后自动挂载 `/_hai/scalar.js` 本地脚本路由 |
619
+ | `rpc` | `false` | 内部 RPC endpoint,显式开启;`loopback` / `private-network` 基于真实连接 IP,网关场景用 `gateway-only` |
620
+ | `transport` | `undefined` | 顶层配置;启用后默认挂载 `{apiPrefix}/_hai/key-exchange` |
621
+
622
+ ```ts
623
+ import type { ServHttpConfigInput } from '@h-ai/serv'
624
+
625
+ const http: ServHttpConfigInput = {
626
+ apiPrefix: '/api/v1',
627
+ openapi: { path: '/openapi.json' },
628
+ docs: { path: '/docs' },
629
+ health: { path: '/health', readyPath: '/ready' },
630
+ rpc: false,
631
+ }
632
+ ```
633
+
634
+ ## Feature Procedures(默认实现)
635
+
636
+ `serv.implement(...).context().route(...).build()` 同步返回普通 router,可直接按嵌套属性选取 `@h-ai/serv/features/iam` 等默认 procedures。挂载 contract 只包含实际选用的路由;不要为复用当前用户或刷新功能而同时开放注册、OTP 或通用管理入口。
637
+
638
+ `@h-ai/serv` 内置了三个 feature 模块,开箱即用:
639
+
640
+ | 导入路径 | 工厂函数 | 所需依赖 |
641
+ | ----------------------------- | ------------------------------------------------- | ------------------------------ |
642
+ | `@h-ai/serv/features/iam` | `createIamProcedures({ iam })` | `@h-ai/iam` |
643
+ | `@h-ai/serv/features/storage` | `createStorageProcedures({ storage, authorize })` | `@h-ai/storage` + 资源授权策略 |
644
+ | `@h-ai/serv/features/ai` | `createAiProcedures({ ai })` | `@h-ai/ai` |
645
+
646
+ `authorize` 从可信 `context.session` 校验资源所有者/租户,不能只信任客户端前缀;缺失策略时默认拒绝。列表会把 prefix 交给策略,空 prefix 不应获准全桶列举;批量全部授权后才执行。示例用户根为 `users/<编码后的 userId>/`,管理员跨用户访问必须显式授权。
647
+
648
+ AI 默认 feature 把 objectId 绑定到可信 `context.session.userId`,客户端可省略;所有读写与记忆查询都使用授权后的主体。sessionId 与 objectId 组成复合会话,写入默认 sessionId 为 `default`。其它 objectId 默认被拒绝;跨用户管理需显式提供 `authorizeObject({ context, objectId, sessionId, operation })`,按可信权限及租户关系严格返回 true。
649
+
650
+ `chats.createCompletion` 只提供非流式 JSON 输出,stream 字段无论 true/false 都在 procedure 前以 HTTP 400 拒绝。完整 function tools(嵌套 JSON Schema/strict)、tool_choice(auto/none/required/已定义函数名)和角色消息直接传给模型,不自动执行工具。工具结果必填 tool_call_id,不能把未知字段静默裁剪为看似成功。流式调用使用应用自己的 endpoint 与 ai.llm.chatStream 输出契约。
651
+
652
+ ```typescript
653
+ const aiProcedures = createAiProcedures({
654
+ ai,
655
+ authorizeObject: ({ context }) => context.session.permissions.includes('ai.manage'),
656
+ })
657
+ ```
658
+
659
+ ## 常见模式
660
+
661
+ ### 自定义领域 procedure
662
+
663
+ ```typescript
664
+ import type { ServContext } from '@h-ai/serv'
665
+ import { serv } from '@h-ai/serv'
666
+ import { myContract } from './my-contract.js'
667
+
668
+ export function createMyProcedures() {
669
+ return serv
670
+ .implement(myContract)
671
+ .context<ServContext>()
672
+ .route('widget.list')
673
+ .auth()
674
+ .handle(({ input }) => widgetService.list(input))
675
+ .route('widget.create')
676
+ .permission('widget:write')
677
+ .handle(({ input }) => widgetService.create(input))
678
+ .build()
679
+ }
680
+ ```
681
+
682
+ ### 自定义 contract 组合
683
+
684
+ ```typescript
685
+ import { apiContract } from '@h-ai/api-contract'
686
+ import { widgetContract } from './widget-contract.js'
687
+
688
+ export const myAppContract = apiContract.create({
689
+ iam: apiContract.iam,
690
+ widget: widgetContract,
691
+ })
692
+
693
+ const app = serv.createApp({
694
+ contract: myAppContract,
695
+ procedures: {
696
+ iam: createIamProcedures({ iam }),
697
+ widget: createMyProcedures(),
698
+ },
699
+ http: { apiPrefix: '/api/v1' },
700
+ })
701
+ ```
702
+
703
+ ## 错误码
704
+
705
+ route 实现器内置以下错误:
706
+
707
+ | 错误码 | 触发条件 |
708
+ | ------------------------------- | --------------------------------------------------------------- |
709
+ | `HaiCommonError.UNAUTHORIZED` | `.auth()`:`context.session` 为空(无 token 或 token 校验失败) |
710
+ | `HaiCommonError.FORBIDDEN` | `.permission()` / `.role()`:无对应权限或角色 |
711
+ | `HaiCommonError.INTERNAL_ERROR` | route handler 抛出未处理异常 |
712
+
713
+ ## httpOnly Cookie 认证
714
+
715
+ 将 refresh token 存储在 `HttpOnly` cookie 中,避免 XSS 风险(浏览器 JS 无法读取)。
716
+
717
+ 先区分两个 token:
718
+
719
+ - `accessToken`:短期凭证,客户端放在内存,通过 `Authorization: Bearer <token>` 发送;
720
+ `serv.parseRequestContext()` / `extractBearerToken()` 解析、`buildAuthContextFactory()` 校验的就是它。
721
+ - `refreshToken`:长期凭证,启用 `refreshCookie` 后只保存在 httpOnly cookie;
722
+ 它不会走 `Authorization`,也不会被 `extractBearerToken()` 读取,只会在 `/auth/refresh` 被服务端取出。
723
+ 受信原生客户端可通过 `nativeTokenTransport` 改用 JSON body,但必须同时校验客户端标识与 Origin。
724
+
725
+ ### 适用场景
726
+
727
+ - 浏览器端(Web / H5)使用 `@h-ai/api-client` 的 `apiClient.tokenStorage.httpOnlyCookie()`
728
+ - 对 refresh token 有更高安全要求,不希望存储在 localStorage
729
+
730
+ ### 工作流程
731
+
732
+ 1. 浏览器 POST `/auth/login` → serv 拦截 oRPC 成功响应 → `Set-Cookie: hai_refresh_token=...;HttpOnly;SameSite=Strict`,并从 JSON 响应体擦除 `refreshToken`
733
+ 2. Access token 由客户端存内存(不持久化)
734
+ 3. Access token 过期 → 客户端 POST `/auth/refresh`(浏览器自动携带 cookie)
735
+ 4. serv 读取 cookie → 调用 `iam.session.refresh` → 返回新 access token + 更新 cookie(响应体不暴露 refresh token)
736
+ 5. POST `/auth/logout` → serv 清除 cookie(`Max-Age=0`)
737
+
738
+ 原生客户端匹配 `nativeTokenTransport.isRequest` 后,登录/注册/刷新响应保留轮换后的
739
+ `refreshToken`,刷新请求从 `{ refreshToken }` body 读取,且不读写浏览器 Cookie。
740
+
741
+ 若同时启用 `transport: { crypto, serverKeyRing, keyStore }`,`/auth/refresh` 也必须走加密链路;cookie-only 刷新请求允许空 body,服务端不要把 `Request.body === null` 误判为需要解密的请求体。
742
+
743
+ ### 配置
744
+
745
+ ```typescript
746
+ const app = serv.createApp({
747
+ contract,
748
+ procedures,
749
+ http: { apiPrefix: '/api/v1' },
750
+ iam, // 顶层句柄:同时驱动 verifyToken 与 refresh
751
+ refreshCookie: {
752
+ // cookieName?: string 默认 'hai_refresh_token'
753
+ // maxAge?: number 默认 30 * 24 * 3600(30 天)
754
+ // secure?: boolean 默认 NODE_ENV 或 HAI_CORE_ENV 为 production 时开启;测试开关不放宽
755
+ // onRefresh?: (token) => ... 可选:覆盖默认的 iam.session.refresh
756
+ nativeTokenTransport: {
757
+ // 必须 fail-closed:同时校验平台标识与允许的原生 Origin。
758
+ isRequest: request =>
759
+ request.headers.get('x-client-platform') === 'capacitor'
760
+ && security.isNativeOrigin(request.headers.get('origin') ?? ''),
761
+ },
762
+ },
763
+ // ✅ 顶层 iam 同时为 access token 校验提供默认实现,无需再传 verifyToken
764
+ })
765
+ ```
766
+
767
+ > **逃脱口**:不使用 `@h-ai/iam` 时,可显式传入顶层 `verifyToken: token => myAuthService.verify(token)`。
768
+
769
+ ### Cookie 规格
770
+
771
+ | 属性 | 值 |
772
+ | -------- | ---------------------------------------------------- |
773
+ | Name | `hai_refresh_token`(可配置) |
774
+ | Path | `{apiPrefix}/auth/refresh`(最小范围) |
775
+ | HttpOnly | ✓ |
776
+ | SameSite | `Strict` |
777
+ | Secure | 生产自动开启,`secure: false` 可在 HTTP 开发环境关闭 |
778
+ | Max-Age | 30 天(可配置) |
779
+
780
+ ## 测试
781
+
782
+ ```bash
783
+ pnpm --filter @h-ai/serv test
784
+ pnpm --filter @h-ai/serv typecheck
785
+ pnpm --filter @h-ai/serv lint
786
+ ```
787
+
788
+ 标准登出入口 `auth.logout` 从 input.accessToken 或 Authorization Bearer 取得要撤销的完整令牌,不要求 access 仍有效。它不接受 userId 授权或撤销目标;缺少令牌返回失败,未知令牌幂等成功。共享存储故障原样保留失败 HaiResult,Cookie middleware 只对成功结果清理 refresh Cookie。普通 procedure 继续使用既有 HTTP 200 / HaiResult 协议,客户端必须检查 success,不能只检查状态码。
789
+
790
+ 声明 JSDoc 的公共例子只从实际根入口或 features 子路径导入,内部 Hono/guard/router/transport 例子标注“模块内部上下文”。应用使用所安装版本的公开类型,并通过本应用已有的类型检查验证使用示例。启动工厂可显式抛配置异常,领域 procedure 必须检查 HaiResult;HTTP middleware 与普通 procedure 的状态语义分别处理。监听句柄 close 返回 Promise<void>,并不提供 serv.init/close 或替宿主管理业务模块。默认 AI JSON procedure 不支持 stream。
791
+
792
+ toFetch 统一使用标准 Request(input, init):Request 输入也应用 method/headers/body/signal 等覆盖;非法、已消费或锁定正文以 Promise 拒绝。取消在派发前阻止 handler,进行中终止响应等待并传递有效 request.signal;完成/失败/取消均移除 adapter 监听器,丢弃的迟到响应会取消其正文。取消不回滚外部效果,Response 交付后的流由应用自行监听 request.signal 清理。原 Request 可否继续使用其正文遵循标准 Request 的流所有权规则,不承诺重复消费正文。