@h-ai/cli 0.1.0-alpha.66 → 0.1.0-alpha.68

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 (198) hide show
  1. package/README.en.md +86 -241
  2. package/README.ja.md +86 -241
  3. package/README.md +86 -241
  4. package/dist/{src-RDQ2EIBT.js → chunk-FI56GONY.js} +32 -2669
  5. package/dist/chunk-FI56GONY.js.map +1 -0
  6. package/dist/chunk-MBPB37FR.js +1348 -0
  7. package/dist/chunk-MBPB37FR.js.map +1 -0
  8. package/dist/{chunk-QYX6V7H2.js → chunk-ZFNBUF77.js} +347 -276
  9. package/dist/chunk-ZFNBUF77.js.map +1 -0
  10. package/dist/cli-config-templates-W2LLPMB6.js +5 -0
  11. package/dist/{cli-config-templates-3XSFIXBP.js.map → cli-config-templates-W2LLPMB6.js.map} +1 -1
  12. package/dist/{cli-skill-templates-TSYEQTCY.js → cli-skill-templates-US5CCAJ4.js} +3 -3
  13. package/dist/{cli-skill-templates-TSYEQTCY.js.map → cli-skill-templates-US5CCAJ4.js.map} +1 -1
  14. package/dist/index.js +1304 -532
  15. package/dist/index.js.map +1 -1
  16. package/dist/metafile-esm.json +1 -0
  17. package/dist/src-DSJJXM2F.js +3849 -0
  18. package/dist/src-DSJJXM2F.js.map +1 -0
  19. package/package.json +9 -8
  20. package/templates/README.md +48 -145
  21. package/templates/apps/admin/messages/ja-JP.json +38 -0
  22. package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -6
  23. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +3 -3
  24. package/templates/apps/api/.dockerignore.hbs +14 -7
  25. package/templates/apps/api/.gitignore.hbs +7 -0
  26. package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +23 -2
  27. package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +2 -0
  28. package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +4 -1
  29. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +33 -8
  30. package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +0 -10
  31. package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +2 -1
  32. package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -2
  33. package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +24 -11
  34. package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +8 -1
  35. package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +68 -11
  36. package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +10 -2
  37. package/templates/apps/api/e2e/api-service.spec.ts.hbs +2 -2
  38. package/templates/apps/api/hai.build.json.hbs +1 -1
  39. package/templates/apps/api/images/app.Dockerfile.hbs +35 -0
  40. package/templates/apps/api/package.json.hbs +6 -1
  41. package/templates/apps/api/playwright.config.ts.hbs +15 -7
  42. package/templates/apps/fullstack/.dockerignore.hbs +14 -9
  43. package/templates/apps/fullstack/.gitignore.hbs +7 -0
  44. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +24 -6
  45. package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +1 -0
  46. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/ja-JP.json.hbs +7 -0
  47. package/templates/apps/fullstack/apps/{{projectName}}-app/public/runtime-config.js.hbs +4 -0
  48. package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +7 -1
  49. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +2 -2
  50. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +23 -5
  51. package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/ja-JP.json.hbs +7 -0
  52. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +1 -1
  53. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +16 -6
  54. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/ja-JP.json.hbs +12 -0
  55. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +24 -6
  56. package/templates/apps/fullstack/apps/{{projectName}}-web/messages/ja-JP.json.hbs +7 -0
  57. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +2 -2
  58. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +30 -9
  59. package/templates/apps/fullstack/hai.build.json.hbs +2 -2
  60. package/templates/apps/fullstack/images/app.Dockerfile.hbs +36 -0
  61. package/templates/apps/fullstack/images/web.Dockerfile.hbs +34 -0
  62. package/templates/apps/fullstack/package.json.hbs +3 -1
  63. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +23 -2
  64. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
  65. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +2 -0
  66. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +3 -0
  67. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +32 -8
  68. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +0 -6
  69. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -0
  70. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +2 -1
  71. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +6 -0
  72. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +9 -5
  73. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +3 -2
  74. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/messages.ts.hbs +16 -1
  75. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +7 -3
  76. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -0
  77. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +58 -7
  78. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +13 -3
  79. package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +7 -0
  80. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +40 -14
  81. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +105 -0
  82. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +3 -3
  83. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +6 -6
  84. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +7 -7
  85. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +4 -4
  86. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +8 -8
  87. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +13 -14
  88. package/templates/apps/fullstack/playwright.config.ts.hbs +15 -6
  89. package/templates/apps/h5/messages/ja-JP.json +40 -0
  90. package/templates/apps/h5/src/routes/+layout.svelte.hbs +16 -5
  91. package/templates/apps/mobile-app/messages/ja-JP.json +36 -0
  92. package/templates/apps/mobile-app/src/App.svelte.hbs +15 -5
  93. package/templates/apps/mobile-app/src/lib/api.ts.hbs +173 -14
  94. package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +2 -2
  95. package/templates/apps/website/messages/ja-JP.json +32 -0
  96. package/templates/apps/website/src/routes/+layout.svelte.hbs +13 -5
  97. package/templates/apps/website/src/routes/services/+page.svelte.hbs +1 -1
  98. package/templates/base/.dockerignore.hbs +14 -8
  99. package/templates/base/.gitignore.hbs +7 -0
  100. package/templates/base/e2e/app.spec.ts.hbs +23 -8
  101. package/templates/base/hai.build.json.hbs +1 -1
  102. package/templates/base/{Dockerfile.hbs → images/app.Dockerfile.hbs} +19 -17
  103. package/templates/base/package.json.hbs +11 -3
  104. package/templates/base/playwright.config.ts +15 -4
  105. package/templates/base/src/hooks.server.ts.hbs +15 -6
  106. package/templates/base/src/lib/server/init.ts.hbs +139 -30
  107. package/templates/base/vite.config.ts.hbs +5 -3
  108. package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +11 -6
  109. package/templates/features/payment/routes/api/v1/payment/create/+server.ts +18 -25
  110. package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +4 -0
  111. package/templates/i18n/project.inlang/settings.json +1 -1
  112. package/templates/shared/.gitattributes +6 -0
  113. package/templates/shared/e2e-env.ts +98 -0
  114. package/templates/shared/run-e2e.mjs +51 -12
  115. package/templates/skills/AGENTS.md +7 -3
  116. package/templates/skills/bridges/admin/AGENTS.md +6 -2
  117. package/templates/skills/bridges/api/AGENTS.md +3 -1
  118. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +5 -1
  119. package/templates/skills/bridges/h5/AGENTS.md +6 -2
  120. package/templates/skills/bridges/mobile-app/AGENTS.md +6 -2
  121. package/templates/skills/bridges/website/AGENTS.md +6 -2
  122. package/templates/skills/hai-ai/SKILL.md +8 -2
  123. package/templates/skills/hai-ai/guide.md +134 -60
  124. package/templates/skills/hai-ai/reference.md +51 -11
  125. package/templates/skills/hai-api-client/SKILL.md +15 -160
  126. package/templates/skills/hai-api-client/reference.md +178 -0
  127. package/templates/skills/hai-api-contract/SKILL.md +16 -273
  128. package/templates/skills/hai-api-contract/reference.md +280 -0
  129. package/templates/skills/hai-app-create/SKILL.md +6 -2
  130. package/templates/skills/hai-app-create/development.md +24 -8
  131. package/templates/skills/hai-app-create/reference.md +7 -5
  132. package/templates/skills/hai-app-review/SKILL.md +7 -3
  133. package/templates/skills/hai-app-review/differential.md +34 -0
  134. package/templates/skills/hai-app-tests/SKILL.md +6 -3
  135. package/templates/skills/hai-app-tests/reference.md +5 -5
  136. package/templates/skills/hai-app-tests/validation.md +38 -0
  137. package/templates/skills/hai-audit/SKILL.md +24 -147
  138. package/templates/skills/hai-audit/reference.md +148 -0
  139. package/templates/skills/hai-build/SKILL.md +14 -8
  140. package/templates/skills/hai-build/reference.md +16 -13
  141. package/templates/skills/hai-cache/SKILL.md +20 -181
  142. package/templates/skills/hai-cache/reference.md +229 -0
  143. package/templates/skills/hai-capacitor/SKILL.md +7 -4
  144. package/templates/skills/hai-capacitor/reference.md +15 -9
  145. package/templates/skills/hai-ci/SKILL.md +2 -0
  146. package/templates/skills/hai-core/SKILL.md +7 -3
  147. package/templates/skills/hai-core/reference.md +25 -5
  148. package/templates/skills/hai-crypto/SKILL.md +22 -250
  149. package/templates/skills/hai-crypto/reference.md +310 -0
  150. package/templates/skills/hai-datapipe/SKILL.md +16 -195
  151. package/templates/skills/hai-datapipe/reference.md +204 -0
  152. package/templates/skills/hai-deploy/SKILL.md +24 -54
  153. package/templates/skills/hai-deploy/middleware.md +51 -43
  154. package/templates/skills/hai-framework-sync/SKILL.md +2 -2
  155. package/templates/skills/hai-framework-sync/sync.md +1 -1
  156. package/templates/skills/hai-iam/SKILL.md +25 -11
  157. package/templates/skills/hai-iam/reference.md +181 -154
  158. package/templates/skills/hai-kit/SKILL.md +35 -174
  159. package/templates/skills/hai-kit/reference.md +190 -0
  160. package/templates/skills/hai-payment/SKILL.md +33 -263
  161. package/templates/skills/hai-payment/reference.md +295 -0
  162. package/templates/skills/hai-pr-review/SKILL.md +21 -88
  163. package/templates/skills/hai-reach/SKILL.md +6 -3
  164. package/templates/skills/hai-reach/reference.md +83 -22
  165. package/templates/skills/hai-reldb/SKILL.md +6 -3
  166. package/templates/skills/hai-reldb/reference.md +68 -25
  167. package/templates/skills/hai-scheduler/SKILL.md +18 -195
  168. package/templates/skills/hai-scheduler/reference.md +216 -0
  169. package/templates/skills/hai-serv/SKILL.md +25 -10
  170. package/templates/skills/hai-serv/reference.md +123 -87
  171. package/templates/skills/hai-spec-implement/SKILL.md +5 -1
  172. package/templates/skills/hai-spec-implement/evaluation.md +18 -0
  173. package/templates/skills/hai-spec-implement/handoff.md +47 -0
  174. package/templates/skills/hai-spec-implement/templates.md +5 -0
  175. package/templates/skills/hai-spec-implement/workflow.md +5 -3
  176. package/templates/skills/hai-spec-review/SKILL.md +4 -0
  177. package/templates/skills/hai-storage/SKILL.md +23 -238
  178. package/templates/skills/hai-storage/reference.md +282 -0
  179. package/templates/skills/hai-ui/SKILL.md +17 -3
  180. package/templates/skills/hai-ui/guide.md +3 -3
  181. package/templates/skills/hai-ui/reference.md +26 -11
  182. package/templates/skills/hai-ui-design/SKILL.md +31 -0
  183. package/templates/skills/hai-ui-design/patterns.md +26 -0
  184. package/templates/skills/hai-ui-review/SKILL.md +30 -0
  185. package/templates/skills/hai-ui-review/checklist.md +22 -0
  186. package/templates/skills/hai-vecdb/SKILL.md +14 -10
  187. package/templates/skills/hai-vecdb/reference.md +33 -3
  188. package/dist/chunk-FWN5B54L.js +0 -468
  189. package/dist/chunk-FWN5B54L.js.map +0 -1
  190. package/dist/chunk-QYX6V7H2.js.map +0 -1
  191. package/dist/cli-config-templates-3XSFIXBP.js +0 -5
  192. package/dist/src-RDQ2EIBT.js.map +0 -1
  193. package/templates/apps/api/Dockerfile.hbs +0 -35
  194. package/templates/apps/fullstack/Dockerfile.hbs +0 -57
  195. /package/templates/apps/fullstack/{docker → images/web}/19-runtime-config.envsh.hbs +0 -0
  196. /package/templates/apps/fullstack/{docker → images/web}/nginx.conf.hbs +0 -0
  197. /package/templates/apps/fullstack/{docker → images/web}/runtime-config.js.template.hbs +0 -0
  198. /package/templates/base/{docker → images/web}/nginx.conf.hbs +0 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: hai-api-client
3
- description: "使用 @h-ai/api-client 调用 typed API,管理 Token、刷新与加密传输。"
3
+ description: "接入或排查 @h-ai/api-client typed API、登录 Token、自动刷新与加密传输;适用于 Web、SSR 和原生客户端。"
4
4
  ---
5
5
 
6
6
  # hai-api-client
@@ -15,167 +15,22 @@ description: "使用 @h-ai/api-client 调用 typed API,管理 Token、刷新
15
15
  | 输出 | typed client;领域响应为 HaiResult |
16
16
  | 限制 | 先 init,结束 close;浏览器 Cookie 与服务端配置匹配,原生端使用安全 TokenStorage。不要在多用户 SSR 中共享带用户 token 的单例。 |
17
17
 
18
- > `@h-ai/api-client` 是跨端 typed API client。公共 HTTP API 由 `@h-ai/api-contract` 定义,服务端由 `@h-ai/serv` 挂载,客户端直接调用 `apiClient.<domain>.<group>.<operation>()`。
18
+ ## 使用路径
19
19
 
20
- ## 使用步骤
20
+ 1. 读取服务端 contract、apiPrefix、CORS、Cookie 与 transport 配置,选择默认客户端或 `apiClient.create(contract)`。
21
+ 2. 浏览器使用 Cookie/内存;SSR 按请求创建客户端,原生使用安全 TokenStorage,不跨用户复用单例。
22
+ 3. 检查 init 与每次业务调用的 `success`。登录成功后设置 tokens;优先使用 `createAuthActions` 连接认证 UI。
23
+ 4. 同步服务端与客户端的刷新、加密和错误处理;切换配置前 close,任务结束释放客户端。
24
+ 5. 验证登录、刷新重试、失败、退出与会话恢复。
21
25
 
22
- ### 1. 初始化
26
+ ## 按需参考
23
27
 
24
- 浏览器默认使用 httpOnly cookie 存储(推荐):
28
+ 按本次操作读取对应小节;以已安装版本的公开类型核验签名,示例中的业务变量由应用提供。
25
29
 
26
- ```ts
27
- import { apiClient } from '@h-ai/api-client'
30
+ - [使用步骤](reference.md#使用步骤)
31
+ - [Token Storage 适配器](reference.md#token-storage-适配器)
32
+ - [核心 API](reference.md#核心-api)
33
+ - [常见模式](reference.md#常见模式)
34
+ - [配置驱动认证页面](reference.md#配置驱动认证页面)
28
35
 
29
- await apiClient.init({
30
- baseUrl: 'https://api.example.com/api/v1',
31
- auth: {},
32
- })
33
- ```
34
-
35
- 启用传输加密时,只传顶层 `crypto` 句柄;api-client 内部自动调用 `crypto.transport.createClient()`:
36
-
37
- ```ts
38
- import { apiClient } from '@h-ai/api-client'
39
- import { crypto } from '@h-ai/crypto'
40
-
41
- await crypto.init()
42
-
43
- await apiClient.init({
44
- baseUrl: 'https://api.example.com/api/v1',
45
- auth: {},
46
- transport: { crypto }, // 默认协商路径:/api/v1/_hai/key-exchange
47
- })
48
- ```
49
-
50
- App/Capacitor 场景传入自定义 TokenStorage:
51
-
52
- ```ts
53
- import { createCapacitorTokenStorage } from '@h-ai/capacitor'
54
-
55
- await apiClient.init({
56
- baseUrl: 'https://api.example.com/api/v1',
57
- auth: { storage: createCapacitorTokenStorage() },
58
- })
59
- ```
60
-
61
- > `createCapacitorTokenStorage()` 仅在 Android/iOS 原生环境 + `@aparajita/capacitor-secure-storage` 已安装时持久化 token;纯 Web 不会回退到 `localStorage`。
62
-
63
- SSR / Node.js 测试场景请显式传入内存存储:
64
-
65
- ```ts
66
- import { apiClient } from '@h-ai/api-client'
67
-
68
- await apiClient.init({
69
- baseUrl: 'https://api.example.com/api/v1',
70
- auth: { storage: apiClient.tokenStorage.memory() },
71
- })
72
- ```
73
-
74
- ### 2. 调用 typed API
75
-
76
- ```ts
77
- const login = await apiClient.iam.auth.login({ identifier: 'alice', password: 'secret' })
78
- if (login.success) {
79
- // 默认 httpOnly cookie 模式下,refresh token 由服务端 Set-Cookie 管理。
80
- await apiClient.auth.setTokens(login.data.tokens)
81
- }
82
-
83
- const me = await apiClient.iam.auth.currentUser()
84
- if (me.success) {
85
- // currentUser 返回服务端最新角色/权限;不要把权限快照持久化到 localStorage。
86
- const { roles, permissions, ...user } = me.data
87
- }
88
- const upload = await apiClient.storage.presignedUrls.createUpload({ key: 'avatar.png' })
89
- const answer = await apiClient.ai.chats.sendMessage({ message: 'hello' })
90
- ```
91
-
92
- ### 3. 自定义 contract
93
-
94
- ```ts
95
- import { apiClient } from '@h-ai/api-client'
96
- import { apiContract } from '@h-ai/api-contract'
97
-
98
- const contract = apiContract.create({ iam: apiContract.iam })
99
- const client = apiClient.create(contract)
100
- await client.init({ baseUrl: 'https://api.example.com/api/v1' })
101
- ```
102
-
103
- `client.config` / `apiClient.config` 返回的是脱敏后的运行配置快照;`baseUrl` 的内嵌凭证与 `headers.authorization` 等敏感值会自动隐藏。
104
-
105
- ### 4. 关闭
106
-
107
- ```ts
108
- await apiClient.close()
109
- ```
110
-
111
- > 已初始化后再次 `init()` 会返回 `CONFIG_ERROR`,不会覆盖现有 client;切换 `baseUrl` / `fetch` / `transport` 前必须先 `close()`,避免旧 transport 会话泄漏。
112
-
113
- ## Token Storage 适配器
114
-
115
- | 工厂调用 | 存储位置 | 适用场景 |
116
- | --- | --- | --- |
117
- | `apiClient.tokenStorage.httpOnlyCookie()` | httpOnly cookie(服务端管理) | **默认**;浏览器端推荐,refresh token 不暴露给 JS |
118
- | `apiClient.tokenStorage.memory()` | 内存 | SSR / Node.js 单元测试(需显式传入) |
119
- | `apiClient.tokenStorage.localStorage()` | localStorage | 非敏感场景(有 XSS 风险,生产不推荐) |
120
-
121
- ### httpOnly Cookie 模式
122
-
123
- Access token 存在内存中,refresh token 由服务端通过 `HttpOnly` cookie 管理,浏览器 JS 无法读取,可有效防止 XSS 窃取 token。
124
-
125
- **前提**:服务端需配置 `serv.createApp({ iam, refreshCookie: {} })`,参见 `hai-serv` skill。
126
-
127
- 启用 `transport` 时,登录、业务请求以及 401 后的自动刷新都会复用同一个加密 fetch 链路;不要在业务代码中手写明文 `/auth/refresh` 请求。
128
-
129
- 自动刷新识别真实 HTTP 401(`Response.status`),以及 HTTP 200 的有效失败 HaiResult(`success=false`、字符串 `error.code/message`、`error.httpStatus=401`),成功后只重试一次原请求。内置登录、注册与配置的 `auth.refreshPath` 排除自动刷新,403/500 等其它失败也不刷新。serv 普通 procedure 的 HTTP 状态仍为 200;调用方须检查最终 `success/error.code` 并处理刷新失败后的登录失效。
130
-
131
- ```ts
132
- import { apiClient } from '@h-ai/api-client'
133
-
134
- // apiClient.tokenStorage.httpOnlyCookie() 是默认存储,无需显式指定
135
- await apiClient.init({
136
- baseUrl: 'https://api.example.com/api/v1',
137
- auth: {},
138
- })
139
-
140
- // 登录后设置 access token(refresh token 由服务端写入 cookie,前端无需操作)
141
- const login = await apiClient.iam.auth.login({ identifier: 'alice', password: 'secret' })
142
- if (login.success) {
143
- await apiClient.auth.setTokens(login.data.tokens)
144
- }
145
- ```
146
-
147
- ## 核心 API
148
-
149
- | API | 用途 |
150
- | --- | --- |
151
- | `apiClient.init(config)` | 初始化默认 API Service client;重复初始化返回 `CONFIG_ERROR` |
152
- | `apiClient.close()` | 清理 client 状态 |
153
- | `apiClient.auth.setTokens(tokens)` | 写入 access token;非 httpOnly 存储会同时写入 refresh token |
154
- | `apiClient.auth.clear()` | 清理 Token |
155
- | `apiClient.auth.onTokenRefreshed(cb)` | 监听刷新结果 |
156
- | `apiClient.create(contract)` | 创建自定义 typed client |
157
- | `apiClient.tokenStorage.{memory,localStorage,httpOnlyCookie}()` | TokenStorage 工厂 |
158
-
159
- ### Transport 配置
160
-
161
- | 字段 | 说明 |
162
- | --- | --- |
163
- | `transport.crypto` | 已初始化的 `@h-ai/crypto` 实例 |
164
- | `transport.keyExchangePath` | 相对于 `baseUrl` 的协商子路径,默认 `/_hai/key-exchange` |
165
-
166
- 服务端必须对应启用 `serv.createApp({ transport: { crypto } })`。
167
- 自动刷新会沿用 `transport.encryptedFetch`,并在 httpOnly cookie 模式下发送空请求体 + `credentials: 'include'`,由服务端从 cookie 读取 refresh token。
168
-
169
- ## 常见模式
170
-
171
- - `baseUrl` 通常包含服务端 `apiPrefix`,例如 `/api/v1`。
172
- - 业务错误不 throw,统一判断 `HaiResult.success`。
173
- - 客户端不导入服务端 `procedures` 或 `@h-ai/serv`。
174
- - 客户端不导入 `@h-ai/crypto` 的内部 transport 工厂,只通过 `transport: { crypto }` 装配。
175
- - 前端只依赖 `@h-ai/api-client` 和 contract 类型,不依赖业务模块实现。
176
- - httpOnly cookie 模式需服务端与客户端同步配置,不支持跨域刷新(`SameSite=Strict`)。
177
-
178
-
179
- ## 配置驱动认证页面
180
-
181
- `apiClient.createAuthActions(client)` 为含标准 iam contract 的客户端提供 publicConfig、login、register、sendOtp、requestPasswordReset、confirmPasswordReset。login 输入使用 identifier/password/method/code,method 为 password、ldap、email、sms。成功认证自动调用既有安全 TokenStorage;应用只需把动作传给 AuthPanel,并在 onauthenticated 中刷新自己的用户状态或导航。
36
+ 普通请求与 Token 刷新自动将当前 `core.i18n` 语言发送为 `Accept-Language`;切换语言时同步 core。`headers` 中显式配置的同名头优先。
@@ -0,0 +1,178 @@
1
+ # hai-api-client 参考
2
+
3
+ > `@h-ai/api-client` 是跨端 typed API client。公共 HTTP API 由 `@h-ai/api-contract` 定义,服务端由 `@h-ai/serv` 挂载,客户端直接调用 `apiClient.<domain>.<group>.<operation>()`。
4
+
5
+
6
+ 认证协议仅支持 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/可靠持久化策略;各节点时钟须同步。此能力防止同一传输消息重放,业务重试仍需自己的幂等键。
7
+
8
+ 空 POST 也发送空明文的认证封装;GET/HEAD 将封装放在 X-Hai-Transport 头,204/304/HEAD 等无正文响应也使用同名头承载认证封装。其它请求/响应在 JSON 正文承载封装并带 X-Encrypted: true。客户端必须关联原请求上下文和 HTTP status,缺标记、旧格式或认证失败一律 reject;明文接口只能经 excludePaths 显式排除。CORS 须允许并暴露 X-Hai-Transport。握手公钥的真实性依赖 TLS,不替代 IAM 业务授权。
9
+
10
+ ## 使用步骤
11
+
12
+ ### 1. 初始化
13
+
14
+ 浏览器默认使用 httpOnly cookie 存储(推荐):
15
+
16
+ ```ts
17
+ import { apiClient } from '@h-ai/api-client'
18
+
19
+ await apiClient.init({
20
+ baseUrl: 'https://api.example.com/api/v1',
21
+ auth: {},
22
+ })
23
+ ```
24
+
25
+ 启用传输加密时,只传顶层 `crypto` 句柄;api-client 内部自动调用 `crypto.transport.createClient()`:
26
+
27
+ ```ts
28
+ import { apiClient } from '@h-ai/api-client'
29
+ import { crypto } from '@h-ai/crypto'
30
+
31
+ await crypto.init()
32
+
33
+ await apiClient.init({
34
+ baseUrl: 'https://api.example.com/api/v1',
35
+ auth: {},
36
+ transport: { crypto }, // 默认协商路径:/api/v1/_hai/key-exchange
37
+ })
38
+ ```
39
+
40
+ App/Capacitor 场景传入自定义 TokenStorage:
41
+
42
+ ```ts
43
+ import { createCapacitorTokenStorage } from '@h-ai/capacitor'
44
+
45
+ await apiClient.init({
46
+ baseUrl: 'https://api.example.com/api/v1',
47
+ auth: { storage: createCapacitorTokenStorage() },
48
+ })
49
+ ```
50
+
51
+ > `createCapacitorTokenStorage()` 仅在 Android/iOS 原生环境 + `@aparajita/capacitor-secure-storage` 已安装时持久化 token;纯 Web 不会回退到 `localStorage`。
52
+
53
+ SSR / Node.js 测试场景请显式传入内存存储:
54
+
55
+ ```ts
56
+ import { apiClient } from '@h-ai/api-client'
57
+
58
+ await apiClient.init({
59
+ baseUrl: 'https://api.example.com/api/v1',
60
+ auth: { storage: apiClient.tokenStorage.memory() },
61
+ })
62
+ ```
63
+
64
+ ### 2. 调用 typed API
65
+
66
+ ```ts
67
+ const login = await apiClient.iam.auth.login({ identifier: 'alice', password: 'secret' })
68
+ if (login.success) {
69
+ // 默认 httpOnly cookie 模式下,refresh token 由服务端 Set-Cookie 管理。
70
+ await apiClient.auth.setTokens(login.data.tokens)
71
+ }
72
+
73
+ const me = await apiClient.iam.auth.currentUser()
74
+ if (me.success) {
75
+ // currentUser 返回服务端最新角色/权限;不要把权限快照持久化到 localStorage。
76
+ const { roles, permissions, ...user } = me.data
77
+ }
78
+ const upload = await apiClient.storage.presignedUrls.createUpload({ key: 'avatar.png' })
79
+ const answer = await apiClient.ai.chats.sendMessage({ message: 'hello' })
80
+ ```
81
+
82
+ 默认 `ai.chats.createCompletion({ messages, tools, tool_choice })` 只返回非流式 HaiResult JSON;不接受 stream 字段。完整 function 参数 JSON Schema、strict 与工具选择直接到模型。先检查 success,再读取助手 tool_calls;业务方授权执行工具后,以对应 tool_call_id 回传工具消息。流式接口须绑定应用自定义流契约。
83
+
84
+ ### 3. 自定义 contract
85
+
86
+ ```ts
87
+ import { apiClient } from '@h-ai/api-client'
88
+ import { apiContract } from '@h-ai/api-contract'
89
+
90
+ const contract = apiContract.create({ iam: apiContract.iam })
91
+ const client = apiClient.create(contract)
92
+ await client.init({ baseUrl: 'https://api.example.com/api/v1' })
93
+ ```
94
+
95
+ `client.config` / `apiClient.config` 返回的是脱敏后的运行配置快照;`baseUrl` 的内嵌凭证与 `headers.authorization` 等敏感值会自动隐藏。
96
+
97
+ ### 4. 关闭
98
+
99
+ ```ts
100
+ await apiClient.close()
101
+ ```
102
+
103
+ > 已初始化后再次 `init()` 会返回 `CONFIG_ERROR`,不会覆盖现有 client;切换 `baseUrl` / `fetch` / `transport` 前必须先 `close()`,避免旧 transport 会话泄漏。
104
+
105
+ ## Token Storage 适配器
106
+
107
+ | 工厂调用 | 存储位置 | 适用场景 |
108
+ | --- | --- | --- |
109
+ | `apiClient.tokenStorage.httpOnlyCookie()` | httpOnly cookie(服务端管理) | **默认**;浏览器端推荐,refresh token 不暴露给 JS |
110
+ | `apiClient.tokenStorage.memory()` | 内存 | SSR / Node.js 单元测试(需显式传入) |
111
+
112
+ 浏览器只使用内存 access token + 服务端 httpOnly refresh Cookie;原生端注入平台安全 TokenStorage。禁止将认证凭据持久化到 Web Storage。
113
+
114
+ ### httpOnly Cookie 模式
115
+
116
+ Access token 存在内存中,refresh token 由服务端通过 `HttpOnly` cookie 管理,浏览器 JS 无法读取,可有效防止 XSS 窃取 token。
117
+
118
+ **前提**:服务端需配置 `serv.createApp({ iam, refreshCookie: {} })`,参见 `hai-serv` skill。
119
+
120
+ 启用 `transport` 时,登录、业务请求以及 401 后的自动刷新都会复用同一个加密 fetch 链路;不要在业务代码中手写明文 `/auth/refresh` 请求。
121
+
122
+ 自动刷新识别真实 HTTP 401(`Response.status`),以及 HTTP 200 的有效失败 HaiResult(`success=false`、字符串 `error.code/message`、`error.httpStatus=401`),成功后只重试一次原请求。内置登录、注册与配置的 `auth.refreshPath` 排除自动刷新,403/500 等其它失败也不刷新。serv 普通 procedure 的 HTTP 状态仍为 200;调用方须检查最终 `success/error.code` 并处理刷新失败后的登录失效。
123
+
124
+ ```ts
125
+ import { apiClient } from '@h-ai/api-client'
126
+
127
+ // apiClient.tokenStorage.httpOnlyCookie() 是默认存储,无需显式指定
128
+ await apiClient.init({
129
+ baseUrl: 'https://api.example.com/api/v1',
130
+ auth: {},
131
+ })
132
+
133
+ // 登录后设置 access token(refresh token 由服务端写入 cookie,前端无需操作)
134
+ const login = await apiClient.iam.auth.login({ identifier: 'alice', password: 'secret' })
135
+ if (login.success) {
136
+ await apiClient.auth.setTokens(login.data.tokens)
137
+ }
138
+ ```
139
+
140
+ ## 核心 API
141
+
142
+ | API | 用途 |
143
+ | --- | --- |
144
+ | `apiClient.init(config)` | 初始化默认 API Service client;重复初始化返回 `CONFIG_ERROR` |
145
+ | `apiClient.close()` | 清理 client 状态 |
146
+ | `apiClient.auth.setTokens(tokens)` | 使旧刷新失效并替换凭据;非 httpOnly 存储保存提供的 refresh token,缺省时清除旧值 |
147
+ | `apiClient.auth.clear()` | 使旧刷新失效并清理本客户端 Token;服务端会话/Cookie 另由退出接口撤销 |
148
+ | `apiClient.auth.onTokenRefreshed(cb)` | 监听刷新结果 |
149
+ | `apiClient.create(contract)` | 创建自定义 typed client |
150
+ | `apiClient.tokenStorage.{memory,httpOnlyCookie}()` | TokenStorage 工厂 |
151
+
152
+ 退出/换账号使用 `auth.clear()` / `auth.setTokens()`,不直接写 storage。方法立即取消旧刷新,并按顺序提交异步存储操作;迟到的成功/401 不会恢复旧身份、覆盖新账号或触发旧回调。该保证针对同一个客户端的认证管理器,SSR 仍须按请求隔离实例。
153
+
154
+ ### Transport 配置
155
+
156
+ | 字段 | 说明 |
157
+ | --- | --- |
158
+ | `transport.crypto` | 已初始化的 `@h-ai/crypto` 实例 |
159
+ | `transport.keyExchangePath` | 相对于 `baseUrl` 的协商子路径,默认 `/_hai/key-exchange` |
160
+
161
+ 服务端必须对应启用 `serv.createApp({ transport: { crypto, serverKeyRing, keyStore } })`。serverKeyRing 与 keyStore 仅服务端注入,生产各节点共用受管版本化 ring 和共享 registry;客户端只接收 serverKeyId/公钥并在请求密文带回版本,不得接收私钥。旧版本撤销后需销毁客户端会话并重协商,不自动重放非幂等请求。
162
+ 自动刷新会沿用 `transport.encryptedFetch`,并在 httpOnly cookie 模式下发送空请求体 + `credentials: 'include'`,由服务端从 cookie 读取 refresh token。
163
+
164
+ CLI 独立 mobile 模板在选择 IAM/crypto 时安装前端 crypto 并默认启用该 transport,未选择 crypto 时保留明文 API 模式。`PUBLIC_API_TRANSPORT=off` 仅用于明确匹配的明文服务;`PUBLIC_API_KEY_EXCHANGE_PATH` 可调整公开协商子路径。配置先于 client 初始化,握手失败不会回退明文。关闭顺序为 client → 本客户端初始化的 crypto,不能关闭其他调用方已有的 crypto。
165
+
166
+ ## 常见模式
167
+
168
+ - `baseUrl` 通常包含服务端 `apiPrefix`,例如 `/api/v1`。
169
+ - 业务错误不 throw,统一判断 `HaiResult.success`。
170
+ - 客户端不导入服务端 `procedures` 或 `@h-ai/serv`。
171
+ - 客户端不导入 `@h-ai/crypto` 的内部 transport 工厂,只通过 `transport: { crypto }` 装配。
172
+ - 前端只依赖 `@h-ai/api-client` 和 contract 类型,不依赖业务模块实现。
173
+ - httpOnly cookie 模式需服务端与客户端同步配置,跨 Origin 请求需正确设置 CORS 与 credentials;`SameSite=Strict` 限制跨站 Cookie,跨域不等于跨站。
174
+
175
+
176
+ ## 配置驱动认证页面
177
+
178
+ `apiClient.createAuthActions(client)` 为含标准 iam contract 的客户端提供 publicConfig、login、register、sendOtp、requestPasswordReset、confirmPasswordReset。login 输入使用 identifier/password/method/code,method 为 password、ldap、email、sms。成功认证自动调用既有安全 TokenStorage;应用只需把动作传给 AuthPanel,并在 onauthenticated 中刷新自己的用户状态或导航。
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: hai-api-contract
3
- description: "使用 @h-ai/api-contract 定义、组合和扩展共享 HTTP contract 与 Zod 输入输出。"
3
+ description: "新增、组合或修改共享 HTTP 接口的路径与输入输出 Schema,供 serv 与 typed client 共用。"
4
4
  ---
5
5
 
6
6
  # hai-api-contract
@@ -15,279 +15,22 @@ description: "使用 @h-ai/api-contract 定义、组合和扩展共享 HTTP cont
15
15
  | 输出 | 供 serv 实现和 api-client 消费的 contract |
16
16
  | 限制 | 纯定义,无 init/close;不包含服务端实现或密钥。业务路径属于 contract,公共传输配置属于 transport。 |
17
17
 
18
- > `@h-ai/api-contract` 是 hai-framework 的公共 HTTP API 契约包,使用 oRPC contract + Zod v4 作为唯一接口真相源。本包只描述接口边界,不包含任何 procedure 实现。
18
+ ## 使用路径
19
19
 
20
- ## 运行环境
20
+ 1. 查当前公开 exports 与已有领域 contract,确认业务路径唯一来源。
21
+ 2. 在 contract 中声明 method/path 与输入输出 Schema;只导出真正跨接口或跨层复用的 Schema。
22
+ 3. 用 `apiContract.create` 组合领域;不把服务实现、私有配置或传输前缀写进 contract。
23
+ 4. 同步 serv procedures、typed client、调用方与失败/校验测试;普通业务输出按当前 HaiResult 契约。
24
+ 5. 独立 contract 包实际执行声明构建。推导需要的上游公共类型 `ContractProcedureBuilderWithInputOutput`、`ContractProcedureBuilderWithOutput`、`Schema` 已由本包转出;显式类型从公开入口 `import type`,不引用依赖内部路径。
21
25
 
22
- > ✅ **契约定义包,无服务端业务依赖;Zod/oRPC schema 构造仍有运行时代码。** 可在客户端、服务端、测试环境直接引用。
26
+ ## 按需参考
23
27
 
24
- ## 适用场景
28
+ 按本次操作读取对应小节;以已安装版本的公开类型核验签名,示例中的业务变量由应用提供。
25
29
 
26
- - 定义新领域的 oRPC contract(输入/输出 Schema + HTTP 路由元数据)
27
- - 组合多个领域 contract 为应用级 contract
28
- - 向 `@h-ai/serv` 提供 contract(装配为 HTTP API)
29
- - 向 `@h-ai/api-client` 提供 contract(生成类型安全客户端)
30
- - 复用内置领域 contract(iam/storage/ai/payment)
31
- - 复用公共 Schema 工厂(`apiContract.haiResultSchema`、分页 Schema)
32
-
33
- ## 使用步骤
34
-
35
- ### 1. 组合应用级 contract
36
-
37
- ```typescript
38
- import { apiContract } from '@h-ai/api-contract'
39
-
40
- // 按需组合,传 false/undefined 的领域不会出现在 contract 或 OpenAPI spec 中
41
- export const myContract = apiContract.create({
42
- iam: apiContract.iam,
43
- storage: apiContract.storage,
44
- ai: apiContract.ai,
45
- payment: false, // 禁用领域
46
- })
47
-
48
- // myContract.iam.auth.login
49
- // myContract.storage.presignedUrls.createUpload
50
- // myContract.ai.chats.createCompletion
51
- ```
52
-
53
- ### 2. 组合自定义 contract
54
-
55
- ```typescript
56
- import { apiContract } from '@h-ai/api-contract'
57
-
58
- export const myContract = apiContract.create({
59
- iam: apiContract.iam,
60
- storage: apiContract.storage,
61
- payment: false, // 禁用领域(不会出现在 contract 或 OpenAPI spec 中)
62
- })
63
- ```
64
-
65
- ### 3. 定义新领域 contract
66
-
67
- ```typescript
68
- import { apiContract } from '@h-ai/api-contract'
69
- import { z } from 'zod'
70
-
71
- // 输入 Schema
72
- const WidgetCreateInputSchema = z.object({
73
- name: z.string().min(1),
74
- description: z.string().optional(),
75
- })
76
-
77
- // 同一 contract 内两个接口复用,因此保留为文件私有常量,不从 schemas 导出。
78
- const WidgetOutputSchema = apiContract.haiResultSchema(z.object({
79
- id: z.string(),
80
- name: z.string(),
81
- createdAt: z.string(),
82
- }))
83
-
84
- // 领域 contract
85
- export const widgetContract = {
86
- widget: {
87
- create: apiContract
88
- .route({
89
- method: 'POST',
90
- path: '/widgets',
91
- operationId: 'widget.create',
92
- summary: 'Create widget',
93
- tags: ['widget'],
94
- })
95
- .input(WidgetCreateInputSchema)
96
- .output(WidgetOutputSchema),
97
-
98
- getById: apiContract
99
- .route({
100
- method: 'GET',
101
- path: '/widgets/{id}',
102
- operationId: 'widget.getById',
103
- summary: 'Get widget by id',
104
- tags: ['widget'],
105
- })
106
- .input(z.object({ id: z.string() }))
107
- .output(WidgetOutputSchema),
108
- },
109
- }
110
- ```
111
-
112
- ### 4. 将新 contract 挂入应用级 contract
113
-
114
- ```typescript
115
- import { apiContract } from '@h-ai/api-contract'
116
- import { widgetContract } from './widget-contract.js'
117
-
118
- export const appContract = apiContract.create({
119
- iam: apiContract.iam,
120
- widget: widgetContract,
121
- })
122
- ```
123
-
124
- ### 5. 传给 `@h-ai/serv`(服务端)
125
-
126
- ```typescript
127
- import { serv } from '@h-ai/serv'
128
- import { appContract } from './app-contract.js'
129
- import { createMyProcedures } from './procedures/widget.js'
130
-
131
- const app = serv.createApp({
132
- contract: appContract,
133
- procedures: {
134
- iam: createIamProcedures({ iam }),
135
- widget: createMyProcedures(),
136
- },
137
- http: { apiPrefix: '/api/v1' },
138
- })
139
- ```
140
-
141
- ## 核心 API
142
-
143
- ### `apiContract.create(options)` — 组合领域 contract
144
-
145
- ```typescript
146
- import { apiContract } from '@h-ai/api-contract'
147
-
148
- const contract = apiContract.create({
149
- iam: apiContract.iam, // 启用:纳入 contract
150
- storage: false, // 禁用:不出现在 OpenAPI spec 中
151
- custom: myContract, // 自定义领域
152
- })
153
- // 类型自动收窄,被禁用的 key 不出现在 contract 类型中
154
- ```
155
-
156
- ### 内置领域 contract
157
-
158
- | 访问路径 | 领域 | 功能概述 |
159
- | --- | --- | --- |
160
- | `apiContract.iam` | IAM | auth(登录/登出/OTP/刷新/注册/改密码)、users、roles、permissions Admin CRUD |
161
- | `apiContract.storage` | Storage | presignedUrls(上传/下载/批量)、files(元数据查询/删除) |
162
- | `apiContract.ai` | AI | chats(completion/流式)、knowledge(上传/查询) |
163
- | `apiContract.payment` | Payment | orders、subscriptions |
164
-
165
- ### 内置 contract 路径示例
166
-
167
- ```typescript
168
- const myContract = apiContract.create({ iam: apiContract.iam, storage: apiContract.storage, ai: apiContract.ai })
169
-
170
- // IAM
171
- myContract.iam.auth.login // POST /auth/login
172
- myContract.iam.auth.logout // POST /auth/logout
173
- myContract.iam.users.list // GET /iam/users
174
- myContract.iam.roles.create // POST /iam/roles
175
-
176
- // Storage
177
- myContract.storage.presignedUrls.createUpload // POST /storage/presigned-urls/upload
178
-
179
- // AI
180
- myContract.ai.chats.createCompletion // POST /ai/chats/completion
181
- ```
182
-
183
- ### `apiContract.haiResultSchema(dataSchema)` — 标准输出包装
184
-
185
- 所有 HTTP 输出必须通过 `apiContract.haiResultSchema` 包装为 `HaiResult<T>`,与 `@h-ai/core` 的 `ok()/err()` 返回结构一致:
186
-
187
- ```typescript
188
- import { apiContract } from '@h-ai/api-contract'
189
- import { z } from 'zod'
190
-
191
- const getWidget = apiContract
192
- .route({ method: 'GET', path: '/widgets/{id}' })
193
- .output(apiContract.haiResultSchema(z.object({
194
- id: z.string(),
195
- name: z.string(),
196
- })))
197
-
198
- // 等价于:
199
- // { success: true, data: { id: string, name: string } }
200
- // | { success: false, error: HaiError }
201
- ```
202
-
203
- ### `apiContract.pathOf(procedure)` — 读取业务路径
204
-
205
- ```typescript
206
- const refreshPath = apiContract.pathOf(apiContract.iam.auth.refresh)
207
- // '/auth/refresh'
208
- ```
209
-
210
- middleware、client 与 E2E 需要业务路径时从 contract 元数据读取,不维护第二份 route 常量。服务端挂载时再与 `_serv.yml` 的 `http.apiPrefix` 拼接。
211
-
212
- ### 公共 Schema 工具
213
-
214
- | 访问路径 | 说明 |
215
- | --- | --- |
216
- | `apiContract.haiResultSchema(dataSchema)` | `HaiResult<T>` 输出包装 |
217
- | `apiContract.voidResultSchema` | `HaiResult<void>` 空结果包装 |
218
- | `apiContract.paginatedSchema(itemSchema)` | 分页列表输出 Schema |
219
- | `HaiErrorSchema` | HaiError 公共字段 Schema |
220
- | `PaginationQuerySchema` | 分页参数 Schema(可选 page/pageSize,pageSize 上限 100;默认值由服务端处理) |
221
-
222
- ## Contract 定义规范
223
-
224
- ### 必须遵循
225
-
226
- - **输出必须包装为 `apiContract.haiResultSchema`** — 客户端依赖 `success` 判断成功/失败
227
- - **业务 path 直接写在对应 `*-contract.ts`** — 禁止新增 `*-routes.ts` 或业务路径常量
228
- - **一次性 `*OutputSchema` 直接内联** — 不在 schemas 文件声明或导出
229
- - **同一 contract 内重复使用的输出可设私有常量** — 不得为复用扩大公共导出
230
- - **公共 HTTP 传输配置以配置文件为准** — API 前缀、健康检查、OpenAPI/docs、密钥协商路径与跨端 Header 放在 service 的 `_serv.yml`,不放 contract 包
231
- - **schemas 只保留真实复用结构** — 至少被多个接口或 contract/procedure 等不同层共同消费
232
- - **`operationId` 全局唯一** — 格式为 `domain.resource.action`,如 `iam.auth.login`
233
- - **`tags` 至少包含领域名** — 供 OpenAPI 文档分组
234
- - **输入 Schema 用 Zod v4** — 与 `@orpc/zod/zod4` 的 `ZodToJsonSchemaConverter` 配套
235
- - **Contract 不含业务逻辑** — 只有 Schema + 路由元数据,没有 `async function`
236
-
237
- ### 禁止
238
-
239
- - ❌ Contract 文件中 `import` 任何业务模块(`@h-ai/iam`、`@h-ai/storage` 等)
240
- - ❌ 在 contract 中使用 `z.any()` 或 `z.unknown()` 作为输出类型
241
- - ❌ 重复定义已存在的领域 contract
242
- - ❌ 导出只被单个 contract 接口使用的 `*OutputSchema`
243
- - ❌ 用独立 `*-routes.ts` 维护业务接口路径
244
-
245
- ### 文件职责
246
-
247
- | 文件 | 只负责 |
248
- | --- | --- |
249
- | `*-contract.ts` | 业务接口 path、method、输入与输出边界 |
250
- | `*-schemas.ts` | 跨接口或跨层复用的输入、实体、数据结构 |
251
- | service `config/_serv.yml` | API 前缀、基础设施端点、跨端 Header 与加密 transport 配置 |
252
-
253
- ## 常见模式
254
-
255
- ### 带分页的列表接口
256
-
257
- ```typescript
258
- import { apiContract, PaginationQuerySchema } from '@h-ai/api-contract'
259
- import { z } from 'zod'
260
-
261
- const WidgetSchema = z.object({ id: z.string(), name: z.string() })
262
-
263
- const listWidgets = apiContract
264
- .route({ method: 'GET', path: '/widgets', operationId: 'widget.list', tags: ['widget'] })
265
- .input(PaginationQuerySchema.extend({ keyword: z.string().optional() }))
266
- .output(apiContract.haiResultSchema(apiContract.paginatedSchema(WidgetSchema)))
267
- ```
268
-
269
- ### 路径参数
270
-
271
- ```typescript
272
- // 路径中的 {id} 对应 input 中的 id 字段
273
- const getWidget = apiContract
274
- .route({ method: 'GET', path: '/widgets/{id}', operationId: 'widget.getById', tags: ['widget'] })
275
- .input(z.object({ id: z.string() }))
276
- .output(apiContract.haiResultSchema(WidgetSchema))
277
- ```
278
-
279
- ### 无输入的端点
280
-
281
- ```typescript
282
- const versionContract = apiContract
283
- .route({ method: 'GET', path: '/system/version', operationId: 'system.version', tags: ['system'] })
284
- .output(apiContract.haiResultSchema(z.object({ version: z.string() })))
285
- ```
286
-
287
- ## 测试
288
-
289
- ```bash
290
- pnpm --filter @h-ai/api-contract test
291
- pnpm --filter @h-ai/api-contract typecheck
292
- pnpm --filter @h-ai/api-contract lint
293
- ```
30
+ - [运行环境](reference.md#运行环境)
31
+ - [适用场景](reference.md#适用场景)
32
+ - [使用步骤](reference.md#使用步骤)
33
+ - [核心 API](reference.md#核心-api)
34
+ - [Contract 定义规范](reference.md#contract-定义规范)
35
+ - [常见模式](reference.md#常见模式)
36
+ - [测试](reference.md#测试)