@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.
- package/README.en.md +190 -110
- package/README.ja.md +192 -112
- package/README.md +195 -114
- package/dist/adapter/index.d.ts +8 -7
- package/dist/adapter/index.js.map +1 -1
- package/dist/browser.d.ts +9 -26
- package/dist/browser.js +4 -4
- package/dist/browser.js.map +1 -1
- package/dist/{chunk-JRQPMPQC.js → chunk-BCEBXG4X.js} +2 -2
- package/dist/chunk-BCEBXG4X.js.map +1 -0
- package/dist/{chunk-7EH47MTW.js → chunk-BVGXHIXR.js} +30 -9
- package/dist/chunk-BVGXHIXR.js.map +1 -0
- package/dist/{chunk-5VWPH7DO.js → chunk-GQZCU6PC.js} +14 -6
- package/dist/chunk-GQZCU6PC.js.map +1 -0
- package/dist/{chunk-YKJRQCWR.js → chunk-JL2C5ZGQ.js} +54 -20
- package/dist/chunk-JL2C5ZGQ.js.map +1 -0
- package/dist/{chunk-3OHUSKHI.js → chunk-WYGIKXHE.js} +23 -8
- package/dist/chunk-WYGIKXHE.js.map +1 -0
- package/dist/client/index.d.ts +6 -5
- package/dist/client/index.js +5 -5
- package/dist/client/index.js.map +1 -1
- package/dist/index.d.ts +18 -711
- package/dist/index.js +266 -149
- package/dist/index.js.map +1 -1
- package/dist/{kit-a2a-types-4dapztu2.d.ts → kit-a2a-types-sw7sI-ZT.d.ts} +3 -2
- package/dist/kit-config-BQKQnu8Y.d.ts +182 -0
- package/dist/kit-crypto-types-CqFENBqm.d.ts +47 -0
- package/dist/kit-types-Cr1cnB3M.d.ts +2051 -0
- package/dist/modules/a2a/index.d.ts +9 -5
- package/dist/modules/a2a/index.js +1 -1
- package/dist/modules/crypto/index.d.ts +8 -105
- package/dist/modules/crypto/index.js +3 -149
- package/dist/modules/crypto/index.js.map +1 -1
- package/package.json +22 -22
- package/dist/chunk-3OHUSKHI.js.map +0 -1
- package/dist/chunk-5VWPH7DO.js.map +0 -1
- package/dist/chunk-7EH47MTW.js.map +0 -1
- package/dist/chunk-JRQPMPQC.js.map +0 -1
- package/dist/chunk-YKJRQCWR.js.map +0 -1
- package/dist/kit-client-auth-BnhrkzZF.d.ts +0 -422
- package/dist/kit-crypto-types-CXr888wf.d.ts +0 -200
- package/dist/kit-i18n-EBzZKYN-.d.ts +0 -502
- package/dist/kit-types-BkNdyKj8.d.ts +0 -822
|
@@ -0,0 +1,2051 @@
|
|
|
1
|
+
import { HaiResult, HaiError, TrustedProxies, RequestAccessConfigInput, core } from '@h-ai/core';
|
|
2
|
+
import { CryptoFunctions, TransportServerKeyRing, TransportKeyStore } from '@h-ai/crypto';
|
|
3
|
+
import { IamFunctions, AuthnOperations, PasswordCredentials, AuthRequestContext, AuthResult, OtpCredentials, LdapCredentials, ApiKeyCredentials, RegisterOptions } from '@h-ai/iam';
|
|
4
|
+
import * as _sveltejs_kit from '@sveltejs/kit';
|
|
5
|
+
import { RequestEvent, RequestHandler } from '@sveltejs/kit';
|
|
6
|
+
import { IamCreateCaptchaInputSchema, IamPublicConfig, IamLoginInput, IamRegisterInput } from '@h-ai/api-contract';
|
|
7
|
+
import { Handle } from '@sveltejs/kit/hooks';
|
|
8
|
+
import { FeedbackFunctions, FeedbackContext } from '@h-ai/feedback';
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @h-ai/kit — Bearer 认证工具
|
|
13
|
+
*
|
|
14
|
+
* 提供服务端认证 Cookie 管理(login / logout)与浏览器端 Token 存储工具。
|
|
15
|
+
* @module kit-auth
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 浏览器 Token 存储器;使用页面内存或平台安全存储,禁止写入 Web Storage。
|
|
20
|
+
* @example 模块内部上下文
|
|
21
|
+
* ```ts
|
|
22
|
+
* const store: BrowserTokenStore = getDefaultBrowserTokenStore()
|
|
23
|
+
* store.clear() // 当前页面内存状态;不持有服务端会话真值。
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
interface BrowserTokenStore {
|
|
27
|
+
get: () => string | null;
|
|
28
|
+
set: (token: string) => void;
|
|
29
|
+
clear: () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @h-ai/kit — 统一客户端
|
|
34
|
+
*
|
|
35
|
+
* 将 CSRF Token 附加与可选传输加密封装为统一 `apiFetch`。
|
|
36
|
+
* 应用层只需调用一次 `createKitClient()`,后续 API 调用即可复用同一套安全能力。
|
|
37
|
+
* @module kit-client
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 传输加密客户端配置
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* import type { ClientTransportConfig } from '@h-ai/kit'
|
|
45
|
+
* import { crypto } from '@h-ai/crypto'
|
|
46
|
+
*
|
|
47
|
+
* function configAfterInit(): ClientTransportConfig { return { crypto, excludePaths: ['/api/health'] } }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
interface ClientTransportConfig {
|
|
51
|
+
/** 宿主构建挂载目录,默认空字符串;SvelteKit 使用 paths.relative=false 时的 resolve('/').replace(/\/$/, '')。 */
|
|
52
|
+
basePath?: string;
|
|
53
|
+
/** 可选本地明文上限;缺省跟随服务端握手。 */
|
|
54
|
+
maxPlaintextBytes?: number;
|
|
55
|
+
/** @h-ai/crypto 服务实例 */
|
|
56
|
+
crypto: CryptoFunctions;
|
|
57
|
+
/** 密钥交换端点 URL(默认 `'/api/_hai/key-exchange'`) */
|
|
58
|
+
keyExchangeUrl?: string;
|
|
59
|
+
/** 排除路径(命中后强制走明文),需与服务端 transport.excludePaths 保持一致 */
|
|
60
|
+
excludePaths?: readonly string[];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Kit 客户端配置
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* import type { KitClientConfig } from '@h-ai/kit'
|
|
67
|
+
*
|
|
68
|
+
* const value: KitClientConfig = { auth: true }
|
|
69
|
+
* void value
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
interface KitClientConfig {
|
|
73
|
+
/** 标准鉴别明确失效时原地登录原用户;成功才对被拒绝的请求重试一次。 */
|
|
74
|
+
recoverSession?: () => Promise<boolean>;
|
|
75
|
+
/** 会话终端分组,默认web;服务端按终端与全局配额共同限制。 */
|
|
76
|
+
terminalType?: 'web' | 'app' | 'desktop' | 'api';
|
|
77
|
+
/**
|
|
78
|
+
* 传输加密配置。
|
|
79
|
+
* - 不提供 / undefined:不启用传输加密,仅 CSRF
|
|
80
|
+
* - 提供对象:启用传输加密
|
|
81
|
+
*/
|
|
82
|
+
transport?: ClientTransportConfig;
|
|
83
|
+
/** CSRF Cookie 名称(默认 `'hai_csrf'`) */
|
|
84
|
+
csrfCookieName?: string;
|
|
85
|
+
/** CSRF Header 名称(默认 `'X-CSRF-Token'`) */
|
|
86
|
+
csrfHeaderName?: string;
|
|
87
|
+
/**
|
|
88
|
+
* 认证配置:自动从浏览器存储读取 Access Token 并注入请求头。
|
|
89
|
+
* - `true`:使用默认页面内存存储(由 `kit.auth.setBrowserToken()` 写入,不读写 localStorage)
|
|
90
|
+
* - `BrowserTokenStore`:使用自定义存储
|
|
91
|
+
* - 不提供 / `false`:不自动注入
|
|
92
|
+
*/
|
|
93
|
+
auth?: boolean | BrowserTokenStore;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Kit 客户端实例
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* import type { KitClient } from '@h-ai/kit'
|
|
100
|
+
*
|
|
101
|
+
* async function read(client: KitClient) {
|
|
102
|
+
* const response = await client.apiFetch('/api/health')
|
|
103
|
+
* if (!response.ok) throw new Error('Request failed')
|
|
104
|
+
* return response.json()
|
|
105
|
+
* }
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
interface KitClient {
|
|
109
|
+
/** 统一 API fetch(写请求自动附加 CSRF,启用 transport 时自动加解密) */
|
|
110
|
+
apiFetch: (input: RequestInfo | URL, init?: RequestInit, options?: {
|
|
111
|
+
recoverSession?: boolean;
|
|
112
|
+
reauthorize?: () => Promise<string | null | undefined>;
|
|
113
|
+
}) => Promise<Response>;
|
|
114
|
+
/** 传输加密是否就绪(未启用传输加密时始终为 true) */
|
|
115
|
+
readonly ready: boolean;
|
|
116
|
+
/**
|
|
117
|
+
* 手动触发密钥交换(transport 启用时)。
|
|
118
|
+
*
|
|
119
|
+
* 注意:本方法是 Client 形态的合规例外,密钥协商失败时 `throw`(与 `@h-ai/api-client` 一致),
|
|
120
|
+
* 调用方需自行 `try/catch`,不返回 `HaiResult`。
|
|
121
|
+
*/
|
|
122
|
+
init: () => Promise<void>;
|
|
123
|
+
/** 销毁密钥状态 */
|
|
124
|
+
destroy: () => void;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 在浏览器启动时安装同源 transport fetch 包装。
|
|
128
|
+
*
|
|
129
|
+
* 该包装作用于同源 `/api/*` 与 SvelteKit `__data.json` 请求,页面文档、
|
|
130
|
+
* 静态资源以及 multipart/form-data 等上传请求保持原样。应用只需安装一次,
|
|
131
|
+
* 后续 `fetch` / SvelteKit 内部 data fetch / `kit.client.create()` 明文发送路径
|
|
132
|
+
* 都会共用这层传输加密能力。
|
|
133
|
+
* @param transportConfig - 已初始化 crypto、同源密钥交换路径及排除路径;省略时不安装包装。
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* import { kit } from '@h-ai/kit'
|
|
137
|
+
*
|
|
138
|
+
* // 不传配置时不安装全局包装;启用时应传已初始化 crypto 与同源端点。
|
|
139
|
+
* kit.client.installBrowserTransportFetch()
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
declare function installBrowserTransportFetch(transportConfig?: ClientTransportConfig): void;
|
|
143
|
+
/**
|
|
144
|
+
* 用 `config/_kit.yml` 解析后的 {@link KitPublicConfig} 公共投影 一次性安装浏览器端传输加密。
|
|
145
|
+
*
|
|
146
|
+
* 行为:
|
|
147
|
+
* - `config.transport === false` 时直接返回(不启用传输加密);
|
|
148
|
+
* - 否则启动 {@link installBrowserTransportFetch},并预热 {@link CryptoFunctions} 模块。
|
|
149
|
+
*
|
|
150
|
+
* 应用层(SvelteKit `+layout.svelte` 或浏览器入口)只需调用此函数一次,
|
|
151
|
+
* 之后宿主 basePath 下的同源 `/api/*` 与 `__data.json` 请求都会自动走传输加密。
|
|
152
|
+
*
|
|
153
|
+
* @param config 已解析的 kit 公共传输配置
|
|
154
|
+
* @param deps 外部依赖
|
|
155
|
+
* @param deps.crypto `@h-ai/crypto` 模块实例
|
|
156
|
+
* @param deps.basePath 宿主构建挂载目录,默认空字符串;SvelteKit 使用 paths.relative=false 时的 resolve('/').replace(/\/$/, '')。
|
|
157
|
+
* @example
|
|
158
|
+
* ```ts
|
|
159
|
+
* import { kit } from '@h-ai/kit'
|
|
160
|
+
*
|
|
161
|
+
* import { crypto } from '@h-ai/crypto'
|
|
162
|
+
*
|
|
163
|
+
* function installAfterCryptoInit() {
|
|
164
|
+
* kit.client.installBrowserTransport({ transport: { keyExchangePath: '/api/_hai/key-exchange', excludePaths: ['/api/health'] } }, { crypto })
|
|
165
|
+
* }
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
declare function installBrowserTransport(config: KitPublicConfig, deps: {
|
|
169
|
+
crypto: CryptoFunctions;
|
|
170
|
+
basePath?: string;
|
|
171
|
+
}): void;
|
|
172
|
+
/**
|
|
173
|
+
* 创建 Kit 客户端
|
|
174
|
+
*
|
|
175
|
+
* 返回统一的 `apiFetch`,内部自动完成:
|
|
176
|
+
* 1. CSRF Token 附加(写方法自动读取 Cookie + 设置 Header)
|
|
177
|
+
* 2. 密钥交换(首次写请求时 lazy init,transport 启用时)
|
|
178
|
+
* 3. 请求体加密(transport 启用时)
|
|
179
|
+
* 4. 响应体解密(transport 启用时)
|
|
180
|
+
*
|
|
181
|
+
* @param config - 客户端配置
|
|
182
|
+
* @returns KitClient 实例
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* import { kit } from '@h-ai/kit'
|
|
186
|
+
*
|
|
187
|
+
* const client = kit.client.create({ auth: true })
|
|
188
|
+
* try {
|
|
189
|
+
* await client.init() // Client API 使用 Promise 拒绝;不是 HaiResult。
|
|
190
|
+
* const response = await client.apiFetch('/api/health')
|
|
191
|
+
* if (!response.ok) throw new Error('Request failed')
|
|
192
|
+
* } finally {
|
|
193
|
+
* client.destroy()
|
|
194
|
+
* }
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
declare function createKitClient(config?: KitClientConfig): KitClient;
|
|
198
|
+
|
|
199
|
+
/** 与标准 Kit 认证端点配套的表单动作;复用已有 CSRF/transport fetch。 */
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* 创建标准 Cookie 认证表单动作;复用可选 apiFetch 以保留 CSRF 和传输配置。
|
|
203
|
+
* @param options - 可复用的 apiFetch 和标准认证端点 basePath。
|
|
204
|
+
* @param options.apiFetch - 已装配 CSRF/transport 的 fetch;省略时使用默认 Kit 客户端。
|
|
205
|
+
* @param options.basePath - 同源认证端点前缀,默认 /api/auth。
|
|
206
|
+
* @returns 标准表单动作;业务和已认证的时间错误返回 HaiResult,网络/格式错误会拒绝 Promise。
|
|
207
|
+
* @example
|
|
208
|
+
* ```ts
|
|
209
|
+
* import { kit } from '@h-ai/kit'
|
|
210
|
+
*
|
|
211
|
+
* const actions = kit.client.createAuthActions({ basePath: '/api/auth' })
|
|
212
|
+
* void actions
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
declare function createKitAuthActions(options?: {
|
|
216
|
+
apiFetch?: KitClient['apiFetch'];
|
|
217
|
+
basePath?: string;
|
|
218
|
+
}): {
|
|
219
|
+
bindPageExit(options?: {
|
|
220
|
+
enabled?: boolean;
|
|
221
|
+
}): () => void;
|
|
222
|
+
createCaptcha(input: z.input<typeof IamCreateCaptchaInputSchema>): Promise<HaiResult<{
|
|
223
|
+
id: string;
|
|
224
|
+
challenge: {
|
|
225
|
+
parameters: {
|
|
226
|
+
algorithm: "PBKDF2/SHA-256";
|
|
227
|
+
nonce: string;
|
|
228
|
+
salt: string;
|
|
229
|
+
cost: number;
|
|
230
|
+
keyLength: number;
|
|
231
|
+
keyPrefix: string;
|
|
232
|
+
keySignature?: string | undefined;
|
|
233
|
+
data?: Record<string, string | number | boolean | null> | undefined;
|
|
234
|
+
};
|
|
235
|
+
signature?: string | undefined;
|
|
236
|
+
};
|
|
237
|
+
}>>;
|
|
238
|
+
publicConfig(): Promise<HaiResult<IamPublicConfig | null>>;
|
|
239
|
+
login(input: IamLoginInput & {
|
|
240
|
+
method?: "password" | "ldap" | "email" | "sms";
|
|
241
|
+
code?: string;
|
|
242
|
+
}): Promise<HaiResult<unknown>>;
|
|
243
|
+
register: (input: IamRegisterInput) => Promise<HaiResult<unknown>>;
|
|
244
|
+
sendOtp: (identifier: string, options?: {
|
|
245
|
+
captcha?: string;
|
|
246
|
+
}) => Promise<HaiResult<unknown>>;
|
|
247
|
+
requestPasswordReset: (identifier: string, options?: {
|
|
248
|
+
captcha?: string;
|
|
249
|
+
}) => Promise<HaiResult<unknown>>;
|
|
250
|
+
confirmPasswordReset: (token: string, newPassword: string) => Promise<HaiResult<unknown>>;
|
|
251
|
+
renewPassword: (input: {
|
|
252
|
+
identifier: string;
|
|
253
|
+
password: string;
|
|
254
|
+
newPassword: string;
|
|
255
|
+
captcha?: string;
|
|
256
|
+
}) => Promise<HaiResult<unknown>>;
|
|
257
|
+
reauthenticate(password: string, operation: string): Promise<HaiResult<{
|
|
258
|
+
ticket: string;
|
|
259
|
+
expiresAt: number;
|
|
260
|
+
}>>;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* @h-ai/kit — CRUD 类型定义
|
|
265
|
+
*
|
|
266
|
+
* 声明式 CRUD 资源定义所需的全部类型。
|
|
267
|
+
* @module kit-crud-types
|
|
268
|
+
*/
|
|
269
|
+
/**
|
|
270
|
+
* 支持的字段类型
|
|
271
|
+
* @example
|
|
272
|
+
* ```ts
|
|
273
|
+
* import type { CrudFieldType } from '@h-ai/kit'
|
|
274
|
+
*
|
|
275
|
+
* const value: CrudFieldType = 'date-range'
|
|
276
|
+
* void value
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
type CrudFieldType = 'string' | 'number' | 'boolean' | 'date' | 'date-range' | 'datetime' | 'email' | 'url' | 'tel' | 'password' | 'textarea' | 'select' | 'multi-select' | 'radio' | 'checkbox' | 'custom';
|
|
280
|
+
/**
|
|
281
|
+
* 字段校验规则
|
|
282
|
+
* @example
|
|
283
|
+
* ```ts
|
|
284
|
+
* import type { CrudFieldValidation } from '@h-ai/kit'
|
|
285
|
+
*
|
|
286
|
+
* const value: CrudFieldValidation = { required: true, max: 120 }
|
|
287
|
+
* void value
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
interface CrudFieldValidation {
|
|
291
|
+
/** 是否必填 */
|
|
292
|
+
required?: boolean;
|
|
293
|
+
/** 最小值(number)或 最小长度(string) */
|
|
294
|
+
min?: number;
|
|
295
|
+
/** 最大值(number)或 最大长度(string) */
|
|
296
|
+
max?: number;
|
|
297
|
+
/** 正则表达式 */
|
|
298
|
+
pattern?: string;
|
|
299
|
+
/** 自定义错误提示 */
|
|
300
|
+
message?: CrudText;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* 选项项(用于 select/radio 等)
|
|
304
|
+
* @example
|
|
305
|
+
* ```ts
|
|
306
|
+
* import type { CrudFieldOption } from '@h-ai/kit'
|
|
307
|
+
*
|
|
308
|
+
* const value: CrudFieldOption = { label: 'Published', value: 'published' }
|
|
309
|
+
* void value
|
|
310
|
+
* ```
|
|
311
|
+
*/
|
|
312
|
+
interface CrudFieldOption {
|
|
313
|
+
label: string;
|
|
314
|
+
value: string | number | boolean;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* 选项来源:静态数组或动态工厂函数
|
|
318
|
+
* @example
|
|
319
|
+
* ```ts
|
|
320
|
+
* import type { CrudFieldOptions } from '@h-ai/kit'
|
|
321
|
+
*
|
|
322
|
+
* const value: CrudFieldOptions = [{ label: 'Published', value: 'published' }]
|
|
323
|
+
* void value
|
|
324
|
+
* ```
|
|
325
|
+
*/
|
|
326
|
+
type CrudFieldOptions = CrudFieldOption[] | (() => CrudFieldOption[]);
|
|
327
|
+
/**
|
|
328
|
+
* 可解析为字符串的文本值(支持 i18n)
|
|
329
|
+
* @example
|
|
330
|
+
* ```ts
|
|
331
|
+
* import type { CrudText } from '@h-ai/kit'
|
|
332
|
+
*
|
|
333
|
+
* const value: CrudText = () => 'Articles'
|
|
334
|
+
* void value
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
337
|
+
type CrudText = string | (() => string);
|
|
338
|
+
/**
|
|
339
|
+
* 字段定义
|
|
340
|
+
* @example
|
|
341
|
+
* ```ts
|
|
342
|
+
* import type { CrudFieldDef } from '@h-ai/kit'
|
|
343
|
+
*
|
|
344
|
+
* const value: CrudFieldDef = { id: 'title', label: 'Title', type: 'string', validation: { required: true } }
|
|
345
|
+
* void value
|
|
346
|
+
* ```
|
|
347
|
+
*/
|
|
348
|
+
interface CrudFieldDef {
|
|
349
|
+
/** 字段 ID(对应数据对象的 key) */
|
|
350
|
+
id: string;
|
|
351
|
+
/** 显示名称(支持 i18n 函数) */
|
|
352
|
+
label: CrudText;
|
|
353
|
+
/** 字段类型 */
|
|
354
|
+
type: CrudFieldType;
|
|
355
|
+
/** 是否在列表中显示(默认 true) */
|
|
356
|
+
inList?: boolean;
|
|
357
|
+
/** 是否是过滤项(默认 false) */
|
|
358
|
+
filterable?: boolean;
|
|
359
|
+
/** 是否只读(编辑时不可修改,默认 false) */
|
|
360
|
+
readonly?: boolean;
|
|
361
|
+
/** 是否在新建表单中显示(默认 true) */
|
|
362
|
+
inCreate?: boolean;
|
|
363
|
+
/** 是否在编辑表单中显示(默认 true) */
|
|
364
|
+
inEdit?: boolean;
|
|
365
|
+
/** 是否在详情中显示(默认 true) */
|
|
366
|
+
inDetail?: boolean;
|
|
367
|
+
/** 选项列表(select/radio/checkbox/multi-select) */
|
|
368
|
+
options?: CrudFieldOptions;
|
|
369
|
+
/** 校验规则 */
|
|
370
|
+
validation?: CrudFieldValidation;
|
|
371
|
+
/** 双列表单中的占用列数;auto 按控件类型判断,默认 auto。 */
|
|
372
|
+
colSpan?: 'auto' | 1 | 2;
|
|
373
|
+
/** 列宽(列表模式) */
|
|
374
|
+
width?: string;
|
|
375
|
+
/** 列对齐方式 */
|
|
376
|
+
align?: 'left' | 'center' | 'right';
|
|
377
|
+
/** 自定义列表渲染 */
|
|
378
|
+
render?: (value: unknown, item: Record<string, unknown>) => string;
|
|
379
|
+
/** 占位符 */
|
|
380
|
+
placeholder?: CrudText;
|
|
381
|
+
/** 日期范围起始值的查询参数名;仅 date-range 字段使用,默认 `${id}Start` */
|
|
382
|
+
startKey?: string;
|
|
383
|
+
/** 日期范围结束值的查询参数名;仅 date-range 字段使用,默认 `${id}End` */
|
|
384
|
+
endKey?: string;
|
|
385
|
+
/** 默认值(新建时) */
|
|
386
|
+
defaultValue?: unknown;
|
|
387
|
+
/** 排列权重(数值越小越靠前) */
|
|
388
|
+
order?: number;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* 分页参数
|
|
392
|
+
* @example
|
|
393
|
+
* ```ts
|
|
394
|
+
* import type { CrudPaginationParams } from '@h-ai/kit'
|
|
395
|
+
*
|
|
396
|
+
* const value: CrudPaginationParams = { page: 1, pageSize: 20 }
|
|
397
|
+
* void value
|
|
398
|
+
* ```
|
|
399
|
+
*/
|
|
400
|
+
interface CrudPaginationParams {
|
|
401
|
+
page: number;
|
|
402
|
+
pageSize: number;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* 过滤参数(搜索 + 自定义过滤字段)
|
|
406
|
+
* @example
|
|
407
|
+
* ```ts
|
|
408
|
+
* import type { CrudFilterParams } from '@h-ai/kit'
|
|
409
|
+
*
|
|
410
|
+
* const value: CrudFilterParams = { search: 'example', status: 'published' }
|
|
411
|
+
* void value
|
|
412
|
+
* ```
|
|
413
|
+
*/
|
|
414
|
+
interface CrudFilterParams {
|
|
415
|
+
search?: string;
|
|
416
|
+
[key: string]: unknown;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* 分页结果
|
|
420
|
+
* @example
|
|
421
|
+
* ```ts
|
|
422
|
+
* import type { CrudPaginatedResult } from '@h-ai/kit'
|
|
423
|
+
*
|
|
424
|
+
* const value: CrudPaginatedResult<{ id: string }> = { items: [], total: 0, page: 1, pageSize: 20 }
|
|
425
|
+
* void value
|
|
426
|
+
* ```
|
|
427
|
+
*/
|
|
428
|
+
interface CrudPaginatedResult<T> {
|
|
429
|
+
items: T[];
|
|
430
|
+
total: number;
|
|
431
|
+
page: number;
|
|
432
|
+
pageSize: number;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* CRUD API 接口定义
|
|
436
|
+
* @example
|
|
437
|
+
* ```ts
|
|
438
|
+
* import type { CrudApiDef } from '@h-ai/kit'
|
|
439
|
+
*
|
|
440
|
+
* // 应用适配层把持久化查询结果转为此结构,失败应拒绝 Promise。
|
|
441
|
+
* function listAdapter(list: CrudApiDef<{ id: string }>['list']): CrudApiDef<{ id: string }> { return { list } }
|
|
442
|
+
* ```
|
|
443
|
+
*/
|
|
444
|
+
interface CrudApiDef<T = Record<string, unknown>> {
|
|
445
|
+
/** 获取分页列表 */
|
|
446
|
+
list: (params: CrudPaginationParams & CrudFilterParams) => Promise<CrudPaginatedResult<T>>;
|
|
447
|
+
/** 获取单条记录 */
|
|
448
|
+
get?: (id: string) => Promise<T | null>;
|
|
449
|
+
/** 新建记录 */
|
|
450
|
+
create?: (data: Partial<T>) => Promise<T>;
|
|
451
|
+
/** 更新记录(patch) */
|
|
452
|
+
update?: (id: string, data: Partial<T>) => Promise<T>;
|
|
453
|
+
/** 删除记录 */
|
|
454
|
+
remove?: (id: string) => Promise<void>;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* CRUD 资源定义
|
|
458
|
+
* @example
|
|
459
|
+
* ```ts
|
|
460
|
+
* import type { CrudApiDef, CrudResourceDef } from '@h-ai/kit'
|
|
461
|
+
*
|
|
462
|
+
* function articles(api: CrudApiDef<{ id: string, title: string }>): CrudResourceDef<{ id: string, title: string }> {
|
|
463
|
+
* return { name: 'article', label: 'Articles', fields: [{ id: 'title', label: 'Title', type: 'string' }], api }
|
|
464
|
+
* }
|
|
465
|
+
* ```
|
|
466
|
+
*/
|
|
467
|
+
interface CrudResourceDef<T = Record<string, unknown>> {
|
|
468
|
+
/** 资源名称(如 'user', 'role'),用于 URL 路由 */
|
|
469
|
+
name: string;
|
|
470
|
+
/** 显示名称(面包屑、标题用,支持 i18n) */
|
|
471
|
+
label: CrudText;
|
|
472
|
+
/** 主键字段名(默认 'id') */
|
|
473
|
+
keyField?: string;
|
|
474
|
+
/** 字段列表 */
|
|
475
|
+
fields: CrudFieldDef[];
|
|
476
|
+
/** API 接口 */
|
|
477
|
+
api: CrudApiDef<T>;
|
|
478
|
+
/** 默认分页大小(默认 20) */
|
|
479
|
+
defaultPageSize?: number;
|
|
480
|
+
/** 是否支持搜索(默认 true) */
|
|
481
|
+
searchable?: boolean;
|
|
482
|
+
/** 搜索占位符 */
|
|
483
|
+
searchPlaceholder?: CrudText;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* DataTable 列配置(适配 @h-ai/ui DataTable)
|
|
487
|
+
* @example
|
|
488
|
+
* ```ts
|
|
489
|
+
* import type { CrudTableColumn } from '@h-ai/kit'
|
|
490
|
+
*
|
|
491
|
+
* const value: CrudTableColumn = { key: 'title', label: 'Title' }
|
|
492
|
+
* void value
|
|
493
|
+
* ```
|
|
494
|
+
*/
|
|
495
|
+
interface CrudTableColumn {
|
|
496
|
+
key: string;
|
|
497
|
+
label: string;
|
|
498
|
+
width?: string;
|
|
499
|
+
align?: 'left' | 'center' | 'right';
|
|
500
|
+
render?: (item: Record<string, unknown>) => string;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* CRUD 操作对象(由 defineCrud 返回)
|
|
504
|
+
* @example
|
|
505
|
+
* ```ts
|
|
506
|
+
* import type { CrudOperations } from '@h-ai/kit'
|
|
507
|
+
*
|
|
508
|
+
* function columns(resource: CrudOperations<{ id: string }>) { return resource.toTableColumns() }
|
|
509
|
+
* ```
|
|
510
|
+
*/
|
|
511
|
+
interface CrudOperations<T = Record<string, unknown>> {
|
|
512
|
+
/** 资源元信息 */
|
|
513
|
+
resource: CrudResourceDef<T>;
|
|
514
|
+
/** 获取列表列字段(inList=true) */
|
|
515
|
+
getListFields: () => CrudFieldDef[];
|
|
516
|
+
/** 获取过滤字段(filterable=true) */
|
|
517
|
+
getFilterFields: () => CrudFieldDef[];
|
|
518
|
+
/** 获取新建表单字段(inCreate=true) */
|
|
519
|
+
getCreateFields: () => CrudFieldDef[];
|
|
520
|
+
/** 获取编辑表单字段(inEdit=true) */
|
|
521
|
+
getEditFields: () => CrudFieldDef[];
|
|
522
|
+
/** 获取详情字段(inDetail=true) */
|
|
523
|
+
getDetailFields: () => CrudFieldDef[];
|
|
524
|
+
/** 构建 DataTable 列配置 */
|
|
525
|
+
toTableColumns: () => CrudTableColumn[];
|
|
526
|
+
/** 构建新建时的默认值 */
|
|
527
|
+
getDefaultValues: () => Record<string, unknown>;
|
|
528
|
+
/** API 操作代理 */
|
|
529
|
+
api: CrudApiDef<T>;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* @h-ai/kit — CRUD 操作定义
|
|
534
|
+
*
|
|
535
|
+
* 声明式 CRUD 资源定义工厂函数。
|
|
536
|
+
* @module kit-crud
|
|
537
|
+
*/
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* 解析 CrudText 为字符串
|
|
541
|
+
*/
|
|
542
|
+
declare function resolveText(text: CrudText): string;
|
|
543
|
+
/**
|
|
544
|
+
* 解析选项列表
|
|
545
|
+
*/
|
|
546
|
+
declare function resolveOptions(options: CrudFieldOptions | undefined): Array<{
|
|
547
|
+
label: string;
|
|
548
|
+
value: string | number | boolean;
|
|
549
|
+
}>;
|
|
550
|
+
/**
|
|
551
|
+
* 定义 CRUD 资源
|
|
552
|
+
*
|
|
553
|
+
* 返回 CrudOperations 对象,包含字段过滤、列构建和 API 代理等能力。
|
|
554
|
+
* 与 @h-ai/ui 的 CrudPage 组件配合使用。
|
|
555
|
+
*
|
|
556
|
+
* @param resource - 资源定义
|
|
557
|
+
* @returns CRUD 操作对象
|
|
558
|
+
* @example
|
|
559
|
+
* ```ts
|
|
560
|
+
* import { kit } from '@h-ai/kit'
|
|
561
|
+
*
|
|
562
|
+
* import type { CrudApiDef } from '@h-ai/kit'
|
|
563
|
+
*
|
|
564
|
+
* function articleResource(api: CrudApiDef<{ id: string, title: string }>) {
|
|
565
|
+
* return kit.crud.define({ name: 'article', label: 'Articles', fields: [{ id: 'title', label: 'Title', type: 'string' }], api })
|
|
566
|
+
* }
|
|
567
|
+
* ```
|
|
568
|
+
*/
|
|
569
|
+
declare function defineCrud<T = Record<string, unknown>>(resource: CrudResourceDef<T>): CrudOperations<T>;
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* @h-ai/kit — 权限守卫
|
|
573
|
+
*
|
|
574
|
+
* 验证用户是否具有指定权限。
|
|
575
|
+
* @module kit-permission
|
|
576
|
+
*/
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* 权限守卫配置
|
|
580
|
+
* @example
|
|
581
|
+
* ```ts
|
|
582
|
+
* import type { PermissionGuardConfig } from '@h-ai/kit'
|
|
583
|
+
*
|
|
584
|
+
* const value: PermissionGuardConfig = { permissions: ['article:read'] }
|
|
585
|
+
* void value
|
|
586
|
+
* ```
|
|
587
|
+
*/
|
|
588
|
+
interface PermissionGuardConfig {
|
|
589
|
+
/** 需要的权限列表(默认 OR 逻辑,满足任一即通过) */
|
|
590
|
+
permissions: string[];
|
|
591
|
+
/** 为 `true` 时要求用户拥有 **全部** 权限(AND 逻辑) */
|
|
592
|
+
requireAll?: boolean;
|
|
593
|
+
/** 无权限时重定向 URL(默认 `'/403'`) */
|
|
594
|
+
forbiddenUrl?: string;
|
|
595
|
+
/** 为 `true` 时返回 JSON 403 而非重定向 */
|
|
596
|
+
apiMode?: boolean;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* 匹配权限
|
|
600
|
+
*
|
|
601
|
+
* 支持通配符:
|
|
602
|
+
* - `admin:*` 匹配 `admin:read`、`admin:write` 等
|
|
603
|
+
* - `*` 匹配所有权限
|
|
604
|
+
*
|
|
605
|
+
* @param required - 需要的权限码
|
|
606
|
+
* @param userPermissions - 用户已有的权限列表
|
|
607
|
+
* @returns 是否匹配
|
|
608
|
+
* @example
|
|
609
|
+
* ```ts
|
|
610
|
+
* import { matchPermission } from '@h-ai/kit'
|
|
611
|
+
*
|
|
612
|
+
* const allowed = matchPermission('article:read', ['article:*'])
|
|
613
|
+
* void allowed
|
|
614
|
+
* ```
|
|
615
|
+
*/
|
|
616
|
+
declare function matchPermission(required: string, userPermissions: string[]): boolean;
|
|
617
|
+
/**
|
|
618
|
+
* 检查会话是否具有指定权限(布尔判断)
|
|
619
|
+
*
|
|
620
|
+
* 适用于条件分支场景(如菜单过滤、按钮显示等)。
|
|
621
|
+
*
|
|
622
|
+
* @param session - 当前会话数据,null/undefined 视为无权限
|
|
623
|
+
* @param permission - 需要的权限码,如 `user:read`
|
|
624
|
+
* @returns 是否拥有权限
|
|
625
|
+
* @example
|
|
626
|
+
* ```ts
|
|
627
|
+
* import { kit } from '@h-ai/kit'
|
|
628
|
+
*
|
|
629
|
+
* const allowed = kit.guard.check({ userId: 'user_1', roles: ['editor'], permissions: ['article:read'] }, 'article:read')
|
|
630
|
+
* void allowed
|
|
631
|
+
* ```
|
|
632
|
+
*/
|
|
633
|
+
declare function hasPermission(session: SessionLike | null | undefined, permission: string): boolean;
|
|
634
|
+
/**
|
|
635
|
+
* 要求会话具有指定权限,不满足时 throw Response
|
|
636
|
+
*
|
|
637
|
+
* throw 的 Response 对象由 kit.handler 捕获并直接作为 API 响应返回。
|
|
638
|
+
* 搭配 `kit.handler()` 使用时,无需手动检查返回值。
|
|
639
|
+
*
|
|
640
|
+
* @param session - 当前会话数据,null/undefined 视为未认证
|
|
641
|
+
* @param permission - 需要的权限码,如 `user:create`
|
|
642
|
+
* @throws Response - 未认证 401 / 无权限 403
|
|
643
|
+
* @example
|
|
644
|
+
* ```ts
|
|
645
|
+
* import { kit } from '@h-ai/kit'
|
|
646
|
+
*
|
|
647
|
+
* kit.guard.require({ userId: 'user_1', roles: ['editor'], permissions: ['article:read'] }, 'article:read') // 拒绝时抛出 Response,由 kit.handler 传递。
|
|
648
|
+
* ```
|
|
649
|
+
*/
|
|
650
|
+
declare function requirePermission(session: SessionLike | null | undefined, permission: string): void;
|
|
651
|
+
|
|
652
|
+
/** 可选敏感操作再鉴别;由操作入口在现有权限/资源检查之后调用。 */
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* 要求选定操作携带短期单次再鉴别票据;失败抛出 Response,由 kit.handler 返回。
|
|
656
|
+
* 不替代已有认证、权限与资源范围守卫;操作 ID 必须由服务端指定。
|
|
657
|
+
* @param iam - 已初始化的 IAM 服务。
|
|
658
|
+
* @param event - 当前请求与 Cookie,不能跨请求保存。
|
|
659
|
+
* @param operation - 当前服务端操作 ID。
|
|
660
|
+
* @returns 策略关闭、未选操作或证明校验成功时无返回值。
|
|
661
|
+
* @example
|
|
662
|
+
* ```ts
|
|
663
|
+
* import { iam } from '@h-ai/iam'
|
|
664
|
+
* import type { SessionLike } from '@h-ai/kit'
|
|
665
|
+
* import type { RequestEvent } from '@sveltejs/kit'
|
|
666
|
+
* import { kit } from '@h-ai/kit'
|
|
667
|
+
* // session 由应用在当前请求完成鉴别后传入,不在跨请求闭包中保存。
|
|
668
|
+
* async function remove(event: Pick<RequestEvent, 'request' | 'cookies'>, session: SessionLike) {
|
|
669
|
+
* kit.guard.require(session, 'user:delete')
|
|
670
|
+
* await kit.guard.requireSensitive(iam, event, 'iam.users.delete')
|
|
671
|
+
* return kit.response.ok({ allowed: true })
|
|
672
|
+
* }
|
|
673
|
+
* ```
|
|
674
|
+
*/
|
|
675
|
+
declare function requireSensitiveOperation(iam: IamFunctions, event: Pick<RequestEvent, 'request' | 'cookies'>, operation: string): Promise<void>;
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* @h-ai/kit — SvelteKit Handle Hook
|
|
679
|
+
*
|
|
680
|
+
* 创建 SvelteKit Handle Hook,集成请求 ID 生成、会话验证、Cookie 加密代理、
|
|
681
|
+
* 路由守卫、中间件链与可选传输加密;同时提供 `sequence()` 组合多个 Handle。
|
|
682
|
+
* @module kit-handle
|
|
683
|
+
*/
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* 创建 hai handle hook
|
|
687
|
+
*
|
|
688
|
+
* 整合会话解析、路由守卫、中间件链与统一错误处理的 SvelteKit Handle 工厂。
|
|
689
|
+
*
|
|
690
|
+
* 执行顺序:
|
|
691
|
+
* 1. 生成 `requestId` 并写入 `event.locals`
|
|
692
|
+
* 2. 根据 `auth` 配置从 Bearer / Cookie 解析会话
|
|
693
|
+
* 3. 根据 `auth.protectedPaths` 自动执行路由守卫 + 额外自定义守卫
|
|
694
|
+
* 4. 构建中间件链(内置 logging / rateLimit + 自定义)并执行
|
|
695
|
+
* 5. 调用 `resolve(event)` 获取业务响应
|
|
696
|
+
* 6. 附加 `X-Request-Id` 响应头
|
|
697
|
+
*
|
|
698
|
+
* @param config - Handle 配置(均有合理默认值,可零配置使用)
|
|
699
|
+
* @returns SvelteKit Handle 函数
|
|
700
|
+
* @example
|
|
701
|
+
* ```ts
|
|
702
|
+
* import { kit } from '@h-ai/kit'
|
|
703
|
+
*
|
|
704
|
+
* const handle = kit.createHandle({ auth: { verifyToken: async () => null, protectedPaths: ['/admin/**'] } })
|
|
705
|
+
* void handle
|
|
706
|
+
* ```
|
|
707
|
+
*/
|
|
708
|
+
declare function createHandle(config?: HandleConfig): Handle;
|
|
709
|
+
/**
|
|
710
|
+
* 组合多个 SvelteKit handle 为单一 handle
|
|
711
|
+
*
|
|
712
|
+
* 洋葱模型:`sequence(a, b, c)` 执行顺序为 a → b → c → resolve → c → b → a。
|
|
713
|
+
*
|
|
714
|
+
* @param handles - 待组合的 handle 函数列表
|
|
715
|
+
* @returns 组合后的单一 Handle 函数
|
|
716
|
+
* @example
|
|
717
|
+
* ```ts
|
|
718
|
+
* import { kit } from '@h-ai/kit'
|
|
719
|
+
*
|
|
720
|
+
* const handle = kit.sequence(kit.createHandle({ logging: true }), kit.createHandle())
|
|
721
|
+
* void handle
|
|
722
|
+
* ```
|
|
723
|
+
*/
|
|
724
|
+
declare function sequence(...handles: Handle[]): Handle;
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* 应用路由直接导出对应 handler;认证 ops 仍由 createHandle 注入。
|
|
728
|
+
* @param iam - 已由应用初始化的 IAM 服务对象。
|
|
729
|
+
* @returns 标准认证端点的 RequestHandler 集合。
|
|
730
|
+
* @example 模块内部上下文
|
|
731
|
+
* ```ts
|
|
732
|
+
* function build(iam: IamFunctions) {
|
|
733
|
+
* return createAuthApi(iam) // 调用方负责初始化 IAM;工厂只装配 handler。
|
|
734
|
+
* }
|
|
735
|
+
* ```
|
|
736
|
+
*/
|
|
737
|
+
declare function createAuthApi(iam: IamFunctions): {
|
|
738
|
+
closeSession: _sveltejs_kit.RequestHandler;
|
|
739
|
+
createCaptcha: _sveltejs_kit.RequestHandler;
|
|
740
|
+
reauthenticate: _sveltejs_kit.RequestHandler;
|
|
741
|
+
publicConfig: _sveltejs_kit.RequestHandler;
|
|
742
|
+
renewPassword: _sveltejs_kit.RequestHandler;
|
|
743
|
+
login: _sveltejs_kit.RequestHandler;
|
|
744
|
+
loginWithLdap: _sveltejs_kit.RequestHandler;
|
|
745
|
+
loginWithOtp: _sveltejs_kit.RequestHandler;
|
|
746
|
+
register: _sveltejs_kit.RequestHandler;
|
|
747
|
+
sendOtp: _sveltejs_kit.RequestHandler;
|
|
748
|
+
requestPasswordReset: _sveltejs_kit.RequestHandler;
|
|
749
|
+
confirmPasswordReset: _sveltejs_kit.RequestHandler;
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
/** 可直接挂到 SvelteKit catch-all 路由的反馈适配,无 serv 或 Svelte UI 依赖。 */
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* 反馈服务及可信范围解析。resolveContext 必须使用已验证 session/宿主策略。
|
|
756
|
+
* @example
|
|
757
|
+
* ```ts
|
|
758
|
+
* import type { FeedbackHandlerOptions } from '@h-ai/kit'
|
|
759
|
+
* import { feedback } from '@h-ai/feedback'
|
|
760
|
+
* const options: FeedbackHandlerOptions = { feedback, resolveContext: () => null }
|
|
761
|
+
* // 应用实现真实 session 映射;null 拒绝访问,不能从 JSON 取得身份。
|
|
762
|
+
* ```
|
|
763
|
+
*/
|
|
764
|
+
interface FeedbackHandlerOptions {
|
|
765
|
+
readonly feedback: FeedbackFunctions;
|
|
766
|
+
readonly resolveContext: (event: RequestEvent) => FeedbackContext | null | Promise<FeedbackContext | null>;
|
|
767
|
+
readonly basePath?: string;
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* 装配反馈 REST 路由;默认路径 /api/feedback,由宿主 handle 保持 CSRF、认证和传输加密。
|
|
771
|
+
* @param options - 已初始化服务与可信范围映射。
|
|
772
|
+
* @returns 可赋值给 catch-all 路由 GET/POST/PATCH 的同一 handler。
|
|
773
|
+
* @example
|
|
774
|
+
* ```ts
|
|
775
|
+
* import { kit } from '@h-ai/kit'
|
|
776
|
+
* import { feedback } from '@h-ai/feedback'
|
|
777
|
+
* const handler = kit.createFeedbackHandler({ feedback, resolveContext: () => null })
|
|
778
|
+
* // 路由 export const GET = handler;真实应用从受信 session 返回范围,null 默认拒绝。
|
|
779
|
+
* ```
|
|
780
|
+
*/
|
|
781
|
+
declare function createFeedbackHandler(options: FeedbackHandlerOptions): RequestHandler;
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* @h-ai/kit — API Handler 包装器
|
|
785
|
+
*
|
|
786
|
+
* 统一封装 SvelteKit API Handler 的错误处理逻辑。
|
|
787
|
+
* @module kit-handler
|
|
788
|
+
*/
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* 创建 API Handler 包装器
|
|
792
|
+
*
|
|
793
|
+
* 将业务逻辑包裹在统一的异常边界中:
|
|
794
|
+
* 1. 正常执行 `fn(event)` 返回 Response
|
|
795
|
+
* 2. 若 `fn` throw 了 `Response`(如 `kit.guard.require` / `kit.validate.body`),直接返回该 Response
|
|
796
|
+
* 3. 若 `fn` throw 了 SvelteKit 控制流(`redirect()` / `error()`),继续抛出
|
|
797
|
+
* 4. 其他异常:记录日志 → 返回 `kit.response.internalError()`
|
|
798
|
+
*
|
|
799
|
+
* @param fn - 业务处理函数
|
|
800
|
+
* @returns SvelteKit RequestHandler
|
|
801
|
+
* @example
|
|
802
|
+
* ```ts
|
|
803
|
+
* import { kit } from '@h-ai/kit'
|
|
804
|
+
*
|
|
805
|
+
* const GET = kit.handler(() => kit.response.ok({ healthy: true }))
|
|
806
|
+
* void GET
|
|
807
|
+
* ```
|
|
808
|
+
*/
|
|
809
|
+
declare function handler(fn: (event: RequestEvent) => Promise<Response> | Response): RequestHandler;
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* @h-ai/kit — API 响应工具
|
|
813
|
+
*
|
|
814
|
+
* 标准化 API 响应工具集,统一 `{ success, data?, error?, requestId? }` 结构。
|
|
815
|
+
* 提供成功响应(ok / created / noContent)、错误响应
|
|
816
|
+
* (badRequest / unauthorized / forbidden / notFound / conflict / validationError / internalError)
|
|
817
|
+
* 以及重定向响应。
|
|
818
|
+
* @module kit-response
|
|
819
|
+
*/
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* 创建 200 成功响应
|
|
823
|
+
*
|
|
824
|
+
* @param data - 响应数据,序列化为 JSON
|
|
825
|
+
* @param requestId - 可选请求 ID,用于链路追踪
|
|
826
|
+
* @returns `{ success: true, data, requestId }` 格式的 JSON Response
|
|
827
|
+
* @example
|
|
828
|
+
* ```ts
|
|
829
|
+
* import { kit } from '@h-ai/kit'
|
|
830
|
+
*
|
|
831
|
+
* const response = kit.response.ok({ id: 'article_1' })
|
|
832
|
+
* void response.status
|
|
833
|
+
* ```
|
|
834
|
+
*/
|
|
835
|
+
declare function ok<T>(data: T, requestId?: string): Response;
|
|
836
|
+
/**
|
|
837
|
+
* 创建 201 资源创建成功响应
|
|
838
|
+
*
|
|
839
|
+
* @param data - 新创建的资源数据
|
|
840
|
+
* @param requestId - 可选请求 ID
|
|
841
|
+
* @returns `{ success: true, data }` 格式的 JSON Response(status 201)
|
|
842
|
+
* @example
|
|
843
|
+
* ```ts
|
|
844
|
+
* import { kit } from '@h-ai/kit'
|
|
845
|
+
*
|
|
846
|
+
* const response = kit.response.created({ id: 'article_1' })
|
|
847
|
+
* void response.status
|
|
848
|
+
* ```
|
|
849
|
+
*/
|
|
850
|
+
declare function created<T>(data: T, requestId?: string): Response;
|
|
851
|
+
/**
|
|
852
|
+
* 创建 204 无内容响应
|
|
853
|
+
*
|
|
854
|
+
* 通常用于 DELETE 成功或无返回值的更新操作。
|
|
855
|
+
*
|
|
856
|
+
* @returns 空 body、status 204 的 Response
|
|
857
|
+
* @example
|
|
858
|
+
* ```ts
|
|
859
|
+
* import { kit } from '@h-ai/kit'
|
|
860
|
+
*
|
|
861
|
+
* const response = kit.response.noContent()
|
|
862
|
+
* void response.status
|
|
863
|
+
* ```
|
|
864
|
+
*/
|
|
865
|
+
declare function noContent(): Response;
|
|
866
|
+
/**
|
|
867
|
+
* 创建自定义错误响应
|
|
868
|
+
*
|
|
869
|
+
* @param code - 错误码(如 `'CUSTOM_ERROR'`)
|
|
870
|
+
* @param message - 人可读错误消息
|
|
871
|
+
* @param status - HTTP 状态码,默认 400
|
|
872
|
+
* @param requestId - 可选请求 ID
|
|
873
|
+
* @param details - 显式公开的业务详情;不要传原始异常、SQL、输入值或内部上下文
|
|
874
|
+
* @returns `{ success: false, error: { code, message, details } }` 格式的 JSON Response
|
|
875
|
+
* @example
|
|
876
|
+
* ```ts
|
|
877
|
+
* import { kit } from '@h-ai/kit'
|
|
878
|
+
*
|
|
879
|
+
* const response = kit.response.error('ARTICLE_MISSING', 'Article missing', 404)
|
|
880
|
+
* void response.status
|
|
881
|
+
* ```
|
|
882
|
+
*/
|
|
883
|
+
declare function error(code: HaiError['code'], message: string, status?: number, requestId?: string, details?: unknown): Response;
|
|
884
|
+
/**
|
|
885
|
+
* 创建 400 Bad Request 响应
|
|
886
|
+
*
|
|
887
|
+
* @param message - 错误消息
|
|
888
|
+
* @param requestId - 可选请求 ID
|
|
889
|
+
* @param details - 可选额外详情
|
|
890
|
+
* @returns error code 为 `HaiCommonError.INVALID_REQUEST.code` 的 JSON Response
|
|
891
|
+
* @example
|
|
892
|
+
* ```ts
|
|
893
|
+
* import { kit } from '@h-ai/kit'
|
|
894
|
+
*
|
|
895
|
+
* const response = kit.response.badRequest('Invalid article')
|
|
896
|
+
* void response.status
|
|
897
|
+
* ```
|
|
898
|
+
*/
|
|
899
|
+
declare function badRequest(message: string, requestId?: string, details?: unknown): Response;
|
|
900
|
+
/**
|
|
901
|
+
* 创建 401 Unauthorized 响应
|
|
902
|
+
*
|
|
903
|
+
* @param message - 错误消息,默认 `'Authentication required'`
|
|
904
|
+
* @param requestId - 可选请求 ID
|
|
905
|
+
* @returns error code 为 `HaiCommonError.UNAUTHORIZED.code` 的 JSON Response
|
|
906
|
+
* @example
|
|
907
|
+
* ```ts
|
|
908
|
+
* import { kit } from '@h-ai/kit'
|
|
909
|
+
*
|
|
910
|
+
* const response = kit.response.unauthorized()
|
|
911
|
+
* void response.status
|
|
912
|
+
* ```
|
|
913
|
+
*/
|
|
914
|
+
declare function unauthorized(message?: string, requestId?: string): Response;
|
|
915
|
+
/**
|
|
916
|
+
* 创建 403 Forbidden 响应
|
|
917
|
+
*
|
|
918
|
+
* @param message - 错误消息,默认 `'Access denied'`
|
|
919
|
+
* @param requestId - 可选请求 ID
|
|
920
|
+
* @returns error code 为 `HaiCommonError.FORBIDDEN.code` 的 JSON Response
|
|
921
|
+
* @example
|
|
922
|
+
* ```ts
|
|
923
|
+
* import { kit } from '@h-ai/kit'
|
|
924
|
+
*
|
|
925
|
+
* const response = kit.response.forbidden()
|
|
926
|
+
* void response.status
|
|
927
|
+
* ```
|
|
928
|
+
*/
|
|
929
|
+
declare function forbidden(message?: string, requestId?: string): Response;
|
|
930
|
+
/**
|
|
931
|
+
* 创建 404 Not Found 响应
|
|
932
|
+
*
|
|
933
|
+
* @param message - 错误消息,默认 `'Resource not found'`
|
|
934
|
+
* @param requestId - 可选请求 ID
|
|
935
|
+
* @returns error code 为 `HaiCommonError.NOT_FOUND.code` 的 JSON Response
|
|
936
|
+
* @example
|
|
937
|
+
* ```ts
|
|
938
|
+
* import { kit } from '@h-ai/kit'
|
|
939
|
+
*
|
|
940
|
+
* const response = kit.response.notFound()
|
|
941
|
+
* void response.status
|
|
942
|
+
* ```
|
|
943
|
+
*/
|
|
944
|
+
declare function notFound(message?: string, requestId?: string): Response;
|
|
945
|
+
/**
|
|
946
|
+
* 创建 409 Conflict 响应
|
|
947
|
+
*
|
|
948
|
+
* @param message - 冲突描述(如重复创建等)
|
|
949
|
+
* @param requestId - 可选请求 ID
|
|
950
|
+
* @returns error code 为 `HaiCommonError.CONFLICT.code` 的 JSON Response
|
|
951
|
+
* @example
|
|
952
|
+
* ```ts
|
|
953
|
+
* import { kit } from '@h-ai/kit'
|
|
954
|
+
*
|
|
955
|
+
* const response = kit.response.conflict('Already updated')
|
|
956
|
+
* void response.status
|
|
957
|
+
* ```
|
|
958
|
+
*/
|
|
959
|
+
declare function conflict(message: string, requestId?: string): Response;
|
|
960
|
+
/**
|
|
961
|
+
* 创建 422 Unprocessable Entity 响应(验证错误)
|
|
962
|
+
*
|
|
963
|
+
* @param errors - 字段级别的验证错误列表
|
|
964
|
+
* @param requestId - 可选请求 ID
|
|
965
|
+
* @returns error code 为 `HaiCommonError.VALIDATION_ERROR.code`,cause 仅包含 field/message
|
|
966
|
+
* @example
|
|
967
|
+
* ```ts
|
|
968
|
+
* import { kit } from '@h-ai/kit'
|
|
969
|
+
*
|
|
970
|
+
* const response = kit.response.validationError([{ field: 'title', message: 'Title is required' }])
|
|
971
|
+
* void response.status
|
|
972
|
+
* ```
|
|
973
|
+
*/
|
|
974
|
+
declare function validationError(errors: Array<{
|
|
975
|
+
field: string;
|
|
976
|
+
message: string;
|
|
977
|
+
}>, requestId?: string): Response;
|
|
978
|
+
/**
|
|
979
|
+
* 创建 500 Internal Server Error 响应
|
|
980
|
+
*
|
|
981
|
+
* @param message - 错误消息,默认 `'Internal server error'`
|
|
982
|
+
* @param requestId - 可选请求 ID
|
|
983
|
+
* @returns error code 为 `HaiCommonError.INTERNAL_ERROR.code` 的 JSON Response
|
|
984
|
+
* @example
|
|
985
|
+
* ```ts
|
|
986
|
+
* import { kit } from '@h-ai/kit'
|
|
987
|
+
*
|
|
988
|
+
* const response = kit.response.internalError()
|
|
989
|
+
* void response.status
|
|
990
|
+
* ```
|
|
991
|
+
*/
|
|
992
|
+
declare function internalError(message?: string, requestId?: string): Response;
|
|
993
|
+
/**
|
|
994
|
+
* 创建重定向响应
|
|
995
|
+
*
|
|
996
|
+
* @param url - 目标 URL
|
|
997
|
+
* @param status - HTTP 状态码,默认 302;常用 303(POST 后重定向)
|
|
998
|
+
* @returns 带 `Location` 头的空 body Response
|
|
999
|
+
* @example
|
|
1000
|
+
* ```ts
|
|
1001
|
+
* import { kit } from '@h-ai/kit'
|
|
1002
|
+
*
|
|
1003
|
+
* const response = kit.response.redirect('/articles', 303)
|
|
1004
|
+
* void response.status
|
|
1005
|
+
* ```
|
|
1006
|
+
*/
|
|
1007
|
+
declare function redirect(url: string, status?: 301 | 302 | 303 | 307 | 308): Response;
|
|
1008
|
+
/**
|
|
1009
|
+
* 将 HaiResult<T> 转换为标准 API Response
|
|
1010
|
+
*
|
|
1011
|
+
* 成功时返回 200 ok(data),失败时从 error 对象中提取 code、message,
|
|
1012
|
+
* 并根据 httpStatusMap 或错误定义映射 HTTP 状态码;未知状态按 500 安全处理。
|
|
1013
|
+
*
|
|
1014
|
+
* @param result - core HaiResult 对象({ success, data } 或 { success: false, error })
|
|
1015
|
+
* @param httpStatusMap - 模块导出的错误码 → HTTP 状态码映射表(如 IamErrorHttpStatus)
|
|
1016
|
+
* @param requestId - 可选请求 ID,用于链路追踪
|
|
1017
|
+
* @returns 标准化 JSON Response
|
|
1018
|
+
* @example
|
|
1019
|
+
* ```ts
|
|
1020
|
+
* import { err, HaiCommonError, type HaiResult } from '@h-ai/core'
|
|
1021
|
+
* import { kit } from '@h-ai/kit'
|
|
1022
|
+
*
|
|
1023
|
+
* function respond(result: HaiResult<{ id: string }>): Response {
|
|
1024
|
+
* return kit.response.fromResult(result)
|
|
1025
|
+
* }
|
|
1026
|
+
* const response = kit.response.fromResult(err(HaiCommonError.NOT_FOUND, 'Article missing'))
|
|
1027
|
+
* void response.status
|
|
1028
|
+
* ```
|
|
1029
|
+
*/
|
|
1030
|
+
declare function fromResult<T>(result: HaiResult<T>, httpStatusMap?: Record<number | string, number>, requestId?: string): Response;
|
|
1031
|
+
/**
|
|
1032
|
+
* 按模块错误的 HTTP 状态构建安全的统一 API 错误响应。
|
|
1033
|
+
* @param haiError - 包含 code、message 和可选 httpStatus 的模块错误。
|
|
1034
|
+
* @param requestId - 可选的请求追踪标识。
|
|
1035
|
+
* @returns 包含统一错误结构和对应 HTTP 状态的 Response。
|
|
1036
|
+
* @example
|
|
1037
|
+
* ```ts
|
|
1038
|
+
* import { kit } from '@h-ai/kit'
|
|
1039
|
+
*
|
|
1040
|
+
* const response = kit.response.fromError({ code: 'app:articles:001', message: 'Article missing', httpStatus: 404 })
|
|
1041
|
+
* void response.status
|
|
1042
|
+
* ```
|
|
1043
|
+
*/
|
|
1044
|
+
declare function fromError(haiError: HaiError, requestId?: string): Response;
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* @h-ai/kit — 请求数据验证
|
|
1048
|
+
*
|
|
1049
|
+
* 基于 Zod 的请求数据验证工具,支持表单/JSON Body、URL 查询参数与路由参数。
|
|
1050
|
+
* Zod 默认错误消息的本地化规则委托给 `core.zodValidation`,避免 kit / serv
|
|
1051
|
+
* 等模块重复维护同一套 issue 解析与消息映射逻辑;模块前缀映射也统一交给
|
|
1052
|
+
* `core.zodValidation.createPrefixedZodMessageGetter()` 处理。
|
|
1053
|
+
* 每种数据源都提供两类 API:
|
|
1054
|
+
* - 安全返回:`FormValidationResult`
|
|
1055
|
+
* - 失败抛出:`OrFail`(抛出 `Response`,由 kit.handler 作为 API 控制流处理)
|
|
1056
|
+
* @module kit-validation
|
|
1057
|
+
*/
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* 从 Request 解析并验证表单数据,失败时 throw Response
|
|
1061
|
+
*
|
|
1062
|
+
* 与 `validateForm` 功能相同,但校验失败时 throw 400 Response(SvelteKit 控制流),
|
|
1063
|
+
* 搭配 `kit.handler()` 使用可精简 handler 代码。
|
|
1064
|
+
*
|
|
1065
|
+
* @param request - SvelteKit 请求对象
|
|
1066
|
+
* @param schema - Zod Schema
|
|
1067
|
+
* @returns 校验通过的数据(类型安全)
|
|
1068
|
+
* @throws Response - 400 BadRequest(含首条错误消息)
|
|
1069
|
+
* @example 模块内部上下文
|
|
1070
|
+
* ```ts
|
|
1071
|
+
* const schema = zod.object({ title: zod.string().min(1) })
|
|
1072
|
+
* const result = await validateFormOrFail(new Request('https://app.example.com/api', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: 'Example' }) }), schema)
|
|
1073
|
+
* // 校验失败抛出 Response,由框架错误边界透传。
|
|
1074
|
+
* void result.title
|
|
1075
|
+
* ```
|
|
1076
|
+
*/
|
|
1077
|
+
declare function validateFormOrFail<T extends z.ZodType>(request: Request, schema: T): Promise<z.infer<T>>;
|
|
1078
|
+
/**
|
|
1079
|
+
* 从 URL 查询参数验证,失败时 throw Response
|
|
1080
|
+
*
|
|
1081
|
+
* @param url - 请求 URL 对象
|
|
1082
|
+
* @param schema - Zod Schema
|
|
1083
|
+
* @returns 校验通过的数据
|
|
1084
|
+
* @throws Response - 400 BadRequest
|
|
1085
|
+
* @example 模块内部上下文
|
|
1086
|
+
* ```ts
|
|
1087
|
+
* const schema = zod.object({ title: zod.string().min(1) })
|
|
1088
|
+
* const result = validateQueryOrFail(new URL('https://app.example.com/articles?title=Example'), schema)
|
|
1089
|
+
* // 校验失败抛出 Response,由框架错误边界透传。
|
|
1090
|
+
* void result.title
|
|
1091
|
+
* ```
|
|
1092
|
+
*/
|
|
1093
|
+
declare function validateQueryOrFail<T extends z.ZodType>(url: URL, schema: T): z.infer<T>;
|
|
1094
|
+
/**
|
|
1095
|
+
* 验证路径参数,失败时 throw Response
|
|
1096
|
+
*
|
|
1097
|
+
* @param params - SvelteKit 路由参数
|
|
1098
|
+
* @param schema - Zod Schema
|
|
1099
|
+
* @returns 校验通过的数据
|
|
1100
|
+
* @throws Response - 400 BadRequest
|
|
1101
|
+
* @example 模块内部上下文
|
|
1102
|
+
* ```ts
|
|
1103
|
+
* const schema = zod.object({ title: zod.string().min(1) })
|
|
1104
|
+
* const result = validateParamsOrFail({ title: 'Example' }, schema)
|
|
1105
|
+
* // 校验失败抛出 Response,由框架错误边界透传。
|
|
1106
|
+
* void result.title
|
|
1107
|
+
* ```
|
|
1108
|
+
*/
|
|
1109
|
+
declare function validateParamsOrFail<T extends z.ZodType>(params: Record<string, string>, schema: T): z.infer<T>;
|
|
1110
|
+
/**
|
|
1111
|
+
* 路径参数 id 校验 Schema
|
|
1112
|
+
*
|
|
1113
|
+
* 验证 `event.params.id` 为非空字符串。
|
|
1114
|
+
* @example
|
|
1115
|
+
* ```ts
|
|
1116
|
+
* import { IdParamSchema } from '@h-ai/kit'
|
|
1117
|
+
*
|
|
1118
|
+
* const value = IdParamSchema.parse({ id: 'article_1' })
|
|
1119
|
+
* void value
|
|
1120
|
+
* ```
|
|
1121
|
+
*/
|
|
1122
|
+
declare const IdParamSchema: z.ZodObject<{
|
|
1123
|
+
id: z.ZodString;
|
|
1124
|
+
}, z.core.$strip>;
|
|
1125
|
+
/**
|
|
1126
|
+
* 通用分页查询参数 Schema
|
|
1127
|
+
*
|
|
1128
|
+
* 包含 page(默认 1)、pageSize(默认 20,上限 100)、search(可选)。
|
|
1129
|
+
* 可通过 `.extend()` 扩展业务字段。
|
|
1130
|
+
* @example
|
|
1131
|
+
* ```ts
|
|
1132
|
+
* import { PaginationQuerySchema } from '@h-ai/kit'
|
|
1133
|
+
*
|
|
1134
|
+
* const value = PaginationQuerySchema.parse({ page: '1', pageSize: '20' })
|
|
1135
|
+
* void value
|
|
1136
|
+
* ```
|
|
1137
|
+
*/
|
|
1138
|
+
declare const PaginationQuerySchema: z.ZodObject<{
|
|
1139
|
+
page: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
1140
|
+
pageSize: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
1141
|
+
search: z.ZodOptional<z.ZodString>;
|
|
1142
|
+
}, z.core.$strip>;
|
|
1143
|
+
|
|
1144
|
+
/**
|
|
1145
|
+
* @h-ai/kit — 速率限制中间件
|
|
1146
|
+
*
|
|
1147
|
+
* 以首个成功请求为起点的固定窗口限流;后续计数不延长窗口。
|
|
1148
|
+
* 内置内存存储(`MemoryRateLimitStore`),并支持通过 `RateLimitStore` 接入
|
|
1149
|
+
* Redis 等外部存储后端。
|
|
1150
|
+
* @module kit-ratelimit
|
|
1151
|
+
*/
|
|
1152
|
+
|
|
1153
|
+
/**
|
|
1154
|
+
* 速率限制存储条目
|
|
1155
|
+
* @example
|
|
1156
|
+
* ```ts
|
|
1157
|
+
* import type { RateLimitEntry } from '@h-ai/kit'
|
|
1158
|
+
*
|
|
1159
|
+
* const value: RateLimitEntry = { count: 1, resetAt: Date.now() + 60_000 }
|
|
1160
|
+
* void value
|
|
1161
|
+
* ```
|
|
1162
|
+
*/
|
|
1163
|
+
interface RateLimitEntry {
|
|
1164
|
+
/** 当前窗口内的请求次数 */
|
|
1165
|
+
count: number;
|
|
1166
|
+
/** 窗口重置时间戳(ms) */
|
|
1167
|
+
resetAt: number;
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* 原子固定窗口计数器;故障抛错由中间件转为503,禁止读改写回退。
|
|
1171
|
+
* @example
|
|
1172
|
+
* ```ts
|
|
1173
|
+
* import type { RateLimitStore } from '@h-ai/kit'
|
|
1174
|
+
*
|
|
1175
|
+
* async function count(store: RateLimitStore) {
|
|
1176
|
+
* return store.increment('client_1', 60_000)
|
|
1177
|
+
* }
|
|
1178
|
+
* ```
|
|
1179
|
+
*/
|
|
1180
|
+
interface RateLimitStore {
|
|
1181
|
+
/** shared要求共享后端;memory仅限显式开发/测试。 */
|
|
1182
|
+
readonly scope: 'shared' | 'memory';
|
|
1183
|
+
/** 原子计数并保留首次请求的窗口,返回提交后的计数/重置点。 */
|
|
1184
|
+
increment: (key: string, windowMs: number) => Promise<RateLimitEntry> | RateLimitEntry;
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* 共享缓存的最小结构契约;使用其单键CAS与服务端TTL,不使用锁或客户端到期清零。
|
|
1188
|
+
* @example
|
|
1189
|
+
* ```ts
|
|
1190
|
+
* import type { RateLimitCache } from '@h-ai/kit'
|
|
1191
|
+
* import { cache } from '@h-ai/cache'
|
|
1192
|
+
*
|
|
1193
|
+
* const backend: RateLimitCache = cache
|
|
1194
|
+
* void backend
|
|
1195
|
+
* ```
|
|
1196
|
+
*/
|
|
1197
|
+
interface RateLimitCache {
|
|
1198
|
+
readonly config: {
|
|
1199
|
+
type: string;
|
|
1200
|
+
} | null;
|
|
1201
|
+
readonly kv: {
|
|
1202
|
+
get: <T>(key: string) => Promise<HaiResult<T | null>>;
|
|
1203
|
+
compareAndSwap: (key: string, expected: RateLimitEntry | undefined, value: RateLimitEntry, options: {
|
|
1204
|
+
px?: number;
|
|
1205
|
+
keepTtl?: boolean;
|
|
1206
|
+
}) => Promise<HaiResult<boolean>>;
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* 内存速率限制存储
|
|
1211
|
+
*
|
|
1212
|
+
* 基于 Map 实现,仅用于单进程开发/测试;每次请求最多扫描 64 项过期窗口,
|
|
1213
|
+
* 不持有定时器。最多 10000 项,容量耗尽时拒绝新 key,不驱逐有效计数以免绕过限流。
|
|
1214
|
+
* @example
|
|
1215
|
+
* ```ts
|
|
1216
|
+
* import { kit } from '@h-ai/kit'
|
|
1217
|
+
*
|
|
1218
|
+
* // 仅开发/测试;丢弃引用即可回收,无内部定时器。
|
|
1219
|
+
* const store = kit.middleware.createMemoryRateLimitStore()
|
|
1220
|
+
* void store.increment('client_1', 60_000)
|
|
1221
|
+
* ```
|
|
1222
|
+
*/
|
|
1223
|
+
declare class MemoryRateLimitStore implements RateLimitStore {
|
|
1224
|
+
readonly scope: "memory";
|
|
1225
|
+
private store;
|
|
1226
|
+
private cleanupCursor;
|
|
1227
|
+
/**
|
|
1228
|
+
* 有界惰性回收;保留迭代位置,避免每次从头扫描使尾部过期项永久滞留。
|
|
1229
|
+
* @param now - 当前请求时间,等于 resetAt 时窗口已经失效。
|
|
1230
|
+
*/
|
|
1231
|
+
private prune;
|
|
1232
|
+
/**
|
|
1233
|
+
* 原子自增(内存实现为同步操作,天然原子)
|
|
1234
|
+
*
|
|
1235
|
+
* @param key - 限流键
|
|
1236
|
+
* @param windowMs - 窗口时长
|
|
1237
|
+
* @returns 自增后的快照,修改返回对象不会改变已保存的计数。
|
|
1238
|
+
* @throws 新 key 超出容量或期限非法;中间件将其转换为 503。
|
|
1239
|
+
* @example store.increment('client', 60_000)
|
|
1240
|
+
*/
|
|
1241
|
+
increment(key: string, windowMs: number): RateLimitEntry;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
/**
|
|
1245
|
+
* @h-ai/kit — 类型定义
|
|
1246
|
+
*
|
|
1247
|
+
* SvelteKit 集成相关类型
|
|
1248
|
+
* @module kit-types
|
|
1249
|
+
*/
|
|
1250
|
+
|
|
1251
|
+
/**
|
|
1252
|
+
* 当前请求的认证 Cookie 写入契约;不持有跨请求状态。
|
|
1253
|
+
* @example
|
|
1254
|
+
* ```ts
|
|
1255
|
+
* import type { AuthCookieWriter } from '@h-ai/kit'
|
|
1256
|
+
* import { kit } from '@h-ai/kit'
|
|
1257
|
+
*
|
|
1258
|
+
* async function signOut(cookies: AuthCookieWriter) {
|
|
1259
|
+
* const result = await kit.auth.logout(cookies)
|
|
1260
|
+
* return result.success
|
|
1261
|
+
* }
|
|
1262
|
+
* ```
|
|
1263
|
+
*/
|
|
1264
|
+
interface AuthCookieWriter {
|
|
1265
|
+
set: (name: string, value: string, options: {
|
|
1266
|
+
path: string;
|
|
1267
|
+
httpOnly: boolean;
|
|
1268
|
+
sameSite: 'lax' | 'strict' | 'none';
|
|
1269
|
+
secure: boolean;
|
|
1270
|
+
maxAge?: number;
|
|
1271
|
+
}) => void;
|
|
1272
|
+
delete: (name: string, options: {
|
|
1273
|
+
path: string;
|
|
1274
|
+
}) => void;
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Kit 服务端公开命名空间;消费者声明只引用公开类型,不暴露内部函数入口。
|
|
1278
|
+
* @example
|
|
1279
|
+
* ```ts
|
|
1280
|
+
* import type { KitFunctions } from '@h-ai/kit'
|
|
1281
|
+
* import { kit } from '@h-ai/kit'
|
|
1282
|
+
*
|
|
1283
|
+
* const runtime: KitFunctions = kit
|
|
1284
|
+
* const response = runtime.response.ok({ ready: true })
|
|
1285
|
+
* void response.status
|
|
1286
|
+
* ```
|
|
1287
|
+
*/
|
|
1288
|
+
interface KitFunctions {
|
|
1289
|
+
createFeedbackHandler: typeof createFeedbackHandler;
|
|
1290
|
+
middleware: {
|
|
1291
|
+
rateLimit: (config: RateLimitConfig) => Middleware;
|
|
1292
|
+
createCacheRateLimitStore: (backend: RateLimitCache, options: {
|
|
1293
|
+
namespace: string;
|
|
1294
|
+
allowMemory?: boolean;
|
|
1295
|
+
}) => RateLimitStore;
|
|
1296
|
+
createMemoryRateLimitStore: () => MemoryRateLimitStore;
|
|
1297
|
+
};
|
|
1298
|
+
createHandle: typeof createHandle;
|
|
1299
|
+
sequence: typeof sequence;
|
|
1300
|
+
handler: typeof handler;
|
|
1301
|
+
guard: {
|
|
1302
|
+
requireSensitive: typeof requireSensitiveOperation;
|
|
1303
|
+
require: typeof requirePermission;
|
|
1304
|
+
check: typeof hasPermission;
|
|
1305
|
+
};
|
|
1306
|
+
response: {
|
|
1307
|
+
ok: typeof ok;
|
|
1308
|
+
created: typeof created;
|
|
1309
|
+
noContent: typeof noContent;
|
|
1310
|
+
error: typeof error;
|
|
1311
|
+
badRequest: typeof badRequest;
|
|
1312
|
+
unauthorized: typeof unauthorized;
|
|
1313
|
+
forbidden: typeof forbidden;
|
|
1314
|
+
notFound: typeof notFound;
|
|
1315
|
+
conflict: typeof conflict;
|
|
1316
|
+
validationError: typeof validationError;
|
|
1317
|
+
internalError: typeof internalError;
|
|
1318
|
+
redirect: typeof redirect;
|
|
1319
|
+
fromResult: typeof fromResult;
|
|
1320
|
+
fromError: typeof fromError;
|
|
1321
|
+
};
|
|
1322
|
+
validate: {
|
|
1323
|
+
body: typeof validateFormOrFail;
|
|
1324
|
+
query: typeof validateQueryOrFail;
|
|
1325
|
+
params: typeof validateParamsOrFail;
|
|
1326
|
+
IdParamSchema: typeof IdParamSchema;
|
|
1327
|
+
PaginationQuerySchema: typeof PaginationQuerySchema;
|
|
1328
|
+
};
|
|
1329
|
+
client: {
|
|
1330
|
+
create: typeof createKitClient;
|
|
1331
|
+
createAuthActions: typeof createKitAuthActions;
|
|
1332
|
+
installBrowserTransport: typeof installBrowserTransport;
|
|
1333
|
+
installBrowserTransportFetch: typeof installBrowserTransportFetch;
|
|
1334
|
+
};
|
|
1335
|
+
auth: {
|
|
1336
|
+
publicPaths: string[];
|
|
1337
|
+
createApi: typeof createAuthApi;
|
|
1338
|
+
login: (cookies: AuthCookieWriter, credentials: PasswordCredentials, context?: AuthRequestContext) => Promise<HaiResult<AuthResult>>;
|
|
1339
|
+
loginWithOtp: (cookies: AuthCookieWriter, credentials: OtpCredentials, context?: AuthRequestContext) => Promise<HaiResult<AuthResult>>;
|
|
1340
|
+
loginWithLdap: (cookies: AuthCookieWriter, credentials: LdapCredentials, context?: AuthRequestContext) => Promise<HaiResult<AuthResult>>;
|
|
1341
|
+
loginWithApiKey: (cookies: AuthCookieWriter, credentials: ApiKeyCredentials, context?: AuthRequestContext) => Promise<HaiResult<AuthResult>>;
|
|
1342
|
+
registerAndLogin: (cookies: AuthCookieWriter, options: RegisterOptions, context?: AuthRequestContext) => Promise<HaiResult<AuthResult>>;
|
|
1343
|
+
logout: (cookies: AuthCookieWriter, accessToken?: string | null) => Promise<HaiResult<void>>;
|
|
1344
|
+
setBrowserToken: (token: string) => void;
|
|
1345
|
+
clearBrowserToken: () => void;
|
|
1346
|
+
};
|
|
1347
|
+
crud: {
|
|
1348
|
+
define: typeof defineCrud;
|
|
1349
|
+
};
|
|
1350
|
+
i18n: {
|
|
1351
|
+
setLocale: (locale: string) => void;
|
|
1352
|
+
runWithLocale: typeof core.i18n.runWithLocale;
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
1356
|
+
* 浏览器实际可用的 Kit 命名空间;不含服务端认证、Hook 或中间件。
|
|
1357
|
+
* @example
|
|
1358
|
+
* ```ts
|
|
1359
|
+
* import type { BrowserKitFunctions } from '@h-ai/kit'
|
|
1360
|
+
* import { kit } from '@h-ai/kit'
|
|
1361
|
+
*
|
|
1362
|
+
* const browser: BrowserKitFunctions = kit
|
|
1363
|
+
* browser.auth.clearBrowserToken()
|
|
1364
|
+
* ```
|
|
1365
|
+
*/
|
|
1366
|
+
interface BrowserKitFunctions {
|
|
1367
|
+
client: {
|
|
1368
|
+
create: typeof createKitClient;
|
|
1369
|
+
createAuthActions: typeof createKitAuthActions;
|
|
1370
|
+
installBrowserTransport: typeof installBrowserTransport;
|
|
1371
|
+
installBrowserTransportFetch: typeof installBrowserTransportFetch;
|
|
1372
|
+
};
|
|
1373
|
+
crud: {
|
|
1374
|
+
define: typeof defineCrud;
|
|
1375
|
+
};
|
|
1376
|
+
auth: {
|
|
1377
|
+
setBrowserToken: (token: string) => void;
|
|
1378
|
+
clearBrowserToken: () => void;
|
|
1379
|
+
};
|
|
1380
|
+
i18n: {
|
|
1381
|
+
setLocale: (locale: string) => void;
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* 服务器监听配置(对应 `config/_kit.yml` 的 `server`)。
|
|
1386
|
+
* @example
|
|
1387
|
+
* ```ts
|
|
1388
|
+
* import type { KitServerConfig } from '@h-ai/kit'
|
|
1389
|
+
*
|
|
1390
|
+
* const value: KitServerConfig = { host: '127.0.0.1', port: 3000 }
|
|
1391
|
+
* void value
|
|
1392
|
+
* ```
|
|
1393
|
+
*/
|
|
1394
|
+
interface KitServerConfig {
|
|
1395
|
+
readonly host: string;
|
|
1396
|
+
readonly port: number;
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* `config/_kit.yml` 中的服务器监听输入类型。
|
|
1400
|
+
* @example
|
|
1401
|
+
* ```ts
|
|
1402
|
+
* import type { KitServerConfigInput } from '@h-ai/kit'
|
|
1403
|
+
*
|
|
1404
|
+
* const value: KitServerConfigInput = { port: 4000 }
|
|
1405
|
+
* void value
|
|
1406
|
+
* ```
|
|
1407
|
+
*/
|
|
1408
|
+
type KitServerConfigInput = Partial<KitServerConfig>;
|
|
1409
|
+
/**
|
|
1410
|
+
* `config/_kit.yml` 中的 transport 配置。
|
|
1411
|
+
* @example
|
|
1412
|
+
* ```ts
|
|
1413
|
+
* import type { KitTransportConfig } from '@h-ai/kit'
|
|
1414
|
+
*
|
|
1415
|
+
* const value: KitTransportConfig = { keyExchangePath: '/api/_hai/key-exchange', excludePaths: ['/api/health'] }
|
|
1416
|
+
* void value
|
|
1417
|
+
* ```
|
|
1418
|
+
*/
|
|
1419
|
+
interface KitTransportConfig {
|
|
1420
|
+
/** 双向明文 UTF-8 字节上限;省略使用 1 MiB,范围 1 KiB~64 MiB。 */
|
|
1421
|
+
readonly maxPlaintextBytes?: number;
|
|
1422
|
+
/** 请求时间允许领先服务器的秒数,默认 30,整数范围 0~300。 */
|
|
1423
|
+
readonly maxFutureSkewSeconds?: number;
|
|
1424
|
+
/** 请求从签发起的有效秒数,默认 300,整数范围 1~600。 */
|
|
1425
|
+
readonly maxRequestAgeSeconds?: number;
|
|
1426
|
+
/** 按完整路径段匹配的明文容量;* 匹配单段。 */
|
|
1427
|
+
readonly routePlaintextBytes?: Readonly<Record<string, number>>;
|
|
1428
|
+
readonly keyExchangePath: `/${string}`;
|
|
1429
|
+
readonly excludePaths: readonly `/${string}`[];
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
* `config/_kit.yml` 的顶层配置结构。
|
|
1433
|
+
* @example
|
|
1434
|
+
* ```ts
|
|
1435
|
+
* import type { KitConfig } from '@h-ai/kit'
|
|
1436
|
+
*
|
|
1437
|
+
* const value: KitConfig = { server: { host: '127.0.0.1', port: 3000 }, transport: false, trustedProxies: 'auto', access: false }
|
|
1438
|
+
* void value
|
|
1439
|
+
* ```
|
|
1440
|
+
*/
|
|
1441
|
+
interface KitConfig {
|
|
1442
|
+
readonly server: KitServerConfig;
|
|
1443
|
+
readonly transport: false | KitTransportConfig;
|
|
1444
|
+
/** auto 仅在受管私有容器入口采用容器网段;普通宿主忽略转发头。 */
|
|
1445
|
+
readonly trustedProxies: TrustedProxies;
|
|
1446
|
+
/** 每次请求的地址/时间/路径限制,默认 false;不会限制由静态服务器直接提供的资源。 */
|
|
1447
|
+
readonly access: RequestAccessConfigInput;
|
|
1448
|
+
}
|
|
1449
|
+
/**
|
|
1450
|
+
* `config/_kit.yml` 中的 transport 输入类型。
|
|
1451
|
+
* @example
|
|
1452
|
+
* ```ts
|
|
1453
|
+
* import type { KitTransportConfigInput } from '@h-ai/kit'
|
|
1454
|
+
*
|
|
1455
|
+
* const value: KitTransportConfigInput = { excludePaths: ['/api/health'] }
|
|
1456
|
+
* void value
|
|
1457
|
+
* ```
|
|
1458
|
+
*/
|
|
1459
|
+
type KitTransportConfigInput = Partial<{
|
|
1460
|
+
readonly maxPlaintextBytes: number | 'auto';
|
|
1461
|
+
readonly maxFutureSkewSeconds: number;
|
|
1462
|
+
readonly maxRequestAgeSeconds: number;
|
|
1463
|
+
readonly routePlaintextBytes: Readonly<Record<string, number>>;
|
|
1464
|
+
readonly keyExchangePath: `/${string}`;
|
|
1465
|
+
readonly excludePaths: readonly `/${string}`[];
|
|
1466
|
+
}>;
|
|
1467
|
+
/**
|
|
1468
|
+
* `config/_kit.yml` 的顶层输入类型。
|
|
1469
|
+
* @example
|
|
1470
|
+
* ```ts
|
|
1471
|
+
* import type { KitConfigInput } from '@h-ai/kit'
|
|
1472
|
+
*
|
|
1473
|
+
* const value: KitConfigInput = { transport: false }
|
|
1474
|
+
* void value
|
|
1475
|
+
* ```
|
|
1476
|
+
*/
|
|
1477
|
+
interface KitConfigInput {
|
|
1478
|
+
readonly server?: KitServerConfigInput;
|
|
1479
|
+
readonly transport?: false | KitTransportConfigInput;
|
|
1480
|
+
readonly trustedProxies?: TrustedProxies;
|
|
1481
|
+
readonly access?: RequestAccessConfigInput;
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* 浏览器可接收的 Kit 公共配置,仅包含传输协议路径和开关。
|
|
1485
|
+
* @example
|
|
1486
|
+
* ```ts
|
|
1487
|
+
* import type { KitPublicConfig } from '@h-ai/kit'
|
|
1488
|
+
* const config: KitPublicConfig = { transport: false }
|
|
1489
|
+
* ```
|
|
1490
|
+
*/
|
|
1491
|
+
type KitPublicConfig = Pick<KitConfig, 'transport'>;
|
|
1492
|
+
/**
|
|
1493
|
+
* kit.auth 认证操作(由 createHandle auth.operations 注入)
|
|
1494
|
+
*
|
|
1495
|
+
* 注入后,kit.auth.login / kit.auth.logout 等函数自动委托到 iam.auth 对应方法。
|
|
1496
|
+
* 传入 `iam.auth` 即可,kit 会自动提取所需方法。
|
|
1497
|
+
* @example
|
|
1498
|
+
* ```ts
|
|
1499
|
+
* import type { AuthOperations } from '@h-ai/kit'
|
|
1500
|
+
* import { iam } from '@h-ai/iam'
|
|
1501
|
+
*
|
|
1502
|
+
* // 在 IAM 初始化后注入其当前操作对象。
|
|
1503
|
+
* const operations: AuthOperations = iam.auth
|
|
1504
|
+
* void operations
|
|
1505
|
+
* ```
|
|
1506
|
+
*/
|
|
1507
|
+
type AuthOperations = Pick<AuthnOperations, 'login' | 'loginWithOtp' | 'loginWithLdap' | 'loginWithApiKey' | 'registerAndLogin' | 'logout'>;
|
|
1508
|
+
/**
|
|
1509
|
+
* 认证操作提供器:支持直接传对象或按需返回最新对象的工厂函数
|
|
1510
|
+
* @example
|
|
1511
|
+
* ```ts
|
|
1512
|
+
* import type { AuthOperationsProvider } from '@h-ai/kit'
|
|
1513
|
+
* import { iam } from '@h-ai/iam'
|
|
1514
|
+
*
|
|
1515
|
+
* const operations: AuthOperationsProvider = () => iam.auth
|
|
1516
|
+
* void operations
|
|
1517
|
+
* ```
|
|
1518
|
+
*/
|
|
1519
|
+
type AuthOperationsProvider = AuthOperations | (() => AuthOperations);
|
|
1520
|
+
/**
|
|
1521
|
+
* 会话数据最小接口
|
|
1522
|
+
*
|
|
1523
|
+
* 守卫和权限检查函数所需的最小会话形状。
|
|
1524
|
+
* 应用层扩展的 session(如添加 displayName / avatarUrl 等字段)
|
|
1525
|
+
* 只要包含此接口的字段即可直接传入守卫函数,无需类型断言。
|
|
1526
|
+
* @example
|
|
1527
|
+
* ```ts
|
|
1528
|
+
* import type { SessionLike } from '@h-ai/kit'
|
|
1529
|
+
*
|
|
1530
|
+
* const value: SessionLike = { userId: 'user_1', roles: ['editor'], permissions: ['article:read'] }
|
|
1531
|
+
* void value
|
|
1532
|
+
* ```
|
|
1533
|
+
*/
|
|
1534
|
+
interface SessionLike {
|
|
1535
|
+
/** 用户 ID */
|
|
1536
|
+
userId: string;
|
|
1537
|
+
/** 角色列表 */
|
|
1538
|
+
roles: string[];
|
|
1539
|
+
/** 权限列表 */
|
|
1540
|
+
permissions: string[];
|
|
1541
|
+
/** 允许任意扩展字段 */
|
|
1542
|
+
[key: string]: unknown;
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* 用户会话数据
|
|
1546
|
+
*
|
|
1547
|
+
* 在 Handle Hook 中通过 `validateSession` 解析后注入 `event.locals.session`。
|
|
1548
|
+
* 守卫和中间件通过此结构判断用户身份、角色与权限。
|
|
1549
|
+
*
|
|
1550
|
+
* 应用层可通过 `& { displayName: string }` 等方式扩展,
|
|
1551
|
+
* 扩展后的类型自动兼容所有守卫和权限检查函数(它们接受 `SessionLike`)。
|
|
1552
|
+
* @example
|
|
1553
|
+
* ```ts
|
|
1554
|
+
* import type { SessionData } from '@h-ai/kit'
|
|
1555
|
+
*
|
|
1556
|
+
* const value: SessionData = { userId: 'user_1', username: 'alice', roles: ['editor'], permissions: ['article:read'] }
|
|
1557
|
+
* void value
|
|
1558
|
+
* ```
|
|
1559
|
+
*/
|
|
1560
|
+
interface SessionData {
|
|
1561
|
+
/** 用户 ID */
|
|
1562
|
+
userId: string;
|
|
1563
|
+
/** 用户名 */
|
|
1564
|
+
username?: string;
|
|
1565
|
+
/** 角色列表 */
|
|
1566
|
+
roles: string[];
|
|
1567
|
+
/** 权限列表 */
|
|
1568
|
+
permissions: string[];
|
|
1569
|
+
/** 自定义数据 */
|
|
1570
|
+
data?: Record<string, unknown>;
|
|
1571
|
+
/** 允许任意扩展字段 */
|
|
1572
|
+
[key: string]: unknown;
|
|
1573
|
+
}
|
|
1574
|
+
/**
|
|
1575
|
+
* 扩展的请求事件
|
|
1576
|
+
*
|
|
1577
|
+
* 在 SvelteKit 原生 `RequestEvent` 基础上注入会话和请求 ID。
|
|
1578
|
+
* @example
|
|
1579
|
+
* ```ts
|
|
1580
|
+
* import type { HaiRequestEvent } from '@h-ai/kit'
|
|
1581
|
+
*
|
|
1582
|
+
* function requestLabel(event: HaiRequestEvent) {
|
|
1583
|
+
* return { requestId: event.requestId, userId: event.session?.userId }
|
|
1584
|
+
* }
|
|
1585
|
+
* ```
|
|
1586
|
+
*/
|
|
1587
|
+
interface HaiRequestEvent extends RequestEvent {
|
|
1588
|
+
/** 会话数据 */
|
|
1589
|
+
session?: SessionData;
|
|
1590
|
+
/** 请求 ID */
|
|
1591
|
+
requestId: string;
|
|
1592
|
+
}
|
|
1593
|
+
/**
|
|
1594
|
+
* 中间件上下文
|
|
1595
|
+
*
|
|
1596
|
+
* 由 `createHandle` 构建,传递给所有中间件函数。
|
|
1597
|
+
* 包含当前请求事件、已解析的会话以及请求唯一 ID。
|
|
1598
|
+
* @example
|
|
1599
|
+
* ```ts
|
|
1600
|
+
* import type { MiddlewareContext } from '@h-ai/kit'
|
|
1601
|
+
*
|
|
1602
|
+
* function requestLabel(context: MiddlewareContext) {
|
|
1603
|
+
* return context.event.url.pathname + ':' + context.requestId
|
|
1604
|
+
* }
|
|
1605
|
+
* ```
|
|
1606
|
+
*/
|
|
1607
|
+
interface MiddlewareContext {
|
|
1608
|
+
/** 请求事件 */
|
|
1609
|
+
event: RequestEvent;
|
|
1610
|
+
/** 会话数据 */
|
|
1611
|
+
session?: SessionData;
|
|
1612
|
+
/** 请求 ID */
|
|
1613
|
+
requestId: string;
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* 中间件函数
|
|
1617
|
+
*
|
|
1618
|
+
* 遵循洋葱模型:调用 `next()` 前执行前置逻辑,调用后执行后置逻辑。
|
|
1619
|
+
* @example
|
|
1620
|
+
* ```ts
|
|
1621
|
+
* import type { Middleware } from '@h-ai/kit'
|
|
1622
|
+
*
|
|
1623
|
+
* const value: Middleware = async (_context, next) => { const response = await next(); response.headers.set('X-Example', 'enabled'); return response }
|
|
1624
|
+
* void value
|
|
1625
|
+
* ```
|
|
1626
|
+
*/
|
|
1627
|
+
type Middleware = (context: MiddlewareContext, next: () => Promise<Response>) => Promise<Response>;
|
|
1628
|
+
/**
|
|
1629
|
+
* 路由守卫结果
|
|
1630
|
+
*
|
|
1631
|
+
* 守卫函数必须返回此结构。`allowed: true` 表示放行,否则根据
|
|
1632
|
+
* `redirect` / `message` / `status` 决定拦截行为。
|
|
1633
|
+
* @example
|
|
1634
|
+
* ```ts
|
|
1635
|
+
* import type { GuardResult } from '@h-ai/kit'
|
|
1636
|
+
*
|
|
1637
|
+
* const value: GuardResult = { allowed: false, status: 403 }
|
|
1638
|
+
* void value
|
|
1639
|
+
* ```
|
|
1640
|
+
*/
|
|
1641
|
+
interface GuardResult {
|
|
1642
|
+
/** 是否允许访问 */
|
|
1643
|
+
allowed: boolean;
|
|
1644
|
+
/** 重定向 URL(拒绝时) */
|
|
1645
|
+
redirect?: string;
|
|
1646
|
+
/** 错误消息(拒绝时) */
|
|
1647
|
+
message?: string;
|
|
1648
|
+
/** HTTP 状态码 */
|
|
1649
|
+
status?: number;
|
|
1650
|
+
}
|
|
1651
|
+
/**
|
|
1652
|
+
* 路由守卫函数
|
|
1653
|
+
*
|
|
1654
|
+
* 接收请求事件与可选会话,返回 `GuardResult`(同步或异步均可)。
|
|
1655
|
+
* @example
|
|
1656
|
+
* ```ts
|
|
1657
|
+
* import type { RouteGuard } from '@h-ai/kit'
|
|
1658
|
+
*
|
|
1659
|
+
* const value: RouteGuard = (_event, session) => ({ allowed: session?.permissions.includes('article:read') ?? false, status: 403 })
|
|
1660
|
+
* void value
|
|
1661
|
+
* ```
|
|
1662
|
+
*/
|
|
1663
|
+
type RouteGuard = (event: RequestEvent, session?: SessionData) => Promise<GuardResult> | GuardResult;
|
|
1664
|
+
/**
|
|
1665
|
+
* 守卫配置
|
|
1666
|
+
*
|
|
1667
|
+
* 在 `createHandle({ guards })` 中使用。
|
|
1668
|
+
* `paths` 支持 `/*` 和 `/**` 通配符,`exclude` 优先于 `paths`。
|
|
1669
|
+
* @example
|
|
1670
|
+
* ```ts
|
|
1671
|
+
* import type { GuardConfig } from '@h-ai/kit'
|
|
1672
|
+
*
|
|
1673
|
+
* const value: GuardConfig = { paths: ['/admin/**'], guard: (_event, session) => ({ allowed: Boolean(session) }) }
|
|
1674
|
+
* void value
|
|
1675
|
+
* ```
|
|
1676
|
+
*/
|
|
1677
|
+
interface GuardConfig {
|
|
1678
|
+
/** 守卫函数 */
|
|
1679
|
+
guard: RouteGuard;
|
|
1680
|
+
/** 适用路径(glob 模式) */
|
|
1681
|
+
paths?: string[];
|
|
1682
|
+
/** 排除路径 */
|
|
1683
|
+
exclude?: string[];
|
|
1684
|
+
}
|
|
1685
|
+
/**
|
|
1686
|
+
* 认证配置
|
|
1687
|
+
*
|
|
1688
|
+
* 在 `HandleConfig.auth` 中使用,自动完成:
|
|
1689
|
+
* 1. 从 Bearer header / Cookie 提取 Token
|
|
1690
|
+
* 2. 调用 verifyToken 恢复会话
|
|
1691
|
+
* 3. 根据 protectedPaths / publicPaths 自动生成路由守卫
|
|
1692
|
+
*
|
|
1693
|
+
* API 路径(以 `/api/` 开头)未认证时返回 401 JSON;
|
|
1694
|
+
* 非 API 路径未认证时重定向到 `loginUrl`(若配置)或返回 401。
|
|
1695
|
+
* @example
|
|
1696
|
+
* ```ts
|
|
1697
|
+
* import type { HandleAuthConfig } from '@h-ai/kit'
|
|
1698
|
+
*
|
|
1699
|
+
* const value: HandleAuthConfig = { verifyToken: async () => null, protectedPaths: ['/admin/**'], loginUrl: '/auth/login' }
|
|
1700
|
+
* void value
|
|
1701
|
+
* ```
|
|
1702
|
+
*/
|
|
1703
|
+
interface HandleAuthConfig {
|
|
1704
|
+
/** 令牌验证函数,返回 SessionData 或 null */
|
|
1705
|
+
verifyToken: (token: string) => Promise<SessionData | null>;
|
|
1706
|
+
/**
|
|
1707
|
+
* 认证操作。
|
|
1708
|
+
*
|
|
1709
|
+
* - 直接对象:`operations: iam.auth`
|
|
1710
|
+
* - 工厂函数:`operations: () => iam.auth`(推荐,避免模块加载阶段捕获旧引用)
|
|
1711
|
+
*/
|
|
1712
|
+
operations?: AuthOperationsProvider;
|
|
1713
|
+
/** 未认证时 UI 路由重定向地址(如 `'/auth/login'`) */
|
|
1714
|
+
loginUrl?: string;
|
|
1715
|
+
/** Token Cookie 名(默认 `'hai_access_token'`) */
|
|
1716
|
+
cookieName?: string;
|
|
1717
|
+
/** 需要认证的路径(支持 `/*` `/**` 通配符) */
|
|
1718
|
+
protectedPaths?: string[];
|
|
1719
|
+
/** 免认证的路径(优先于 protectedPaths) */
|
|
1720
|
+
publicPaths?: string[];
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* Handle Hook 配置
|
|
1724
|
+
*
|
|
1725
|
+
* 传给 `kit.createHandle()` 的配置。
|
|
1726
|
+
*
|
|
1727
|
+
* 自动完成:
|
|
1728
|
+
* 1. 生成 requestId 并注入 `event.locals`
|
|
1729
|
+
* 2. 根据 `auth` 配置解析会话
|
|
1730
|
+
* 3. 执行路由守卫
|
|
1731
|
+
* 4. 执行中间件链(logging / rateLimit / 自定义)
|
|
1732
|
+
* 5. 调用 resolve + 附加 `X-Request-Id` 响应头
|
|
1733
|
+
* @example
|
|
1734
|
+
* ```ts
|
|
1735
|
+
* import type { HandleConfig } from '@h-ai/kit'
|
|
1736
|
+
*
|
|
1737
|
+
* const value: HandleConfig = { logging: true, auth: { verifyToken: async () => null, protectedPaths: ['/admin/**'] } }
|
|
1738
|
+
* void value
|
|
1739
|
+
* ```
|
|
1740
|
+
*/
|
|
1741
|
+
interface HandleConfig {
|
|
1742
|
+
/** 可信入口地址;auto 只信任受管私有容器网段,空数组忽略转发头。 */
|
|
1743
|
+
trustedProxies?: TrustedProxies;
|
|
1744
|
+
/** 每次请求的 IP/时间/路径策略;独立文件在创建 Handle 时读取并验证一次。 */
|
|
1745
|
+
access?: RequestAccessConfigInput;
|
|
1746
|
+
/**
|
|
1747
|
+
* 认证配置
|
|
1748
|
+
*
|
|
1749
|
+
* 提供后自动完成 Token 解析、会话恢复、路由守卫等。
|
|
1750
|
+
* 不提供则不做认证处理。
|
|
1751
|
+
*/
|
|
1752
|
+
auth?: HandleAuthConfig;
|
|
1753
|
+
/**
|
|
1754
|
+
* 速率限制配置
|
|
1755
|
+
*
|
|
1756
|
+
* 提供对象启用速率限制;设为 `false` 显式禁用。
|
|
1757
|
+
* 不提供则不开启速率限制。
|
|
1758
|
+
*/
|
|
1759
|
+
rateLimit?: Omit<RateLimitConfig, 'windowMs' | 'maxRequests'> & {
|
|
1760
|
+
windowMs?: number;
|
|
1761
|
+
maxRequests?: number;
|
|
1762
|
+
} | false;
|
|
1763
|
+
/**
|
|
1764
|
+
* 请求日志配置
|
|
1765
|
+
*
|
|
1766
|
+
* - `true`(默认):启用日志中间件
|
|
1767
|
+
* - `{ logBody: true }`:启用并记录请求体
|
|
1768
|
+
* - `false`:禁用日志
|
|
1769
|
+
*/
|
|
1770
|
+
logging?: boolean | {
|
|
1771
|
+
logBody?: boolean;
|
|
1772
|
+
};
|
|
1773
|
+
/**
|
|
1774
|
+
* 加密配置(传输加密 + Cookie 加密)
|
|
1775
|
+
*
|
|
1776
|
+
* 启用后 kit 自动在 Handle 中完成:
|
|
1777
|
+
* - 传输加密:请求体解密 / 响应体加密 / 密钥交换端点
|
|
1778
|
+
* - Cookie 加密:对指定 Cookie 的 get/set 使用 SM4-CBC 加解密并验证 HMAC-SHA256 认证
|
|
1779
|
+
*/
|
|
1780
|
+
crypto?: HookCryptoConfig;
|
|
1781
|
+
/** 自定义错误处理(不提供则使用内置的 500 JSON 响应) */
|
|
1782
|
+
onError?: (error: unknown, event: RequestEvent) => Response | Promise<Response>;
|
|
1783
|
+
/** 自定义守卫(在 auth 自动守卫之后执行) */
|
|
1784
|
+
guards?: GuardConfig[];
|
|
1785
|
+
/** 自定义中间件(在内置 logging / rateLimit 之后执行) */
|
|
1786
|
+
middleware?: Middleware[];
|
|
1787
|
+
/**
|
|
1788
|
+
* A2A 协议集成
|
|
1789
|
+
*
|
|
1790
|
+
* 传入 `ai.a2a` 操作对象后,自动挂载 Agent Card 发现端点和 JSON-RPC 处理端点。
|
|
1791
|
+
*
|
|
1792
|
+
* - 简单模式:`a2a: ai.a2a`(使用默认路径)
|
|
1793
|
+
* - 配置模式:`a2a: { operations: ai.a2a, rpcPath: '/api/a2a' }`
|
|
1794
|
+
*/
|
|
1795
|
+
a2a?: HandleA2AOperations | HandleA2AConfig;
|
|
1796
|
+
}
|
|
1797
|
+
/**
|
|
1798
|
+
* A2A 操作接口(用于 Handle 集成)
|
|
1799
|
+
*
|
|
1800
|
+
* 与 `ai.a2a` 结构兼容,可直接传入 `ai.a2a` 对象。
|
|
1801
|
+
* @example
|
|
1802
|
+
* ```ts
|
|
1803
|
+
* import type { HandleA2AOperations } from '@h-ai/kit'
|
|
1804
|
+
* import { ai } from '@h-ai/ai'
|
|
1805
|
+
*
|
|
1806
|
+
* function operationsAfterInit(): HandleA2AOperations { return ai.a2a }
|
|
1807
|
+
* ```
|
|
1808
|
+
*/
|
|
1809
|
+
interface HandleA2AOperations {
|
|
1810
|
+
/** 获取 Agent Card(返回 HaiResult 对象) */
|
|
1811
|
+
getAgentCard: () => {
|
|
1812
|
+
success: boolean;
|
|
1813
|
+
data?: unknown;
|
|
1814
|
+
error?: unknown;
|
|
1815
|
+
};
|
|
1816
|
+
/** 处理 JSON-RPC 请求 */
|
|
1817
|
+
handleRequest: (body: unknown, context?: Record<string, unknown>) => Promise<{
|
|
1818
|
+
streaming: boolean;
|
|
1819
|
+
body?: unknown;
|
|
1820
|
+
stream?: AsyncGenerator<unknown, void, undefined>;
|
|
1821
|
+
}>;
|
|
1822
|
+
}
|
|
1823
|
+
/**
|
|
1824
|
+
* A2A Handle 配置(高级模式)
|
|
1825
|
+
* @example
|
|
1826
|
+
* ```ts
|
|
1827
|
+
* import type { HandleA2AConfig } from '@h-ai/kit'
|
|
1828
|
+
* import { ai } from '@h-ai/ai'
|
|
1829
|
+
*
|
|
1830
|
+
* function configAfterInit(): HandleA2AConfig { return { operations: ai.a2a, authenticate: async () => null } }
|
|
1831
|
+
* ```
|
|
1832
|
+
*/
|
|
1833
|
+
interface HandleA2AConfig {
|
|
1834
|
+
/** A2A 操作接口(通常传入 `ai.a2a`) */
|
|
1835
|
+
operations: HandleA2AOperations;
|
|
1836
|
+
/** Agent Card 端点路径(默认 `/.well-known/agent-card.json`) */
|
|
1837
|
+
cardPath?: string;
|
|
1838
|
+
/** JSON-RPC 端点路径(默认 `/a2a`) */
|
|
1839
|
+
rpcPath?: string;
|
|
1840
|
+
/**
|
|
1841
|
+
* A2A 认证
|
|
1842
|
+
*
|
|
1843
|
+
* - `'apiKey'`:使用 IAM API Key 认证(读取 Agent Card.securitySchemes.apiKey)
|
|
1844
|
+
* - 函数:自定义认证回调;返回 null/undefined 拒绝请求(401)
|
|
1845
|
+
* 未配置此项才允许匿名请求。
|
|
1846
|
+
*/
|
|
1847
|
+
authenticate?: 'apiKey' | ((event: RequestEvent) => Promise<Record<string, unknown> | null | undefined>);
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* 传输加密详细配置
|
|
1851
|
+
* @example
|
|
1852
|
+
* ```ts
|
|
1853
|
+
* import type { TransportEncryptionOptions } from '@h-ai/kit'
|
|
1854
|
+
* import type { TransportServerKeyRing, TransportKeyStore } from '@h-ai/crypto'
|
|
1855
|
+
*
|
|
1856
|
+
* function transport(serverKeyRing: TransportServerKeyRing, keyStore: TransportKeyStore): TransportEncryptionOptions {
|
|
1857
|
+
* return { serverKeyRing, keyStore, excludePaths: ['/api/health'] }
|
|
1858
|
+
* }
|
|
1859
|
+
* ```
|
|
1860
|
+
*/
|
|
1861
|
+
interface TransportEncryptionOptions {
|
|
1862
|
+
/** 宿主构建挂载目录,默认空字符串;SvelteKit 传入 paths.relative=false 时的 resolve('/').replace(/\/$/, '')。 */
|
|
1863
|
+
basePath?: string;
|
|
1864
|
+
/** 双向业务明文 UTF-8 字节上限;默认 1 MiB,整数范围 1 KiB~64 MiB。 */
|
|
1865
|
+
maxPlaintextBytes?: number;
|
|
1866
|
+
/** 请求时间允许领先服务器的秒数,默认 30,整数范围 0~300。 */
|
|
1867
|
+
maxFutureSkewSeconds?: number;
|
|
1868
|
+
/** 请求从签发起的有效秒数,默认 300,整数范围 1~600。 */
|
|
1869
|
+
maxRequestAgeSeconds?: number;
|
|
1870
|
+
/** 路由明文容量;* 匹配一个完整路径段。 */
|
|
1871
|
+
routePlaintextBytes?: Readonly<Record<string, number>>;
|
|
1872
|
+
/** 受管服务端版本化密钥环,不进入公开 YAML/客户端配置。 */
|
|
1873
|
+
serverKeyRing: TransportServerKeyRing;
|
|
1874
|
+
/** 密钥交换端点路径(默认 `'/api/_hai/key-exchange'`) */
|
|
1875
|
+
keyExchangePath?: string;
|
|
1876
|
+
/** 排除路径(不加密),支持精确匹配和前缀匹配 */
|
|
1877
|
+
excludePaths?: readonly string[];
|
|
1878
|
+
/** 共享客户端公钥存储;适用于多节点部署。 */
|
|
1879
|
+
keyStore: TransportKeyStore;
|
|
1880
|
+
}
|
|
1881
|
+
/**
|
|
1882
|
+
* Handle Hook 加密配置
|
|
1883
|
+
*
|
|
1884
|
+
* 在 `kit.createHandle({ crypto: { ... } })` 中使用。
|
|
1885
|
+
* @example
|
|
1886
|
+
* ```ts
|
|
1887
|
+
* import type { HookCryptoConfig, TransportEncryptionOptions } from '@h-ai/kit'
|
|
1888
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1889
|
+
*
|
|
1890
|
+
* function config(transport: TransportEncryptionOptions): HookCryptoConfig { return { crypto, transport } }
|
|
1891
|
+
* ```
|
|
1892
|
+
*/
|
|
1893
|
+
interface HookCryptoConfig {
|
|
1894
|
+
/** 注入 @h-ai/crypto 实例 */
|
|
1895
|
+
crypto: CryptoFunctions;
|
|
1896
|
+
/**
|
|
1897
|
+
* 启用传输加密。
|
|
1898
|
+
* - `false` 或省略:关闭
|
|
1899
|
+
* - 对象:必填受管 serverKeyRing / keyStore,可附加公开路径配置
|
|
1900
|
+
*/
|
|
1901
|
+
transport?: false | TransportEncryptionOptions;
|
|
1902
|
+
/**
|
|
1903
|
+
* 需要加密的 Cookie 名称列表
|
|
1904
|
+
*
|
|
1905
|
+
* 列出的 Cookie 在 set 时自动 SM4-CBC 加密,get 时自动解密。
|
|
1906
|
+
*/
|
|
1907
|
+
encryptedCookies?: string[];
|
|
1908
|
+
/**
|
|
1909
|
+
* Cookie 加密密钥(32 字符十六进制 = 16 字节 SM4 密钥)
|
|
1910
|
+
*
|
|
1911
|
+
* 如不提供,则从环境变量 `HAI_KIT_COOKIE_KEY` 读取。
|
|
1912
|
+
* 配置了 `encryptedCookies` 但两者都未设置时,`kit.createHandle()` 会立即抛出配置错误,
|
|
1913
|
+
* 避免受保护 Cookie 静默降级为明文。
|
|
1914
|
+
*/
|
|
1915
|
+
cookieEncryptionKey?: string;
|
|
1916
|
+
}
|
|
1917
|
+
/**
|
|
1918
|
+
* API 响应包装
|
|
1919
|
+
*
|
|
1920
|
+
* `kit.response.*` 系列函数统一返回此结构的 JSON 响应。
|
|
1921
|
+
* 成功时 `success: true` 且 `data` 有值;失败时 `success: false` 且 `error` 有值。
|
|
1922
|
+
* @example
|
|
1923
|
+
* ```ts
|
|
1924
|
+
* import type { ApiResponse } from '@h-ai/kit'
|
|
1925
|
+
*
|
|
1926
|
+
* const value: ApiResponse<{ id: string }> = { success: true, data: { id: 'article_1' } }
|
|
1927
|
+
* void value
|
|
1928
|
+
* ```
|
|
1929
|
+
*/
|
|
1930
|
+
interface ApiResponse<T = unknown> {
|
|
1931
|
+
/** 是否成功 */
|
|
1932
|
+
success: boolean;
|
|
1933
|
+
/** 数据 */
|
|
1934
|
+
data?: T;
|
|
1935
|
+
/** 错误信息 */
|
|
1936
|
+
error?: {
|
|
1937
|
+
code: string;
|
|
1938
|
+
message: string;
|
|
1939
|
+
traceId?: string;
|
|
1940
|
+
/** 对应 HTTP 状态 */
|
|
1941
|
+
httpStatus?: number;
|
|
1942
|
+
/** 仅含已本地化 field/message 的字段校验信息 */
|
|
1943
|
+
cause?: HaiError['cause'];
|
|
1944
|
+
/** 经公开边界脱敏的扩展信息 */
|
|
1945
|
+
ext?: HaiError['ext'];
|
|
1946
|
+
details?: unknown;
|
|
1947
|
+
};
|
|
1948
|
+
/** 请求 ID */
|
|
1949
|
+
requestId?: string;
|
|
1950
|
+
}
|
|
1951
|
+
/**
|
|
1952
|
+
* 表单验证错误
|
|
1953
|
+
*
|
|
1954
|
+
* 由 `kit.validate.*` 在校验失败时返回。
|
|
1955
|
+
* `field` 使用点号路径(如 `'address.city'`),`_` 表示全局错误。
|
|
1956
|
+
* @example
|
|
1957
|
+
* ```ts
|
|
1958
|
+
* import type { FormError } from '@h-ai/kit'
|
|
1959
|
+
*
|
|
1960
|
+
* const value: FormError = { field: 'title', message: 'Title is required' }
|
|
1961
|
+
* void value
|
|
1962
|
+
* ```
|
|
1963
|
+
*/
|
|
1964
|
+
interface FormError {
|
|
1965
|
+
/** 字段名 */
|
|
1966
|
+
field: string;
|
|
1967
|
+
/** 错误消息 */
|
|
1968
|
+
message: string;
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* 表单验证结果
|
|
1972
|
+
*
|
|
1973
|
+
* @template T - Zod schema 推导出的数据类型
|
|
1974
|
+
* @example
|
|
1975
|
+
* ```ts
|
|
1976
|
+
* import type { FormValidationResult } from '@h-ai/kit'
|
|
1977
|
+
*
|
|
1978
|
+
* const value: FormValidationResult<{ title: string }> = { valid: true, data: { title: 'Example' }, errors: [] }
|
|
1979
|
+
* void value
|
|
1980
|
+
* ```
|
|
1981
|
+
*/
|
|
1982
|
+
interface FormValidationResult<T> {
|
|
1983
|
+
/** 是否有效 */
|
|
1984
|
+
valid: boolean;
|
|
1985
|
+
/** 解析后的数据 */
|
|
1986
|
+
data?: T;
|
|
1987
|
+
/** 错误列表 */
|
|
1988
|
+
errors: FormError[];
|
|
1989
|
+
}
|
|
1990
|
+
/**
|
|
1991
|
+
* 速率限制配置
|
|
1992
|
+
*
|
|
1993
|
+
* 基于可插拔原子存储的固定窗口限流。超限后返回 429 状态码与 `Retry-After` 响应头。
|
|
1994
|
+
* 必须显式传入 store;生产使用共享计数器,故障返回503。
|
|
1995
|
+
* @example
|
|
1996
|
+
* ```ts
|
|
1997
|
+
* import type { RateLimitConfig } from '@h-ai/kit'
|
|
1998
|
+
* import { kit } from '@h-ai/kit'
|
|
1999
|
+
* import { cache } from '@h-ai/cache'
|
|
2000
|
+
*
|
|
2001
|
+
* const config: RateLimitConfig = { windowMs: 60_000, maxRequests: 100, store: kit.middleware.createCacheRateLimitStore(cache, { namespace: 'api' }) }
|
|
2002
|
+
* void config
|
|
2003
|
+
* ```
|
|
2004
|
+
*/
|
|
2005
|
+
interface RateLimitConfig {
|
|
2006
|
+
/** 时间窗口 (ms) */
|
|
2007
|
+
windowMs: number;
|
|
2008
|
+
/** 最大请求数 */
|
|
2009
|
+
maxRequests: number;
|
|
2010
|
+
/** 自定义 key 生成 */
|
|
2011
|
+
keyGenerator?: (event: RequestEvent) => string;
|
|
2012
|
+
/** 超限处理 */
|
|
2013
|
+
onLimitReached?: (event: RequestEvent) => Response;
|
|
2014
|
+
/**
|
|
2015
|
+
* 自定义存储实现
|
|
2016
|
+
*
|
|
2017
|
+
* 必须提供原子 increment;不支持 get+set 回退。共享缓存由调用方初始化和关闭。
|
|
2018
|
+
*/
|
|
2019
|
+
store: RateLimitStore;
|
|
2020
|
+
}
|
|
2021
|
+
/**
|
|
2022
|
+
* CORS 中间件配置
|
|
2023
|
+
*
|
|
2024
|
+
* 配置跨域资源共享策略。预检请求(OPTIONS)自动返回 204。
|
|
2025
|
+
* @example
|
|
2026
|
+
* ```ts
|
|
2027
|
+
* import type { CorsConfig } from '@h-ai/kit'
|
|
2028
|
+
*
|
|
2029
|
+
* const value: CorsConfig = { origin: ['https://app.example.com'], credentials: true }
|
|
2030
|
+
* void value
|
|
2031
|
+
* ```
|
|
2032
|
+
*/
|
|
2033
|
+
interface CorsConfig {
|
|
2034
|
+
/** 允许的源(支持通配符,如 `*.example.com`) */
|
|
2035
|
+
origin?: string | string[] | ((origin: string) => boolean);
|
|
2036
|
+
/** 允许的方法 */
|
|
2037
|
+
methods?: string[];
|
|
2038
|
+
/** 允许的头 */
|
|
2039
|
+
allowedHeaders?: string[];
|
|
2040
|
+
/** 暴露的头 */
|
|
2041
|
+
exposedHeaders?: string[];
|
|
2042
|
+
/** 是否允许凭证 */
|
|
2043
|
+
credentials?: boolean;
|
|
2044
|
+
/** 预检缓存时间 (秒) */
|
|
2045
|
+
maxAge?: number;
|
|
2046
|
+
/** 是否自动允许 Capacitor WebView origin(默认 true) */
|
|
2047
|
+
capacitor?: boolean;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
export { MemoryRateLimitStore as $, IdParamSchema as _, PaginationQuerySchema as a0, defineCrud as a5, matchPermission as a6, resolveOptions as a7, resolveText as a8, createKitAuthActions as v, createKitClient as w, installBrowserTransport as x, installBrowserTransportFetch as y };
|
|
2051
|
+
export type { ApiResponse as A, BrowserKitFunctions as B, ClientTransportConfig as C, AuthCookieWriter as D, CrudApiDef as E, FormError as F, GuardConfig as G, HaiRequestEvent as H, CrudFieldDef as I, CrudFieldOption as J, KitClient as K, CrudFieldOptions as L, Middleware as M, CrudFieldType as N, CrudFieldValidation as O, CrudFilterParams as P, CrudOperations as Q, RateLimitConfig as R, SessionData as S, TransportEncryptionOptions as T, CrudPaginatedResult as U, CrudPaginationParams as V, CrudResourceDef as W, CrudTableColumn as X, CrudText as Y, FeedbackHandlerOptions as Z, AuthOperations as a, PermissionGuardConfig as a1, RateLimitCache as a2, RateLimitEntry as a3, RateLimitStore as a4, AuthOperationsProvider as b, CorsConfig as c, FormValidationResult as d, GuardResult as e, HandleA2AConfig as f, HandleA2AOperations as g, HandleAuthConfig as h, HandleConfig as i, HookCryptoConfig as j, KitClientConfig as k, KitConfig as l, KitConfigInput as m, KitPublicConfig as n, KitServerConfig as o, KitServerConfigInput as p, KitTransportConfig as q, KitTransportConfigInput as r, MiddlewareContext as s, RouteGuard as t, SessionLike as u, KitFunctions as z };
|