@h-ai/kit 0.1.0-alpha.66 → 0.1.0-alpha.68
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 +80 -46
- package/README.ja.md +72 -40
- package/README.md +73 -41
- package/dist/adapter/index.d.ts +17 -2
- package/dist/adapter/index.js.map +1 -1
- package/dist/browser.d.ts +12 -6
- package/dist/browser.js +6 -7
- package/dist/browser.js.map +1 -1
- package/dist/{chunk-DVW7CQ3H.js → chunk-2PWGJCWX.js} +3 -3
- package/dist/chunk-2PWGJCWX.js.map +1 -0
- package/dist/{chunk-VOTDAVCB.js → chunk-2YYRP7HH.js} +17 -33
- package/dist/chunk-2YYRP7HH.js.map +1 -0
- package/dist/{chunk-MLSP67JF.js → chunk-333FGMGQ.js} +59 -111
- package/dist/chunk-333FGMGQ.js.map +1 -0
- package/dist/chunk-OVRZIDCD.js +175 -0
- package/dist/chunk-OVRZIDCD.js.map +1 -0
- package/dist/{chunk-L7BTMAKX.js → chunk-P6XH7G4N.js} +3 -6
- package/dist/chunk-P6XH7G4N.js.map +1 -0
- package/dist/{chunk-NVYIUBU7.js → chunk-XC7I2L3G.js} +2 -2
- package/dist/chunk-XC7I2L3G.js.map +1 -0
- package/dist/client/index.d.ts +47 -1
- package/dist/client/index.js +3 -3
- package/dist/client/index.js.map +1 -1
- package/dist/index.d.ts +217 -347
- package/dist/index.js +138 -83
- package/dist/index.js.map +1 -1
- package/dist/kit-a2a-types-CwT1AFQu.d.ts +136 -0
- package/dist/{index-B7s-0mZl.d.ts → kit-client-auth-HnZIYUia.d.ts} +220 -101
- package/dist/kit-crypto-types-Dd24aJ4m.d.ts +194 -0
- package/dist/{kit-i18n-D9Qqk-1k.d.ts → kit-i18n-DEzmzE5T.d.ts} +142 -29
- package/dist/{kit-types-ByWdbCeO.d.ts → kit-types-CnzhN-AS.d.ts} +240 -162
- package/dist/modules/a2a/index.d.ts +13 -111
- package/dist/modules/a2a/index.js +2 -2
- package/dist/modules/crypto/index.d.ts +28 -195
- package/dist/modules/crypto/index.js +3 -3
- package/dist/modules/crypto/index.js.map +1 -1
- package/package.json +10 -10
- package/dist/chunk-DVW7CQ3H.js.map +0 -1
- package/dist/chunk-L7BTMAKX.js.map +0 -1
- package/dist/chunk-MLSP67JF.js.map +0 -1
- package/dist/chunk-NVYIUBU7.js.map +0 -1
- package/dist/chunk-QHYNL24G.js +0 -117
- package/dist/chunk-QHYNL24G.js.map +0 -1
- package/dist/chunk-VOTDAVCB.js.map +0 -1
package/README.en.md
CHANGED
|
@@ -4,23 +4,11 @@
|
|
|
4
4
|
|
|
5
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
6
|
|
|
7
|
-
|
|
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
|
|
7
|
+
## Introduction
|
|
16
8
|
|
|
17
|
-
|
|
18
|
-
npm install @h-ai/kit
|
|
19
|
-
npm install svelte @sveltejs/kit zod
|
|
20
|
-
npm install -D @sveltejs/vite-plugin-svelte vite
|
|
21
|
-
```
|
|
9
|
+
`@h-ai/kit` integrates SvelteKit with hai-framework, providing a shared request pipeline and same-origin endpoint utilities:
|
|
22
10
|
|
|
23
|
-
##
|
|
11
|
+
## Core features
|
|
24
12
|
|
|
25
13
|
- Handle hook composition (`createHandle` / `sequence`)
|
|
26
14
|
- Authentication/role/permission guards (`guard.require` / `guard.check`)
|
|
@@ -32,14 +20,26 @@ npm install -D @sveltejs/vite-plugin-svelte vite
|
|
|
32
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.
|
|
33
21
|
- Dual build mode (`createAdapter()`)
|
|
34
22
|
|
|
35
|
-
##
|
|
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
36
|
|
|
37
37
|
- `@h-ai/iam`: optional authentication/authorization for A2A; initialize before using kit's A2A features.
|
|
38
38
|
- `@h-ai/crypto`: optional transport/cookie encryption; initialize before using encryption in kit.
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
### Quick start
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
#### Project configuration
|
|
43
43
|
|
|
44
44
|
**svelte.config.js**:
|
|
45
45
|
|
|
@@ -53,7 +53,7 @@ const config = {
|
|
|
53
53
|
kit: {
|
|
54
54
|
adapter: adapter(),
|
|
55
55
|
// 双构建模式:import { createAdapter } from '@h-ai/kit/adapter'
|
|
56
|
-
// adapter: createAdapter(),
|
|
56
|
+
// adapter: await createAdapter(),
|
|
57
57
|
},
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -70,35 +70,41 @@ import { defineConfig } from 'vite'
|
|
|
70
70
|
export default defineConfig({
|
|
71
71
|
plugins: [sveltekit(), tailwindcss()],
|
|
72
72
|
optimizeDeps: { exclude: ['bits-ui'] },
|
|
73
|
-
ssr: { noExternal: [
|
|
73
|
+
ssr: { noExternal: ['@h-ai/ui', '@h-ai/kit'], external: ['@h-ai/core', 'sm-crypto'] },
|
|
74
74
|
})
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
|
|
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
|
|
78
80
|
|
|
79
81
|
**src/hooks.server.ts**:
|
|
80
82
|
|
|
81
83
|
```typescript
|
|
82
84
|
import type { Handle } from '@sveltejs/kit'
|
|
85
|
+
import { cache } from '@h-ai/cache'
|
|
83
86
|
import { kit } from '@h-ai/kit'
|
|
84
87
|
|
|
85
88
|
const haiHandle = kit.createHandle({
|
|
86
89
|
auth: {
|
|
87
90
|
verifyToken: async (token) => {
|
|
88
|
-
|
|
91
|
+
const verified = await iam.auth.verifyToken(token)
|
|
92
|
+
return verified.success ? verified.data : null
|
|
89
93
|
},
|
|
90
94
|
loginUrl: '/auth/login',
|
|
91
95
|
protectedPaths: ['/admin/*', '/api/*'],
|
|
92
96
|
publicPaths: ['/api/auth/*', '/api/public/*'],
|
|
93
97
|
},
|
|
94
|
-
rateLimit: { windowMs: 60_000, maxRequests: 100 },
|
|
98
|
+
rateLimit: { windowMs: 60_000, maxRequests: 100, store: kit.middleware.createCacheRateLimitStore(cache, { namespace: 'app' }) },
|
|
95
99
|
logging: true,
|
|
96
100
|
})
|
|
97
101
|
|
|
98
102
|
export const handle: Handle = haiHandle
|
|
99
103
|
```
|
|
100
104
|
|
|
101
|
-
|
|
105
|
+
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.
|
|
106
|
+
|
|
107
|
+
#### API endpoints
|
|
102
108
|
|
|
103
109
|
```typescript
|
|
104
110
|
import { kit } from '@h-ai/kit'
|
|
@@ -116,7 +122,7 @@ export const POST = kit.handler(async ({ request, locals }) => {
|
|
|
116
122
|
})
|
|
117
123
|
```
|
|
118
124
|
|
|
119
|
-
|
|
125
|
+
#### Browser client
|
|
120
126
|
|
|
121
127
|
`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
128
|
|
|
@@ -137,20 +143,24 @@ const response = await apiFetch('/api/users', { method: 'GET' })
|
|
|
137
143
|
|
|
138
144
|
> `apiFetch` injects the CSRF header automatically; `@h-ai/kit` does not expose a separate CSRF middleware factory.
|
|
139
145
|
>
|
|
140
|
-
>
|
|
146
|
+
> 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.
|
|
141
147
|
|
|
142
|
-
|
|
148
|
+
#### Same-origin transport encryption
|
|
143
149
|
|
|
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 })`.
|
|
150
|
+
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.
|
|
145
151
|
|
|
146
152
|
**src/hooks.server.ts**:
|
|
147
153
|
|
|
148
154
|
```ts
|
|
149
155
|
import { cache } from '@h-ai/cache'
|
|
150
|
-
import { createRedisTransportKeyStore, crypto } from '@h-ai/crypto'
|
|
156
|
+
import { createRedisTransportKeyStore, crypto, parseTransportServerKeyRing } from '@h-ai/crypto'
|
|
151
157
|
import { kit } from '@h-ai/kit'
|
|
152
158
|
|
|
153
159
|
await crypto.init()
|
|
160
|
+
const ring = parseTransportServerKeyRing(process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING)
|
|
161
|
+
if (!ring.success)
|
|
162
|
+
throw new Error(ring.error.message)
|
|
163
|
+
const serverKeyRing = ring.data
|
|
154
164
|
await cache.init({ type: 'redis', host: '127.0.0.1', port: 6379 })
|
|
155
165
|
|
|
156
166
|
export const handle = kit.createHandle({
|
|
@@ -162,7 +172,7 @@ export const handle = kit.createHandle({
|
|
|
162
172
|
crypto: {
|
|
163
173
|
crypto,
|
|
164
174
|
transport: {
|
|
165
|
-
|
|
175
|
+
serverKeyRing,
|
|
166
176
|
keyStore: createRedisTransportKeyStore({ cache, ttlSeconds: 3600 }),
|
|
167
177
|
},
|
|
168
178
|
},
|
|
@@ -197,13 +207,17 @@ export const { apiFetch } = kit.client.create()
|
|
|
197
207
|
|
|
198
208
|
The default key exchange path is `/api/_hai/key-exchange`. If the server customizes `transport.keyExchangePath`, update the client's `transport.keyExchangeUrl` accordingly.
|
|
199
209
|
|
|
200
|
-
|
|
210
|
+
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.
|
|
201
211
|
|
|
202
212
|
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
213
|
|
|
204
|
-
|
|
214
|
+
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.
|
|
205
215
|
|
|
206
|
-
|
|
216
|
+
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. Exclude plaintext endpoints explicitly with excludePaths. CORS must allow and expose X-Hai-Transport. TLS authenticates handshake keys; this protocol does not replace IAM authorization.
|
|
217
|
+
|
|
218
|
+
Fixed budgets share `@h-ai/crypto`'s `TRANSPORT_BODY_LIMITS` with serv and the client: 16 KiB key exchange JSON, 4 KiB per public key, 1 MiB plaintext, and 2 MiB + 32 KiB encrypted JSON, counting actual UTF-8 bytes. Incremental reads consume the original stream; exceeding limits or cancellation stops/cancels it and releases its reader without cloning and fully buffering it. Oversized requests return 413, oversized responses return 500, and invalid public keys return 400. Rebuilt requests preserve signals; rebuilt requests/responses remove stale Content-Length/Content-Encoding. These budgets are not `_kit.yml` or environment settings.
|
|
219
|
+
|
|
220
|
+
#### Shared transport configuration with `_kit.yml`
|
|
207
221
|
|
|
208
222
|
`@h-ai/kit` provides `KitConfigSchema` / `resolveKitConfig()` so a single `_kit.yml`
|
|
209
223
|
can drive both `hooks.server.ts` and browser `kit.client.installBrowserTransport()`:
|
|
@@ -215,13 +229,10 @@ server:
|
|
|
215
229
|
|
|
216
230
|
transport:
|
|
217
231
|
keyExchangePath: /api/_hai/key-exchange
|
|
218
|
-
requireEncryption: true
|
|
219
|
-
encryptResponse: true
|
|
220
232
|
excludePaths:
|
|
221
233
|
- /api/storage
|
|
222
234
|
- /api/public
|
|
223
235
|
- /api/auth/profile/avatar
|
|
224
|
-
maxClients: 10000
|
|
225
236
|
```
|
|
226
237
|
|
|
227
238
|
```ts
|
|
@@ -236,7 +247,7 @@ export const appKitConfig = resolveKitConfig(parse(rawKitConfig) ?? {})
|
|
|
236
247
|
For server-only configuration, use `core.config.validate('kit', KitConfigSchema)` as well.
|
|
237
248
|
Because the browser may read this configuration, `_kit.yml` must contain only public server/transport paths and switches, never secrets.
|
|
238
249
|
|
|
239
|
-
|
|
250
|
+
### Configuration
|
|
240
251
|
|
|
241
252
|
`kit.createHandle()` options:
|
|
242
253
|
|
|
@@ -253,16 +264,21 @@ Because the browser may read this configuration, `_kit.yml` must contain only pu
|
|
|
253
264
|
|
|
254
265
|
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
266
|
|
|
256
|
-
|
|
267
|
+
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.
|
|
268
|
+
|
|
269
|
+
### Export paths
|
|
270
|
+
|
|
271
|
+
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.
|
|
257
272
|
|
|
258
273
|
- `@h-ai/kit`: main entry, with the `kit` namespace and types.
|
|
259
274
|
- `@h-ai/kit/client`: browser client.
|
|
260
275
|
- `@h-ai/kit/adapter`: dual build adapter (`createAdapter()`).
|
|
261
|
-
- `@h-ai/kit/vite`: Vite plugin.
|
|
262
276
|
- `@h-ai/kit/a2a`: A2A integration.
|
|
263
277
|
- `@h-ai/kit/crypto`: crypto integration.
|
|
264
278
|
|
|
265
|
-
|
|
279
|
+
### Configuration driven authentication
|
|
280
|
+
|
|
281
|
+
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.
|
|
266
282
|
|
|
267
283
|
Agreements support configuration-loaded userAgreementMarkdown/privacyPolicyMarkdown and URLs. Markdown takes priority for the same agreement and is safely rendered in a modal.
|
|
268
284
|
|
|
@@ -270,22 +286,40 @@ Export standard handlers from kit.auth.createApi(iam), use kit.auth.publicPaths
|
|
|
270
286
|
|
|
271
287
|
[完整 IAM 配置 / IAM configuration](../iam/README.en.md)
|
|
272
288
|
|
|
273
|
-
|
|
289
|
+
`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.
|
|
290
|
+
|
|
291
|
+
### Error handling
|
|
274
292
|
|
|
275
293
|
- Wrap API endpoints with `kit.handler()`.
|
|
276
294
|
- `kit.validate.body` / `kit.guard.require` throw `Response` for SvelteKit control flow.
|
|
277
295
|
- `kit.handler()` converts non-control-flow exceptions into `500 INTERNAL_ERROR` responses.
|
|
278
296
|
|
|
279
|
-
|
|
297
|
+
### CRUD fields
|
|
298
|
+
|
|
299
|
+
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.
|
|
300
|
+
|
|
301
|
+
### Runtime behavior and limits
|
|
302
|
+
|
|
303
|
+
- `kit.createHandle()`: session parsing, route guards, middleware chains, and optional transport/cookie encryption.
|
|
304
|
+
- `kit.handler()`: an exception boundary for API endpoints.
|
|
305
|
+
- `kit.guard` / `kit.response` / `kit.validate` / `kit.auth` / `kit.client` / `kit.crud`
|
|
306
|
+
|
|
307
|
+
> 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.
|
|
308
|
+
|
|
309
|
+
### Request locale isolation
|
|
310
|
+
|
|
311
|
+
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.
|
|
312
|
+
|
|
313
|
+
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.
|
|
314
|
+
|
|
315
|
+
## Development and testing
|
|
280
316
|
|
|
281
317
|
```bash
|
|
282
318
|
pnpm --filter @h-ai/kit test
|
|
283
319
|
```
|
|
284
320
|
|
|
321
|
+
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.
|
|
322
|
+
|
|
285
323
|
## License
|
|
286
324
|
|
|
287
325
|
Apache-2.0
|
|
288
|
-
|
|
289
|
-
## CRUD fields
|
|
290
|
-
|
|
291
|
-
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.
|
package/README.ja.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
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
6
|
|
|
7
|
+
## 概要
|
|
8
|
+
|
|
7
9
|
`@h-ai/kit` は hai-framework の SvelteKit 統合モジュールで、共通のリクエストパイプラインと同一オリジンの endpoint ツールを提供します:
|
|
8
10
|
|
|
9
11
|
- `kit.createHandle()`:セッション解析、ルートガード、middleware チェーン、任意の通信・Cookie 暗号化。
|
|
@@ -12,14 +14,6 @@
|
|
|
12
14
|
|
|
13
15
|
> クロスプラットフォームの公開 HTTP contract は `@h-ai/api-contract` で定義し、`@h-ai/serv` にマウント、`@h-ai/api-client` の型付きクライアントで呼び出します。`@h-ai/kit` は業務 API contract を定義・提供しません。
|
|
14
16
|
|
|
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
17
|
## 主な機能
|
|
24
18
|
|
|
25
19
|
- Handle Hook の合成(`createHandle` / `sequence`)
|
|
@@ -32,14 +26,26 @@ npm install -D @sveltejs/vite-plugin-svelte vite
|
|
|
32
26
|
- A2A 統合:既定の検出パスは `/.well-known/agent-card.json`。`authenticate` 設定時は null/undefined が 401 となり、認証未設定時のみ匿名を許可します。API Key の引数は完全な Agent Card の `securitySchemes.apiKey` から取得します。Hook と明示的 A2A ルートは JSON-RPC 解析エラー・SSE キャンセル時の解放処理を共有します。
|
|
33
27
|
- 2 種類のビルドモード(`createAdapter()`)
|
|
34
28
|
|
|
35
|
-
##
|
|
29
|
+
## 使い始める
|
|
30
|
+
|
|
31
|
+
`sequence` は SvelteKit の resolve オプションを保持します。ページ変換は内側から外側へ実行し、preload とシリアライズ対象ヘッダーのフィルターは最初の設定を優先します。
|
|
32
|
+
|
|
33
|
+
### インストール
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install @h-ai/kit @h-ai/core sm-crypto@0.5.7
|
|
37
|
+
npm install svelte @sveltejs/kit zod
|
|
38
|
+
npm install -D @sveltejs/vite-plugin-svelte vite
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 依存関係
|
|
36
42
|
|
|
37
43
|
- `@h-ai/iam`:A2A 用の認証・認可(任意)。A2A 機能を使う前に初期化。
|
|
38
44
|
- `@h-ai/crypto`:通信・Cookie 暗号化(任意)。暗号化を使う前に初期化。
|
|
39
45
|
|
|
40
|
-
|
|
46
|
+
### クイックスタート
|
|
41
47
|
|
|
42
|
-
|
|
48
|
+
#### プロジェクト設定
|
|
43
49
|
|
|
44
50
|
**svelte.config.js**:
|
|
45
51
|
|
|
@@ -53,7 +59,7 @@ const config = {
|
|
|
53
59
|
kit: {
|
|
54
60
|
adapter: adapter(),
|
|
55
61
|
// 双构建模式:import { createAdapter } from '@h-ai/kit/adapter'
|
|
56
|
-
// adapter: createAdapter(),
|
|
62
|
+
// adapter: await createAdapter(),
|
|
57
63
|
},
|
|
58
64
|
}
|
|
59
65
|
|
|
@@ -70,35 +76,41 @@ import { defineConfig } from 'vite'
|
|
|
70
76
|
export default defineConfig({
|
|
71
77
|
plugins: [sveltekit(), tailwindcss()],
|
|
72
78
|
optimizeDeps: { exclude: ['bits-ui'] },
|
|
73
|
-
ssr: { noExternal: [
|
|
79
|
+
ssr: { noExternal: ['@h-ai/ui', '@h-ai/kit'], external: ['@h-ai/core', 'sm-crypto'] },
|
|
74
80
|
})
|
|
75
81
|
```
|
|
76
82
|
|
|
77
|
-
|
|
83
|
+
Crypto または Kit/IAM を使う場合、アプリの本番依存に `sm-crypto@0.5.7` を含めます。CLI 生成済みのプロジェクトでは設定済みです。手動導入は `pnpm add sm-crypto@0.5.7` を実行してください。adapter-node は直接の本番依存も参照するため、external 指定だけでは不十分です。
|
|
84
|
+
|
|
85
|
+
#### リクエストパイプラインの構築
|
|
78
86
|
|
|
79
87
|
**src/hooks.server.ts**:
|
|
80
88
|
|
|
81
89
|
```typescript
|
|
82
90
|
import type { Handle } from '@sveltejs/kit'
|
|
91
|
+
import { cache } from '@h-ai/cache'
|
|
83
92
|
import { kit } from '@h-ai/kit'
|
|
84
93
|
|
|
85
94
|
const haiHandle = kit.createHandle({
|
|
86
95
|
auth: {
|
|
87
96
|
verifyToken: async (token) => {
|
|
88
|
-
|
|
97
|
+
const verified = await iam.auth.verifyToken(token)
|
|
98
|
+
return verified.success ? verified.data : null
|
|
89
99
|
},
|
|
90
100
|
loginUrl: '/auth/login',
|
|
91
101
|
protectedPaths: ['/admin/*', '/api/*'],
|
|
92
102
|
publicPaths: ['/api/auth/*', '/api/public/*'],
|
|
93
103
|
},
|
|
94
|
-
rateLimit: { windowMs: 60_000, maxRequests: 100 },
|
|
104
|
+
rateLimit: { windowMs: 60_000, maxRequests: 100, store: kit.middleware.createCacheRateLimitStore(cache, { namespace: 'app' }) },
|
|
95
105
|
logging: true,
|
|
96
106
|
})
|
|
97
107
|
|
|
98
108
|
export const handle: Handle = haiHandle
|
|
99
109
|
```
|
|
100
110
|
|
|
101
|
-
|
|
111
|
+
最初の確定時点から固定ウィンドウを開始し、後続リクエストはTTLを延長しません。storeは必須です。共有Redisの初期化成功を確認し、全ノードのnamespace/windowMs/maxRequestsを一致させます。キャッシュ障害や競合上限は503、超過は429で、get+setやローカル計数へフォールバックしません。createMemoryRateLimitStoreは開発または明示HAI_E2E/HAI_E2E_MOCKテスト用で、常駐タイマーを使わず参照解放で回収されます。1リクエスト最大64項目を遅延走査し、上限10000ウィンドウです。満杯時は有効な計数を追い出さず新キーを503にし、外部storeのライフサイクルは作成者が管理します。本番でテスト変数を設定しません。ヘッダー時刻は推定、実際の期間はRedis TTLで制御するため時計を同期します。キーは信頼済みadapterのgetClientAddressのみ使い、等価IPv6/IPv4表記を正規化します。生の転送ヘッダーを信頼せず、代理信頼はadapterで設定します。
|
|
112
|
+
|
|
113
|
+
#### API endpoint
|
|
102
114
|
|
|
103
115
|
```typescript
|
|
104
116
|
import { kit } from '@h-ai/kit'
|
|
@@ -116,7 +128,7 @@ export const POST = kit.handler(async ({ request, locals }) => {
|
|
|
116
128
|
})
|
|
117
129
|
```
|
|
118
130
|
|
|
119
|
-
|
|
131
|
+
#### ブラウザークライアント
|
|
120
132
|
|
|
121
133
|
`kit.client.create()` は SvelteKit の同一オリジン endpoint 向けです。Web・アプリ・ミニプログラムからクロスオリジンで公開 API を使う場合は `@h-ai/api-client` を使用してください。
|
|
122
134
|
|
|
@@ -137,20 +149,24 @@ const response = await apiFetch('/api/users', { method: 'GET' })
|
|
|
137
149
|
|
|
138
150
|
> CSRF Header は `apiFetch` が自動付与します。`@h-ai/kit` は独立した CSRF middleware ファクトリーを提供しません。
|
|
139
151
|
>
|
|
140
|
-
>
|
|
152
|
+
> 同一オリジンの SvelteKit endpoint は httpOnly Cookie を使い、サーバーで検証します。ブラウザーの Bearer Header が必要なら `kit.auth.setBrowserToken()` でページ内メモリーに保存してから `kit.client.create({ auth: true })` を使います。独自の `BrowserTokenStore` もメモリーまたはプラットフォームの安全なストレージを使用し、機密の認証情報を Web Storage に永続化しないでください。
|
|
141
153
|
|
|
142
|
-
|
|
154
|
+
#### 同一オリジンの通信暗号化
|
|
143
155
|
|
|
144
|
-
kit の通信暗号化は `@h-ai/crypto` に委譲します。サーバーは `crypto.transport.createServer()` で管理器を作成し、ブラウザーは `kit.client.installBrowserTransport(kitConfig, { crypto })` の 1 行で同一オリジンの fetch
|
|
156
|
+
kit の通信暗号化は `@h-ai/crypto` に委譲します。サーバーは `crypto.transport.createServer({ serverKeyRing, keyStore })` で管理器を作成し、ブラウザーは `kit.client.installBrowserTransport(kitConfig, { crypto })` の 1 行で同一オリジンの fetch ラッパーを導入します。サーバーには管理済み鍵環と共有 registry も渡します。
|
|
145
157
|
|
|
146
158
|
**src/hooks.server.ts**:
|
|
147
159
|
|
|
148
160
|
```ts
|
|
149
161
|
import { cache } from '@h-ai/cache'
|
|
150
|
-
import { createRedisTransportKeyStore, crypto } from '@h-ai/crypto'
|
|
162
|
+
import { createRedisTransportKeyStore, crypto, parseTransportServerKeyRing } from '@h-ai/crypto'
|
|
151
163
|
import { kit } from '@h-ai/kit'
|
|
152
164
|
|
|
153
165
|
await crypto.init()
|
|
166
|
+
const ring = parseTransportServerKeyRing(process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING)
|
|
167
|
+
if (!ring.success)
|
|
168
|
+
throw new Error(ring.error.message)
|
|
169
|
+
const serverKeyRing = ring.data
|
|
154
170
|
await cache.init({ type: 'redis', host: '127.0.0.1', port: 6379 })
|
|
155
171
|
|
|
156
172
|
export const handle = kit.createHandle({
|
|
@@ -162,7 +178,7 @@ export const handle = kit.createHandle({
|
|
|
162
178
|
crypto: {
|
|
163
179
|
crypto,
|
|
164
180
|
transport: {
|
|
165
|
-
|
|
181
|
+
serverKeyRing,
|
|
166
182
|
keyStore: createRedisTransportKeyStore({ cache, ttlSeconds: 3600 }),
|
|
167
183
|
},
|
|
168
184
|
},
|
|
@@ -197,13 +213,17 @@ export const { apiFetch } = kit.client.create()
|
|
|
197
213
|
|
|
198
214
|
既定の鍵交換パスは `/api/_hai/key-exchange` です。サーバーで `transport.keyExchangePath` を変更した場合、クライアントの `transport.keyExchangeUrl` も合わせて設定してください。
|
|
199
215
|
|
|
200
|
-
|
|
216
|
+
サーバーには同じ管理済み `serverKeyRing` と共有 `keyStore`(Redis または共有 DB)を明示的に渡します。クライアント公開鍵の共有だけではノード間で復号できません。暗黙のプロセス鍵・メモリー registry はなく、開発・テストでは `createInMemoryKeyStore(capacity)` を明示します。再起動後は同じ鍵環を読み込み、sticky session は不要です。
|
|
201
217
|
|
|
202
218
|
transport は既定で同一オリジンの `/api/*`、SvelteKit の `__data.json`、鍵交換 endpoint に適用します。ページ文書・静的ファイルは平文で `X-Client-Id` は不要です。アップロードなどの `multipart/form-data` はそのまま送信し、サーバーの `excludePaths` で明示的に許可してください。
|
|
203
219
|
|
|
204
|
-
|
|
220
|
+
認証済み v2 のみを受け付けます。独立したランダム SM4-CBC 鍵と HMAC-SM3 鍵を SM2 で包み、送信者署名、MAC の順に検証してから CBC を復号します。要求はクライアント秘密鍵、応答は選択されたサーバー鍵バージョンで署名します。version、direction、serverKeyId、clientId、nonce を兼ねるランダム 128 ビット requestId、issuedAt、HTTP method、pathname と元の search の SM3 pathHash、応答 status を認証します。要求の有効期限は 5 分、未来方向の時計差は最大 30 秒です。共有 registry が (clientId, requestId) を原子的に消費し、ノード間の時計差を含め issuedAt + 6 分まで保持します。保存障害では拒否します。時計同期と Redis の noeviction/永続化により有効な nonce を保持してください。業務の再試行には別の冪等キーが必要です。
|
|
221
|
+
|
|
222
|
+
空の POST も空平文の認証封装を送ります。GET/HEAD は X-Hai-Transport ヘッダー、204/304/HEAD など本文なし応答も同じヘッダーを使います。他の要求・応答は JSON 本文と X-Encrypted: true を使います。クライアントは元の要求コンテキストと HTTP status を照合し、マーカー欠落、旧形式、認証失敗では reject します。平文 endpoint は excludePaths で明示的に除外し、CORS では X-Hai-Transport を許可・公開してください。鍵交換の公開鍵認証は TLS に依存し、IAM の業務認可は別に必要です。
|
|
223
|
+
|
|
224
|
+
固定上限は serv・クライアントと `@h-ai/crypto` の `TRANSPORT_BODY_LIMITS` を共有し、鍵交換 JSON は 16 KiB、公開鍵は 1 個あたり 4 KiB、平文は 1 MiB、暗号文 JSON は 2 MiB + 32 KiB です。実際の UTF-8 バイトを元のストリームから逐次読み取り、超過・キャンセル時は停止・キャンセルして reader を解放します。clone による全量読み取りは行いません。リクエスト超過は 413、応答超過は 500、不正な公開鍵は 400 です。再構築した Request は signal を維持し、リクエスト・応答の古い Content-Length/Content-Encoding を削除します。上限は `_kit.yml`・環境変数の設定ではありません。
|
|
205
225
|
|
|
206
|
-
|
|
226
|
+
#### `_kit.yml` による共通 transport 設定
|
|
207
227
|
|
|
208
228
|
`@h-ai/kit` は `KitConfigSchema` / `resolveKitConfig()` を提供し、同じ `_kit.yml` から
|
|
209
229
|
`hooks.server.ts` とブラウザーの `kit.client.installBrowserTransport()` の両方を設定できます:
|
|
@@ -215,13 +235,10 @@ server:
|
|
|
215
235
|
|
|
216
236
|
transport:
|
|
217
237
|
keyExchangePath: /api/_hai/key-exchange
|
|
218
|
-
requireEncryption: true
|
|
219
|
-
encryptResponse: true
|
|
220
238
|
excludePaths:
|
|
221
239
|
- /api/storage
|
|
222
240
|
- /api/public
|
|
223
241
|
- /api/auth/profile/avatar
|
|
224
|
-
maxClients: 10000
|
|
225
242
|
```
|
|
226
243
|
|
|
227
244
|
```ts
|
|
@@ -236,7 +253,7 @@ export const appKitConfig = resolveKitConfig(parse(rawKitConfig) ?? {})
|
|
|
236
253
|
サーバーのみで設定を読む場合は `core.config.validate('kit', KitConfigSchema)` も利用できます。
|
|
237
254
|
ブラウザーも読む可能性があるため、`_kit.yml` は公開 server/transport パスとスイッチのみを含め、秘密鍵などを書かないでください。
|
|
238
255
|
|
|
239
|
-
|
|
256
|
+
### 設定
|
|
240
257
|
|
|
241
258
|
`kit.createHandle()` の設定項目:
|
|
242
259
|
|
|
@@ -253,16 +270,21 @@ export const appKitConfig = resolveKitConfig(parse(rawKitConfig) ?? {})
|
|
|
253
270
|
|
|
254
271
|
`crypto.encryptedCookies` を設定する場合、`crypto.cookieEncryptionKey` または `HAI_KIT_COOKIE_KEY` が必要です。鍵がなければ `createHandle()` ですぐエラーになります。保護対象 Cookie の平文・破損暗号文・復号失敗は未存在として扱い、暗号化失敗時も平文保存に切り替えません。
|
|
255
272
|
|
|
256
|
-
|
|
273
|
+
認証 Cookie は `NODE_ENV` または `HAI_CORE_ENV` が `production` の場合に `Secure` を既定で有効にします。`HAI_E2E` / `HAI_E2E_MOCK` は Cookie の安全要件を緩和しません。本番のレート制限も両方の環境変数を認識し、明示的なテストフラグだけがメモリカウンターを許可します。
|
|
274
|
+
|
|
275
|
+
### エクスポートパス
|
|
276
|
+
|
|
277
|
+
実行時メソッドは `kit` 名前空間または明示したサブパスを使用し、型は個別に公開します。browser 宣言はサーバーファクトリを公開しません。Vite 設定には `@sveltejs/kit/vite` を使い、`@h-ai/kit/vite` プラグインはありません。
|
|
257
278
|
|
|
258
279
|
- `@h-ai/kit`:主入口。`kit` 名前空間と型を提供。
|
|
259
280
|
- `@h-ai/kit/client`:ブラウザークライアント。
|
|
260
281
|
- `@h-ai/kit/adapter`:2 モードのビルド adapter(`createAdapter()`)。
|
|
261
|
-
- `@h-ai/kit/vite`:Vite プラグイン。
|
|
262
282
|
- `@h-ai/kit/a2a`:A2A 統合。
|
|
263
283
|
- `@h-ai/kit/crypto`:暗号モジュール統合。
|
|
264
284
|
|
|
265
|
-
|
|
285
|
+
### 設定による認証画面
|
|
286
|
+
|
|
287
|
+
パスワードログインの送信項目は `identifier` と `password` のみです。`AuthPanel` は `LoginForm` の `username` を `identifier` に変換します。有効期間はサーバーの `session.maxAge` / `session.refreshTokenMaxAge` で設定し、フォームにログインごとの永続セッション選択肢はありません。
|
|
266
288
|
|
|
267
289
|
規約は設定の userAgreementMarkdown/privacyPolicyMarkdown または URL を使用できます。同じ規約は Markdown 本文を優先して安全な Modal で描画します。
|
|
268
290
|
|
|
@@ -270,22 +292,32 @@ kit.auth.createApi(iam) の標準 handler と kit.auth.publicPaths を使用し
|
|
|
270
292
|
|
|
271
293
|
[完整 IAM 配置 / IAM configuration](../iam/README.ja.md)
|
|
272
294
|
|
|
273
|
-
|
|
295
|
+
`kit.auth.logout(cookies, accessToken)` は `Promise<HaiResult<void>>` を返します。Token がなければ Cookie のみ消去して成功し、Token があるのに認証操作が未設定なら失敗します。サーバー結果にかかわらず Cookie は消去しますが、呼出側は失敗を HTTP 応答や UI に伝えます。ローカル消去はサーバー失効の確認ではありません。制御フロー例外は引き続き伝播します。
|
|
296
|
+
|
|
297
|
+
### エラー処理
|
|
274
298
|
|
|
275
299
|
- API endpoint は `kit.handler()` でラップすることを推奨。
|
|
276
300
|
- `kit.validate.body` / `kit.guard.require` は SvelteKit の制御フローとして `Response` を throw。
|
|
277
301
|
- 制御フロー以外の例外は `kit.handler()` が `500 INTERNAL_ERROR` 応答に変換。
|
|
278
302
|
|
|
279
|
-
|
|
303
|
+
### CRUD fields
|
|
304
|
+
|
|
305
|
+
宣言型 CRUD 項目は UI フォームの幅を指定する `colSpan: 'auto' | 1 | 2` に対応します。`validation.message` は文字列または `() => string` を受け取り、現在の言語で表示できます。レイアウトは `CrudPage.form` で設定し、サーバーでも検証してください。
|
|
306
|
+
|
|
307
|
+
### リクエスト言語の分離
|
|
308
|
+
|
|
309
|
+
Node リクエストと SSR は `core.i18n.runWithLocale(locale, () => handleRequest())` で囲みます。SvelteKit は `kit.i18n.runWithLocale(locale, () => resolve(event))` を使えます。serv はリクエストヘッダーから自動設定します。非同期の子処理も言語を継承し、明示的な `MessageOptions.locale`、リクエスト言語、起動時の既定値の順で優先します。コンテキスト内の `setGlobalLocale` はそのリクエストだけを変更します。コンテキスト外の設定は起動時に限定し、リクエスト hook から呼びません。
|
|
310
|
+
|
|
311
|
+
ブラウザーはアプリ全体の `setGlobalLocale` / `createLocaleStore` を使います。SSR の UI store 初期化と setter は自身の状態のみ変更し、メッセージはリクエストコンテキストを参照します。`runWithLocale` は Node 入口専用です。
|
|
312
|
+
|
|
313
|
+
## 開発とテスト
|
|
280
314
|
|
|
281
315
|
```bash
|
|
282
316
|
pnpm --filter @h-ai/kit test
|
|
283
317
|
```
|
|
284
318
|
|
|
285
|
-
|
|
319
|
+
Kit に共通の init/close はありません。共有 cache・IAM・crypto はアプリが初期化と終了を管理します。型検査できる例は [JSDoc 規約(中国語)](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证) を参照してください。認証は iam.auth.verifyToken で検証し、空でない token だけでは許可しません。
|
|
286
320
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
## CRUD fields
|
|
321
|
+
## ライセンス
|
|
290
322
|
|
|
291
|
-
|
|
323
|
+
Apache-2.0
|