@h-ai/kit 0.1.0-alpha9 → 1.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +430 -0
- package/README.ja.md +430 -0
- package/README.md +351 -40
- package/dist/adapter/index.d.ts +17 -2
- package/dist/adapter/index.js.map +1 -1
- package/dist/browser.d.ts +19 -8
- package/dist/browser.js +10 -7
- package/dist/browser.js.map +1 -1
- package/dist/chunk-3OHUSKHI.js +175 -0
- package/dist/chunk-3OHUSKHI.js.map +1 -0
- package/dist/chunk-5VWPH7DO.js +126 -0
- package/dist/chunk-5VWPH7DO.js.map +1 -0
- package/dist/chunk-7EH47MTW.js +141 -0
- package/dist/chunk-7EH47MTW.js.map +1 -0
- package/dist/chunk-JRQPMPQC.js +63 -0
- package/dist/chunk-JRQPMPQC.js.map +1 -0
- package/dist/chunk-USFJN7IO.js +187 -0
- package/dist/chunk-USFJN7IO.js.map +1 -0
- package/dist/chunk-YKJRQCWR.js +363 -0
- package/dist/chunk-YKJRQCWR.js.map +1 -0
- package/dist/client/index.d.ts +51 -2
- package/dist/client/index.js +21 -2
- package/dist/client/index.js.map +1 -1
- package/dist/index.d.ts +303 -409
- package/dist/index.js +598 -310
- package/dist/index.js.map +1 -1
- package/dist/kit-a2a-types-4dapztu2.d.ts +136 -0
- package/dist/kit-client-auth-BnhrkzZF.d.ts +422 -0
- package/dist/kit-crypto-types-CXr888wf.d.ts +200 -0
- package/dist/kit-i18n-EBzZKYN-.d.ts +502 -0
- package/dist/kit-types-BkNdyKj8.d.ts +822 -0
- package/dist/modules/a2a/index.d.ts +15 -113
- package/dist/modules/a2a/index.js +2 -55
- package/dist/modules/a2a/index.js.map +1 -1
- package/dist/modules/crypto/index.d.ts +31 -79
- package/dist/modules/crypto/index.js +3 -3
- package/dist/modules/crypto/index.js.map +1 -1
- package/package.json +36 -22
- package/dist/chunk-475WECWK.js +0 -110
- package/dist/chunk-475WECWK.js.map +0 -1
- package/dist/chunk-6TMSXG7J.js +0 -169
- package/dist/chunk-6TMSXG7J.js.map +0 -1
- package/dist/chunk-AD6S473Y.js +0 -137
- package/dist/chunk-AD6S473Y.js.map +0 -1
- package/dist/chunk-FRYJMRTP.js +0 -71
- package/dist/chunk-FRYJMRTP.js.map +0 -1
- package/dist/chunk-PHS7ZD5L.js +0 -274
- package/dist/chunk-PHS7ZD5L.js.map +0 -1
- package/dist/index-BotXLTck.d.ts +0 -203
- package/dist/kit-crypto-types-C-gEvx04.d.ts +0 -253
- package/dist/kit-i18n-CgpNTh9G.d.ts +0 -206
- package/dist/kit-types-BHlDNNpd.d.ts +0 -618
package/README.en.md
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
# @h-ai/kit
|
|
2
|
+
|
|
3
|
+
[简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
|
|
4
|
+
|
|
5
|
+
[Framework home](../../README.en.md) · Related modules: [@h-ai/core](../core/README.en.md) · [@h-ai/crypto](../crypto/README.en.md) · [@h-ai/iam](../iam/README.en.md)
|
|
6
|
+
|
|
7
|
+
## Introduction
|
|
8
|
+
|
|
9
|
+
`@h-ai/kit` integrates SvelteKit with hai-framework, providing a shared request pipeline and same-origin endpoint utilities:
|
|
10
|
+
|
|
11
|
+
## Core features
|
|
12
|
+
|
|
13
|
+
- Handle hook composition (`createHandle` / `sequence`)
|
|
14
|
+
- Authentication/role/permission guards (`guard.require` / `guard.check`)
|
|
15
|
+
- Built-in Logging / RateLimit / CORS middleware, enabled through `createHandle` configuration
|
|
16
|
+
- Shared API responses and request validation (Zod)
|
|
17
|
+
- Bearer token utilities (server login/logout + browser token storage)
|
|
18
|
+
- Browser client with automatic CSRF and transport encryption support
|
|
19
|
+
- Declarative CRUD resources (`crud.define`)
|
|
20
|
+
- A2A integration: discovery defaults to `/.well-known/agent-card.json`. With `authenticate` configured, null/undefined returns 401; anonymous access is allowed only without authentication configured. API Key parameters come from the complete Agent Card's `securitySchemes.apiKey`. Hooks and explicit A2A routes share JSON-RPC parse-error and SSE cancellation cleanup logic.
|
|
21
|
+
- Dual build mode (`createAdapter()`)
|
|
22
|
+
|
|
23
|
+
## Getting started
|
|
24
|
+
|
|
25
|
+
`sequence` preserves SvelteKit resolve options: page transformations run from inner to outer, while the first preload and serialized-header filter win. This preserves the i18n HTML language replacement.
|
|
26
|
+
|
|
27
|
+
### Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @h-ai/kit @h-ai/core sm-crypto@0.5.7
|
|
31
|
+
npm install svelte @sveltejs/kit zod
|
|
32
|
+
npm install -D @sveltejs/vite-plugin-svelte vite
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Dependencies
|
|
36
|
+
|
|
37
|
+
- `@h-ai/iam`: optional authentication/authorization for A2A; initialize before using kit's A2A features.
|
|
38
|
+
- `@h-ai/crypto`: optional transport/cookie encryption; initialize before using encryption in kit.
|
|
39
|
+
|
|
40
|
+
### Quick start
|
|
41
|
+
|
|
42
|
+
#### Project configuration
|
|
43
|
+
|
|
44
|
+
**svelte.config.js**:
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
import adapter from '@sveltejs/adapter-node'
|
|
48
|
+
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
|
49
|
+
|
|
50
|
+
const config = {
|
|
51
|
+
preprocess: [vitePreprocess()],
|
|
52
|
+
compilerOptions: { runes: true },
|
|
53
|
+
kit: {
|
|
54
|
+
adapter: adapter(),
|
|
55
|
+
// 双构建模式:import { createAdapter } from '@h-ai/kit/adapter'
|
|
56
|
+
// adapter: await createAdapter(),
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default config
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**vite.config.ts**:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { sveltekit } from '@sveltejs/kit/vite'
|
|
67
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
68
|
+
import { defineConfig } from 'vite'
|
|
69
|
+
|
|
70
|
+
export default defineConfig({
|
|
71
|
+
plugins: [sveltekit(), tailwindcss()],
|
|
72
|
+
optimizeDeps: { exclude: ['bits-ui'] },
|
|
73
|
+
ssr: { noExternal: ['@h-ai/ui', '@h-ai/kit'], external: ['@h-ai/core', 'sm-crypto'] },
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
When using Crypto or Kit/IAM, include `sm-crypto@0.5.7` in application production dependencies. CLI projects already do this; for manual setup, run `pnpm add sm-crypto@0.5.7`. The external option alone is insufficient: adapter-node also uses direct production dependencies to preserve native package loading.
|
|
78
|
+
|
|
79
|
+
#### Build the request pipeline
|
|
80
|
+
|
|
81
|
+
When mounting an application under `/yyy/`, keep SvelteKit's `kit.paths.base` and the actual URLs used by guards consistent. Prefix `protectedPaths`, `publicPaths` and `loginUrl` with `base` from `$app/paths`, for example `${base}/api/*`. Authentication pages use ``kit.client.createAuthActions({ basePath: `${base}/api/auth` })``. Kit recognizes application `/api/*` routes through route groups and mount prefixes: unauthenticated APIs return 401, while pages follow the configured login redirect. Generated templates include this wiring.
|
|
82
|
+
|
|
83
|
+
The Hook examples assume the application has initialized its database, shared cache and IAM and checked every result. The application owns resource cleanup; do not initialize modules per request.
|
|
84
|
+
|
|
85
|
+
**src/hooks.server.ts**:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import type { Handle } from '@sveltejs/kit'
|
|
89
|
+
import { cache } from '@h-ai/cache'
|
|
90
|
+
import { iam } from '@h-ai/iam'
|
|
91
|
+
import { kit } from '@h-ai/kit'
|
|
92
|
+
|
|
93
|
+
const haiHandle = kit.createHandle({
|
|
94
|
+
auth: {
|
|
95
|
+
verifyToken: async (token) => {
|
|
96
|
+
const verified = await iam.auth.verifyToken(token)
|
|
97
|
+
return verified.success ? verified.data : null
|
|
98
|
+
},
|
|
99
|
+
loginUrl: '/auth/login',
|
|
100
|
+
protectedPaths: ['/admin/*', '/api/*'],
|
|
101
|
+
publicPaths: ['/api/auth/*', '/api/public/*'],
|
|
102
|
+
},
|
|
103
|
+
rateLimit: { windowMs: 60_000, maxRequests: 100, store: kit.middleware.createCacheRateLimitStore(cache, { namespace: 'app' }) },
|
|
104
|
+
logging: true,
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
export const handle: Handle = haiHandle
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Rate limits use fixed windows beginning at the first successful commit; later requests preserve the TTL. A store is required. Initialize shared Redis and check its result before serving; all nodes use the same namespace/windowMs/maxRequests. Cache read/commit failure or exhausted contention returns 503, while exceeding the limit returns 429, without get+set or local fallback. createMemoryRateLimitStore is for development or explicit HAI_E2E/HAI_E2E_MOCK tests; it has no timer and can be garbage collected when released; each request scans at most 64 entries, capacity is 10000 windows, new keys receive 503 at capacity without evicting active counts, and external stores remain owned by their creators. Never enable test flags in production. Reset headers estimate node time; Redis TTL controls the actual window, so synchronize clocks. The default key uses the trusted adapter getClientAddress, normalizes equivalent IPv6/mapped IPv4 addresses and ignores raw forwarded headers; configure proxy trust in the deployment adapter.
|
|
111
|
+
|
|
112
|
+
#### API endpoints
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { kit } from '@h-ai/kit'
|
|
116
|
+
import { z } from 'zod'
|
|
117
|
+
|
|
118
|
+
const CreateUserSchema = z.object({
|
|
119
|
+
name: z.string().min(1),
|
|
120
|
+
email: z.string().email(),
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
export const POST = kit.handler(async ({ request, locals }) => {
|
|
124
|
+
kit.guard.require(locals.session, 'user:create')
|
|
125
|
+
const payload = await kit.validate.body(request, CreateUserSchema)
|
|
126
|
+
return kit.response.created(payload)
|
|
127
|
+
})
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### Browser client
|
|
131
|
+
|
|
132
|
+
`kit.client.create()` targets SvelteKit same-origin endpoints. Use `@h-ai/api-client` for cross-origin access to public APIs from Web, apps, or mini programs.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// src/lib/utils/api.ts — 全应用共享
|
|
136
|
+
import { kit } from '@h-ai/kit'
|
|
137
|
+
|
|
138
|
+
const client = kit.client.create()
|
|
139
|
+
export const { apiFetch } = client
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// 使用
|
|
144
|
+
import { apiFetch } from '$lib/utils/api'
|
|
145
|
+
|
|
146
|
+
const response = await apiFetch('/api/users', { method: 'GET' })
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
> `apiFetch` injects the CSRF header automatically; `@h-ai/kit` does not expose a separate CSRF middleware factory.
|
|
150
|
+
> `apiFetch` accepts a string, URL, or Request. It preserves the Request method, headers, and body and adds CSRF for writes. With transport enabled, supported bodies are encrypted and multipart uploads retain their original boundaries.
|
|
151
|
+
>
|
|
152
|
+
> Use httpOnly cookies for same-origin SvelteKit endpoints and verify them on the server. For browser Bearer headers, set an in-memory token with `kit.auth.setBrowserToken()` before `kit.client.create({ auth: true })`. Custom `BrowserTokenStore` implementations must use memory or platform secure storage; never persist sensitive credentials in Web Storage.
|
|
153
|
+
|
|
154
|
+
#### Same-origin transport encryption
|
|
155
|
+
|
|
156
|
+
kit delegates transport encryption to `@h-ai/crypto`. The server creates a manager with `crypto.transport.createServer({ serverKeyRing, keyStore })`; the browser installs a same-origin fetch wrapper with one call to `kit.client.installBrowserTransport(kitConfig, { crypto })`. Servers must also inject a managed key ring and shared registry.
|
|
157
|
+
|
|
158
|
+
For subdirectory hosting, read `base` from `$app/paths`: pass `basePath: base` in server `crypto.transport`, use `installBrowserTransport(kitConfig, { crypto, basePath: base })` in the browser, and set the same `transport.basePath` for a standalone `kit.client.create`. The default empty string means the domain root. This runtime argument comes from the host build path, without a duplicate YAML setting or inference from forwarding headers. Exchange paths, exclusions and route budgets still use full request paths. CLI SSR templates assemble both sides.
|
|
159
|
+
|
|
160
|
+
**src/hooks.server.ts**:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { base } from '$app/paths'
|
|
164
|
+
import { appKitConfig } from '$lib/server/kit-config'
|
|
165
|
+
import { cache } from '@h-ai/cache'
|
|
166
|
+
import { createRedisTransportKeyStore, crypto, parseTransportServerKeyRing } from '@h-ai/crypto'
|
|
167
|
+
import { iam } from '@h-ai/iam'
|
|
168
|
+
import { kit } from '@h-ai/kit'
|
|
169
|
+
|
|
170
|
+
const cryptoReady = await crypto.init()
|
|
171
|
+
if (!cryptoReady.success)
|
|
172
|
+
throw new Error(cryptoReady.error.message)
|
|
173
|
+
const ring = parseTransportServerKeyRing(process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING)
|
|
174
|
+
if (!ring.success)
|
|
175
|
+
throw new Error(ring.error.message)
|
|
176
|
+
const serverKeyRing = ring.data
|
|
177
|
+
// Redis/IAM 由应用启动流程初始化并检查;此处复用同一实例。
|
|
178
|
+
|
|
179
|
+
export const handle = kit.createHandle({
|
|
180
|
+
auth: {
|
|
181
|
+
verifyToken: async (token) => {
|
|
182
|
+
const verified = await iam.auth.verifyToken(token)
|
|
183
|
+
return verified.success ? verified.data : null
|
|
184
|
+
},
|
|
185
|
+
protectedPaths: [`${base}/api/*`],
|
|
186
|
+
publicPaths: [`${base}/api/_hai/*`], // 默认密钥协商端点
|
|
187
|
+
},
|
|
188
|
+
crypto: {
|
|
189
|
+
crypto,
|
|
190
|
+
transport: appKitConfig.transport === false
|
|
191
|
+
? false
|
|
192
|
+
: {
|
|
193
|
+
...appKitConfig.transport,
|
|
194
|
+
basePath: base,
|
|
195
|
+
serverKeyRing,
|
|
196
|
+
keyStore: createRedisTransportKeyStore({ cache, ttlSeconds: 3600 }),
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
})
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**src/routes/+layout.svelte** (recommended entry: one browser-side call):
|
|
203
|
+
|
|
204
|
+
```svelte
|
|
205
|
+
<script lang='ts'>
|
|
206
|
+
import { browser } from '$app/environment'
|
|
207
|
+
import { base } from '$app/paths'
|
|
208
|
+
import { page } from '$app/state'
|
|
209
|
+
import { crypto } from '@h-ai/crypto'
|
|
210
|
+
import { kit } from '@h-ai/kit'
|
|
211
|
+
import { untrack } from 'svelte'
|
|
212
|
+
|
|
213
|
+
// 一次性安装:内部按 _kit.yml 决定是否启用 transport,并预热 crypto
|
|
214
|
+
if (browser) {
|
|
215
|
+
kit.client.installBrowserTransport(untrack(() => page.data.kitConfig), { crypto, basePath: base })
|
|
216
|
+
}
|
|
217
|
+
</script>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**src/lib/utils/api.ts** (business code sees only `apiFetch`):
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import { kit } from '@h-ai/kit'
|
|
224
|
+
|
|
225
|
+
export const { apiFetch } = kit.client.create()
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
> `src/hooks.client.ts` is no longer needed: default `handleFetch` behavior is sufficient, and SvelteKit `__data.json` requests use the installed global fetch wrapper automatically. For same-origin Authorization headers, use explicit `BrowserTokenStore` or `kit.auth.setBrowserToken()` + `kit.client.create({ auth: true })`. Do not store sensitive tokens in `localStorage`.
|
|
229
|
+
|
|
230
|
+
The default key exchange path is `/api/_hai/key-exchange`. If the server customizes `transport.keyExchangePath`, update the client's `transport.keyExchangeUrl` accordingly.
|
|
231
|
+
|
|
232
|
+
Servers must explicitly inject the same managed `serverKeyRing` and a shared `keyStore` (Redis or a shared database). Sharing client public keys alone cannot enable cross-node decryption. There is no implicit process key or memory-registry fallback; development/tests may explicitly use `createInMemoryKeyStore(capacity)`. Reload the same ring after restart; sticky sessions are unnecessary.
|
|
233
|
+
|
|
234
|
+
Transport applies by default to same-origin `/api/*` endpoints, SvelteKit `__data.json` page data, and key exchange itself. Page documents and static assets remain plaintext and do not require `X-Client-Id`. `multipart/form-data` requests such as uploads are sent unchanged; explicitly allow their paths through server `excludePaths`.
|
|
235
|
+
|
|
236
|
+
Only authenticated protocol v2 is accepted. Independent random SM4-CBC and HMAC-SM3 keys are wrapped with SM2; verify the sender signature and MAC before CBC decryption. Clients sign requests and the selected server key version signs responses. Authentication covers version, direction, serverKeyId, clientId, a random 128-bit requestId acting as the nonce, issuedAt, HTTP method, the SM3 pathHash of pathname plus the original search, and response status. Requests have a five-minute lifetime and at most 30 seconds of future clock skew. The shared registry atomically consumes (clientId, requestId), retaining it until at least issuedAt plus six minutes to cover node skew; storage failures reject requests. Synchronize node clocks and use a Redis noeviction/durable policy that preserves live nonces. Transport replay prevention does not replace business idempotency keys.
|
|
237
|
+
|
|
238
|
+
Empty POSTs still send an authenticated envelope for empty plaintext. GET/HEAD carry it in X-Hai-Transport; bodyless responses such as 204/304/HEAD use the same header. Other requests/responses carry a JSON envelope and X-Encrypted: true. The client must match the original request context and HTTP status; missing markers, old formats and authentication failures reject. Serv/Kit hosts explicitly allow plaintext through `encryption: disabled` path rules or host `excludePaths`. Callers must select ordinary requests; API Client/Kit wrappers use their own `excludePaths`. CORS must allow and expose X-Hai-Transport. TLS authenticates handshake keys; this protocol does not replace IAM authorization.
|
|
239
|
+
|
|
240
|
+
Set `transport.maxPlaintextBytes` for both request and response JSON plaintext, measured in UTF-8 bytes: default 1 MiB, integer range 1 KiB–64 MiB. Clients learn the server limit during key exchange. `resolveTransportBodyLimits()` derives encrypted/proxy bytes as twice the plaintext limit plus 32 KiB. Handshake, public-key, header and replay constraints remain independent. Oversized streams are cancelled and readers released: requests return 413; oversized responses return an encrypted 500 capacity error, recognized as `TransportBodyTooLargeError` / `hai:crypto:070`. [Transport capacity and upgrade](../crypto/TRANSPORT-CAPACITY.md)。
|
|
241
|
+
|
|
242
|
+
Ordinary routes retain `transport.maxPlaintextBytes` (auto/omitted: 1 MiB). Declare Feedback report intake, evidence, attachments and ZIP download capacity separately using `routePlaintextBytes` and `feedback.budget.maxTransportBytes`; `*` matches one complete path segment and explicit host route values take precedence. Handshakes, adapter-node and proxies use the maximum route capacity. HTTP adapters admit at most 2 explicitly declared material requests above 1 MiB and a separate 16 ordinary business requests, releasing admission after the entire response or failure. Busy admission returns 503 / `hai:crypto:072`; business writes are not automatically replayed.
|
|
243
|
+
|
|
244
|
+
serv/kit return an encrypted HTTP 400 time error for an out-of-window message only after request binding, signature and MAC verification. The client throws `TransportTimeError` (`hai:crypto:071`) with advice to synchronize device/server clocks and refresh. `api-client` maps it to `VALIDATION_FAILED` and preserves `cause.transportCode`. Requests are not replayed automatically and replay windows remain unchanged; tampered or unauthenticated messages retain the generic authentication failure.
|
|
245
|
+
|
|
246
|
+
#### Shared transport configuration with `_kit.yml`
|
|
247
|
+
|
|
248
|
+
`@h-ai/kit` provides `KitConfigSchema` / `resolveKitConfig()` so a single `_kit.yml`
|
|
249
|
+
can drive both `hooks.server.ts` and browser `kit.client.installBrowserTransport()`:
|
|
250
|
+
|
|
251
|
+
```yml
|
|
252
|
+
server:
|
|
253
|
+
host: 127.0.0.1
|
|
254
|
+
port: 3000
|
|
255
|
+
|
|
256
|
+
transport:
|
|
257
|
+
keyExchangePath: /api/_hai/key-exchange
|
|
258
|
+
excludePaths:
|
|
259
|
+
- /api/storage
|
|
260
|
+
- /api/public
|
|
261
|
+
- /api/auth/profile/avatar
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
import { core } from '@h-ai/core'
|
|
266
|
+
import { KitConfigSchema } from '@h-ai/kit'
|
|
267
|
+
|
|
268
|
+
// 仅放在 src/lib/server 中;浏览器通过 layout data 接收公共投影。
|
|
269
|
+
const loaded = core.config.load('kit', './config/_kit.yml', KitConfigSchema)
|
|
270
|
+
if (!loaded.success)
|
|
271
|
+
throw new Error(loaded.error.message)
|
|
272
|
+
export const appKitConfig = loaded.data
|
|
273
|
+
export const publicKitConfig = { transport: appKitConfig.transport }
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Pass the public projection through `src/routes/+layout.server.ts` to the browser layout above, without importing the server configuration into browser code:
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
import { publicKitConfig } from '$lib/server/kit-config'
|
|
280
|
+
|
|
281
|
+
export function load() {
|
|
282
|
+
return { kitConfig: publicKitConfig }
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
`resolveKitConfig()` resolves both `server` (host/port) and `transport`. Host/port precedence is `HOST` / `PORT` environment variables > `_kit.yml` `server` > `127.0.0.1:3000`, for shared consumption by the SvelteKit server entry, matching `@h-ai/serv`.
|
|
287
|
+
For server-only configuration, use `core.config.validate('kit', KitConfigSchema)` as well.
|
|
288
|
+
Read the complete `_kit.yml` on the server; it can contain private proxy networks and policy-file paths. Return only an explicit public projection such as `{ kitConfig: publicKitConfig }` from `+layout.server.ts`, and consume its transport fields in the browser. Never bundle the full configuration or policy file with `?raw`, and keep secrets outside these files.
|
|
289
|
+
|
|
290
|
+
### Configuration
|
|
291
|
+
|
|
292
|
+
`kit.createHandle()` options:
|
|
293
|
+
|
|
294
|
+
| Field | Type | Description |
|
|
295
|
+
| ------------ | ---------------------------------------- | ---------------------------------------------------------------------- |
|
|
296
|
+
| `auth` | `HandleAuthConfig` | Authentication (verifyToken / protectedPaths / publicPaths / loginUrl) |
|
|
297
|
+
| `rateLimit` | `object \| false` | Rate limiting (windowMs / maxRequests) |
|
|
298
|
+
| `logging` | `boolean \| object` | Request logging (default `true`; `{ logBody: true }` logs the body) |
|
|
299
|
+
| `crypto` | `HookCryptoConfig` | Transport + cookie encryption |
|
|
300
|
+
| `guards` | `GuardConfig[]` | Custom guards (after automatic auth guards) |
|
|
301
|
+
| `middleware` | `Middleware[]` | Custom middleware (after built-in middleware) |
|
|
302
|
+
| `a2a` | `HandleA2AOperations \| HandleA2AConfig` | A2A integration (Agent Card + JSON-RPC) |
|
|
303
|
+
| `onError` | `function` | Custom error handling |
|
|
304
|
+
|
|
305
|
+
Request/response summaries use the `trace` level. Logging is enabled by default and `logBody` defaults to false. Query values come from the original `request.url`, while the path remains SvelteKit's current `event.url.pathname`. Logging does not access the tracked `event.url.search/searchParams`, so static pages can prerender with logging enabled. Password/token/secret, authorization and API-key fields are redacted in queries and optional JSON-body logs. The body is read from a clone and remains available to application code.
|
|
306
|
+
|
|
307
|
+
Automatic request auditing runs only when `audit.isInitialized`. Static sites and applications without Audit need not initialize it for this handle. State is checked per request, so modules may initialize after the handle is created. Applications selecting Audit must still check `audit.init()` and stop startup on failure. Explicit `audit.log/capture` calls retain their uninitialized errors, and initialized modules still report actual write failures as `Request audit failed`. Mandatory business auditing must check explicit results and enforce the application's transaction rules.
|
|
308
|
+
|
|
309
|
+
With `crypto.encryptedCookies`, also provide `crypto.cookieEncryptionKey` or `HAI_KIT_COOKIE_KEY`. Missing keys fail immediately in `createHandle()`. Plaintext, damaged ciphertext, or decryption failures for protected cookies are treated as absent; encryption failures never fall back to writing plaintext.
|
|
310
|
+
|
|
311
|
+
Authentication cookies default to `Secure` when either `NODE_ENV` or `HAI_CORE_ENV` is `production`; `HAI_E2E` / `HAI_E2E_MOCK` do not relax cookie security. Production rate limiting recognizes both flags; only explicit test switches permit memory counters.
|
|
312
|
+
|
|
313
|
+
### Export paths
|
|
314
|
+
|
|
315
|
+
Use runtime methods through the `kit` namespace or explicit subpaths; types are exported separately. Browser declarations do not expose server factories. Vite configuration uses `@sveltejs/kit/vite`; there is no `@h-ai/kit/vite` plugin.
|
|
316
|
+
|
|
317
|
+
- `@h-ai/kit`: main entry, with the `kit` namespace and types.
|
|
318
|
+
- `@h-ai/kit/client`: browser client.
|
|
319
|
+
- `@h-ai/kit/adapter`: dual build adapter (`createAdapter()`).
|
|
320
|
+
- `@h-ai/kit/a2a`: A2A integration.
|
|
321
|
+
- `@h-ai/kit/crypto`: crypto integration.
|
|
322
|
+
|
|
323
|
+
### Configuration driven authentication
|
|
324
|
+
|
|
325
|
+
Password login submits only `identifier` and `password`; `AuthPanel` maps the `LoginForm` field `username` to `identifier`. Server settings `session.maxAge` / `session.refreshTokenMaxAge` control session lifetimes. The login form has no per-login persistent-session option.
|
|
326
|
+
|
|
327
|
+
Agreements support configuration-loaded userAgreementMarkdown/privacyPolicyMarkdown and URLs. Markdown takes priority for the same agreement and is safely rendered in a modal.
|
|
328
|
+
|
|
329
|
+
Export standard handlers from kit.auth.createApi(iam), use kit.auth.publicPaths for anonymous endpoints, and connect AuthPanel with kit.client.createAuthActions({ apiFetch }). Validation, Cookie writes and HaiResult mapping are built in; current-user and password-change endpoints remain guarded.
|
|
330
|
+
|
|
331
|
+
[完整 IAM 配置 / IAM configuration](../iam/README.en.md)
|
|
332
|
+
|
|
333
|
+
`kit.auth.logout(cookies, accessToken)` returns `Promise<HaiResult<void>>`. Without a token it only clears cookies and succeeds; with a token but no configured auth operations it fails. Local cookies are cleared for every server outcome. Callers must preserve failed results in their HTTP response or UI; local clearing does not confirm server revocation. Underlying control-flow exceptions still propagate.
|
|
334
|
+
|
|
335
|
+
### Error handling
|
|
336
|
+
|
|
337
|
+
- Wrap API endpoints with `kit.handler()`.
|
|
338
|
+
- `kit.validate.body` / `kit.guard.require` throw `Response`, handled by `kit.handler` as API control flow. Page `load` uses `kit.guard.check` with SvelteKit `error`; parse query schemas with `safeParse` and raise native page errors instead of throwing API responses.
|
|
339
|
+
- `kit.handler()` converts non-control-flow exceptions into `500 INTERNAL_ERROR` responses.
|
|
340
|
+
|
|
341
|
+
### CRUD fields
|
|
342
|
+
|
|
343
|
+
Declarative CRUD fields support `colSpan: 'auto' | 1 | 2` for UI form widths. `validation.message` accepts a string or `() => string` for current-locale validation messages. Configure layout through `CrudPage.form`; validate independently on the server.
|
|
344
|
+
|
|
345
|
+
### Runtime behavior and limits
|
|
346
|
+
|
|
347
|
+
- `kit.createHandle()`: session parsing, route guards, middleware chains, and optional transport/cookie encryption.
|
|
348
|
+
- `kit.handler()`: an exception boundary for API endpoints.
|
|
349
|
+
- `kit.guard` / `kit.response` / `kit.validate` / `kit.auth` / `kit.client` / `kit.crud`
|
|
350
|
+
|
|
351
|
+
> Shared cross-platform HTTP contracts belong in `@h-ai/api-contract`, are mounted by `@h-ai/serv`, and are called through the `@h-ai/api-client` typed client. `@h-ai/kit` does not define or host business API contracts.
|
|
352
|
+
|
|
353
|
+
### Request locale isolation
|
|
354
|
+
|
|
355
|
+
Wrap Node requests and SSR in `core.i18n.runWithLocale(locale, () => handleRequest())`. SvelteKit can use `kit.i18n.runWithLocale(locale, () => resolve(event))`; serv creates the scope automatically from request headers. Async descendants inherit it. Explicit `MessageOptions.locale` takes precedence over the request locale and the startup default. Within a scope, `setGlobalLocale` only changes that request; outside a scope it configures the startup default and must not be called by request hooks.
|
|
356
|
+
|
|
357
|
+
Browsers keep application-level `setGlobalLocale` / `createLocaleStore`. During SSR, UI store initialization and setters only update the store; messages read the request scope. `runWithLocale` requires the Node entry; browser translations use explicit locale options or application state.
|
|
358
|
+
|
|
359
|
+
## Feedback endpoint and request correlation
|
|
360
|
+
|
|
361
|
+
`kit.createFeedbackHandler` can serve GET/POST/PATCH in `/api/feedback/[...path]/+server.ts`. It reuses the same api-contract schemas as serv, bounds the input stream, and rejects client identity/scope fields. resolveContext maps an already verified locals.session to server-controlled scope and permissions; null denies access. Keep the existing createHandle authentication, CSRF, and transport encryption.
|
|
362
|
+
|
|
363
|
+
createHandle preserves a valid bounded incoming X-Request-Id, adds it to responses, and propagates the request and verified user across asynchronous work. To collect ordinary business logs, wrap the actual business operation in a host middleware using `core.context.run({ scopeId: 'configured-product' }, next)`. This trusted scope must match feedback scope and cannot come from client headers.
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
import type { SessionData } from '@h-ai/kit'
|
|
367
|
+
import { feedback } from '@h-ai/feedback'
|
|
368
|
+
import { kit } from '@h-ai/kit'
|
|
369
|
+
|
|
370
|
+
const handler = kit.createFeedbackHandler({
|
|
371
|
+
feedback,
|
|
372
|
+
resolveContext: (event) => {
|
|
373
|
+
const session = (event.locals as { session?: SessionData }).session
|
|
374
|
+
return session
|
|
375
|
+
? { scopeId: 'configured-product', userId: session.userId, permissions: session.permissions }
|
|
376
|
+
: null
|
|
377
|
+
},
|
|
378
|
+
})
|
|
379
|
+
export const GET = handler
|
|
380
|
+
export const POST = handler
|
|
381
|
+
export const PATCH = handler
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## Error messages and correlated logs
|
|
385
|
+
|
|
386
|
+
HTTP errors carry safe localized messages and a reference ID. ToastContainer hides the ID by default and reveals it through its diagnostic icon. The same ID remains in `error.traceId` and `X-Trace-Id`. The framework reuses the request ID to correlate browser and server logs. Sanitized exceptions, stacks and operation context remain in logs; internal cause/ext are not exposed. Field validation returns localized field names and messages only.
|
|
387
|
+
|
|
388
|
+
Use `core.error.toPublic(error, traceId?)` at HTTP/UI boundaries and `core.error.formatMessage(error)` after translating messages; formatting is idempotent. Pass complete Error objects to logs instead of discarding their context. Database failures log driver codes, sanitized messages/stacks and table/column context; SQL text, bound values and connection credentials are excluded.
|
|
389
|
+
|
|
390
|
+
When audit is initialized, createHandle binds verified session identity/roles, the platform getClientAddress result and request ID for domain audit.log calls. Automatic request.execute events contain the route template, method, status and success/failure/denial, including guard redirects and SvelteKit HTTP control flow. Bodies and error details are not copied. capture.enabled controls automatic capture; capture.includeReads enables successful reads. Failed reads remain recorded by default. Applications still explicitly record domain completion/failure events; disabling automatic capture does not disable domain logs.
|
|
391
|
+
|
|
392
|
+
## Optional interactive challenges
|
|
393
|
+
|
|
394
|
+
Export kit.auth.createApi(iam).createCaptcha as POST /api/auth/captcha; kit.auth.publicPaths includes it. kit.client.createAuthActions preserves captcha and supplies createCaptcha for automatic AuthPanel solving. Export /api/auth/password/renew for initial/expired password changes. IAM enforces proofs server-side.
|
|
395
|
+
|
|
396
|
+
See policy, defaults and recovery boundaries in [IAM](../iam/README.en.md).
|
|
397
|
+
|
|
398
|
+
## Recovering an expired session in place
|
|
399
|
+
|
|
400
|
+
Set `kit.client.create({ recoverSession })` to sign in again without discarding the current page. Use UI `createSessionRecovery` / `SessionRecoveryDialog`: configure the public policy and original user in the root layout, pass `recovery.recover` as the callback, provide standard authentication actions, and cancel on teardown. IAM checks login `expectedUserId` before issuing a session; captcha, lockout, password renewal and access rules remain active. Only an explicitly authentication-rejected request is retried once. Cancellation, 5xx, timeouts and ambiguous writes are not replayed. Sensitive-operation proofs from an old session may need a new confirmation. Standard authentication actions disable recovery for their own calls. Custom authentication requests should pass `{ recoverSession: false }` as the third apiFetch argument to avoid recursive dialogs. terminalType (web/app/desktop/api; default web) selects a session group and does not attest hardware identity.
|
|
401
|
+
|
|
402
|
+
## Page exit session policy
|
|
403
|
+
|
|
404
|
+
`session.endOnPageHide` defaults to `false`. Hosts call `actions.bindPageExit({ enabled: config.session.endOnPageHide })` and invoke its returned cleanup on unmount; standard admin/H5/fullstack templates already bind it. A non-BFCache `pagehide` (including reload and full document navigation) makes a best effort POST to `/auth/session/close`, revoking the held access and related refresh through the existing CSRF/encrypted transport with `keepalive` and no automatic refresh. This response does not clear cookies/local credentials, avoiding a late old response overwriting a new login. Explicit logout still revokes and clears current credentials normally. Browsers may omit page exit requests; configure server `idleTimeout`/`absoluteTimeout` as appropriate. Immediate revocation on browser close is not guaranteed.
|
|
405
|
+
|
|
406
|
+
## Transport capacity and module budgets
|
|
407
|
+
|
|
408
|
+
For a sensitive call, supply `reauthorize: () => sensitiveActions.authorize(operation)` in the third Kit apiFetch argument or typed-client options.context. After session rotation/recovery or an explicit proof rejection, obtain a new purpose-bound proof before one retry. Cancellation sends no further write; without a callback, an old proof is never replayed.
|
|
409
|
+
|
|
410
|
+
## Access checks on every request
|
|
411
|
+
|
|
412
|
+
Path rules accept `encryption: required` or `disabled`; omission inherits the host. Disabled endpoints skip application encryption while retaining access and identity checks, and reject framework envelopes. Required mode can protect custom JSON endpoints and overrides `excludePaths`; Crypto, a managed key ring and a registry are required. Conflicting modes fail when creating the Handle. Crypto-enabled CLI templates install both server and browser transport; use db and a shared database in production. Private policy and proxy configuration remain server-side; plaintext callers select their protocol using the public transport exclusions.
|
|
413
|
+
|
|
414
|
+
Pass server-side `_kit.yml` fields `trustedProxies` and `access` explicitly to `kit.createHandle`. Examples and generated templates create one Handle after application initialization. `access` defaults to `false`. Inline `{ rules: [{ id: 'external', paths: ['/api/v1/external/**'], allowIps: ['192.0.2.0/24'] }] }` restricts matching endpoints only; `global` applies to all host-handled requests. Paths include the deployment prefix. Assets served directly by static hosting do not pass through Handle.
|
|
415
|
+
|
|
416
|
+
Use `access: { file: './config/security/request-access.yml' }` for a separate YAML file. It resolves from the working directory and is read once at startup, up to 1 MiB; changes require a new Handle. Global conditions and all matching rules must pass. Empty lists leave their dimension unrestricted; the default timezone is UTC. Existing sessions are denied after an allowed time window ends. Preflight and key exchange also follow policy; ordinary transport requests preserve encrypted HTTP 403 / `hai:common:104` responses.
|
|
417
|
+
|
|
418
|
+
Default `trustedProxies: auto` ignores forwarded headers on ordinary hosts and adopts container networks only for private managed Compose proxy topologies. Custom ingress uses explicit proxy IPs/CIDRs; `[]` disables trust. Resolved `event.getClientAddress()`, policy, IAM and audit share the same address. See the [request access handbook](../../docs/usage/configuration/request-access.md) for edge proxies, lost source addresses, environment overrides and rollback.
|
|
419
|
+
|
|
420
|
+
## Development and testing
|
|
421
|
+
|
|
422
|
+
```bash
|
|
423
|
+
pnpm --filter @h-ai/kit test
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
Kit has no module-wide init/close. The application initializes and closes shared cache, IAM and crypto dependencies. See the [JSDoc conventions (Chinese)](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证) for typechecked public examples. Authenticate requests with iam.auth.verifyToken; a nonempty token is not proof of identity.
|
|
427
|
+
|
|
428
|
+
## License
|
|
429
|
+
|
|
430
|
+
Apache-2.0
|