@h-ai/kit 0.1.0-alpha.67 → 0.1.0-alpha.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.en.md +67 -13
  2. package/README.ja.md +67 -13
  3. package/README.md +67 -13
  4. package/dist/adapter/index.d.ts +17 -2
  5. package/dist/adapter/index.js.map +1 -1
  6. package/dist/browser.d.ts +12 -6
  7. package/dist/browser.js +6 -7
  8. package/dist/browser.js.map +1 -1
  9. package/dist/{chunk-DVW7CQ3H.js → chunk-2PWGJCWX.js} +3 -3
  10. package/dist/chunk-2PWGJCWX.js.map +1 -0
  11. package/dist/chunk-OVRZIDCD.js +175 -0
  12. package/dist/chunk-OVRZIDCD.js.map +1 -0
  13. package/dist/{chunk-VOTDAVCB.js → chunk-QNLZLTVC.js} +29 -42
  14. package/dist/chunk-QNLZLTVC.js.map +1 -0
  15. package/dist/{chunk-MLSP67JF.js → chunk-UTCS6FR7.js} +67 -110
  16. package/dist/chunk-UTCS6FR7.js.map +1 -0
  17. package/dist/{chunk-NVYIUBU7.js → chunk-XC7I2L3G.js} +2 -2
  18. package/dist/chunk-XC7I2L3G.js.map +1 -0
  19. package/dist/{chunk-L7BTMAKX.js → chunk-Y2YUA3VR.js} +5 -6
  20. package/dist/chunk-Y2YUA3VR.js.map +1 -0
  21. package/dist/client/index.d.ts +47 -1
  22. package/dist/client/index.js +3 -3
  23. package/dist/client/index.js.map +1 -1
  24. package/dist/index.d.ts +251 -347
  25. package/dist/index.js +347 -171
  26. package/dist/index.js.map +1 -1
  27. package/dist/kit-a2a-types-DwcmE668.d.ts +136 -0
  28. package/dist/{index-B7s-0mZl.d.ts → kit-client-auth-B5y1g5_A.d.ts} +228 -102
  29. package/dist/kit-crypto-types-Cwj9bprV.d.ts +196 -0
  30. package/dist/{kit-i18n-D9Qqk-1k.d.ts → kit-i18n-DEzmzE5T.d.ts} +142 -29
  31. package/dist/{kit-types-ByWdbCeO.d.ts → kit-types-DQYqP1Cn.d.ts} +242 -162
  32. package/dist/modules/a2a/index.d.ts +13 -111
  33. package/dist/modules/a2a/index.js +2 -2
  34. package/dist/modules/crypto/index.d.ts +28 -195
  35. package/dist/modules/crypto/index.js +3 -3
  36. package/dist/modules/crypto/index.js.map +1 -1
  37. package/package.json +16 -11
  38. package/dist/chunk-DVW7CQ3H.js.map +0 -1
  39. package/dist/chunk-L7BTMAKX.js.map +0 -1
  40. package/dist/chunk-MLSP67JF.js.map +0 -1
  41. package/dist/chunk-NVYIUBU7.js.map +0 -1
  42. package/dist/chunk-QHYNL24G.js +0 -117
  43. package/dist/chunk-QHYNL24G.js.map +0 -1
  44. package/dist/chunk-VOTDAVCB.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,12 +1,15 @@
1
- import { defineCrud } from './chunk-L7BTMAKX.js';
2
- export { KitConfigSchema, defineCrud, resolveKitConfig, resolveKitTransportConfig, resolveOptions, resolveText } from './chunk-L7BTMAKX.js';
3
- import { createTokenStore, clearBrowserToken, setBrowserToken, logout, registerAndLogin, loginWithApiKey, loginWithLdap, loginWithOtp, login, installBrowserTransportFetch, installBrowserTransport, createKitAuthActions, createKitClient, configureAuth, getAccessToken } from './chunk-VOTDAVCB.js';
4
- import { resolveA2AConfig, handleA2ARequest } from './chunk-DVW7CQ3H.js';
5
- import { transportEncryptionMiddleware } from './chunk-MLSP67JF.js';
6
- import './chunk-NVYIUBU7.js';
7
- import { kitM, setAllModulesLocale } from './chunk-QHYNL24G.js';
8
- import { core } from '@h-ai/core';
9
- import { IamConfirmPasswordResetInputSchema, IamRequestPasswordResetInputSchema, IamSendOtpInputSchema, IamRegisterInputSchema, IamOtpLoginInputSchema, IamLdapLoginInputSchema, IamLoginInputSchema } from '@h-ai/api-contract';
1
+ import { defineCrud } from './chunk-Y2YUA3VR.js';
2
+ export { KitConfigSchema, defineCrud, resolveKitConfig, resolveKitTransportConfig, resolveOptions, resolveText } from './chunk-Y2YUA3VR.js';
3
+ import { clearBrowserToken, setBrowserToken, logout, registerAndLogin, loginWithApiKey, loginWithLdap, loginWithOtp, login, installBrowserTransportFetch, installBrowserTransport, createKitAuthActions, createKitClient, configureAuth, getAccessToken } from './chunk-QNLZLTVC.js';
4
+ import { resolveA2AConfig, handleA2ARequest } from './chunk-2PWGJCWX.js';
5
+ import { transportEncryptionMiddleware } from './chunk-UTCS6FR7.js';
6
+ import './chunk-XC7I2L3G.js';
7
+ import { kitM, setAllModulesLocale } from './chunk-OVRZIDCD.js';
8
+ import { core, HaiCommonError } from '@h-ai/core';
9
+ import { createHash } from 'crypto';
10
+ import { isIP } from 'net';
11
+ import process2 from 'process';
12
+ import { IamConfirmPasswordResetInputSchema, IamRequestPasswordResetInputSchema, IamSendOtpInputSchema, IamRegisterInputSchema, IamOtpLoginInputSchema, IamLdapLoginInputSchema, IamLoginInputSchema, FeedbackSubmitInputSchema, FeedbackListInputSchema, FeedbackIdInputSchema, FeedbackEvidenceQuerySchema, FeedbackAppendEvidenceInputSchema, FeedbackSetStatusInputSchema, FeedbackRetryInputSchema, FeedbackCompleteInputSchema, FeedbackExportInputSchema, FeedbackUploadInputSchema, FeedbackAttachmentQuerySchema, FeedbackExportQuerySchema } from '@h-ai/api-contract';
10
13
  import { z } from 'zod';
11
14
 
12
15
  // src/guards/kit-permission.ts
@@ -116,67 +119,93 @@ function loggingMiddleware(config = {}) {
116
119
  function redactObject(obj, fields) {
117
120
  return core.sanitize.sanitizeSensitiveFields(obj, { matcher: fields });
118
121
  }
122
+ function permitsMemory() {
123
+ return !core.module.requiresSharedState(process2.env);
124
+ }
125
+ function createCacheRateLimitStore(backend, options) {
126
+ if (!options.namespace || options.namespace.length > 128)
127
+ throw new TypeError("A rate limit namespace of 1-128 characters is required");
128
+ return {
129
+ scope: "shared",
130
+ async increment(key, windowMs) {
131
+ if (!backend.config || backend.config.type !== "redis" && !(backend.config.type === "memory" && options.allowMemory && permitsMemory()))
132
+ throw new Error("Rate limiting requires a shared Redis cache");
133
+ const digest = createHash("sha256").update(JSON.stringify([options.namespace, windowMs, key])).digest("hex");
134
+ const cacheKey = `hai:kit:ratelimit:{${digest}}`;
135
+ for (let attempt = 0; attempt < 64; attempt++) {
136
+ const read = await backend.kv.get(cacheKey);
137
+ if (!read.success)
138
+ throw new Error("Rate limit cache read failed");
139
+ const old = read.data ?? void 0;
140
+ if (old && (!Number.isSafeInteger(old.count) || old.count <= 0 || old.count >= Number.MAX_SAFE_INTEGER || !Number.isSafeInteger(old.resetAt)))
141
+ throw new Error("Invalid rate limit state");
142
+ const next = old ? { count: old.count + 1, resetAt: old.resetAt } : { count: 1, resetAt: Date.now() + windowMs };
143
+ const written = await backend.kv.compareAndSwap(cacheKey, old, next, old ? { keepTtl: true } : { px: windowMs });
144
+ if (!written.success)
145
+ throw new Error("Rate limit cache commit failed");
146
+ if (written.data)
147
+ return next;
148
+ }
149
+ throw new Error("Rate limit contention limit exceeded");
150
+ }
151
+ };
152
+ }
153
+ function createMemoryRateLimitStore() {
154
+ if (!permitsMemory())
155
+ throw new Error("Memory rate limiting is restricted to development and tests");
156
+ return new MemoryRateLimitStore();
157
+ }
158
+ function clientKey(address) {
159
+ if (isIP(address) !== 6)
160
+ return address;
161
+ const normalized = new URL(`http://[${address}]`).hostname.slice(1, -1);
162
+ const mapped = /^::ffff:([0-9a-f]+):([0-9a-f]+)$/.exec(normalized);
163
+ if (!mapped)
164
+ return normalized;
165
+ const value = Number.parseInt(mapped[1], 16) * 65536 + Number.parseInt(mapped[2], 16);
166
+ return [value >>> 24, value >>> 16 & 255, value >>> 8 & 255, value & 255].join(".");
167
+ }
119
168
  var MemoryRateLimitStore = class {
169
+ scope = "memory";
120
170
  store = /* @__PURE__ */ new Map();
121
- cleanupTimer = null;
171
+ cleanupCursor;
122
172
  /**
123
- * 启动定期清理过期条目
124
- *
125
- * @param intervalMs - 清理间隔(毫秒)
173
+ * 有界惰性回收;保留迭代位置,避免每次从头扫描使尾部过期项永久滞留。
174
+ * @param now - 当前请求时间,等于 resetAt 时窗口已经失效。
126
175
  */
127
- startCleanup(intervalMs) {
128
- if (this.cleanupTimer)
129
- return;
130
- this.cleanupTimer = setInterval(() => {
131
- const now = Date.now();
132
- for (const [key, entry] of this.store) {
133
- if (entry.resetAt < now) {
134
- this.store.delete(key);
135
- }
176
+ prune(now) {
177
+ this.cleanupCursor ??= this.store.entries();
178
+ for (let index = 0; index < 64; index++) {
179
+ const next = this.cleanupCursor.next();
180
+ if (next.done) {
181
+ this.cleanupCursor = void 0;
182
+ break;
136
183
  }
137
- }, intervalMs);
138
- if (this.cleanupTimer && typeof this.cleanupTimer === "object" && "unref" in this.cleanupTimer) {
139
- this.cleanupTimer.unref();
184
+ const [key, entry] = next.value;
185
+ if (entry.resetAt <= now)
186
+ this.store.delete(key);
140
187
  }
141
188
  }
142
- /**
143
- * 停止定期清理并释放定时器资源
144
- *
145
- * 调用后可再次 startCleanup 重启。进程退出 / 热重载 / 测试拆装场景必须调用以
146
- * 避免资源泄漏。
147
- */
148
- stopCleanup() {
149
- if (this.cleanupTimer) {
150
- clearInterval(this.cleanupTimer);
151
- this.cleanupTimer = null;
152
- }
153
- }
154
- get(key) {
155
- return this.store.get(key);
156
- }
157
- set(key, entry) {
158
- this.store.set(key, entry);
159
- }
160
- delete(key) {
161
- this.store.delete(key);
162
- }
163
189
  /**
164
190
  * 原子自增(内存实现为同步操作,天然原子)
165
191
  *
166
192
  * @param key - 限流键
167
193
  * @param windowMs - 窗口时长
168
- * @returns 自增后的条目
194
+ * @returns 自增后的快照,修改返回对象不会改变已保存的计数。
195
+ * @throws 新 key 超出容量或期限非法;中间件将其转换为 503。
196
+ * @example store.increment('client', 60_000)
169
197
  */
170
198
  increment(key, windowMs) {
171
199
  const now = Date.now();
172
- let entry = this.store.get(key);
173
- if (!entry || entry.resetAt < now) {
174
- entry = { count: 1, resetAt: now + windowMs };
175
- } else {
176
- entry.count++;
177
- }
200
+ if (!Number.isSafeInteger(windowMs) || windowMs <= 0 || windowMs > 2147483647)
201
+ throw new TypeError(kitM("kit_rateLimitWindowInvalid"));
202
+ this.prune(now);
203
+ const current = this.store.get(key);
204
+ if (!current && this.store.size >= 1e4)
205
+ throw new Error(kitM("kit_rateLimitMemoryCapacity"));
206
+ const entry = !current || current.resetAt <= now ? { count: 1, resetAt: now + windowMs } : { count: current.count + 1, resetAt: current.resetAt };
178
207
  this.store.set(key, entry);
179
- return entry;
208
+ return { ...entry };
180
209
  }
181
210
  };
182
211
  function rateLimitMiddleware(config) {
@@ -185,38 +214,36 @@ function rateLimitMiddleware(config) {
185
214
  maxRequests,
186
215
  keyGenerator = (event) => {
187
216
  try {
188
- return event.getClientAddress?.() ?? "unknown";
217
+ return clientKey(event.getClientAddress?.() ?? "unknown");
189
218
  } catch {
190
219
  return "unknown";
191
220
  }
192
221
  },
193
222
  onLimitReached
194
223
  } = config;
195
- const store = config.store ?? (() => {
196
- const memStore = new MemoryRateLimitStore();
197
- memStore.startCleanup(windowMs);
198
- return memStore;
199
- })();
224
+ if (!Number.isSafeInteger(windowMs) || windowMs <= 0 || windowMs > 2147483647 || !Number.isSafeInteger(maxRequests) || maxRequests <= 0)
225
+ throw new TypeError("Rate limit window/count must be positive integers (window <= 2147483647ms)");
226
+ const store = config.store;
227
+ if (!store || typeof store.increment !== "function")
228
+ throw new TypeError("An atomic rate limit store is required");
229
+ if (store.scope === "memory" && !permitsMemory())
230
+ throw new Error("Memory rate limiting is restricted to development and tests");
200
231
  return async (context, next) => {
201
232
  const { event, requestId } = context;
202
- const key = keyGenerator(event);
203
233
  const now = Date.now();
204
234
  let entry;
205
- if (store.increment) {
206
- entry = await store.increment(key, windowMs);
207
- } else {
208
- const existing = await store.get(key);
209
- if (!existing || existing.resetAt < now) {
210
- entry = { count: 1, resetAt: now + windowMs };
211
- } else {
212
- entry = { ...existing, count: existing.count + 1 };
213
- }
214
- await store.set(key, entry);
235
+ try {
236
+ entry = await store.increment(keyGenerator(event), windowMs);
237
+ if (!Number.isSafeInteger(entry.count) || entry.count <= 0 || !Number.isSafeInteger(entry.resetAt))
238
+ throw new TypeError("Invalid atomic rate limit result");
239
+ } catch {
240
+ core.logger.error("Rate limit store unavailable", { requestId });
241
+ return new Response(JSON.stringify({ success: false, error: { code: "RATE_LIMIT_UNAVAILABLE", message: kitM("kit_internalError") }, requestId }), { status: 503, headers: { "Content-Type": "application/json" } });
215
242
  }
216
243
  const remaining = Math.max(0, maxRequests - entry.count);
217
244
  const resetTime = Math.ceil(entry.resetAt / 1e3);
218
245
  if (entry.count > maxRequests) {
219
- core.logger.warn("Rate limit exceeded", { key, requestId });
246
+ core.logger.warn("Rate limit exceeded", { requestId });
220
247
  if (onLimitReached) {
221
248
  return onLimitReached(event);
222
249
  }
@@ -236,7 +263,7 @@ function rateLimitMiddleware(config) {
236
263
  "X-RateLimit-Limit": String(maxRequests),
237
264
  "X-RateLimit-Remaining": "0",
238
265
  "X-RateLimit-Reset": String(resetTime),
239
- "Retry-After": String(Math.ceil((entry.resetAt - now) / 1e3))
266
+ "Retry-After": String(Math.max(1, Math.ceil((entry.resetAt - now) / 1e3)))
240
267
  }
241
268
  }
242
269
  );
@@ -344,6 +371,7 @@ function createHandle(config = {}) {
344
371
  }
345
372
  if (rateLimitConfig) {
346
373
  builtinMiddleware.push(rateLimitMiddleware({
374
+ ...rateLimitConfig,
347
375
  windowMs: rateLimitConfig.windowMs ?? 6e4,
348
376
  maxRequests: rateLimitConfig.maxRequests ?? 100
349
377
  }));
@@ -352,99 +380,104 @@ function createHandle(config = {}) {
352
380
  const transportMiddleware = buildTransportMiddleware(cryptoConfig);
353
381
  const cookieProxyConfig = buildCookieProxyConfig(cryptoConfig);
354
382
  return async ({ event, resolve }) => {
355
- const requestId = generateId("req");
356
- const context = {
357
- event,
358
- requestId
359
- };
360
- const locals = event.locals;
361
- locals.requestId = requestId;
362
- if (cookieProxyConfig) {
363
- const proxiedCookies = createEncryptedCookieProxy(event.cookies, cookieProxyConfig);
364
- Object.defineProperty(event, "cookies", {
365
- value: proxiedCookies,
366
- writable: true,
367
- configurable: true
368
- });
369
- }
370
- const runRequest = async () => {
371
- try {
372
- if (a2aResolved) {
373
- const a2aResponse = await handleA2ARequest(event, requestId, a2aResolved);
374
- if (a2aResponse)
375
- return a2aResponse;
376
- }
377
- let session;
378
- if (authConfig) {
379
- const token = getAccessToken(event.request, event.cookies);
380
- if (token) {
381
- session = await authConfig.verifyToken(token) ?? void 0;
382
- locals.session = session;
383
- if (session) {
384
- locals.accessToken = token;
385
- }
383
+ const suppliedRequestId = event.request.headers.get("x-request-id");
384
+ const requestId = suppliedRequestId && /^[\w.:-]{1,128}$/.test(suppliedRequestId) ? suppliedRequestId : generateId("req");
385
+ return core.context.run({ requestId, userId: void 0, scopeId: void 0, jobId: void 0 }, async () => {
386
+ const context = {
387
+ event,
388
+ requestId
389
+ };
390
+ const locals = event.locals;
391
+ locals.requestId = requestId;
392
+ if (cookieProxyConfig) {
393
+ const proxiedCookies = createEncryptedCookieProxy(event.cookies, cookieProxyConfig);
394
+ Object.defineProperty(event, "cookies", {
395
+ value: proxiedCookies,
396
+ writable: true,
397
+ configurable: true
398
+ });
399
+ }
400
+ const runRequest = async () => {
401
+ try {
402
+ if (a2aResolved) {
403
+ const a2aResponse = await handleA2ARequest(event, requestId, a2aResolved);
404
+ if (a2aResponse)
405
+ return a2aResponse;
386
406
  }
387
- }
388
- context.session = session;
389
- for (const guardConfig of guards) {
390
- const guardResult = await executeGuard(guardConfig, event, session);
391
- if (!guardResult.allowed) {
392
- if (guardResult.redirect) {
393
- return new Response(null, {
394
- status: 302,
395
- headers: { Location: guardResult.redirect }
396
- });
407
+ let session;
408
+ if (authConfig) {
409
+ const token = getAccessToken(event.request, event.cookies);
410
+ if (token) {
411
+ session = await authConfig.verifyToken(token) ?? void 0;
412
+ locals.session = session;
413
+ if (session) {
414
+ locals.accessToken = token;
415
+ }
397
416
  }
398
- return new Response(
399
- JSON.stringify({
400
- success: false,
401
- error: {
402
- code: "FORBIDDEN",
403
- message: guardResult.message ?? kitM("kit_accessDenied")
404
- },
405
- requestId
406
- }),
407
- {
408
- status: guardResult.status ?? 403,
409
- headers: { "Content-Type": "application/json" }
417
+ }
418
+ context.session = session;
419
+ return await core.context.run({ userId: session?.userId }, async () => {
420
+ for (const guardConfig of guards) {
421
+ const guardResult = await executeGuard(guardConfig, event, session);
422
+ if (!guardResult.allowed) {
423
+ if (guardResult.redirect) {
424
+ return new Response(null, {
425
+ status: 302,
426
+ headers: { Location: guardResult.redirect }
427
+ });
428
+ }
429
+ return new Response(
430
+ JSON.stringify({
431
+ success: false,
432
+ error: {
433
+ code: "FORBIDDEN",
434
+ message: guardResult.message ?? kitM("kit_accessDenied")
435
+ },
436
+ requestId
437
+ }),
438
+ {
439
+ status: guardResult.status ?? 403,
440
+ headers: { "Content-Type": "application/json" }
441
+ }
442
+ );
410
443
  }
444
+ }
445
+ const response2 = await executeMiddlewareChain(
446
+ allMiddleware,
447
+ context,
448
+ () => resolve(event)
411
449
  );
450
+ return response2;
451
+ });
452
+ } catch (error2) {
453
+ if (isSvelteKitControlFlow(error2)) {
454
+ throw error2;
412
455
  }
413
- }
414
- const response2 = await executeMiddlewareChain(
415
- allMiddleware,
416
- context,
417
- () => resolve(event)
418
- );
419
- return response2;
420
- } catch (error2) {
421
- if (isSvelteKitControlFlow(error2)) {
422
- throw error2;
423
- }
424
- core.logger.error("Request failed", { requestId, error: error2 instanceof Error ? error2.message : error2 });
425
- if (onError) {
426
- return onError(error2, event);
427
- }
428
- return new Response(
429
- JSON.stringify({
430
- success: false,
431
- error: {
432
- code: "INTERNAL_ERROR",
433
- message: kitM("kit_internalError")
434
- },
435
- requestId
436
- }),
437
- {
438
- status: 500,
439
- headers: { "Content-Type": "application/json" }
456
+ core.logger.error("Request failed", { requestId, error: error2 instanceof Error ? error2.message : error2 });
457
+ if (onError) {
458
+ return onError(error2, event);
440
459
  }
441
- );
442
- }
443
- };
444
- const response = transportMiddleware ? await transportMiddleware(context, runRequest) : await runRequest();
445
- applyDefaultSecurityHeaders(response);
446
- response.headers.set("X-Request-Id", requestId);
447
- return response;
460
+ return new Response(
461
+ JSON.stringify({
462
+ success: false,
463
+ error: {
464
+ code: "INTERNAL_ERROR",
465
+ message: kitM("kit_internalError")
466
+ },
467
+ requestId
468
+ }),
469
+ {
470
+ status: 500,
471
+ headers: { "Content-Type": "application/json" }
472
+ }
473
+ );
474
+ }
475
+ };
476
+ const response = transportMiddleware ? await transportMiddleware(context, runRequest) : await runRequest();
477
+ applyDefaultSecurityHeaders(response);
478
+ response.headers.set("X-Request-Id", requestId);
479
+ return response;
480
+ });
448
481
  };
449
482
  }
450
483
  function buildAuthGuard(verifyToken, loginUrl) {
@@ -506,16 +539,15 @@ function matchPath(pathname, pattern) {
506
539
  function buildTransportMiddleware(cryptoConfig) {
507
540
  if (!cryptoConfig?.transport)
508
541
  return null;
509
- const transportOpts = typeof cryptoConfig.transport === "object" ? cryptoConfig.transport : {};
542
+ const transportOpts = cryptoConfig.transport;
510
543
  return transportEncryptionMiddleware({
511
544
  enabled: true,
512
545
  crypto: cryptoConfig.crypto,
546
+ serverKeyRing: transportOpts.serverKeyRing,
513
547
  keyExchangePath: transportOpts.keyExchangePath,
514
548
  excludePaths: transportOpts.excludePaths,
515
- encryptResponse: transportOpts.encryptResponse,
516
549
  keyStore: transportOpts.keyStore,
517
- maxClients: transportOpts.maxClients,
518
- requireEncryption: transportOpts.requireEncryption
550
+ maxPlaintextBytes: transportOpts.maxPlaintextBytes
519
551
  });
520
552
  }
521
553
  function buildCookieProxyConfig(cryptoConfig) {
@@ -540,10 +572,32 @@ function sequence(...handles) {
540
572
  return filtered[0];
541
573
  }
542
574
  return async ({ event, resolve }) => {
543
- return filtered.reduceRight(
544
- (next, handle) => (event2) => handle({ event: event2, resolve: next }),
545
- resolve
546
- )(event);
575
+ function applyHandle(index, currentEvent, options) {
576
+ if (index === filtered.length)
577
+ return resolve(currentEvent, options);
578
+ return filtered[index]({
579
+ event: currentEvent,
580
+ resolve: async (nextEvent, nextOptions) => {
581
+ const merged = {
582
+ ...options,
583
+ ...nextOptions,
584
+ preload: options?.preload ?? nextOptions?.preload,
585
+ filterSerializedResponseHeaders: options?.filterSerializedResponseHeaders ?? nextOptions?.filterSerializedResponseHeaders
586
+ };
587
+ if (options?.transformPageChunk || nextOptions?.transformPageChunk) {
588
+ merged.transformPageChunk = async ({ html, done }) => {
589
+ if (nextOptions?.transformPageChunk)
590
+ html = await nextOptions.transformPageChunk({ html, done }) ?? "";
591
+ if (options?.transformPageChunk)
592
+ html = await options.transformPageChunk({ html, done }) ?? "";
593
+ return html;
594
+ };
595
+ }
596
+ return applyHandle(index + 1, nextEvent, merged);
597
+ }
598
+ });
599
+ }
600
+ return applyHandle(0, event);
547
601
  };
548
602
  }
549
603
 
@@ -767,9 +821,130 @@ function createAuthApi(iam) {
767
821
  })
768
822
  };
769
823
  }
824
+ function createFeedbackHandler(options) {
825
+ const basePath = (options.basePath ?? "/api/feedback").replace(/\/+$/, "");
826
+ return async (event) => {
827
+ let actor;
828
+ try {
829
+ actor = await options.resolveContext(event);
830
+ } catch {
831
+ return fromError({ ...HaiCommonError.INTERNAL_ERROR, message: kitM("kit_internalError") });
832
+ }
833
+ if (!actor?.scopeId || !actor.userId)
834
+ return fromError({ ...HaiCommonError.UNAUTHORIZED, message: kitM("kit_unauthorized") });
835
+ const suffix = event.url.pathname.startsWith(`${basePath}/`) ? event.url.pathname.slice(basePath.length + 1) : "";
836
+ let parts;
837
+ try {
838
+ parts = suffix.split("/").map(decodeURIComponent);
839
+ } catch {
840
+ return invalid(400);
841
+ }
842
+ if (parts[0] !== "reports" || parts.length > 4)
843
+ return fromError({ ...HaiCommonError.NOT_FOUND, message: core.i18n.coreM("core_errorNotFound") });
844
+ let body = {};
845
+ if (!["GET", "HEAD"].includes(event.request.method)) {
846
+ if (event.request.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() !== "application/json")
847
+ return invalid(415);
848
+ try {
849
+ body = await readBoundedJson(event.request);
850
+ } catch (error2) {
851
+ return invalid(error2 instanceof RangeError ? 413 : 400);
852
+ }
853
+ }
854
+ if (!body || typeof body !== "object" || Array.isArray(body))
855
+ return invalid(400);
856
+ const bodyRecord = body;
857
+ if (parts[1] && Object.hasOwn(bodyRecord, "id") && bodyRecord.id !== parts[1])
858
+ return invalid(400);
859
+ const input = { ...Object.fromEntries(event.url.searchParams), ...bodyRecord, ...parts[1] ? { id: parts[1] } : {} };
860
+ const method = event.request.method;
861
+ const service = options.feedback;
862
+ async function execute(schema, operation) {
863
+ const parsed = schema.safeParse(input);
864
+ if (!parsed.success)
865
+ return invalid(400);
866
+ try {
867
+ const result = await core.context.run(actor, () => operation(parsed.data));
868
+ return result.success ? ok(result.data) : fromError(result.error);
869
+ } catch {
870
+ return fromError({ ...HaiCommonError.INTERNAL_ERROR, message: kitM("kit_internalError") });
871
+ }
872
+ }
873
+ if (parts.length === 1) {
874
+ if (method === "POST")
875
+ return execute(FeedbackSubmitInputSchema, (value) => service.submit(value, actor));
876
+ if (method === "GET")
877
+ return execute(FeedbackListInputSchema, (value) => service.list(value, actor));
878
+ }
879
+ if (parts.length === 2 && method === "GET")
880
+ return execute(FeedbackIdInputSchema, (value) => service.get(value.id, actor));
881
+ if (parts.length === 3) {
882
+ if (parts[2] === "evidence" && method === "GET")
883
+ return execute(FeedbackEvidenceQuerySchema, (value) => service.getEvidence(value, actor));
884
+ if (parts[2] === "evidence" && method === "POST")
885
+ return execute(FeedbackAppendEvidenceInputSchema, (value) => service.appendEvidence(value, actor));
886
+ if (parts[2] === "status" && method === "PATCH")
887
+ return execute(FeedbackSetStatusInputSchema, (value) => service.setStatus(value, actor));
888
+ if (parts[2] === "retry" && method === "POST")
889
+ return execute(FeedbackRetryInputSchema, (value) => service.retry(value, actor));
890
+ if (parts[2] === "complete" && method === "POST")
891
+ return execute(FeedbackCompleteInputSchema, (value) => service.completeCollection(value, actor));
892
+ if (parts[2] === "exports" && method === "POST")
893
+ return execute(FeedbackExportInputSchema, (value) => service.export(value, actor));
894
+ }
895
+ if (parts.length === 4 && parts[2] === "attachments") {
896
+ Object.assign(input, { attachmentId: parts[3] });
897
+ if (method === "POST")
898
+ return execute(FeedbackUploadInputSchema, (value) => service.uploadAttachment(value, actor));
899
+ if (method === "GET")
900
+ return execute(FeedbackAttachmentQuerySchema, (value) => service.getAttachment(value, actor));
901
+ }
902
+ if (parts.length === 4 && parts[2] === "exports" && method === "GET") {
903
+ Object.assign(input, { jobId: parts[3] });
904
+ return execute(FeedbackExportQuerySchema, (value) => service.getExport(value, actor));
905
+ }
906
+ return fromError({ ...HaiCommonError.NOT_FOUND, message: core.i18n.coreM("core_errorNotFound") });
907
+ };
908
+ }
909
+ function invalid(status) {
910
+ return fromError({ ...HaiCommonError.INVALID_REQUEST, httpStatus: status, message: kitM("kit_validationFailed") });
911
+ }
912
+ async function readBoundedJson(request) {
913
+ const reader = request.body?.getReader();
914
+ if (!reader)
915
+ return {};
916
+ const decoder = new TextDecoder();
917
+ let length = 0;
918
+ let text = "";
919
+ try {
920
+ while (true) {
921
+ const chunk = await reader.read();
922
+ if (chunk.done)
923
+ break;
924
+ length += chunk.value.byteLength;
925
+ if (length > 8 * 1024 * 1024) {
926
+ await reader.cancel();
927
+ throw new RangeError("Feedback request is too large");
928
+ }
929
+ text += decoder.decode(chunk.value, { stream: true });
930
+ }
931
+ text += decoder.decode();
932
+ return text ? JSON.parse(text) : {};
933
+ } finally {
934
+ reader.releaseLock();
935
+ }
936
+ }
770
937
 
771
938
  // src/kit-main.ts
772
939
  var kit = {
940
+ /** 创建反馈的受控 catch-all HTTP handler,身份范围由宿主解析。 */
941
+ createFeedbackHandler,
942
+ /** 原子限流工厂;共享store生命周期由应用管理。 */
943
+ middleware: {
944
+ rateLimit: rateLimitMiddleware,
945
+ createCacheRateLimitStore,
946
+ createMemoryRateLimitStore
947
+ },
773
948
  // ─── Handle Hook ───
774
949
  /** 创建 SvelteKit Handle Hook(含 auth / logging / rateLimit 内置配置) */
775
950
  createHandle,
@@ -863,9 +1038,8 @@ var kit = {
863
1038
  /** 写入浏览器端 Access Token(客户端 login/register 用) */
864
1039
  setBrowserToken,
865
1040
  /** 清除浏览器端 Access Token(客户端 logout 用) */
866
- clearBrowserToken,
1041
+ clearBrowserToken
867
1042
  /** 创建浏览器端 Token 存储器(自定义 key 时使用) */
868
- createTokenStore
869
1043
  },
870
1044
  // ─── CRUD ───
871
1045
  crud: {
@@ -875,7 +1049,9 @@ var kit = {
875
1049
  // ─── i18n ───
876
1050
  i18n: {
877
1051
  /** 统一设置所有 hai 模块的默认语言 */
878
- setLocale: setAllModulesLocale
1052
+ setLocale: setAllModulesLocale,
1053
+ /** 请求/SSR 语言隔离;异步处理使用同一请求上下文。 */
1054
+ runWithLocale: core.i18n.runWithLocale
879
1055
  }
880
1056
  };
881
1057