@h-ai/cli 0.1.0-alpha.12 → 0.1.0-alpha.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/dist/index.js +1 -1
- package/dist/{src-XJ6YXNAF.js → src-RZ4DPYNJ.js} +4 -4
- package/dist/{src-XJ6YXNAF.js.map → src-RZ4DPYNJ.js.map} +1 -1
- package/package.json +2 -2
- package/templates/apps/android-app/src/lib/api.ts.hbs +5 -4
- package/templates/base/package.json.hbs +21 -21
- package/templates/base/src/app.css.hbs +2 -1
- package/templates/skills/AGENTS.md +1 -0
- package/templates/skills/copilot-instructions.md +3 -0
- package/templates/skills/hai-ai/SKILL.md +44 -30
- package/templates/skills/hai-api-client/SKILL.md +94 -295
- package/templates/skills/hai-api-contract/SKILL.md +272 -0
- package/templates/skills/hai-build/SKILL.md +25 -13
- package/templates/skills/hai-capacitor/SKILL.md +7 -7
- package/templates/skills/hai-ci/SKILL.md +112 -0
- package/templates/skills/hai-core/SKILL.md +65 -2
- package/templates/skills/hai-crypto/SKILL.md +42 -1
- package/templates/skills/hai-framework-sync/SKILL.md +98 -0
- package/templates/skills/hai-iam/SKILL.md +13 -35
- package/templates/skills/hai-kit/SKILL.md +85 -412
- package/templates/skills/hai-payment/SKILL.md +8 -10
- package/templates/skills/hai-pr-review/SKILL.md +109 -0
- package/templates/skills/hai-reldb/SKILL.md +1 -1
- package/templates/skills/hai-serv/SKILL.md +658 -0
- package/templates/skills/hai-storage/SKILL.md +7 -47
- package/templates/skills/hai-ui/SKILL.md +2 -1
|
@@ -1,484 +1,157 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hai-kit
|
|
3
|
-
description: 使用 @h-ai/kit 构建 SvelteKit
|
|
3
|
+
description: 使用 @h-ai/kit 构建 SvelteKit 请求管道、认证守卫、统一响应、Zod 校验、浏览器端 apiFetch、A2A 与双构建适配;当需求涉及 hooks.server.ts、权限守卫、CORS、限流、同源 transport 或 SvelteKit API endpoint 时使用。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# hai-kit
|
|
7
7
|
|
|
8
|
-
`@h-ai/kit`
|
|
8
|
+
> `@h-ai/kit` 只负责 SvelteKit 集成:Handle Hook、guard、response、validate、client、auth、crud、A2A。公共跨端 HTTP API 契约统一使用 `@h-ai/api-contract` + `@h-ai/serv`,不要在 kit 中定义业务 API contract。
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
- 安全边界:`guard` / `middleware`
|
|
12
|
-
- API 工具:`handler` / `response` / `validate`
|
|
13
|
-
- Bearer Token 认证:从 `Authorization` header 解析,SSR 端通过 `hai_token` Cookie 透传
|
|
14
|
-
- 契约处理:`fromContract(endpoint, fn)` — 基于 EndpointDef 的类型安全 handler
|
|
15
|
-
- 双构建模式:`createAdapter()` 根据环境变量选择 adapter-node 或 adapter-static
|
|
16
|
-
|
|
17
|
-
## 模块概述
|
|
18
|
-
|
|
19
|
-
当任务涉及以下关键词时,优先使用本 Skill:
|
|
20
|
-
|
|
21
|
-
- `hooks.server.ts` / `hooks.client.ts`
|
|
22
|
-
- `svelte.config.js` / `vite.config.ts` / `playwright.config.ts`
|
|
23
|
-
- `kit.createHandle` / `kit.sequence`
|
|
24
|
-
- `kit.guard.*`(`require` / `check`)
|
|
25
|
-
- `kit.handler` / `kit.response` / `kit.validate`
|
|
26
|
-
- `kit.fromContract(endpoint, fn)` — 契约到 SvelteKit handler
|
|
27
|
-
- `kit.client.create()` — 浏览器端统一 API 客户端(`apiFetch`)
|
|
28
|
-
- `kit.auth.createTokenStore()` — 浏览器端 Token 存储
|
|
29
|
-
- `kit.auth.createHandleFetch()` — `hooks.client.ts` 自动附加 Token
|
|
30
|
-
- Bearer Token / `Authorization` header
|
|
31
|
-
- CORS 配置(含 Capacitor Origin 预设)
|
|
32
|
-
- 传输加密(key exchange / `X-Client-Id`)
|
|
33
|
-
|
|
34
|
-
> **重要变更**:Cookie Session 和 CSRF 中间件已移除。统一使用 Bearer Token 认证。
|
|
35
|
-
|
|
36
|
-
## 依赖
|
|
37
|
-
|
|
38
|
-
| 模块 | 用途 | 是否必需 | 初始化要求 |
|
|
39
|
-
| --- | --- | --- | --- |
|
|
40
|
-
| `@h-ai/iam` | 身份认证与授权(A2A 认证) | 可选 | 使用 A2A 功能时需在 kit 使用前初始化 |
|
|
41
|
-
| `@h-ai/crypto` | 加密(传输加密 / Cookie 加密) | 可选 | 启用加密时需在 kit 使用前初始化 |
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
## 项目配置(从 npm 安装)
|
|
46
|
-
|
|
47
|
-
> 以下示例面向通过 `npm install @h-ai/kit` 引用发布包的项目。monorepo 内部使用 `workspace:*` 时路径略有不同。
|
|
48
|
-
|
|
49
|
-
### 1. 安装依赖
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
# 核心包
|
|
53
|
-
npm install @h-ai/kit
|
|
54
|
-
|
|
55
|
-
# 必须的对等依赖
|
|
56
|
-
npm install svelte @sveltejs/kit
|
|
57
|
-
npm install -D @sveltejs/vite-plugin-svelte vite
|
|
58
|
-
|
|
59
|
-
# 可选:双构建 adapter
|
|
60
|
-
npm install -D @sveltejs/adapter-node @sveltejs/adapter-static
|
|
61
|
-
|
|
62
|
-
# API 校验
|
|
63
|
-
npm install zod
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### 2. svelte.config.js
|
|
67
|
-
|
|
68
|
-
```js
|
|
69
|
-
import adapter from '@sveltejs/adapter-auto'
|
|
70
|
-
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
|
71
|
-
|
|
72
|
-
/** @type {import('@sveltejs/kit').Config} */
|
|
73
|
-
const config = {
|
|
74
|
-
preprocess: [vitePreprocess()],
|
|
75
|
-
compilerOptions: {
|
|
76
|
-
runes: true,
|
|
77
|
-
},
|
|
78
|
-
kit: {
|
|
79
|
-
adapter: adapter(),
|
|
80
|
-
// 如需双构建模式(SSR/SSG 按环境切换):
|
|
81
|
-
// import { createAdapter } from '@h-ai/kit/adapter'
|
|
82
|
-
// adapter: createAdapter(),
|
|
83
|
-
csrf: {
|
|
84
|
-
// E2E 测试时信任本地域名
|
|
85
|
-
trustedOrigins: process.env.HAI_E2E === '1'
|
|
86
|
-
? ['http://localhost:4173', 'http://127.0.0.1:4173']
|
|
87
|
-
: [],
|
|
88
|
-
},
|
|
89
|
-
alias: {
|
|
90
|
-
$components: './src/lib/components',
|
|
91
|
-
$stores: './src/lib/stores',
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export default config
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
> 若同时使用 `@h-ai/ui`,在 `preprocess` 数组前方加 `autoImportHaiUi()`(详见 hai-ui Skill)。
|
|
100
|
-
|
|
101
|
-
### 3. vite.config.ts
|
|
102
|
-
|
|
103
|
-
```ts
|
|
104
|
-
import process from 'node:process'
|
|
105
|
-
import { sveltekit } from '@sveltejs/kit/vite'
|
|
106
|
-
import tailwindcss from '@tailwindcss/vite'
|
|
107
|
-
import { defineConfig, loadEnv } from 'vite'
|
|
108
|
-
|
|
109
|
-
export default defineConfig(({ mode }) => {
|
|
110
|
-
const env = loadEnv(mode, process.cwd(), '')
|
|
111
|
-
Object.assign(process.env, env)
|
|
112
|
-
|
|
113
|
-
return {
|
|
114
|
-
plugins: [
|
|
115
|
-
sveltekit(),
|
|
116
|
-
tailwindcss(),
|
|
117
|
-
// 如使用 Paraglide i18n:
|
|
118
|
-
// paraglideVitePlugin({ project: './project.inlang', outdir: './src/lib/paraglide', strategy: ['cookie', 'baseLocale'] }),
|
|
119
|
-
],
|
|
120
|
-
optimizeDeps: {
|
|
121
|
-
exclude: ['bits-ui'], // bits-ui 使用 Svelte 虚拟模块
|
|
122
|
-
},
|
|
123
|
-
ssr: {
|
|
124
|
-
// @h-ai 包为 ESM,需 Vite 在 SSR 时处理
|
|
125
|
-
noExternal: [/@h-ai\//],
|
|
126
|
-
},
|
|
127
|
-
server: {
|
|
128
|
-
port: 5173,
|
|
129
|
-
},
|
|
130
|
-
}
|
|
131
|
-
})
|
|
132
|
-
```
|
|
10
|
+
## 使用步骤
|
|
133
11
|
|
|
134
|
-
###
|
|
12
|
+
### 1. 请求管道
|
|
135
13
|
|
|
136
14
|
```ts
|
|
137
15
|
import type { Handle } from '@sveltejs/kit'
|
|
138
16
|
import { kit } from '@h-ai/kit'
|
|
139
17
|
|
|
140
|
-
// ─── 模块初始化(延迟 + 并发安全) ─── //
|
|
141
|
-
let initPromise: Promise<void> | null = null
|
|
142
|
-
|
|
143
|
-
async function ensureInit() {
|
|
144
|
-
if (!initPromise) {
|
|
145
|
-
initPromise = initModules().catch((err) => {
|
|
146
|
-
initPromise = null // 失败清除,允许重试
|
|
147
|
-
throw err
|
|
148
|
-
})
|
|
149
|
-
}
|
|
150
|
-
await initPromise
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
async function initModules() {
|
|
154
|
-
// 按需初始化 reldb、iam、cache 等模块
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// ─── 初始化 Handle ─── //
|
|
158
|
-
const initHandle: Handle = async ({ event, resolve }) => {
|
|
159
|
-
await ensureInit()
|
|
160
|
-
return resolve(event)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// ─── 认证 Handle ─── //
|
|
164
18
|
const haiHandle = kit.createHandle({
|
|
165
19
|
auth: {
|
|
166
|
-
verifyToken: async
|
|
167
|
-
// 验证 Bearer Token,返回 SessionData 或 null
|
|
168
|
-
return token ? { userId: 'u_1', roles: ['admin'], permissions: ['user:read'] } : null
|
|
169
|
-
},
|
|
20
|
+
verifyToken: async token => token ? { userId: 'u_1', roles: ['admin'], permissions: ['user:read'] } : null,
|
|
170
21
|
loginUrl: '/auth/login',
|
|
171
|
-
protectedPaths: ['/admin/*'
|
|
172
|
-
publicPaths: ['/api/auth/*', '/api/public/*'],
|
|
22
|
+
protectedPaths: ['/admin/*'],
|
|
173
23
|
},
|
|
174
24
|
rateLimit: { windowMs: 60_000, maxRequests: 100 },
|
|
175
25
|
logging: true,
|
|
176
|
-
cors: {
|
|
177
|
-
origins: ['https://example.com'],
|
|
178
|
-
capacitor: true, // 自动允许 Capacitor 原生 App WebView Origin
|
|
179
|
-
credentials: true,
|
|
180
|
-
},
|
|
181
26
|
})
|
|
182
27
|
|
|
183
|
-
export const handle: Handle =
|
|
28
|
+
export const handle: Handle = haiHandle
|
|
184
29
|
```
|
|
185
30
|
|
|
186
|
-
###
|
|
31
|
+
### 2. SvelteKit API endpoint
|
|
187
32
|
|
|
188
33
|
```ts
|
|
189
34
|
import { kit } from '@h-ai/kit'
|
|
190
|
-
|
|
191
|
-
// 自动将 localStorage 中的 Bearer Token 附加到 SSR fetch 请求
|
|
192
|
-
export const handleFetch = kit.auth.createHandleFetch()
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
### 6. playwright.config.ts — E2E 测试
|
|
196
|
-
|
|
197
|
-
```ts
|
|
198
|
-
import process from 'node:process'
|
|
199
|
-
import { defineConfig } from '@playwright/test'
|
|
200
|
-
|
|
201
|
-
const baseURL = process.env.BASE_URL || 'http://localhost:4173'
|
|
202
|
-
|
|
203
|
-
export default defineConfig({
|
|
204
|
-
testDir: './e2e',
|
|
205
|
-
fullyParallel: false,
|
|
206
|
-
forbidOnly: !!process.env.CI,
|
|
207
|
-
retries: process.env.CI ? 2 : 0,
|
|
208
|
-
workers: 2,
|
|
209
|
-
reporter: 'list',
|
|
210
|
-
timeout: 30_000,
|
|
211
|
-
|
|
212
|
-
use: {
|
|
213
|
-
baseURL,
|
|
214
|
-
channel: 'chrome',
|
|
215
|
-
extraHTTPHeaders: { Origin: baseURL },
|
|
216
|
-
trace: 'on-first-retry',
|
|
217
|
-
screenshot: 'only-on-failure',
|
|
218
|
-
},
|
|
219
|
-
|
|
220
|
-
webServer: {
|
|
221
|
-
command: 'pnpm build && pnpm exec vite preview --port 4173 --strictPort',
|
|
222
|
-
env: { HAI_E2E: '1', NODE_ENV: 'test' },
|
|
223
|
-
url: baseURL,
|
|
224
|
-
reuseExistingServer: false,
|
|
225
|
-
timeout: 180_000,
|
|
226
|
-
},
|
|
227
|
-
})
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
> `HAI_E2E=1` 与 `NODE_ENV=test` 搭配使用:提高 rateLimit 上限、信任本地 Origin。
|
|
231
|
-
|
|
232
|
-
---
|
|
233
|
-
|
|
234
|
-
## 使用步骤
|
|
235
|
-
|
|
236
|
-
### 1) API 端点使用 `kit.handler`
|
|
237
|
-
|
|
238
|
-
```typescript
|
|
239
|
-
import { kit } from '@h-ai/kit'
|
|
240
35
|
import { z } from 'zod'
|
|
241
36
|
|
|
242
|
-
const
|
|
37
|
+
const CreateUserSchema = z.object({ name: z.string().min(1) })
|
|
243
38
|
|
|
244
39
|
export const POST = kit.handler(async ({ request, locals }) => {
|
|
245
40
|
kit.guard.require(locals.session, 'user:create')
|
|
246
|
-
const
|
|
247
|
-
return kit.response.created(
|
|
41
|
+
const payload = await kit.validate.body(request, CreateUserSchema)
|
|
42
|
+
return kit.response.created(payload)
|
|
248
43
|
})
|
|
249
44
|
```
|
|
250
45
|
|
|
251
|
-
###
|
|
46
|
+
### 3. 浏览器同源请求
|
|
252
47
|
|
|
253
|
-
```
|
|
254
|
-
import { iam } from '@h-ai/iam'
|
|
255
|
-
import { iamEndpoints } from '@h-ai/iam/api'
|
|
48
|
+
```ts
|
|
256
49
|
import { kit } from '@h-ai/kit'
|
|
257
50
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (!result.success) {
|
|
261
|
-
return kit.response.unauthorized(result.error.message)
|
|
262
|
-
}
|
|
263
|
-
return kit.response.ok(result.data)
|
|
264
|
-
})
|
|
51
|
+
const client = kit.client.create({ auth: true })
|
|
52
|
+
export const { apiFetch } = client
|
|
265
53
|
```
|
|
266
54
|
|
|
267
|
-
###
|
|
268
|
-
|
|
269
|
-
```typescript
|
|
270
|
-
export const handle = kit.createHandle({
|
|
271
|
-
cors: {
|
|
272
|
-
origins: ['https://example.com', 'https://*.example.com'],
|
|
273
|
-
capacitor: true, // Capacitor 原生 App WebView Origin
|
|
274
|
-
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
275
|
-
credentials: true,
|
|
276
|
-
},
|
|
277
|
-
})
|
|
278
|
-
```
|
|
55
|
+
### 4. 同源传输加密
|
|
279
56
|
|
|
280
|
-
|
|
57
|
+
kit 不再维护本地传输加密实现;服务端和客户端都统一委托 `@h-ai/crypto` 的 `crypto.transport`。
|
|
281
58
|
|
|
282
|
-
```
|
|
59
|
+
```ts
|
|
60
|
+
// hooks.server.ts
|
|
283
61
|
import { crypto } from '@h-ai/crypto'
|
|
284
62
|
import { kit } from '@h-ai/kit'
|
|
285
63
|
|
|
286
|
-
crypto.init()
|
|
64
|
+
await crypto.init()
|
|
287
65
|
|
|
288
66
|
export const handle = kit.createHandle({
|
|
67
|
+
auth: {
|
|
68
|
+
verifyToken,
|
|
69
|
+
protectedPaths: ['/api/*'],
|
|
70
|
+
publicPaths: ['/api/_hai/*'],
|
|
71
|
+
},
|
|
289
72
|
crypto: {
|
|
290
73
|
crypto,
|
|
291
|
-
transport: { requireEncryption:
|
|
292
|
-
cookieEncryptionKey: process.env.HAI_KIT_COOKIE_KEY,
|
|
74
|
+
transport: { requireEncryption: true },
|
|
293
75
|
},
|
|
294
76
|
})
|
|
295
77
|
```
|
|
296
78
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
| `kit.i18n.setLocale(locale)` | 设置请求级 locale | 在 handle 中调用,供服务端模块使用 |
|
|
309
|
-
| `defineEndpoint(def)` | 定义 EndpointDef 契约 | 与 api-client 配合使用 |
|
|
310
|
-
|
|
311
|
-
### EndpointDef 契约
|
|
312
|
-
|
|
313
|
-
```typescript
|
|
314
|
-
import { defineEndpoint } from '@h-ai/kit'
|
|
315
|
-
import { z } from 'zod'
|
|
316
|
-
|
|
317
|
-
const myEndpoint = defineEndpoint({
|
|
318
|
-
method: 'POST',
|
|
319
|
-
path: '/api/v1/items',
|
|
320
|
-
input: z.object({ name: z.string() }),
|
|
321
|
-
output: z.object({ id: z.string(), name: z.string() }),
|
|
322
|
-
requireAuth: true,
|
|
323
|
-
})
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
## 错误码(常见)
|
|
327
|
-
|
|
328
|
-
### API 响应错误码(`kit.response`)
|
|
329
|
-
|
|
330
|
-
| code | status | 场景 |
|
|
331
|
-
| ------------------ | ------ | ------------------ |
|
|
332
|
-
| `BAD_REQUEST` | 400 | 参数或请求格式错误 |
|
|
333
|
-
| `UNAUTHORIZED` | 401 | 未认证 |
|
|
334
|
-
| `FORBIDDEN` | 403 | 无权限 |
|
|
335
|
-
| `NOT_FOUND` | 404 | 资源不存在 |
|
|
336
|
-
| `CONFLICT` | 409 | 资源冲突 |
|
|
337
|
-
| `VALIDATION_ERROR` | 422 | 数据校验失败 |
|
|
338
|
-
| `INTERNAL_ERROR` | 500 | 未处理异常 |
|
|
339
|
-
|
|
340
|
-
## API 契约范式(端到端类型安全)
|
|
341
|
-
|
|
342
|
-
> `kit.fromContract` 和 `api.call` 共同消费模块 `api/` 下的 `EndpointDef`,实现服务端↔客户端的全链路类型安全 + 运行时 Zod 校验。
|
|
343
|
-
|
|
344
|
-
### 服务端流程(本模块责任)
|
|
345
|
-
|
|
346
|
-
1. 从 `@h-ai/xx/api` 导入 `xxEndpoints`
|
|
347
|
-
2. `kit.fromContract(xxEndpoints.xxx, handler)` → 自动 Zod 校验入参 → handler 获得类型安全的 `input`
|
|
348
|
-
3. 需权限时在 handler 内调用 `kit.guard.require`
|
|
349
|
-
|
|
350
|
-
### 多模块契约示例
|
|
351
|
-
|
|
352
|
-
```typescript
|
|
353
|
-
// ─── 存储:预签名上传 ───
|
|
354
|
-
import { storageEndpoints } from '@h-ai/storage/api'
|
|
355
|
-
import { storage } from '$lib/server/init'
|
|
356
|
-
|
|
357
|
-
export const POST = kit.fromContract(storageEndpoints.presignUpload, async (input, event) => {
|
|
358
|
-
kit.guard.require(event.locals.session, 'storage:write')
|
|
359
|
-
const result = await storage.presign.putUrl(input.key, input)
|
|
360
|
-
if (!result.success) {
|
|
361
|
-
return kit.response.internalError(result.error.message)
|
|
362
|
-
}
|
|
363
|
-
return kit.response.ok({ url: result.data, key: input.key })
|
|
364
|
-
})
|
|
365
|
-
|
|
366
|
-
// ─── IAM:登录 ───
|
|
367
|
-
import { iamEndpoints } from '@h-ai/iam/api'
|
|
368
|
-
import { iam } from '$lib/server/init'
|
|
369
|
-
|
|
370
|
-
export const POST = kit.fromContract(iamEndpoints.login, async (input) => {
|
|
371
|
-
const result = await iam.auth.login(input)
|
|
372
|
-
if (!result.success) {
|
|
373
|
-
return kit.response.unauthorized(result.error.message)
|
|
79
|
+
```ts
|
|
80
|
+
// routes/+layout.svelte
|
|
81
|
+
<script lang='ts'>
|
|
82
|
+
import { browser } from '$app/environment'
|
|
83
|
+
import { crypto } from '@h-ai/crypto'
|
|
84
|
+
import { kit } from '@h-ai/kit'
|
|
85
|
+
import { appKitConfig } from '$lib/config/kit-config'
|
|
86
|
+
|
|
87
|
+
// 一次性安装:内部按 appKitConfig.transport 是否启用决定行为,并预热 crypto
|
|
88
|
+
if (browser) {
|
|
89
|
+
kit.client.installBrowserTransport(appKitConfig, { crypto })
|
|
374
90
|
}
|
|
375
|
-
|
|
376
|
-
})
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
## 常见模式
|
|
380
|
-
|
|
381
|
-
### 契约模式(推荐新项目使用)
|
|
382
|
-
|
|
383
|
-
1. 在模块的 `api/` 目录定义 `EndpointDef`(Zod schema)
|
|
384
|
-
2. 服务端用 `kit.fromContract(endpoint, handler)` 处理
|
|
385
|
-
3. 客户端用 `api.call(endpoint, input)` 调用
|
|
386
|
-
4. 入参/出参全链路类型安全 + 运行时校验
|
|
387
|
-
|
|
388
|
-
### 双构建模式
|
|
389
|
-
|
|
390
|
-
```typescript
|
|
391
|
-
// svelte.config.js
|
|
392
|
-
import { createAdapter } from '@h-ai/kit/adapter'
|
|
393
|
-
|
|
394
|
-
const config = {
|
|
395
|
-
kit: {
|
|
396
|
-
adapter: createAdapter(), // 自动选择 adapter-node 或 adapter-static
|
|
397
|
-
},
|
|
398
|
-
}
|
|
91
|
+
</script>
|
|
399
92
|
```
|
|
400
93
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
## 浏览器侧使用
|
|
404
|
-
|
|
405
|
-
### 创建统一 API 客户端 — `kit.client`
|
|
406
|
-
|
|
407
|
-
> **所有浏览器端 API 请求必须通过 `apiFetch`,禁止直接 `fetch()`。**
|
|
408
|
-
|
|
409
|
-
在 `src/lib/utils/api.ts` 中一次性创建,全应用共享:
|
|
410
|
-
|
|
411
|
-
```typescript
|
|
412
|
-
// src/lib/utils/api.ts
|
|
94
|
+
```ts
|
|
95
|
+
// lib/utils/api.ts —— 业务层只看到 apiFetch
|
|
413
96
|
import { kit } from '@h-ai/kit'
|
|
414
97
|
|
|
415
|
-
const
|
|
416
|
-
export const { apiFetch } = client
|
|
98
|
+
export const { apiFetch } = kit.client.create({ auth: true })
|
|
417
99
|
```
|
|
418
100
|
|
|
419
|
-
|
|
101
|
+
默认协商端点:`/api/_hai/key-exchange`。如 `keyExchangePath` 自定义,浏览器端同步设置 `keyExchangeUrl`。
|
|
420
102
|
|
|
421
|
-
|
|
422
|
-
import { crypto } from '@h-ai/crypto'
|
|
423
|
-
import { kit } from '@h-ai/kit'
|
|
424
|
-
|
|
425
|
-
if (typeof window !== 'undefined') {
|
|
426
|
-
crypto.init()
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
const client = kit.client.create({
|
|
430
|
-
transport: { crypto },
|
|
431
|
-
auth: true,
|
|
432
|
-
})
|
|
103
|
+
transport 默认保护同源 `/api/*` endpoint 与 SvelteKit `__data.json` 页面数据请求;页面文档、静态资源与 `multipart/form-data` 上传请求保持明文透传。
|
|
433
104
|
|
|
434
|
-
|
|
435
|
-
```
|
|
105
|
+
安全策略默认 fail-closed:`requireEncryption: true` 时,受保护路径缺少 `X-Client-Id` 必须返回 400;服务端 transport 管理器不可用、响应体无法加密或超过单次加密上限时返回错误,禁止明文业务响应回退。`requireEncryption: false` 只适合迁移期灰度。
|
|
436
106
|
|
|
437
|
-
`
|
|
438
|
-
- **CSRF**:写方法自动读取 `hai_csrf` Cookie 并设置 `X-CSRF-Token`
|
|
439
|
-
- **Bearer Token**:自动从 localStorage 读取并注入 `Authorization` header
|
|
440
|
-
- **传输加密**:可选,首次写请求时自动密钥交换
|
|
107
|
+
### 5. 使用 `_kit.yml` 统一 transport 配置
|
|
441
108
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
```typescript
|
|
445
|
-
import { kit } from '@h-ai/kit'
|
|
109
|
+
`@h-ai/kit` 现在提供 `KitConfigSchema` / `resolveKitConfig()`,适合同一份 `_kit.yml`
|
|
110
|
+
同时驱动 `hooks.server.ts` 与浏览器端 `kit.client.installBrowserTransport()`:
|
|
446
111
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
112
|
+
```yml
|
|
113
|
+
transport:
|
|
114
|
+
keyExchangePath: /api/_hai/key-exchange
|
|
115
|
+
requireEncryption: true
|
|
116
|
+
encryptResponse: true
|
|
117
|
+
excludePaths:
|
|
118
|
+
- /api/storage
|
|
119
|
+
- /api/public
|
|
120
|
+
- /api/auth/profile/avatar
|
|
121
|
+
maxClients: 10000
|
|
451
122
|
```
|
|
452
123
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
| SSR SvelteKit 应用(同源请求) | `kit.client.create().apiFetch` | 自动 CSRF、同源 Cookie 透传、传输加密 |
|
|
458
|
-
| SPA / 原生 App(跨域请求) | `api.init()` + `api.call()` | 完整 Token 管理、401 自动刷新 |
|
|
459
|
-
| 服务端 `+page.server.ts` | 直接调用模块 API | 无需 HTTP 自环 |
|
|
460
|
-
| 契约调用(类型安全) | `api.call(endpoint, input)` | EndpointDef 双向 Zod 校验 |
|
|
124
|
+
```ts
|
|
125
|
+
import { resolveKitConfig } from '@h-ai/kit'
|
|
126
|
+
import { parse } from 'yaml'
|
|
127
|
+
import rawKitConfig from '../config/_kit.yml?raw'
|
|
461
128
|
|
|
462
|
-
|
|
129
|
+
export const appKitConfig = resolveKitConfig(parse(rawKitConfig) ?? {})
|
|
130
|
+
```
|
|
463
131
|
|
|
464
|
-
|
|
132
|
+
`_kit.yml` 只放公开的 transport 路径/开关,不放密钥;如自定义 `keyExchangePath`,浏览器端必须同步映射为 `keyExchangeUrl`。
|
|
465
133
|
|
|
466
|
-
|
|
467
|
-
| ------------------- | ------------------------------------------------ |
|
|
468
|
-
| `@h-ai/kit` | 主入口:`kit` 命名空间、类型、`defineEndpoint` |
|
|
469
|
-
| `@h-ai/kit/client` | 浏览器端客户端(单独引用时) |
|
|
470
|
-
| `@h-ai/kit/adapter` | 双构建 adapter(`createAdapter()`) |
|
|
471
|
-
| `@h-ai/kit/vite` | Vite 插件 |
|
|
472
|
-
| `@h-ai/kit/a2a` | A2A 协议集成 |
|
|
473
|
-
| `@h-ai/kit/crypto` | 加密模块集成 |
|
|
134
|
+
## 核心 API
|
|
474
135
|
|
|
475
|
-
|
|
136
|
+
| API | 用途 |
|
|
137
|
+
| --- | --- |
|
|
138
|
+
| `kit.createHandle()` | 创建 SvelteKit Handle |
|
|
139
|
+
| `kit.sequence()` | 组合多个 Handle |
|
|
140
|
+
| `kit.handler()` | API endpoint 错误边界 |
|
|
141
|
+
| `kit.guard.require()` | 权限守卫 |
|
|
142
|
+
| `kit.response.*` | 统一响应工厂 |
|
|
143
|
+
| `kit.validate.*` | Zod 请求校验 |
|
|
144
|
+
| `kit.auth.*` | Cookie / Token 辅助 |
|
|
145
|
+
| `kit.client.create()` | 浏览器端同源 apiFetch |
|
|
146
|
+
| `kit.client.installBrowserTransport(config, { crypto })` | 推荐入口:按解析后的 `_kit.yml` 一键安装浏览器端传输加密 |
|
|
147
|
+
| `kit.client.installBrowserTransportFetch()` | 底层入口:直接传 transport 配置安装浏览器全局 fetch 包装 |
|
|
148
|
+
| `kit.crud.define()` | 声明式 CRUD 资源 |
|
|
476
149
|
|
|
477
|
-
##
|
|
150
|
+
## 常见模式
|
|
478
151
|
|
|
479
|
-
- `
|
|
480
|
-
- `
|
|
481
|
-
- `
|
|
482
|
-
- `
|
|
483
|
-
-
|
|
484
|
-
-
|
|
152
|
+
- SvelteKit 应用内同源请求用 `kit.client.create().apiFetch`。
|
|
153
|
+
- SvelteKit 应用启用 transport 时,在 `+layout.svelte` 的 `if (browser)` 分支调用一次 `kit.client.installBrowserTransport(appKitConfig, { crypto })`,无需重复连线 `keyExchangeUrl` / `excludePaths`。
|
|
154
|
+
- Web/App/小程序跨域访问公共 API 用 `@h-ai/api-client` typed client。
|
|
155
|
+
- 传输加密只通过 `crypto.transport` 间接装配,不在 kit 中新增本地加密工厂或鸭子类型。
|
|
156
|
+
- 服务端业务模块直接调用模块 API,不通过 HTTP 自环。
|
|
157
|
+
- API endpoint 必须用 Zod 校验用户输入,并设置认证/权限边界。
|
|
@@ -203,17 +203,15 @@ const payResult = await invokePayment(orderResult.data)
|
|
|
203
203
|
|
|
204
204
|
自定义 Provider 通过 `payment.registerProvider(provider)` 注册。
|
|
205
205
|
|
|
206
|
-
### API 契约
|
|
206
|
+
### HTTP API 契约
|
|
207
207
|
|
|
208
208
|
```typescript
|
|
209
|
-
import {
|
|
210
|
-
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
// paymentEndpoints.notifyStripe — POST /payment/notify/stripe
|
|
216
|
-
// paymentEndpoints.refund — POST /payment/refund
|
|
209
|
+
import { apiContract } from '@h-ai/api-contract'
|
|
210
|
+
|
|
211
|
+
// apiContract.payment.orders.create — POST /payment/orders
|
|
212
|
+
// apiContract.payment.orders.get — GET /payment/orders/{orderNo}
|
|
213
|
+
// apiContract.payment.orders.refund — POST /payment/orders/{orderNo}/refunds
|
|
214
|
+
// apiContract.payment.notifications.wechat — POST /payment/notifications/wechat
|
|
217
215
|
```
|
|
218
216
|
|
|
219
217
|
---
|
|
@@ -249,7 +247,7 @@ export const POST = kit.handler(async ({ request, locals }) => {
|
|
|
249
247
|
### 完整支付流程
|
|
250
248
|
|
|
251
249
|
```
|
|
252
|
-
1. 客户端 → 服务端:创建订单(api.
|
|
250
|
+
1. 客户端 → 服务端:创建订单(api.payment.orders.create({...}))
|
|
253
251
|
2. 服务端 → 支付平台:调用 Provider 创建预支付
|
|
254
252
|
3. 服务端 → 客户端:返回 providerData(含调起参数)
|
|
255
253
|
4. 客户端:invokePayment(providerData) 调起支付
|