@h-ai/kit 0.1.0-alpha.56 → 0.1.0-alpha.58
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 +279 -0
- package/README.ja.md +279 -0
- package/README.md +4 -0
- package/package.json +14 -14
package/README.en.md
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
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
|
+
`@h-ai/kit` integrates SvelteKit with hai-framework, providing a shared request pipeline and same-origin endpoint utilities:
|
|
8
|
+
|
|
9
|
+
- `kit.createHandle()`: session parsing, route guards, middleware chains, and optional transport/cookie encryption.
|
|
10
|
+
- `kit.handler()`: an exception boundary for API endpoints.
|
|
11
|
+
- `kit.guard` / `kit.response` / `kit.validate` / `kit.auth` / `kit.client` / `kit.crud`
|
|
12
|
+
|
|
13
|
+
> 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.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @h-ai/kit
|
|
19
|
+
npm install svelte @sveltejs/kit zod
|
|
20
|
+
npm install -D @sveltejs/vite-plugin-svelte vite
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Capabilities
|
|
24
|
+
|
|
25
|
+
- Handle hook composition (`createHandle` / `sequence`)
|
|
26
|
+
- Authentication/role/permission guards (`guard.require` / `guard.check`)
|
|
27
|
+
- Built-in Logging / RateLimit / CORS middleware, enabled through `createHandle` configuration
|
|
28
|
+
- Shared API responses and request validation (Zod)
|
|
29
|
+
- Bearer token utilities (server login/logout + browser token storage)
|
|
30
|
+
- Browser client with automatic CSRF and transport encryption support
|
|
31
|
+
- Declarative CRUD resources (`crud.define`)
|
|
32
|
+
- 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.
|
|
33
|
+
- Dual build mode (`createAdapter()`)
|
|
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: 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\//] },
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Build the request pipeline
|
|
78
|
+
|
|
79
|
+
**src/hooks.server.ts**:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import type { Handle } from '@sveltejs/kit'
|
|
83
|
+
import { kit } from '@h-ai/kit'
|
|
84
|
+
|
|
85
|
+
const haiHandle = kit.createHandle({
|
|
86
|
+
auth: {
|
|
87
|
+
verifyToken: async (token) => {
|
|
88
|
+
return token ? { userId: 'u_1', roles: ['admin'], permissions: ['user:read'] } : null
|
|
89
|
+
},
|
|
90
|
+
loginUrl: '/auth/login',
|
|
91
|
+
protectedPaths: ['/admin/*', '/api/*'],
|
|
92
|
+
publicPaths: ['/api/auth/*', '/api/public/*'],
|
|
93
|
+
},
|
|
94
|
+
rateLimit: { windowMs: 60_000, maxRequests: 100 },
|
|
95
|
+
logging: true,
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
export const handle: Handle = haiHandle
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### API endpoints
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { kit } from '@h-ai/kit'
|
|
105
|
+
import { z } from 'zod'
|
|
106
|
+
|
|
107
|
+
const CreateUserSchema = z.object({
|
|
108
|
+
name: z.string().min(1),
|
|
109
|
+
email: z.string().email(),
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
export const POST = kit.handler(async ({ request, locals }) => {
|
|
113
|
+
kit.guard.require(locals.session, 'user:create')
|
|
114
|
+
const payload = await kit.validate.body(request, CreateUserSchema)
|
|
115
|
+
return kit.response.created(payload)
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Browser client
|
|
120
|
+
|
|
121
|
+
`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.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// src/lib/utils/api.ts — 全应用共享
|
|
125
|
+
import { kit } from '@h-ai/kit'
|
|
126
|
+
|
|
127
|
+
const client = kit.client.create()
|
|
128
|
+
export const { apiFetch } = client
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// 使用
|
|
133
|
+
import { apiFetch } from '$lib/utils/api'
|
|
134
|
+
|
|
135
|
+
const response = await apiFetch('/api/users', { method: 'GET' })
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
> `apiFetch` injects the CSRF header automatically; `@h-ai/kit` does not expose a separate CSRF middleware factory.
|
|
139
|
+
>
|
|
140
|
+
> Authentication: prefer httpOnly cookies for same-origin SvelteKit endpoints and verify them on the server. Browsers need not write sensitive tokens to `localStorage`. If Bearer headers are required, provide a custom `BrowserTokenStore`, or set an in-memory token with `kit.auth.setBrowserToken()` before `kit.client.create({ auth: true })`.
|
|
141
|
+
|
|
142
|
+
### Same-origin transport encryption
|
|
143
|
+
|
|
144
|
+
kit delegates transport encryption to `@h-ai/crypto`. The server creates a manager with `crypto.transport.createServer()`; the browser installs a same-origin fetch wrapper with one call to `kit.client.installBrowserTransport(kitConfig, { crypto })`. Applications only configure the top-level `crypto` handle.
|
|
145
|
+
|
|
146
|
+
**src/hooks.server.ts**:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { cache } from '@h-ai/cache'
|
|
150
|
+
import { createRedisTransportKeyStore, crypto } from '@h-ai/crypto'
|
|
151
|
+
import { kit } from '@h-ai/kit'
|
|
152
|
+
|
|
153
|
+
await crypto.init()
|
|
154
|
+
await cache.init({ type: 'redis', host: '127.0.0.1', port: 6379 })
|
|
155
|
+
|
|
156
|
+
export const handle = kit.createHandle({
|
|
157
|
+
auth: {
|
|
158
|
+
verifyToken,
|
|
159
|
+
protectedPaths: ['/api/*'],
|
|
160
|
+
publicPaths: ['/api/_hai/*'], // 默认密钥协商端点
|
|
161
|
+
},
|
|
162
|
+
crypto: {
|
|
163
|
+
crypto,
|
|
164
|
+
transport: {
|
|
165
|
+
requireEncryption: true,
|
|
166
|
+
keyStore: createRedisTransportKeyStore({ cache, ttlSeconds: 3600 }),
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**src/routes/+layout.svelte** (recommended entry: one browser-side call):
|
|
173
|
+
|
|
174
|
+
```svelte
|
|
175
|
+
<script lang='ts'>
|
|
176
|
+
import { browser } from '$app/environment'
|
|
177
|
+
import { appKitConfig } from '$lib/config/kit-config'
|
|
178
|
+
import { crypto } from '@h-ai/crypto'
|
|
179
|
+
import { kit } from '@h-ai/kit'
|
|
180
|
+
|
|
181
|
+
// 一次性安装:内部按 _kit.yml 决定是否启用 transport,并预热 crypto
|
|
182
|
+
if (browser) {
|
|
183
|
+
kit.client.installBrowserTransport(appKitConfig, { crypto })
|
|
184
|
+
}
|
|
185
|
+
</script>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**src/lib/utils/api.ts** (business code sees only `apiFetch`):
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import { kit } from '@h-ai/kit'
|
|
192
|
+
|
|
193
|
+
export const { apiFetch } = kit.client.create()
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
> `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`.
|
|
197
|
+
|
|
198
|
+
The default key exchange path is `/api/_hai/key-exchange`. If the server customizes `transport.keyExchangePath`, update the client's `transport.keyExchangeUrl` accordingly.
|
|
199
|
+
|
|
200
|
+
For shared client public keys across nodes, inject `createRedisTransportKeyStore()` or `createReldbTransportKeyStore()` through `kit.createHandle({ crypto: { transport: { keyStore } } })` at runtime. These object dependencies **must not** go in `_kit.yml`; configuration files contain only public paths and switches.
|
|
201
|
+
|
|
202
|
+
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`.
|
|
203
|
+
|
|
204
|
+
Security defaults to fail-closed: with `requireEncryption: true`, protected paths without `X-Client-Id` return 400. An unavailable transport manager, unencryptable response, or encryption size limit returns an error rather than forwarding plaintext business data. `requireEncryption: false` is for staged migration only and is not recommended in production.
|
|
205
|
+
|
|
206
|
+
### Shared transport configuration with `_kit.yml`
|
|
207
|
+
|
|
208
|
+
`@h-ai/kit` provides `KitConfigSchema` / `resolveKitConfig()` so a single `_kit.yml`
|
|
209
|
+
can drive both `hooks.server.ts` and browser `kit.client.installBrowserTransport()`:
|
|
210
|
+
|
|
211
|
+
```yml
|
|
212
|
+
server:
|
|
213
|
+
host: 127.0.0.1
|
|
214
|
+
port: 3000
|
|
215
|
+
|
|
216
|
+
transport:
|
|
217
|
+
keyExchangePath: /api/_hai/key-exchange
|
|
218
|
+
requireEncryption: true
|
|
219
|
+
encryptResponse: true
|
|
220
|
+
excludePaths:
|
|
221
|
+
- /api/storage
|
|
222
|
+
- /api/public
|
|
223
|
+
- /api/auth/profile/avatar
|
|
224
|
+
maxClients: 10000
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
import { resolveKitConfig } from '@h-ai/kit'
|
|
229
|
+
import { parse } from 'yaml'
|
|
230
|
+
import rawKitConfig from '../config/_kit.yml?raw'
|
|
231
|
+
|
|
232
|
+
export const appKitConfig = resolveKitConfig(parse(rawKitConfig) ?? {})
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`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`.
|
|
236
|
+
For server-only configuration, use `core.config.validate('kit', KitConfigSchema)` as well.
|
|
237
|
+
Because the browser may read this configuration, `_kit.yml` must contain only public server/transport paths and switches, never secrets.
|
|
238
|
+
|
|
239
|
+
## Configuration
|
|
240
|
+
|
|
241
|
+
`kit.createHandle()` options:
|
|
242
|
+
|
|
243
|
+
| Field | Type | Description |
|
|
244
|
+
| ------------ | ---------------------------------------- | ---------------------------------------------------------------------- |
|
|
245
|
+
| `auth` | `HandleAuthConfig` | Authentication (verifyToken / protectedPaths / publicPaths / loginUrl) |
|
|
246
|
+
| `rateLimit` | `object \| false` | Rate limiting (windowMs / maxRequests) |
|
|
247
|
+
| `logging` | `boolean \| object` | Request logging (default `true`; `{ logBody: true }` logs the body) |
|
|
248
|
+
| `crypto` | `HookCryptoConfig` | Transport + cookie encryption |
|
|
249
|
+
| `guards` | `GuardConfig[]` | Custom guards (after automatic auth guards) |
|
|
250
|
+
| `middleware` | `Middleware[]` | Custom middleware (after built-in middleware) |
|
|
251
|
+
| `a2a` | `HandleA2AOperations \| HandleA2AConfig` | A2A integration (Agent Card + JSON-RPC) |
|
|
252
|
+
| `onError` | `function` | Custom error handling |
|
|
253
|
+
|
|
254
|
+
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.
|
|
255
|
+
|
|
256
|
+
## Export paths
|
|
257
|
+
|
|
258
|
+
- `@h-ai/kit`: main entry, with the `kit` namespace and types.
|
|
259
|
+
- `@h-ai/kit/client`: browser client.
|
|
260
|
+
- `@h-ai/kit/adapter`: dual build adapter (`createAdapter()`).
|
|
261
|
+
- `@h-ai/kit/vite`: Vite plugin.
|
|
262
|
+
- `@h-ai/kit/a2a`: A2A integration.
|
|
263
|
+
- `@h-ai/kit/crypto`: crypto integration.
|
|
264
|
+
|
|
265
|
+
## Error handling
|
|
266
|
+
|
|
267
|
+
- Wrap API endpoints with `kit.handler()`.
|
|
268
|
+
- `kit.validate.body` / `kit.guard.require` throw `Response` for SvelteKit control flow.
|
|
269
|
+
- `kit.handler()` converts non-control-flow exceptions into `500 INTERNAL_ERROR` responses.
|
|
270
|
+
|
|
271
|
+
## Tests
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
pnpm --filter @h-ai/kit test
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## License
|
|
278
|
+
|
|
279
|
+
Apache-2.0
|
package/README.ja.md
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# @h-ai/kit
|
|
2
|
+
|
|
3
|
+
[简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
|
|
4
|
+
|
|
5
|
+
[フレームワークのホーム](../../README.ja.md) · 関連モジュール:[@h-ai/core](../core/README.ja.md) · [@h-ai/crypto](../crypto/README.ja.md) · [@h-ai/iam](../iam/README.ja.md)
|
|
6
|
+
|
|
7
|
+
`@h-ai/kit` は hai-framework の SvelteKit 統合モジュールで、共通のリクエストパイプラインと同一オリジンの endpoint ツールを提供します:
|
|
8
|
+
|
|
9
|
+
- `kit.createHandle()`:セッション解析、ルートガード、middleware チェーン、任意の通信・Cookie 暗号化。
|
|
10
|
+
- `kit.handler()`:API endpoint の例外処理境界。
|
|
11
|
+
- `kit.guard` / `kit.response` / `kit.validate` / `kit.auth` / `kit.client` / `kit.crud`
|
|
12
|
+
|
|
13
|
+
> クロスプラットフォームの公開 HTTP contract は `@h-ai/api-contract` で定義し、`@h-ai/serv` にマウント、`@h-ai/api-client` の型付きクライアントで呼び出します。`@h-ai/kit` は業務 API contract を定義・提供しません。
|
|
14
|
+
|
|
15
|
+
## インストール
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @h-ai/kit
|
|
19
|
+
npm install svelte @sveltejs/kit zod
|
|
20
|
+
npm install -D @sveltejs/vite-plugin-svelte vite
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 主な機能
|
|
24
|
+
|
|
25
|
+
- Handle Hook の合成(`createHandle` / `sequence`)
|
|
26
|
+
- 認証・ロール・権限ガード(`guard.require` / `guard.check`)
|
|
27
|
+
- 組み込み Logging / RateLimit / CORS middleware(`createHandle` 設定で有効化)
|
|
28
|
+
- 共通の API 応答とリクエスト検証(Zod)
|
|
29
|
+
- Bearer Token ツール(サーバー login/logout + ブラウザーの Token 保存)
|
|
30
|
+
- 自動 CSRF と通信暗号化に対応するブラウザークライアント
|
|
31
|
+
- 宣言的な CRUD リソース定義(`crud.define`)
|
|
32
|
+
- A2A 統合:既定の検出パスは `/.well-known/agent-card.json`。`authenticate` 設定時は null/undefined が 401 となり、認証未設定時のみ匿名を許可します。API Key の引数は完全な Agent Card の `securitySchemes.apiKey` から取得します。Hook と明示的 A2A ルートは JSON-RPC 解析エラー・SSE キャンセル時の解放処理を共有します。
|
|
33
|
+
- 2 種類のビルドモード(`createAdapter()`)
|
|
34
|
+
|
|
35
|
+
## 依存関係
|
|
36
|
+
|
|
37
|
+
- `@h-ai/iam`:A2A 用の認証・認可(任意)。A2A 機能を使う前に初期化。
|
|
38
|
+
- `@h-ai/crypto`:通信・Cookie 暗号化(任意)。暗号化を使う前に初期化。
|
|
39
|
+
|
|
40
|
+
## クイックスタート
|
|
41
|
+
|
|
42
|
+
### プロジェクト設定
|
|
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: 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\//] },
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### リクエストパイプラインの構築
|
|
78
|
+
|
|
79
|
+
**src/hooks.server.ts**:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import type { Handle } from '@sveltejs/kit'
|
|
83
|
+
import { kit } from '@h-ai/kit'
|
|
84
|
+
|
|
85
|
+
const haiHandle = kit.createHandle({
|
|
86
|
+
auth: {
|
|
87
|
+
verifyToken: async (token) => {
|
|
88
|
+
return token ? { userId: 'u_1', roles: ['admin'], permissions: ['user:read'] } : null
|
|
89
|
+
},
|
|
90
|
+
loginUrl: '/auth/login',
|
|
91
|
+
protectedPaths: ['/admin/*', '/api/*'],
|
|
92
|
+
publicPaths: ['/api/auth/*', '/api/public/*'],
|
|
93
|
+
},
|
|
94
|
+
rateLimit: { windowMs: 60_000, maxRequests: 100 },
|
|
95
|
+
logging: true,
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
export const handle: Handle = haiHandle
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### API endpoint
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { kit } from '@h-ai/kit'
|
|
105
|
+
import { z } from 'zod'
|
|
106
|
+
|
|
107
|
+
const CreateUserSchema = z.object({
|
|
108
|
+
name: z.string().min(1),
|
|
109
|
+
email: z.string().email(),
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
export const POST = kit.handler(async ({ request, locals }) => {
|
|
113
|
+
kit.guard.require(locals.session, 'user:create')
|
|
114
|
+
const payload = await kit.validate.body(request, CreateUserSchema)
|
|
115
|
+
return kit.response.created(payload)
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### ブラウザークライアント
|
|
120
|
+
|
|
121
|
+
`kit.client.create()` は SvelteKit の同一オリジン endpoint 向けです。Web・アプリ・ミニプログラムからクロスオリジンで公開 API を使う場合は `@h-ai/api-client` を使用してください。
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// src/lib/utils/api.ts — 全应用共享
|
|
125
|
+
import { kit } from '@h-ai/kit'
|
|
126
|
+
|
|
127
|
+
const client = kit.client.create()
|
|
128
|
+
export const { apiFetch } = client
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// 使用
|
|
133
|
+
import { apiFetch } from '$lib/utils/api'
|
|
134
|
+
|
|
135
|
+
const response = await apiFetch('/api/users', { method: 'GET' })
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
> CSRF Header は `apiFetch` が自動付与します。`@h-ai/kit` は独立した CSRF middleware ファクトリーを提供しません。
|
|
139
|
+
>
|
|
140
|
+
> 認証:同一オリジンの SvelteKit endpoint は httpOnly Cookie を優先し、サーバーで Cookie を読んで認証します。機密 Token を `localStorage` に保存する必要はありません。Bearer Header が必要なら独自の `BrowserTokenStore`、または `kit.auth.setBrowserToken()` でページ内メモリーに保存してから `kit.client.create({ auth: true })` を使います。
|
|
141
|
+
|
|
142
|
+
### 同一オリジンの通信暗号化
|
|
143
|
+
|
|
144
|
+
kit の通信暗号化は `@h-ai/crypto` に委譲します。サーバーは `crypto.transport.createServer()` で管理器を作成し、ブラウザーは `kit.client.installBrowserTransport(kitConfig, { crypto })` の 1 行で同一オリジンの fetch ラッパーを導入します。アプリ側はトップレベルの `crypto` のみを設定します。
|
|
145
|
+
|
|
146
|
+
**src/hooks.server.ts**:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { cache } from '@h-ai/cache'
|
|
150
|
+
import { createRedisTransportKeyStore, crypto } from '@h-ai/crypto'
|
|
151
|
+
import { kit } from '@h-ai/kit'
|
|
152
|
+
|
|
153
|
+
await crypto.init()
|
|
154
|
+
await cache.init({ type: 'redis', host: '127.0.0.1', port: 6379 })
|
|
155
|
+
|
|
156
|
+
export const handle = kit.createHandle({
|
|
157
|
+
auth: {
|
|
158
|
+
verifyToken,
|
|
159
|
+
protectedPaths: ['/api/*'],
|
|
160
|
+
publicPaths: ['/api/_hai/*'], // 默认密钥协商端点
|
|
161
|
+
},
|
|
162
|
+
crypto: {
|
|
163
|
+
crypto,
|
|
164
|
+
transport: {
|
|
165
|
+
requireEncryption: true,
|
|
166
|
+
keyStore: createRedisTransportKeyStore({ cache, ttlSeconds: 3600 }),
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**src/routes/+layout.svelte**(推奨入口:ブラウザー側は 1 行):
|
|
173
|
+
|
|
174
|
+
```svelte
|
|
175
|
+
<script lang='ts'>
|
|
176
|
+
import { browser } from '$app/environment'
|
|
177
|
+
import { appKitConfig } from '$lib/config/kit-config'
|
|
178
|
+
import { crypto } from '@h-ai/crypto'
|
|
179
|
+
import { kit } from '@h-ai/kit'
|
|
180
|
+
|
|
181
|
+
// 一次性安装:内部按 _kit.yml 决定是否启用 transport,并预热 crypto
|
|
182
|
+
if (browser) {
|
|
183
|
+
kit.client.installBrowserTransport(appKitConfig, { crypto })
|
|
184
|
+
}
|
|
185
|
+
</script>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**src/lib/utils/api.ts**(業務層では `apiFetch` のみを使用):
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import { kit } from '@h-ai/kit'
|
|
192
|
+
|
|
193
|
+
export const { apiFetch } = kit.client.create()
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
> `src/hooks.client.ts` は不要です。既定の `handleFetch` で十分で、SvelteKit 内部の `__data.json` も導入済みのグローバル fetch ラッパーを自動使用します。同一オリジンの Authorization Header には明示的な `BrowserTokenStore` または `kit.auth.setBrowserToken()` + `kit.client.create({ auth: true })` を使い、機密 Token を `localStorage` に保存しないでください。
|
|
197
|
+
|
|
198
|
+
既定の鍵交換パスは `/api/_hai/key-exchange` です。サーバーで `transport.keyExchangePath` を変更した場合、クライアントの `transport.keyExchangeUrl` も合わせて設定してください。
|
|
199
|
+
|
|
200
|
+
ノード間でクライアント公開鍵を共有するには、実行時に `kit.createHandle({ crypto: { transport: { keyStore } } })` へ `createRedisTransportKeyStore()` または `createReldbTransportKeyStore()` を渡します。このようなオブジェクト依存を `_kit.yml` に**書いてはいけません**。設定ファイルは公開パスとスイッチのみを保持します。
|
|
201
|
+
|
|
202
|
+
transport は既定で同一オリジンの `/api/*`、SvelteKit の `__data.json`、鍵交換 endpoint に適用します。ページ文書・静的ファイルは平文で `X-Client-Id` は不要です。アップロードなどの `multipart/form-data` はそのまま送信し、サーバーの `excludePaths` で明示的に許可してください。
|
|
203
|
+
|
|
204
|
+
既定の安全方針は fail-closed です。`requireEncryption: true` で保護パスに `X-Client-Id` がなければ 400 を返します。管理器の利用不可、応答の暗号化失敗、暗号化サイズ上限超過はエラーになり、平文の業務応答は渡しません。`requireEncryption: false` は段階的移行用で、本番では推奨しません。
|
|
205
|
+
|
|
206
|
+
### `_kit.yml` による共通 transport 設定
|
|
207
|
+
|
|
208
|
+
`@h-ai/kit` は `KitConfigSchema` / `resolveKitConfig()` を提供し、同じ `_kit.yml` から
|
|
209
|
+
`hooks.server.ts` とブラウザーの `kit.client.installBrowserTransport()` の両方を設定できます:
|
|
210
|
+
|
|
211
|
+
```yml
|
|
212
|
+
server:
|
|
213
|
+
host: 127.0.0.1
|
|
214
|
+
port: 3000
|
|
215
|
+
|
|
216
|
+
transport:
|
|
217
|
+
keyExchangePath: /api/_hai/key-exchange
|
|
218
|
+
requireEncryption: true
|
|
219
|
+
encryptResponse: true
|
|
220
|
+
excludePaths:
|
|
221
|
+
- /api/storage
|
|
222
|
+
- /api/public
|
|
223
|
+
- /api/auth/profile/avatar
|
|
224
|
+
maxClients: 10000
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
import { resolveKitConfig } from '@h-ai/kit'
|
|
229
|
+
import { parse } from 'yaml'
|
|
230
|
+
import rawKitConfig from '../config/_kit.yml?raw'
|
|
231
|
+
|
|
232
|
+
export const appKitConfig = resolveKitConfig(parse(rawKitConfig) ?? {})
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`resolveKitConfig()` は `server`(host/port)と `transport` を解決します。host/port は `HOST` / `PORT` 環境変数 > `_kit.yml` の `server` > `127.0.0.1:3000` の順で、SvelteKit サーバー入口で共通利用します(`@h-ai/serv` と同じ構造)。
|
|
236
|
+
サーバーのみで設定を読む場合は `core.config.validate('kit', KitConfigSchema)` も利用できます。
|
|
237
|
+
ブラウザーも読む可能性があるため、`_kit.yml` は公開 server/transport パスとスイッチのみを含め、秘密鍵などを書かないでください。
|
|
238
|
+
|
|
239
|
+
## 設定
|
|
240
|
+
|
|
241
|
+
`kit.createHandle()` の設定項目:
|
|
242
|
+
|
|
243
|
+
| フィールド | 型 | 説明 |
|
|
244
|
+
| ------------ | ---------------------------------------- | --------------------------------------------------------------- |
|
|
245
|
+
| `auth` | `HandleAuthConfig` | 認証(verifyToken / protectedPaths / publicPaths / loginUrl) |
|
|
246
|
+
| `rateLimit` | `object \| false` | レート制限(windowMs / maxRequests) |
|
|
247
|
+
| `logging` | `boolean \| object` | リクエストログ(既定 `true`、`{ logBody: true }` で本文も記録) |
|
|
248
|
+
| `crypto` | `HookCryptoConfig` | 通信暗号化 + Cookie 暗号化 |
|
|
249
|
+
| `guards` | `GuardConfig[]` | 独自ガード(自動認証ガードの後に実行) |
|
|
250
|
+
| `middleware` | `Middleware[]` | 独自 middleware(組み込み middleware の後に実行) |
|
|
251
|
+
| `a2a` | `HandleA2AOperations \| HandleA2AConfig` | A2A 統合(Agent Card + JSON-RPC) |
|
|
252
|
+
| `onError` | `function` | 独自のエラー処理 |
|
|
253
|
+
|
|
254
|
+
`crypto.encryptedCookies` を設定する場合、`crypto.cookieEncryptionKey` または `HAI_KIT_COOKIE_KEY` が必要です。鍵がなければ `createHandle()` ですぐエラーになります。保護対象 Cookie の平文・破損暗号文・復号失敗は未存在として扱い、暗号化失敗時も平文保存に切り替えません。
|
|
255
|
+
|
|
256
|
+
## エクスポートパス
|
|
257
|
+
|
|
258
|
+
- `@h-ai/kit`:主入口。`kit` 名前空間と型を提供。
|
|
259
|
+
- `@h-ai/kit/client`:ブラウザークライアント。
|
|
260
|
+
- `@h-ai/kit/adapter`:2 モードのビルド adapter(`createAdapter()`)。
|
|
261
|
+
- `@h-ai/kit/vite`:Vite プラグイン。
|
|
262
|
+
- `@h-ai/kit/a2a`:A2A 統合。
|
|
263
|
+
- `@h-ai/kit/crypto`:暗号モジュール統合。
|
|
264
|
+
|
|
265
|
+
## エラー処理
|
|
266
|
+
|
|
267
|
+
- API endpoint は `kit.handler()` でラップすることを推奨。
|
|
268
|
+
- `kit.validate.body` / `kit.guard.require` は SvelteKit の制御フローとして `Response` を throw。
|
|
269
|
+
- 制御フロー以外の例外は `kit.handler()` が `500 INTERNAL_ERROR` 応答に変換。
|
|
270
|
+
|
|
271
|
+
## テスト
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
pnpm --filter @h-ai/kit test
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## License
|
|
278
|
+
|
|
279
|
+
Apache-2.0
|
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @h-ai/kit
|
|
2
2
|
|
|
3
|
+
[简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
|
|
4
|
+
|
|
5
|
+
[框架首页](../../README.md) · 相关模块:[@h-ai/core](../core/README.md) · [@h-ai/crypto](../crypto/README.md) · [@h-ai/iam](../iam/README.md)
|
|
6
|
+
|
|
3
7
|
`@h-ai/kit` 是 hai-framework 的 SvelteKit 集成模块,提供统一的请求管道与同源 endpoint 工具:
|
|
4
8
|
|
|
5
9
|
- `kit.createHandle()`:会话解析、路由守卫、中间件链、可选传输加密与 Cookie 加密
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-ai/kit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.0-alpha.
|
|
4
|
+
"version": "0.1.0-alpha.58",
|
|
5
5
|
"description": "Hai Framework SvelteKit integration module.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"exports": {
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"engines": {
|
|
44
|
-
"node": "
|
|
44
|
+
"node": "^22.22.2 || ^24.15.0 || >=26.0.0"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"@sveltejs/adapter-node": "^5.5.
|
|
47
|
+
"@sveltejs/adapter-node": "^5.5.7",
|
|
48
48
|
"@sveltejs/adapter-static": "^3.0.10",
|
|
49
|
-
"@sveltejs/kit": "^2.
|
|
50
|
-
"svelte": "^5.
|
|
49
|
+
"@sveltejs/kit": "^2.70.3",
|
|
50
|
+
"svelte": "^5.57.2"
|
|
51
51
|
},
|
|
52
52
|
"peerDependenciesMeta": {
|
|
53
53
|
"@sveltejs/adapter-node": {
|
|
@@ -58,26 +58,26 @@
|
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"
|
|
62
|
-
"@h-ai/crypto": "0.1.0-alpha.
|
|
63
|
-
"@h-ai/
|
|
64
|
-
"
|
|
61
|
+
"@h-ai/core": "0.1.0-alpha.58",
|
|
62
|
+
"@h-ai/crypto": "0.1.0-alpha.58",
|
|
63
|
+
"@h-ai/iam": "0.1.0-alpha.58",
|
|
64
|
+
"zod": "^4.6.5"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@sveltejs/kit": "^2.
|
|
68
|
-
"@types/node": "^
|
|
67
|
+
"@sveltejs/kit": "^2.70.3",
|
|
68
|
+
"@types/node": "^26.6.5",
|
|
69
69
|
"rimraf": "^6.1.3",
|
|
70
|
-
"svelte": "^5.
|
|
70
|
+
"svelte": "^5.57.2",
|
|
71
71
|
"tsup": "^8.5.1",
|
|
72
72
|
"typescript": "^6.0.3",
|
|
73
|
-
"vitest": "^4.1.
|
|
73
|
+
"vitest": "^4.1.11"
|
|
74
74
|
},
|
|
75
75
|
"scripts": {
|
|
76
76
|
"build": "tsup",
|
|
77
77
|
"dev": "tsup --watch",
|
|
78
78
|
"test": "vitest run",
|
|
79
79
|
"test:watch": "vitest",
|
|
80
|
-
"typecheck": "
|
|
80
|
+
"typecheck": "node ../../scripts/typecheck.mjs --noEmit",
|
|
81
81
|
"lint": "eslint .",
|
|
82
82
|
"lint:fix": "eslint . --fix",
|
|
83
83
|
"clean": "rimraf dist .turbo",
|