@h-ai/kit 1.0.0-beta.2 → 1.0.0-beta.20

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 (43) hide show
  1. package/README.en.md +190 -110
  2. package/README.ja.md +192 -112
  3. package/README.md +195 -114
  4. package/dist/adapter/index.d.ts +8 -7
  5. package/dist/adapter/index.js.map +1 -1
  6. package/dist/browser.d.ts +9 -26
  7. package/dist/browser.js +4 -4
  8. package/dist/browser.js.map +1 -1
  9. package/dist/{chunk-JRQPMPQC.js → chunk-BCEBXG4X.js} +2 -2
  10. package/dist/chunk-BCEBXG4X.js.map +1 -0
  11. package/dist/{chunk-7EH47MTW.js → chunk-BVGXHIXR.js} +30 -9
  12. package/dist/chunk-BVGXHIXR.js.map +1 -0
  13. package/dist/{chunk-5VWPH7DO.js → chunk-GQZCU6PC.js} +14 -6
  14. package/dist/chunk-GQZCU6PC.js.map +1 -0
  15. package/dist/{chunk-YKJRQCWR.js → chunk-JL2C5ZGQ.js} +54 -20
  16. package/dist/chunk-JL2C5ZGQ.js.map +1 -0
  17. package/dist/{chunk-3OHUSKHI.js → chunk-WYGIKXHE.js} +23 -8
  18. package/dist/chunk-WYGIKXHE.js.map +1 -0
  19. package/dist/client/index.d.ts +6 -5
  20. package/dist/client/index.js +5 -5
  21. package/dist/client/index.js.map +1 -1
  22. package/dist/index.d.ts +18 -711
  23. package/dist/index.js +266 -149
  24. package/dist/index.js.map +1 -1
  25. package/dist/{kit-a2a-types-4dapztu2.d.ts → kit-a2a-types-sw7sI-ZT.d.ts} +3 -2
  26. package/dist/kit-config-BQKQnu8Y.d.ts +182 -0
  27. package/dist/kit-crypto-types-CqFENBqm.d.ts +47 -0
  28. package/dist/kit-types-Cr1cnB3M.d.ts +2051 -0
  29. package/dist/modules/a2a/index.d.ts +9 -5
  30. package/dist/modules/a2a/index.js +1 -1
  31. package/dist/modules/crypto/index.d.ts +8 -105
  32. package/dist/modules/crypto/index.js +3 -149
  33. package/dist/modules/crypto/index.js.map +1 -1
  34. package/package.json +22 -22
  35. package/dist/chunk-3OHUSKHI.js.map +0 -1
  36. package/dist/chunk-5VWPH7DO.js.map +0 -1
  37. package/dist/chunk-7EH47MTW.js.map +0 -1
  38. package/dist/chunk-JRQPMPQC.js.map +0 -1
  39. package/dist/chunk-YKJRQCWR.js.map +0 -1
  40. package/dist/kit-client-auth-BnhrkzZF.d.ts +0 -422
  41. package/dist/kit-crypto-types-CXr888wf.d.ts +0 -200
  42. package/dist/kit-i18n-EBzZKYN-.d.ts +0 -502
  43. package/dist/kit-types-BkNdyKj8.d.ts +0 -822
package/README.md CHANGED
@@ -4,94 +4,139 @@
4
4
 
5
5
  [框架首页](../../README.md) · 相关模块:[@h-ai/core](../core/README.md) · [@h-ai/crypto](../crypto/README.md) · [@h-ai/iam](../iam/README.md)
6
6
 
7
- ## 简介
8
-
9
7
  `@h-ai/kit` 是 hai-framework 的 SvelteKit 集成模块,提供统一的请求管道与同源 endpoint 工具:
10
8
 
9
+ 运行时统一通过 `kit.xx` 使用。根入口公开 `KitFunctions`(服务端)和 `BrowserKitFunctions`(浏览器)命名空间类型,浏览器条件入口的 `kit` 仅包含客户端、CRUD、页面 Token 与默认语言操作。`AuthCookieWriter` 是服务端认证对当前请求 Cookie 的最小写入契约。导出这些方法或命名空间时,可直接生成消费者声明,无需导入内部 helper。
10
+
11
11
  - `kit.createHandle()`:会话解析、路由守卫、中间件链、可选传输加密与 Cookie 加密
12
12
  - `kit.handler()`:API 端点异常边界
13
13
  - `kit.guard` / `kit.response` / `kit.validate` / `kit.auth` / `kit.client` / `kit.crud`
14
14
 
15
15
  > 公共跨端 HTTP API contract 统一放在 `@h-ai/api-contract`,由 `@h-ai/serv` 挂载,客户端通过 `@h-ai/api-client` typed client 调用;`@h-ai/kit` 不定义也不承载业务 API contract。
16
16
 
17
- ## 核心功能清单
18
-
19
- - Handle Hook 组合(`createHandle` / `sequence`)
20
- - 认证/角色/权限守卫(`guard.require` / `guard.check`)
21
- - 内置 Logging / RateLimit / CORS 中间件(通过 `createHandle` 配置启用)
22
- - 统一 API 响应与请求验证(Zod)
23
- - Bearer Token 工具(服务端 login/logout + 浏览器端 Token 存储)
24
- - 浏览器端统一客户端(自动 CSRF,支持传输加密)
25
- - 声明式 CRUD 资源定义(`crud.define`)
26
- - A2A 协议集成:默认发现路径 `/.well-known/agent-card.json`;配置 `authenticate` 后 null/undefined 返回 401,仅未配置认证时允许匿名。API Key 参数从完整 Agent Card 的 `securitySchemes.apiKey` 读取。Hook 与显式 A2A 路由共享 JSON-RPC 解析错误和 SSE 取消清理逻辑。
27
- - 双构建模式(`createAdapter()`)
28
-
29
- ## 使用入门
17
+ ## 快速开始
30
18
 
31
19
  `sequence` 保留并组合 SvelteKit 的 `resolve` 选项:页面转换由内向外执行,`preload` 与序列化响应头过滤使用首个配置,确保 i18n 的 HTML 语言标记正确替换。
32
20
 
33
21
  ### 安装
34
22
 
35
23
  ```bash
36
- npm install @h-ai/kit @h-ai/core sm-crypto@0.5.7
24
+ npm install @h-ai/kit @h-ai/core sm-crypto@0.5.8
37
25
  npm install svelte @sveltejs/kit zod
38
26
  npm install -D @sveltejs/vite-plugin-svelte vite
39
27
  ```
40
28
 
41
- ### 依赖
42
-
43
- - `@h-ai/iam` — 身份认证与授权(A2A 认证),可选;使用 A2A 功能时需在 kit 使用前初始化
44
- - `@h-ai/crypto` — 加密(传输加密 / Cookie 加密),可选;启用加密时需在 kit 使用前初始化
45
-
46
- ### 快速开始
47
-
48
- #### 项目配置
49
-
50
- **svelte.config.js**:
51
-
52
- ```js
53
- import adapter from '@sveltejs/adapter-node'
54
- import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
55
-
56
- const config = {
57
- preprocess: [vitePreprocess()],
58
- compilerOptions: { runes: true },
59
- kit: {
60
- adapter: adapter(),
61
- // 双构建模式:import { createAdapter } from '@h-ai/kit/adapter'
62
- // adapter: await createAdapter(),
63
- },
64
- }
65
-
66
- export default config
67
- ```
29
+ ### 项目配置
68
30
 
69
31
  **vite.config.ts**:
70
32
 
71
33
  ```ts
34
+ import adapter from '@sveltejs/adapter-node'
72
35
  import { sveltekit } from '@sveltejs/kit/vite'
36
+ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
73
37
  import tailwindcss from '@tailwindcss/vite'
74
38
  import { defineConfig } from 'vite'
75
39
 
76
40
  export default defineConfig({
77
- plugins: [sveltekit(), tailwindcss()],
78
- optimizeDeps: { exclude: ['bits-ui'] },
79
- ssr: { noExternal: ['@h-ai/ui', '@h-ai/kit'], external: ['@h-ai/core', 'sm-crypto'] },
41
+ plugins: [
42
+ sveltekit({
43
+ adapter: adapter(),
44
+ preprocess: [
45
+ vitePreprocess()
46
+ ],
47
+ compilerOptions: { runes: true },
48
+ // 绝对路径保持 SSR 与浏览器端认证、加密路径一致。
49
+ paths: { base: '', relative: false },
50
+ }),
51
+ tailwindcss(),
52
+ ],
53
+ optimizeDeps: {
54
+ exclude: ['bits-ui']
55
+ },
56
+ ssr: {
57
+ noExternal: ['@h-ai/ui', '@h-ai/kit'],
58
+ external: ['@h-ai/core', 'sm-crypto']
59
+ },
80
60
  })
81
61
  ```
82
62
 
83
- 使用 Crypto 或依赖它的 Kit/IAM 时,应用生产依赖需包含 `sm-crypto@0.5.7`。CLI 已配好这一项;手工接入时运行 `pnpm add sm-crypto@0.5.7`。仅设置 external 不够,adapter-node 还需要这项直接依赖声明才能保留原包加载。
63
+ 使用 Crypto 或依赖它的 Kit/IAM 时,应用生产依赖需包含 `sm-crypto@0.5.8`。CLI 已配好这一项;手工接入时运行 `pnpm add sm-crypto@0.5.8`。仅设置 external 不够,adapter-node 还需要这项直接依赖声明才能保留原包加载。
64
+
65
+ ## 功能
66
+
67
+ ### 用途、输入与输出
68
+
69
+ 下表的调用片段复用本页已初始化的服务;命名变量代表应用已提供的请求、Schema、资源定义或配置。完整步骤和失败处理见对应功能示例。`HaiResult` 先判断 `success`,异步项按签名 `await`。
70
+
71
+ | 入口 | 场景 | 输入 | 输出 | 调用片段 |
72
+ | ------------------------------------------- | ------------------ | ------------------------------------------------------------------ | ----------------------------------------------------------------- | --------------------------------------------------------------------------- |
73
+ | `kit.createHandle` | 请求管道 | `HandleConfig: auth/guards/logging/rateLimit/crypto/access` | `SvelteKit Handle` | `kit.createHandle({ logging: true })` |
74
+ | `kit.sequence` | 组合 Hook | `...Handle` | `Handle preserving resolve options` | `kit.sequence(i18nHandle, authHandle)` |
75
+ | `kit.handler` | 端点异常边界 | `(RequestEvent) => Response \| Promise<Response>` | `SvelteKit request handler` | `kit.handler(async () => kit.response.ok({ ready: true }))` |
76
+ | `kit.guard.check` | 检查权限 | `session, permission` | `boolean` | `kit.guard.check(session, "orders.read")` |
77
+ | `kit.guard.require` | 要求权限 | `session, permission` | `void; denied access throws Response` | `kit.guard.require(session, "orders.write")` |
78
+ | `kit.guard.requireSensitive` | 确认操作用途 | `initialized IAM, current RequestEvent request/cookies, operation` | `Promise<void>; invalid proof throws Response` | `kit.guard.requireSensitive(iam, event, "orders.approve")` |
79
+ | `kit.response.ok` | 成功响应 | `data, requestId?` | `Response with HTTP 200 and success data` | `kit.response.ok({ id: "o1" })` |
80
+ | `kit.response.created` | 创建响应 | `data, requestId?` | `Response with HTTP 201 and success data` | `kit.response.created({ id: "o1" })` |
81
+ | `kit.response.noContent` | 无正文 | `none` | `Response with HTTP 204` | `kit.response.noContent()` |
82
+ | `kit.response.error` | 指定失败 | `code, message, status?, requestId?, public details?` | `safe failure Response` | `kit.response.error("app:orders:001", "请求无效", 400)` |
83
+ | `kit.response.badRequest` | 无效请求 | `message?, requestId?` | `HTTP 400 Response` | `kit.response.badRequest("Invalid request")` |
84
+ | `kit.response.unauthorized` | 未认证 | `message?, requestId?` | `HTTP 401 Response` | `kit.response.unauthorized()` |
85
+ | `kit.response.forbidden` | 无权限 | `message?, requestId?` | `HTTP 403 Response` | `kit.response.forbidden()` |
86
+ | `kit.response.notFound` | 未找到 | `message?, requestId?` | `HTTP 404 Response` | `kit.response.notFound()` |
87
+ | `kit.response.conflict` | 版本冲突 | `message, requestId?` | `HTTP 409 Response` | `kit.response.conflict("Data changed")` |
88
+ | `kit.response.validationError` | 字段校验 | `{ field, message }[], requestId?` | `HTTP 422 Response; errors in safe error.cause` | `kit.response.validationError([{ field: "title", message: "请输入标题" }])` |
89
+ | `kit.response.internalError` | 内部失败 | `message?, requestId?` | `HTTP 500 Response with safe message` | `kit.response.internalError()` |
90
+ | `kit.response.redirect` | 重定向 | `location, status?` | `redirect Response` | `kit.response.redirect("/auth/login")` |
91
+ | `kit.response.fromResult` | 映射领域结果 | `HaiResult<T>, httpStatusMap?, requestId?` | `HTTP Response; preserves safe code/status/traceId` | `kit.response.fromResult(result)` |
92
+ | `kit.response.fromError` | 映射领域错误 | `HaiError, requestId?` | `safe HTTP failure Response` | `kit.response.fromError(result.error)` |
93
+ | `kit.validate.body` | JSON/表单校验 | `Request, Zod schema` | `Promise<schema data>; invalid input throws Response` | `kit.validate.body(request, InputSchema)` |
94
+ | `kit.validate.query` | 查询校验 | `URL, Zod schema` | `schema data; invalid input throws Response` | `kit.validate.query(url, QuerySchema)` |
95
+ | `kit.validate.params` | 路径参数校验 | `Record<string,string>, Zod schema` | `schema data; invalid input throws Response` | `kit.validate.params(params, IdSchema)` |
96
+ | `kit.validate.IdParamSchema` | 通用资源编号 | `{ id: string }` | `Zod parsed ID object` | `kit.validate.IdParamSchema.parse({ id: "o1" })` |
97
+ | `kit.validate.PaginationQuerySchema` | 分页输入 | `page/pageSize/search query` | `normalized pagination object` | `kit.validate.PaginationQuerySchema.parse({ page: "1" })` |
98
+ | `kit.client.create` | 同源客户端 | `KitClientConfig: auth/transport/recoverSession?` | `KitClient { apiFetch, init, destroy, ready }` | `kit.client.create()` |
99
+ | `kit.client.createAuthActions` | 标准认证动作 | `{ basePath?, ...auth client options }?` | `AuthPanel-compatible actions` | `kit.client.createAuthActions({ basePath: "/api/auth" })` |
100
+ | `kit.client.installBrowserTransport` | 按公共配置安装加密 | `public KitConfig, { crypto, basePath? }` | `void; installs same-origin global fetch wrapper when enabled` | `kit.client.installBrowserTransport(publicKitConfig, { crypto })` |
101
+ | `kit.client.installBrowserTransportFetch` | 自定义传输安装 | `ClientTransportConfig` | `void; installs browser global fetch wrapper` | `kit.client.installBrowserTransportFetch(transportOptions)` |
102
+ | `kit.auth.publicPaths` | 匿名端点名单 | `none` | `string[]; prepend the host base path when needed` | `kit.auth.publicPaths` |
103
+ | `kit.auth.createApi` | 认证 handler 集合 | `initialized IAM service` | `RequestHandler map for login/OTP/reset/config/etc.` | `kit.auth.createApi(iam)` |
104
+ | `kit.auth.login` | 密码登录 | `Cookie writer, PasswordCredentials, request context?` | `Promise<HaiResult<AuthResult>>; writes token cookies on success` | `kit.auth.login(cookies, { identifier: "alice", password })` |
105
+ | `kit.auth.loginWithOtp` | 验证码登录 | `Cookie writer, OtpCredentials, request context?` | `Promise<HaiResult<AuthResult>>` | `kit.auth.loginWithOtp(cookies, otpCredentials)` |
106
+ | `kit.auth.loginWithLdap` | LDAP 登录 | `Cookie writer, LdapCredentials, request context?` | `Promise<HaiResult<AuthResult>>` | `kit.auth.loginWithLdap(cookies, ldapCredentials)` |
107
+ | `kit.auth.loginWithApiKey` | API Key 登录 | `Cookie writer, ApiKeyCredentials, request context?` | `Promise<HaiResult<AuthResult>>` | `kit.auth.loginWithApiKey(cookies, apiKeyCredentials)` |
108
+ | `kit.auth.registerAndLogin` | 注册并登录 | `Cookie writer, RegisterOptions, request context?` | `Promise<HaiResult<AuthResult>>` | `kit.auth.registerAndLogin(cookies, registration)` |
109
+ | `kit.auth.logout` | 显式退出 | `Cookie writer, accessToken?` | `Promise<HaiResult<void>>` | `kit.auth.logout(cookies, accessToken)` |
110
+ | `kit.auth.setBrowserToken` | 页面内存凭据 | `access token string` | `void; no Web Storage persistence` | `kit.auth.setBrowserToken(accessToken)` |
111
+ | `kit.auth.clearBrowserToken` | 清理页面凭据 | `none` | `void` | `kit.auth.clearBrowserToken()` |
112
+ | `kit.crud.define` | 声明 CRUD 资源 | `CrudResourceConfig` | `resource operation object` | `kit.crud.define(resourceDefinition)` |
113
+ | `kit.middleware.rateLimit` | 固定窗口限流 | `{ windowMs, maxRequests, store, ...options }` | `Middleware; 429 on quota and 503 on store failure` | `kit.middleware.rateLimit({ windowMs: 60000, maxRequests: 100, store })` |
114
+ | `kit.middleware.createCacheRateLimitStore` | 共享计数器 | `initialized shared cache, { namespace, allowMemory? }` | `RateLimitStore` | `kit.middleware.createCacheRateLimitStore(cache, { namespace: "orders" })` |
115
+ | `kit.middleware.createMemoryRateLimitStore` | 开发计数器 | `none; development or explicit test only` | `MemoryRateLimitStore` | `kit.middleware.createMemoryRateLimitStore()` |
116
+ | `kit.createFeedbackHandler` | 受控反馈入口 | `{ feedback, resolveContext, ...options }` | `catch-all RequestHandler` | `kit.createFeedbackHandler({ feedback, resolveContext })` |
117
+ | `kit.i18n.setLocale` | 启动默认语言 | `locale string` | `void; do not change process default per request` | `kit.i18n.setLocale("zh-CN")` |
118
+ | `kit.i18n.runWithLocale` | 请求语言隔离 | `locale, callback; Node server only` | `same callback return value` | `kit.i18n.runWithLocale("en-US", () => resolve(event))` |
119
+
120
+ - Handle Hook 组合(`createHandle` / `sequence`)
121
+ - 认证/角色/权限守卫(`guard.require` / `guard.check`)
122
+ - 内置 Logging / RateLimit / CORS 中间件(通过 `createHandle` 配置启用)
123
+ - 统一 API 响应与请求验证(Zod)
124
+ - Bearer Token 工具(服务端 login/logout + 浏览器端 Token 存储)
125
+ - 浏览器端统一客户端(自动 CSRF,支持传输加密)
126
+ - 声明式 CRUD 资源定义(`crud.define`)
127
+ - A2A 协议集成:默认发现路径 `/.well-known/agent-card.json`;配置 `authenticate` 后 null/undefined 返回 401,仅未配置认证时允许匿名。API Key 参数从完整 Agent Card 的 `securitySchemes.apiKey` 读取。Hook 与显式 A2A 路由共享 JSON-RPC 解析错误和 SSE 取消清理逻辑。
128
+ - 双构建模式(`createAdapter()`)
84
129
 
85
- #### 搭建请求管道
130
+ ### 搭建请求管道
86
131
 
87
- 应用部署在 `/yyy/` 等子目录时,SvelteKit 的 `kit.paths.base` 与守卫使用的实际 URL 应保持一致:将 `$app/paths` 的 `base` 加到 `protectedPaths`、`publicPaths` 和 `loginUrl`,例如 `${base}/api/*`。认证页面使用 ``kit.client.createAuthActions({ basePath: `${base}/api/auth` })``。Kit 根据应用路由识别 `/api/*`,包括路由分组和挂载前缀;未认证的 API 返回 401,页面按配置跳转登录。生成模板已处理这些路径。
132
+ 应用部署在 `/yyy/` 等子目录时,SvelteKit 的 `paths.base` 与守卫使用的实际 URL 应保持一致:将 `resolve('/').replace(/\/$/, '')`(配置 `paths.relative: false`) 加到 `protectedPaths`、`publicPaths` 和 `loginUrl`,例如 `${base}/api/*`。认证页面使用 ``kit.client.createAuthActions({ basePath: `${base}/api/auth` })``。Kit 根据应用路由识别 `/api/*`,包括路由分组和挂载前缀;未认证的 API 返回 401,页面按配置跳转登录。生成模板已处理这些路径。
88
133
 
89
134
  以下 Hook 片段假设应用已完成数据库、共享缓存和 IAM 初始化,并检查每项初始化结果。关闭资源由应用统一负责;不在每个请求中初始化模块。
90
135
 
91
136
  **src/hooks.server.ts**:
92
137
 
93
138
  ```typescript
94
- import type { Handle } from '@sveltejs/kit'
139
+ import type { Handle } from '@sveltejs/kit/hooks'
95
140
  import { cache } from '@h-ai/cache'
96
141
  import { iam } from '@h-ai/iam'
97
142
  import { kit } from '@h-ai/kit'
@@ -106,7 +151,11 @@ const haiHandle = kit.createHandle({
106
151
  protectedPaths: ['/admin/*', '/api/*'],
107
152
  publicPaths: ['/api/auth/*', '/api/public/*'],
108
153
  },
109
- rateLimit: { windowMs: 60_000, maxRequests: 100, store: kit.middleware.createCacheRateLimitStore(cache, { namespace: 'app' }) },
154
+ rateLimit: {
155
+ windowMs: 60_000,
156
+ maxRequests: 100,
157
+ store: kit.middleware.createCacheRateLimitStore(cache, { namespace: 'app' })
158
+ },
110
159
  logging: true,
111
160
  })
112
161
 
@@ -115,7 +164,7 @@ export const handle: Handle = haiHandle
115
164
 
116
165
  限流采用首次成功提交为起点的固定窗口,后续请求不延长 TTL。必须显式传 store;应用先检查共享 Redis 初始化结果,所有节点使用相同 namespace/windowMs/maxRequests。缓存读取/条件提交失败或冲突耗尽返回503,超限429;不会回退 get+set 或本地计数。createMemoryRateLimitStore 仅开发或显式 HAI_E2E/HAI_E2E_MOCK 测试使用,无常驻定时器,丢弃引用即可回收;每次请求最多惰性扫描 64 项,最多保存 10000 个窗口,容量满时对新 key 返回503且不驱逐有效计数,外部 store 生命周期由创建者管理;生产不得设置测试开关。响应重置时间为节点时钟估算,实际窗口由 Redis TTL 决定,须同步时钟。默认键只读取可信 adapter 的 getClientAddress、规范化等价IPv6和IPv4映射地址,不采信原始转发头;代理信任通过部署adapter配置。
117
166
 
118
- #### API 端点
167
+ ### API 端点
119
168
 
120
169
  ```typescript
121
170
  import { kit } from '@h-ai/kit'
@@ -133,7 +182,7 @@ export const POST = kit.handler(async ({ request, locals }) => {
133
182
  })
134
183
  ```
135
184
 
136
- #### 浏览器客户端
185
+ ### 浏览器客户端
137
186
 
138
187
  `kit.client.create()` 面向 SvelteKit 同源 endpoint;Web / App / 小程序跨域访问公共 API 时,请使用 `@h-ai/api-client`。
139
188
 
@@ -147,7 +196,7 @@ export const { apiFetch } = client
147
196
 
148
197
  ```typescript
149
198
  // 使用
150
- import { apiFetch } from '$lib/utils/api'
199
+ import { apiFetch } from '#lib/utils/api.js'
151
200
 
152
201
  const response = await apiFetch('/api/users', { method: 'GET' })
153
202
  ```
@@ -157,21 +206,23 @@ const response = await apiFetch('/api/users', { method: 'GET' })
157
206
  >
158
207
  > 同源 SvelteKit endpoint 使用 httpOnly Cookie,由服务端读取 Cookie 完成鉴权。浏览器 Bearer Token 使用 `kit.auth.setBrowserToken()` 写入页面内存,再通过 `kit.client.create({ auth: true })` 注入。自定义 `BrowserTokenStore` 也必须使用内存或平台安全存储,禁止将敏感凭据写入 Web Storage。
159
208
 
160
- #### 同源传输加密
209
+ ### 同源传输加密
161
210
 
162
211
  kit 的传输加密也统一委托 `@h-ai/crypto`:服务端通过 `crypto.transport.createServer({ serverKeyRing, keyStore })` 创建管理器,浏览器端通过 `kit.client.installBrowserTransport(kitConfig, { crypto })` 一行安装同源 fetch 包装。服务端还须注入受管密钥环和共享 registry。
163
212
 
164
- 部署到子目录时,从 `$app/paths` 读取 `base`:服务端 `crypto.transport` 传入 `basePath: base`,浏览器调用 `installBrowserTransport(kitConfig, { crypto, basePath: base })`;独立 `kit.client.create` 的 `transport.basePath` 采用同一值。默认空字符串表示域名根目录。这个运行时参数来自宿主的构建目录,不在 YAML 另设一份,也不从转发头推断。`keyExchangePath`、`excludePaths` 和路由容量仍使用完整请求路径。CLI 的 SSR 模板已同步装配。
213
+ 部署到子目录时,配置 `paths.relative: false`,从 `$app/paths` 以 `resolve('/').replace(/\/$/, '')` 得到 `base`:服务端 `crypto.transport` 传入 `basePath: base`,浏览器调用 `installBrowserTransport(kitConfig, { crypto, basePath: base })`;独立 `kit.client.create` 的 `transport.basePath` 采用同一值。默认空字符串表示域名根目录。这个运行时参数来自宿主的构建目录,不在 YAML 另设一份,也不从转发头推断。`keyExchangePath`、`excludePaths` 和路由容量仍使用完整请求路径。CLI 的 SSR 模板已同步装配。
165
214
 
166
215
  **src/hooks.server.ts**:
167
216
 
168
217
  ```ts
169
- import { base } from '$app/paths'
170
- import { appKitConfig } from '$lib/server/kit-config'
218
+ import { resolve } from '$app/paths'
171
219
  import { cache } from '@h-ai/cache'
172
220
  import { createRedisTransportKeyStore, crypto, parseTransportServerKeyRing } from '@h-ai/crypto'
173
221
  import { iam } from '@h-ai/iam'
174
222
  import { kit } from '@h-ai/kit'
223
+ import { appKitConfig } from '#lib/server/kit-config.js'
224
+
225
+ const base = resolve('/').replace(/\/$/, '')
175
226
 
176
227
  const cryptoReady = await crypto.init()
177
228
  if (!cryptoReady.success)
@@ -209,13 +260,15 @@ export const handle = kit.createHandle({
209
260
 
210
261
  ```svelte
211
262
  <script lang='ts'>
212
- import { browser } from '$app/environment'
213
- import { base } from '$app/paths'
263
+ import { browser } from '$app/env'
264
+ import { resolve } from '$app/paths'
214
265
  import { page } from '$app/state'
215
266
  import { crypto } from '@h-ai/crypto'
216
267
  import { kit } from '@h-ai/kit'
217
268
  import { untrack } from 'svelte'
218
269
 
270
+ const base = resolve('/').replace(/\/$/, '')
271
+
219
272
  // 一次性安装:内部按 _kit.yml 决定是否启用 transport,并预热 crypto
220
273
  if (browser) {
221
274
  kit.client.installBrowserTransport(untrack(() => page.data.kitConfig), { crypto, basePath: base })
@@ -239,17 +292,15 @@ export const { apiFetch } = kit.client.create()
239
292
 
240
293
  transport 默认作用于同源 `/api/*` endpoint 与 SvelteKit `__data.json` 页面数据请求(以及密钥协商端点本身);页面文档与静态资源仍保持明文,不要求携带 `X-Client-Id`。文件上传等 `multipart/form-data` 请求会保持原样发送,需在服务端 `excludePaths` 中显式放行对应上传路径。
241
294
 
242
- 认证协议仅支持 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/可靠持久化策略;各节点时钟须同步。此能力防止同一传输消息重放,业务重试仍需自己的幂等键。
243
-
244
- 空 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 业务授权。
295
+ 传输使用认证 v2 封装,先验签和 MAC 再解密,并绑定方法、路径、时间与响应状态。共享 registry 原子消费 nonce;TLS、可信代理、业务授权及幂等仍由宿主负责。自定义跨域入口须允许并暴露 `X-Hai-Transport`。完整字段、无正文响应、时钟窗口和防重放要求统一见 [Transport 协议](../crypto/TRANSPORT-PROTOCOL.md)。
245
296
 
246
297
  业务容量通过 `transport.maxPlaintextBytes` 统一配置:默认 1 MiB,整数范围 1 KiB~64 MiB,请求和响应共用 UTF-8 明文字节预算。客户端从握手自动获取上限,密文与部署代理预算由 `resolveTransportBodyLimits()` 推导(两倍明文 + 32 KiB)。握手、公钥、认证头和防重放约束独立;超限取消原流并释放 reader,请求 413、响应为加密的 500 容量错误,客户端识别 `TransportBodyTooLargeError` / `hai:crypto:070`。 [Transport capacity and upgrade](../crypto/TRANSPORT-CAPACITY.md)。
247
298
 
248
299
  普通业务仍使用 `transport.maxPlaintextBytes`(auto/省略为 1 MiB);Feedback 的报告受理、证据、附件及 ZIP 下载路由通过 `routePlaintextBytes` 单独声明 `feedback.budget.maxTransportBytes`。路径中的 `*` 仅匹配一个完整路径段;宿主显式路由值优先。握手、adapter-node 与代理使用各路由最大容量,普通业务不会随材料预算扩大。HTTP 适配器为单独声明且大于 1 MiB 的材料路由保留最多 2 个槽,普通业务另有 16 个槽,完整响应/失败后释放;忙碌返回 503 / `hai:crypto:072`,不自动重放业务写入。
249
300
 
250
- serv/kit 对通过请求绑定、签名和 MAC 校验但超出时间窗口的报文返回加密的 HTTP 400 时间错误;客户端抛 `TransportTimeError`(`hai:crypto:071`),提示同步设备和服务器时间后刷新重试。`api-client` 映射为 `VALIDATION_FAILED`,在 `cause.transportCode` 保留该原因。不会自动重放请求或放宽防重放窗口;篡改或未认证报文仍返回通用认证失败。
301
+ Serv/Kit 对通过请求绑定、签名和 MAC 校验但超出时间窗口的请求返回加密的 HTTP 400 错误。客户端抛出 `TransportTimeError`(`hai:crypto:071`),显示校验时的服务器 UTC 时间,提示开启设备的自动校时或手工修改时间后刷新。`serverTime` 为 Unix 毫秒,旧服务端未提供该值时仍显示校时提示。`api-client` 映射为 `VALIDATION_FAILED`,在 `cause.transportCode` 和 `cause.serverTime` 保留诊断信息。标准认证动作将已认证的传输时间错误转为 `HaiResult`,保留校验时间和校时提示,AuthPanel 可直接展示。
251
302
 
252
- #### 使用 `_kit.yml` 统一 transport 配置
303
+ ### 使用 `_kit.yml` 统一 transport 配置
253
304
 
254
305
  `@h-ai/kit` 提供 `KitConfigSchema` / `resolveKitConfig()`,方便应用用同一份 `_kit.yml`
255
306
  同时驱动 `hooks.server.ts` 与浏览器端 `kit.client.installBrowserTransport()`:
@@ -282,7 +333,7 @@ export const publicKitConfig = { transport: appKitConfig.transport }
282
333
  在 `src/routes/+layout.server.ts` 将公共投影交给上述浏览器布局,不导入服务端配置文件:
283
334
 
284
335
  ```ts
285
- import { publicKitConfig } from '$lib/server/kit-config'
336
+ import { publicKitConfig } from '#lib/server/kit-config.js'
286
337
 
287
338
  export function load() {
288
339
  return { kitConfig: publicKitConfig }
@@ -293,30 +344,10 @@ export function load() {
293
344
  如只在服务端读取配置,也可配合 `core.config.validate('kit', KitConfigSchema)` 使用。
294
345
  完整 `_kit.yml` 由服务端读取,可包含内部代理网段和访问策略文件路径。`+layout.server.ts` 只返回 `{ kitConfig: publicKitConfig }` 等明确公共投影,浏览器消费其中的 transport;禁止用 `?raw` 把完整配置或策略文件打入客户端,也不要在其中写入密钥。
295
346
 
296
- ### 配置
297
-
298
- `kit.createHandle()` 配置项:
299
-
300
- | 字段 | 类型 | 说明 |
301
- | ---------------- | ---------------------------------------- | ------------------------------------------------------------------ |
302
- | `auth` | `HandleAuthConfig` | 认证配置(verifyToken / protectedPaths / publicPaths / loginUrl) |
303
- | `trustedProxies` | `TrustedProxies` | 默认 `auto`;普通宿主忽略转发头,受管私有 Compose 代理采用容器网段 |
304
- | `access` | `RequestAccessConfigInput` | 默认 `false`;按路径、IP 和时间检查每次请求,可引用独立 YAML |
305
- | `rateLimit` | `object \| false` | 速率限制(windowMs / maxRequests) |
306
- | `logging` | `boolean \| object` | 请求日志(默认 `true`,`{ logBody: true }` 记录请求体) |
307
- | `crypto` | `HookCryptoConfig` | 传输加密 + Cookie 加密 |
308
- | `guards` | `GuardConfig[]` | 自定义守卫(在 auth 自动守卫之后执行) |
309
- | `middleware` | `Middleware[]` | 自定义中间件(在内置中间件之后执行) |
310
- | `a2a` | `HandleA2AOperations \| HandleA2AConfig` | A2A 协议集成(Agent Card + JSON-RPC) |
311
- | `onError` | `function` | 自定义错误处理 |
312
-
313
- 请求/响应摘要使用 `trace` 日志级别;`logging` 默认开启,`logBody` 默认关闭。日志查询从原始 `request.url` 读取,路径保留 SvelteKit 当前路由的 `event.url.pathname`,不会读取预渲染受限的 `event.url.search/searchParams`;静态页无需为构建关闭日志。查询和启用后的 JSON 正文在日志入口对 password/token/secret、授权/API Key 等字段脱敏,正文使用请求副本读取,不影响业务处理。
314
-
315
- 请求自动审计仅在 `audit.isInitialized` 时执行,纯静态站点或未选 Audit 的应用无需为 handle 初始化它。该状态在每个请求中判断,因此 handle 可先创建、模块随后初始化。选择了 Audit 的应用仍必须检查 `audit.init()` 结果并在失败时停止启动;业务显式 `audit.log/capture` 的未初始化错误不变,已初始化模块的真实写入失败仍记录 `Request audit failed`。需要强制审计的业务应检查显式审计结果,并按业务事务约束提交。
316
-
317
- 配置 `crypto.encryptedCookies` 时必须同时提供 `crypto.cookieEncryptionKey`,或设置 `HAI_KIT_COOKIE_KEY`。缺少密钥会在 `createHandle()` 时立即报错;受保护 Cookie 的明文、损坏密文或解密失败都按不存在处理,加密失败也不会回退写入明文。
347
+ ### 依赖
318
348
 
319
- 认证 Cookie 的 `Secure` 默认值在 `NODE_ENV` 或 `HAI_CORE_ENV` 为 `production` 时开启,`HAI_E2E` / `HAI_E2E_MOCK` 不降低 Cookie 安全要求。生产限流同样识别这两个环境标志;仅显式测试开关可放宽内存计数器限制。
349
+ - `@h-ai/iam` — 身份认证与授权(A2A 认证),可选;使用 A2A 功能时需在 kit 使用前初始化
350
+ - `@h-ai/crypto` — 加密(传输加密 / Cookie 加密),可选;启用加密时需在 kit 使用前初始化
320
351
 
321
352
  ### 导出路径
322
353
 
@@ -340,12 +371,6 @@ export function load() {
340
371
 
341
372
  `kit.auth.logout(cookies, accessToken)` 返回 `Promise<HaiResult<void>>`。无令牌时仅清除 Cookie 并成功;有令牌但未配置认证操作时返回失败。无论服务端结果如何都清除本地 Cookie,调用方仍必须把失败转换为错误响应或提示;仅成功结果代表服务端撤销已经确认。底层控制流异常继续传播。
342
373
 
343
- ### 错误处理
344
-
345
- - 推荐使用 `kit.handler()` 包装 API 端点。
346
- - `kit.validate.body` / `kit.guard.require` 抛出 `Response`,由 `kit.handler` 处理 API 控制流。页面 `load` 使用 `kit.guard.check` 和 SvelteKit `error`;校验查询参数用 `schema.safeParse` 后返回原生页面错误,不能直接抛 API Response。
347
- - 非控制流异常会由 `kit.handler()` 统一转换为 `500 INTERNAL_ERROR` 响应。
348
-
349
374
  ### CRUD fields
350
375
 
351
376
  声明式 CRUD 字段支持 `colSpan: 'auto' | 1 | 2`,供 UI 双列表单决定字段宽度;`validation.message` 支持字符串或 `() => string`,便于随当前语言显示校验错误。UI 布局在 `CrudPage.form` 上配置,服务端仍须独立校验。
@@ -356,7 +381,7 @@ Node 请求与 SSR 使用 `core.i18n.runWithLocale(locale, () => handleRequest()
356
381
 
357
382
  浏览器继续使用应用级 `setGlobalLocale` / `createLocaleStore`。SSR 的 UI store 初始化和 setter 只修改自身状态,不写全局语言;UI 消息从请求上下文读取。`runWithLocale` 仅支持 Node 入口,浏览器通过显式 locale 或应用状态翻译。
358
383
 
359
- ## 反馈接口与普通请求关联
384
+ ### 反馈接口与普通请求关联
360
385
 
361
386
  `kit.createFeedbackHandler` 可作为 `/api/feedback/[...path]/+server.ts` 的 GET/POST/PATCH handler,复用 api-contract schema,与 serv 使用同一数据契约;输入流受大小上限约束,客户端身份/租户字段被拒绝。宿主通过 resolveContext 从已经验证的 locals.session 映射范围和权限,null 拒绝访问,并保留现有 createHandle 的 CSRF、认证与传输加密。
362
387
 
@@ -381,35 +406,21 @@ export const POST = handler
381
406
  export const PATCH = handler
382
407
  ```
383
408
 
384
- ## 错误提示与关联日志
385
-
386
- HTTP 错误返回本地化安全文案及追踪编号;ToastContainer 默认隐藏编号,由排查信息图标展开和复制。响应同时包含 `error.traceId` 和 `X-Trace-Id`。框架复用本次 requestId 作为 traceId,前后端日志自动关联。底层异常、堆栈和操作上下文经凭据脱敏后写入日志,不通过 cause/ext 向用户公开。字段校验只返回本地化的字段名和错误。
387
-
388
- `core.error.toPublic(error, traceId?)` 用于接口/UI 边界;`core.error.formatMessage(error)` 用于重新翻译后的提示,重复调用不会重复追加标识。日志传入完整 Error 对象,不能只记录通用标题或提前 String(error) 丢失上下文。数据库失败记录驱动错误码、消息、堆栈及表/列;SQL 原文、绑定值和连接凭据不进入日志。
389
-
390
- audit 已初始化时,createHandle 为业务 audit.log 绑定已验证会话的身份/角色快照、平台 getClientAddress 地址和请求编号。自动入口事件 request.execute 包含路由模板、方法、状态和成功/失败/拒绝,包含守卫跳转及 SvelteKit HTTP 控制流;不复制请求体或错误详情。capture.enabled 可关闭自动采集,capture.includeReads 可开启成功读取;默认保留失败读取、跳过成功读取。业务含义仍在应用完成/失败边界显式记录,关闭自动采集不影响业务 log。
391
-
392
- ## 可选交互挑战
409
+ ### 可选交互挑战
393
410
 
394
411
  将 kit.auth.createApi(iam).createCaptcha 导出为 POST /api/auth/captcha;kit.auth.publicPaths 已包含它。kit.client.createAuthActions() 保留标准交互请求的 captcha 字段,并提供 createCaptcha;AuthPanel 提交时自动求解。匿名首次/过期改密仍须导出 /api/auth/password/renew。服务端 IAM 检查证明,开启策略不会只依赖界面。
395
412
 
396
413
  策略、默认值和恢复边界见 [IAM](../iam/README.md)。
397
414
 
398
- ## 原地恢复过期会话
415
+ ### 原地恢复过期会话
399
416
 
400
417
  配置 `kit.client.create({ recoverSession })` 回调可在服务端明确拒绝会话时原地重新登录并保留页面输入。使用 `@h-ai/ui` 的 `createSessionRecovery` / `SessionRecoveryDialog`:根布局绑定公开配置与原用户,回调设为 `recovery.recover`,弹窗使用标准认证 actions,销毁时 `cancel()`。登录的 `expectedUserId` 由 IAM 限定原主体;常规验证码、锁定、改密和登录限制照常执行。只对明确未认证拒绝的原请求续接一次;取消、5xx、超时和结果不明的网络写入不重放。敏感操作证明绑定旧会话时可能需要再次确认,不复用到新会话。标准认证动作显式禁用自身的恢复重试,避免错误密码递归打开弹窗。自定义认证请求应传第三个参数 `{ recoverSession: false }`。终端分组通过 terminalType(web/app/desktop/api,默认 web)传入;它不是硬件身份认证。
401
418
 
402
- ## 页面离开会话策略
419
+ ### 页面离开会话策略
403
420
 
404
421
  `session.endOnPageHide` 默认 `false`。启用后宿主调用 `actions.bindPageExit({ enabled: config.session.endOnPageHide })` 并在卸载时调用返回的清理函数;标准 admin/H5/fullstack 模板已经接入。非 BFCache 的 `pagehide`(也包括刷新、整页导航)通过 POST `/auth/session/close` 尽力撤销当前 access 及关联 refresh,沿用 CSRF/传输加密,使用 `keepalive` 且不自动刷新会话。该响应不清 Cookie/本地凭据,避免旧响应覆盖下一次登录。显式退出仍使用正常 logout,可靠撤销并清理当前凭据。浏览器可能不发送页面离开请求,因此必须按业务配合服务端 `idleTimeout`/`absoluteTimeout`;不能据此保证关闭即撤销。
405
422
 
406
- ## 传输容量与模块预算
407
-
408
- 配置 `transport.maxPlaintextBytes` 支持 1 KiB~64 MiB 的显式整数或 `auto`。auto/省略在解析后为 undefined:无模块需求时仍为 1 MiB,反馈宿主使用 crypto.resolveTransportBodyLimits 的 requiredPlaintextBytes 统一推导。显式上限不足时启动/打包失败,不能被反馈覆盖;代理、adapter-node、握手与运行时使用一致预算。业务附件、解压和数据行数上限继续独立校验。
409
-
410
- 敏感调用通过本次 `reauthorize: () => sensitiveActions.authorize(operation)` 重新确认用途:Kit 放 apiFetch 第三参数,typed client 放 options.context。会话刷新/恢复或明确的再认证拒绝后仅续发一次;取消不写入,未提供回调不重放旧证明。
411
-
412
- ## 每次请求的访问控制
423
+ ### 每次请求的访问控制
413
424
 
414
425
  路径规则的 `encryption` 可选 required / disabled,省略沿用宿主。disabled 不解密请求或加密响应,仍执行访问和身份检查;不接受框架传输信封。required 可保护自定义 JSON 端点并优先于 `excludePaths`,没有 Crypto、密钥环和 registry 时不能启用。模式冲突在创建 Handle 时拒绝。CLI 的 crypto 模板同步装配服务端和浏览器,生产选择 db 和共享数据库;策略及代理配置不下发浏览器,明文调用方按公开传输排除路径选择协议。
415
426
 
@@ -419,6 +430,76 @@ audit 已初始化时,createHandle 为业务 audit.log 绑定已验证会话
419
430
 
420
431
  `trustedProxies: auto` 在普通宿主不信任转发头;受管 Compose 只有后端私有且存在入口代理时才采用容器网段。自管入口显式指定代理 IP/CIDR,`[]` 可关闭。解析后的 `event.getClientAddress()` 与策略、IAM、审计使用相同地址。外层代理、不可恢复的源地址丢失、环境变量与部署回滚见[请求访问控制手册](../../docs/usage/configuration/request-access.md)。
421
432
 
433
+ ## 配置
434
+
435
+ ### 按场景选择配置入口
436
+
437
+ | 配置入口 | 场景 | 输入 | 生效结果 | 示例 |
438
+ | ----------------------------------------- | ---------------- | ---------------------------------------------------------------- | ------------------------------------------------------------- | ----------------------------------------------- |
439
+ | `server.host / server.port` | 服务监听 | host string, port integer; HOST/PORT override | resolved Kit server config; default 127.0.0.1:3000 | `server: { host: 127.0.0.1, port: 3000 }` |
440
+ | `transport` | 同源认证加密 | false or keyExchangePath/excludePaths/byte/time/route budgets | public transport config for hook and browser; no private keys | `transport: { maxPlaintextBytes: auto }` |
441
+ | `trustedProxies` | 可信连接来源 | auto or explicit IP/CIDR list; [] disables forwarded headers | normalized client address for access policy | `trustedProxies: []` |
442
+ | `access` | 请求访问策略 | false or independent policy configuration | per-request IP/time/path checks; not business ownership | `access: false` |
443
+ | `createHandle.auth / guards / middleware` | 宿主鉴权管道 | verifyToken/operations, paths and guard/middleware callbacks | RequestEvent locals/session and rejection Response | `kit.createHandle({ auth: authOptions })` |
444
+ | `createHandle.rateLimit` | 共享限流 | windowMs, maxRequests and explicit atomic store | fixed window; 429 over quota, 503 on unavailable storage | `kit.createHandle({ rateLimit: limitOptions })` |
445
+ | `createHandle.crypto` | 注入私密运行依赖 | initialized crypto, serverKeyRing/keyStore, encryptedCookies/key | authenticated payload/cookies; ring stays server-only | `kit.createHandle({ crypto: cryptoOptions })` |
446
+ | `client.create` | 浏览器请求选项 | auth memory store, CSRF names, transport, recoverSession | same-origin apiFetch plus init/destroy/ready | `kit.client.create({ auth: true })` |
447
+
448
+ 使用 pnpm 构建 Svelte 应用时,工程需要在 `patchedDependencies` 注册 `deepmerge@4.3.1` 对应的补丁,并保留实际 patch 文件;采用 DaisyUI 时同样保留其样式补丁。CLI 生成工程已携带适用项。安装框架 npm 包不会把框架仓库的 pnpm 补丁配置自动传递到你的工程,手工接入应同步[补丁目录](../../patches/)及相应注册后重新安装依赖。
449
+
450
+ `kit.createHandle()` 配置项:
451
+
452
+ | 字段 | 类型 | 说明 |
453
+ | ---------------- | ---------------------------------------- | ------------------------------------------------------------------ |
454
+ | `auth` | `HandleAuthConfig` | 认证配置(verifyToken / protectedPaths / publicPaths / loginUrl) |
455
+ | `trustedProxies` | `TrustedProxies` | 默认 `auto`;普通宿主忽略转发头,受管私有 Compose 代理采用容器网段 |
456
+ | `access` | `RequestAccessConfigInput` | 默认 `false`;按路径、IP 和时间检查每次请求,可引用独立 YAML |
457
+ | `rateLimit` | `object \| false` | 速率限制(windowMs / maxRequests) |
458
+ | `logging` | `boolean \| object` | 请求日志(默认 `true`,`{ logBody: true }` 记录请求体) |
459
+ | `crypto` | `HookCryptoConfig` | 传输加密 + Cookie 加密 |
460
+ | `guards` | `GuardConfig[]` | 自定义守卫(在 auth 自动守卫之后执行) |
461
+ | `middleware` | `Middleware[]` | 自定义中间件(在内置中间件之后执行) |
462
+ | `a2a` | `HandleA2AOperations \| HandleA2AConfig` | A2A 协议集成(Agent Card + JSON-RPC) |
463
+ | `onError` | `function` | 自定义错误处理 |
464
+
465
+ 请求/响应摘要使用 `trace` 日志级别;`logging` 默认开启,`logBody` 默认关闭。日志查询从原始 `request.url` 读取,路径保留 SvelteKit 当前路由的 `event.url.pathname`,不会读取预渲染受限的 `event.url.search/searchParams`;静态页无需为构建关闭日志。查询和启用后的 JSON 正文在日志入口对 password/token/secret、授权/API Key 等字段脱敏,正文使用请求副本读取,不影响业务处理。
466
+
467
+ 请求自动审计仅在 `audit.isInitialized` 时执行,纯静态站点或未选 Audit 的应用无需为 handle 初始化它。该状态在每个请求中判断,因此 handle 可先创建、模块随后初始化。选择了 Audit 的应用仍必须检查 `audit.init()` 结果并在失败时停止启动;业务显式 `audit.log/capture` 的未初始化错误不变,已初始化模块的真实写入失败仍记录 `Request audit failed`。需要强制审计的业务应检查显式审计结果,并按业务事务约束提交。
468
+
469
+ 配置 `crypto.encryptedCookies` 时必须同时提供 `crypto.cookieEncryptionKey`,或设置 `HAI_KIT_COOKIE_KEY`。缺少密钥会在 `createHandle()` 时立即报错;受保护 Cookie 的明文、损坏密文或解密失败都按不存在处理,加密失败也不会回退写入明文。
470
+
471
+ 密钥必须是 32 位十六进制字符串。Cookie 使用独立派生的 SM4-CBC/HMAC-SHA256 密钥,认证版本、Cookie 名、IV 与密文后才解密,防止修改或跨名称替换。未通过认证的 Cookie 按无效处理。
472
+
473
+ 认证 Cookie 的 `Secure` 默认值在 `NODE_ENV` 或 `HAI_CORE_ENV` 为 `production` 时开启,`HAI_E2E` / `HAI_E2E_MOCK` 不降低 Cookie 安全要求。生产限流同样识别这两个环境标志;仅显式测试开关可放宽内存计数器限制。
474
+
475
+ ### 传输容量与模块预算
476
+
477
+ 配置 `transport.maxPlaintextBytes` 支持 1 KiB~64 MiB 的显式整数或 `auto`。auto/省略在解析后为 undefined:无模块需求时仍为 1 MiB,反馈宿主使用 resolveTransportBodyLimits 的 requiredPlaintextBytes 统一推导。显式上限不足时启动/打包失败,不能被反馈覆盖;代理、adapter-node、握手与运行时使用一致预算。业务附件、解压和数据行数上限继续独立校验。
478
+
479
+ 敏感调用通过本次 `reauthorize: () => sensitiveActions.authorize(operation)` 重新确认用途:Kit 放 apiFetch 第三参数,typed client 放 options.context。会话刷新/恢复或明确的再认证拒绝后仅续发一次;取消不写入,未提供回调不重放旧证明。
480
+
481
+ ## 错误处理
482
+
483
+ - 推荐使用 `kit.handler()` 包装 API 端点。
484
+ - `kit.validate.body` / `kit.guard.require` 抛出 `Response`,由 `kit.handler` 处理 API 控制流。页面 `load` 使用 `kit.guard.check` 和 SvelteKit `error`;校验查询参数用 `schema.safeParse` 后返回原生页面错误,不能直接抛 API Response。
485
+ - 非控制流异常会由 `kit.handler()` 统一转换为 `500 INTERNAL_ERROR` 响应。
486
+
487
+ ### 错误提示与关联日志
488
+
489
+ `kit.response.fromError/fromResult` 保留安全的字段校验 `cause`、显式公开的 `publicExt`(映射为 `ext`)和 `httpStatus`;任意内部 cause/ext 不公开。每次模块失败只写一条关联日志。
490
+
491
+ HTTP/UI 边界只对明确的有效 4xx 状态公开业务提示;错误缺少 `httpStatus` 或状态非法时按 500 返回安全提示。手写业务错误须提供完整错误定义或显式状态,不能依赖缺省状态展示原文。
492
+
493
+ `kit.response.validationError(errors)` 也沿用同一白名单:响应 `error.cause` 只含本地化的 `field/message`,不回显 rejectedValue、SQL 或其他额外字段,字段校验从 `error.cause` 读取。
494
+
495
+ 快捷响应、认证守卫和限流均使用 `HaiCommonError` 标准错误码,附带 `httpStatus`、`traceId` 与 `X-Trace-Id`。未认证返回 `UNAUTHORIZED.code`,无权限返回 `FORBIDDEN.code`,限流返回 `RATE_LIMIT_EXCEEDED.code`(429);限流存储故障返回安全的 503,原始原因只记服务端日志。
496
+
497
+ HTTP 错误返回本地化安全文案及追踪编号;ToastContainer 默认隐藏编号,由排查信息图标展开和复制。响应同时包含 `error.traceId` 和 `X-Trace-Id`。框架复用本次 requestId 作为 traceId,前后端日志自动关联。底层异常、堆栈和操作上下文经凭据脱敏后写入日志,不通过 cause/ext 向用户公开。字段校验只返回本地化的字段名和错误。
498
+
499
+ `core.error.toPublic(error, traceId?)` 用于接口/UI 边界;`core.error.formatMessage(error)` 用于重新翻译后的提示,重复调用不会重复追加标识。日志传入完整 Error 对象,不能只记录通用标题或提前 String(error) 丢失上下文。数据库失败记录驱动错误码、消息、堆栈及表/列;SQL 原文、绑定值和连接凭据不进入日志。
500
+
501
+ audit 已初始化时,createHandle 为业务 audit.log 绑定已验证会话的身份/角色快照、平台 getClientAddress 地址和请求编号。自动入口事件 request.execute 包含路由模板、方法、状态和成功/失败/拒绝,包含守卫跳转及 SvelteKit HTTP 控制流;不复制请求体或错误详情。capture.enabled 可关闭自动采集,capture.includeReads 可开启成功读取;默认保留失败读取、跳过成功读取。业务含义仍在应用完成/失败边界显式记录,关闭自动采集不影响业务 log。
502
+
422
503
  ## 开发测试
423
504
 
424
505
  ```bash
@@ -8,14 +8,14 @@ import * as _sveltejs_kit from '@sveltejs/kit';
8
8
  *
9
9
  * @example
10
10
  * ```js
11
- * // svelte.config.js
11
+ * // vite.config.ts
12
12
  * import { createAdapter } from '@h-ai/kit/adapter'
13
+ * import { sveltekit } from '@sveltejs/kit/vite'
14
+ * import { defineConfig } from 'vite'
13
15
  *
14
- * export default {
15
- * kit: {
16
- * adapter: await createAdapter(),
17
- * },
18
- * }
16
+ * export default defineConfig({
17
+ * plugins: [sveltekit({ adapter: await createAdapter() })],
18
+ * })
19
19
  * ```
20
20
  *
21
21
  * @module kit-adapter
@@ -56,4 +56,5 @@ interface AdapterOptions {
56
56
  */
57
57
  declare function createAdapter(options?: AdapterOptions): Promise<_sveltejs_kit.Adapter>;
58
58
 
59
- export { type AdapterOptions, createAdapter };
59
+ export { createAdapter };
60
+ export type { AdapterOptions };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/kit-adapter.ts"],"names":[],"mappings":";;;AA0DA,eAAsB,cAAc,OAAA,EAA0B;AAC5D,EAAA,MAAM;AAAA,IACJ,YAAA,GAAe,OAAA;AAAA,IACf,cAAA,GAAiB,cAAA;AAAA,IACjB,cAAA,GAAiB;AAAA,GACnB,GAAI,WAAW,EAAC;AAEhB,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,GAAA,CAAI,YAAA,IAAgB,MAAA;AAEzC,EAAA,IAAI,SAAS,QAAA,EAAU;AACrB,IAAA,MAAM,EAAE,OAAA,EAAS,aAAA,EAAc,GAAI,MAAM,OAAO,0BAA0B,CAAA;AAC1E,IAAA,OAAO,aAAA,CAAc;AAAA,MACnB,KAAA,EAAO,cAAA;AAAA,MACP,MAAA,EAAQ,cAAA;AAAA,MACR,QAAA,EAAU,cAAA;AAAA,MACV,WAAA,EAAa;AAAA,KACd,CAAA;AAAA,EACH;AAGA,EAAA,MAAM,EAAE,OAAA,EAAS,WAAA,EAAY,GAAI,MAAM,OAAO,wBAAwB,CAAA;AACtE,EAAA,OAAO,WAAA,CAAY;AAAA,IACjB,GAAA,EAAK;AAAA,GACN,CAAA;AACH","file":"index.js","sourcesContent":["/**\n * SvelteKit 双构建模式 — 适配器工厂\n *\n * 根据 `VITE_ADAPTER` 环境变量自动选择 adapter-node 或 adapter-static,\n * 使同一个 SvelteKit 应用能同时产出 SSR(Web + API)和 SPA(H5/App)两种构建产物。\n *\n * @example\n * ```js\n * // svelte.config.js\n * import { createAdapter } from '@h-ai/kit/adapter'\n *\n * export default {\n * kit: {\n * adapter: await createAdapter(),\n * },\n * }\n * ```\n *\n * @module kit-adapter\n */\n\nimport process from 'node:process'\n\n/**\n * 双构建适配器配置\n * @example 模块内部上下文\n * ```ts\n * const options: AdapterOptions = {}\n * const adapter = await createAdapter(options)\n * void adapter\n * ```\n */\nexport interface AdapterOptions {\n /** adapter-node 输出目录,默认 `'build'` */\n nodeBuildDir?: string\n /** adapter-static 输出目录,默认 `'build-static'` */\n staticBuildDir?: string\n /** adapter-static 的 SPA fallback 页面,默认 `'index.html'` */\n staticFallback?: string\n}\n\n/**\n * 根据环境变量 `VITE_ADAPTER` 选择 SvelteKit adapter\n *\n * - `VITE_ADAPTER=node` → `@sveltejs/adapter-node`(SSR + API)\n * - `VITE_ADAPTER=static` → `@sveltejs/adapter-static`(SPA,可被 Capacitor 包装)\n * - 未设置时默认使用 `node`\n *\n * **注意**:adapter-node / adapter-static 仍然需要在应用的 devDependencies 中声明。\n * 本函数只做动态 import + 统一配置。\n * @param options - Node/static 输出目录和 SPA fallback;应用仍需安装对应适配器。\n * @returns 按 VITE_ADAPTER 选择的 SvelteKit adapter;缺失依赖时拒绝 Promise。\n * @example 模块内部上下文\n * ```ts\n * const adapter = await createAdapter({})\n * void adapter\n * ```\n */\nexport async function createAdapter(options?: AdapterOptions) {\n const {\n nodeBuildDir = 'build',\n staticBuildDir = 'build-static',\n staticFallback = 'index.html',\n } = options ?? {}\n\n const mode = process.env.VITE_ADAPTER ?? 'node'\n\n if (mode === 'static') {\n const { default: adapterStatic } = await import('@sveltejs/adapter-static')\n return adapterStatic({\n pages: staticBuildDir,\n assets: staticBuildDir,\n fallback: staticFallback,\n precompress: false,\n })\n }\n\n // 默认 node\n const { default: adapterNode } = await import('@sveltejs/adapter-node')\n return adapterNode({\n out: nodeBuildDir,\n })\n}\n"]}
1
+ {"version":3,"sources":["../../src/kit-adapter.ts"],"names":[],"mappings":";;;AA0DA,eAAsB,cAAc,OAAA,EAA0B;AAC5D,EAAA,MAAM;AAAA,IACJ,YAAA,GAAe,OAAA;AAAA,IACf,cAAA,GAAiB,cAAA;AAAA,IACjB,cAAA,GAAiB;AAAA,GACnB,GAAI,WAAW,EAAC;AAEhB,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,GAAA,CAAI,YAAA,IAAgB,MAAA;AAEzC,EAAA,IAAI,SAAS,QAAA,EAAU;AACrB,IAAA,MAAM,EAAE,OAAA,EAAS,aAAA,EAAc,GAAI,MAAM,OAAO,0BAA0B,CAAA;AAC1E,IAAA,OAAO,aAAA,CAAc;AAAA,MACnB,KAAA,EAAO,cAAA;AAAA,MACP,MAAA,EAAQ,cAAA;AAAA,MACR,QAAA,EAAU,cAAA;AAAA,MACV,WAAA,EAAa;AAAA,KACd,CAAA;AAAA,EACH;AAGA,EAAA,MAAM,EAAE,OAAA,EAAS,WAAA,EAAY,GAAI,MAAM,OAAO,wBAAwB,CAAA;AACtE,EAAA,OAAO,WAAA,CAAY;AAAA,IACjB,GAAA,EAAK;AAAA,GACN,CAAA;AACH","file":"index.js","sourcesContent":["/**\n * SvelteKit 双构建模式 — 适配器工厂\n *\n * 根据 `VITE_ADAPTER` 环境变量自动选择 adapter-node 或 adapter-static,\n * 使同一个 SvelteKit 应用能同时产出 SSR(Web + API)和 SPA(H5/App)两种构建产物。\n *\n * @example\n * ```js\n * // vite.config.ts\n * import { createAdapter } from '@h-ai/kit/adapter'\n * import { sveltekit } from '@sveltejs/kit/vite'\n * import { defineConfig } from 'vite'\n *\n * export default defineConfig({\n * plugins: [sveltekit({ adapter: await createAdapter() })],\n * })\n * ```\n *\n * @module kit-adapter\n */\n\nimport process from 'node:process'\n\n/**\n * 双构建适配器配置\n * @example 模块内部上下文\n * ```ts\n * const options: AdapterOptions = {}\n * const adapter = await createAdapter(options)\n * void adapter\n * ```\n */\nexport interface AdapterOptions {\n /** adapter-node 输出目录,默认 `'build'` */\n nodeBuildDir?: string\n /** adapter-static 输出目录,默认 `'build-static'` */\n staticBuildDir?: string\n /** adapter-static 的 SPA fallback 页面,默认 `'index.html'` */\n staticFallback?: string\n}\n\n/**\n * 根据环境变量 `VITE_ADAPTER` 选择 SvelteKit adapter\n *\n * - `VITE_ADAPTER=node` → `@sveltejs/adapter-node`(SSR + API)\n * - `VITE_ADAPTER=static` → `@sveltejs/adapter-static`(SPA,可被 Capacitor 包装)\n * - 未设置时默认使用 `node`\n *\n * **注意**:adapter-node / adapter-static 仍然需要在应用的 devDependencies 中声明。\n * 本函数只做动态 import + 统一配置。\n * @param options - Node/static 输出目录和 SPA fallback;应用仍需安装对应适配器。\n * @returns 按 VITE_ADAPTER 选择的 SvelteKit adapter;缺失依赖时拒绝 Promise。\n * @example 模块内部上下文\n * ```ts\n * const adapter = await createAdapter({})\n * void adapter\n * ```\n */\nexport async function createAdapter(options?: AdapterOptions) {\n const {\n nodeBuildDir = 'build',\n staticBuildDir = 'build-static',\n staticFallback = 'index.html',\n } = options ?? {}\n\n const mode = process.env.VITE_ADAPTER ?? 'node'\n\n if (mode === 'static') {\n const { default: adapterStatic } = await import('@sveltejs/adapter-static')\n return adapterStatic({\n pages: staticBuildDir,\n assets: staticBuildDir,\n fallback: staticFallback,\n precompress: false,\n })\n }\n\n // 默认 node\n const { default: adapterNode } = await import('@sveltejs/adapter-node')\n return adapterNode({\n out: nodeBuildDir,\n })\n}\n"]}
package/dist/browser.d.ts CHANGED
@@ -1,14 +1,14 @@
1
- import { c as createKitClient, a as createKitAuthActions, i as installBrowserTransport, b as installBrowserTransportFetch, s as setBrowserToken, d as clearBrowserToken } from './kit-client-auth-BnhrkzZF.js';
2
- export { C as ClientTransportConfig, K as KitClient, e as KitClientConfig } from './kit-client-auth-BnhrkzZF.js';
3
- import { d as defineCrud, s as setAllModulesLocale } from './kit-i18n-EBzZKYN-.js';
4
- export { K as KitConfigSchema, r as resolveKitConfig, a as resolveKitTransportConfig } from './kit-i18n-EBzZKYN-.js';
5
- export { A as ApiResponse, a as AuthOperations, b as AuthOperationsProvider, C as CorsConfig, F as FormError, c as FormValidationResult, G as GuardConfig, d as GuardResult, H as HaiRequestEvent, e as HandleA2AConfig, f as HandleA2AOperations, g as HandleAuthConfig, h as HandleConfig, i as HookCryptoConfig, K as KitConfig, j as KitConfigInput, k as KitPublicConfig, l as KitServerConfig, m as KitServerConfigInput, n as KitTransportConfig, o as KitTransportConfigInput, M as Middleware, p as MiddlewareContext, R as RateLimitConfig, q as RouteGuard, S as SessionData, r as SessionLike, T as TransportEncryptionOptions } from './kit-types-BkNdyKj8.js';
6
- import '@h-ai/api-contract';
1
+ import { B as BrowserKitFunctions } from './kit-types-Cr1cnB3M.js';
2
+ export { A as ApiResponse, a as AuthOperations, b as AuthOperationsProvider, C as ClientTransportConfig, c as CorsConfig, F as FormError, d as FormValidationResult, G as GuardConfig, e as GuardResult, H as HaiRequestEvent, f as HandleA2AConfig, g as HandleA2AOperations, h as HandleAuthConfig, i as HandleConfig, j as HookCryptoConfig, K as KitClient, k as KitClientConfig, l as KitConfig, m as KitConfigInput, n as KitPublicConfig, o as KitServerConfig, p as KitServerConfigInput, q as KitTransportConfig, r as KitTransportConfigInput, M as Middleware, s as MiddlewareContext, R as RateLimitConfig, t as RouteGuard, S as SessionData, u as SessionLike, T as TransportEncryptionOptions } from './kit-types-Cr1cnB3M.js';
3
+ export { K as KitConfigSchema, r as resolveKitConfig, a as resolveKitTransportConfig } from './kit-config-BQKQnu8Y.js';
7
4
  import '@h-ai/core';
8
5
  import '@h-ai/crypto';
9
6
  import '@h-ai/iam';
10
- import 'zod';
11
7
  import '@sveltejs/kit';
8
+ import '@h-ai/api-contract';
9
+ import '@sveltejs/kit/hooks';
10
+ import '@h-ai/feedback';
11
+ import 'zod';
12
12
 
13
13
  /**
14
14
  * @h-ai/kit — 浏览器入口
@@ -27,23 +27,6 @@ import '@sveltejs/kit';
27
27
  * client.destroy()
28
28
  * ```
29
29
  */
30
- declare const kit: {
31
- client: {
32
- create: typeof createKitClient;
33
- createAuthActions: typeof createKitAuthActions;
34
- installBrowserTransport: typeof installBrowserTransport;
35
- installBrowserTransportFetch: typeof installBrowserTransportFetch;
36
- };
37
- crud: {
38
- define: typeof defineCrud;
39
- };
40
- auth: {
41
- setBrowserToken: typeof setBrowserToken;
42
- clearBrowserToken: typeof clearBrowserToken;
43
- };
44
- i18n: {
45
- setLocale: typeof setAllModulesLocale;
46
- };
47
- };
30
+ declare const kit: BrowserKitFunctions;
48
31
 
49
- export { kit };
32
+ export { BrowserKitFunctions, kit };