@h-ai/kit 0.1.0-alpha5

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/dist/index.js ADDED
@@ -0,0 +1,886 @@
1
+ import { defineCrud } from './chunk-FRYJMRTP.js';
2
+ export { defineCrud, resolveOptions, resolveText } from './chunk-FRYJMRTP.js';
3
+ import { createHandleFetch, createTokenStore, clearBrowserToken, setBrowserToken, logout, registerAndLogin, loginWithApiKey, loginWithLdap, loginWithOtp, login, createKitClient, configureAuth, getAccessToken } from './chunk-PHS7ZD5L.js';
4
+ import { resolveA2AConfig, handleA2ARequest } from './chunk-475WECWK.js';
5
+ import { transportEncryptionMiddleware } from './chunk-AD6S473Y.js';
6
+ import { kitM, setAllModulesLocale } from './chunk-6TMSXG7J.js';
7
+ import { core } from '@h-ai/core';
8
+ import { z } from 'zod';
9
+
10
+ // src/guards/kit-permission.ts
11
+ function matchPermission(required, userPermissions) {
12
+ for (const userPerm of userPermissions) {
13
+ if (userPerm === required) {
14
+ return true;
15
+ }
16
+ if (userPerm === "*") {
17
+ return true;
18
+ }
19
+ if (userPerm.endsWith(":*")) {
20
+ const prefix = userPerm.slice(0, -1);
21
+ if (required.startsWith(prefix)) {
22
+ return true;
23
+ }
24
+ }
25
+ }
26
+ return false;
27
+ }
28
+ function hasPermission(session, permission) {
29
+ if (!session)
30
+ return false;
31
+ return matchPermission(permission, session.permissions ?? []);
32
+ }
33
+ function assertPermission(session, permission) {
34
+ if (!session) {
35
+ return new Response(
36
+ JSON.stringify({ success: false, error: kitM("kit_unauthorized") }),
37
+ { status: 401, headers: { "Content-Type": "application/json" } }
38
+ );
39
+ }
40
+ if (!matchPermission(permission, session.permissions ?? [])) {
41
+ return new Response(
42
+ JSON.stringify({ success: false, error: kitM("kit_forbidden", { params: { permission } }) }),
43
+ { status: 403, headers: { "Content-Type": "application/json" } }
44
+ );
45
+ }
46
+ return void 0;
47
+ }
48
+ function requirePermission(session, permission) {
49
+ const denied = assertPermission(session, permission);
50
+ if (denied) {
51
+ throw denied;
52
+ }
53
+ }
54
+
55
+ // src/kit-response.ts
56
+ function ok(data, requestId) {
57
+ const response = {
58
+ success: true,
59
+ data,
60
+ requestId
61
+ };
62
+ return new Response(JSON.stringify(response), {
63
+ status: 200,
64
+ headers: { "Content-Type": "application/json" }
65
+ });
66
+ }
67
+ function created(data, requestId) {
68
+ const response = {
69
+ success: true,
70
+ data,
71
+ requestId
72
+ };
73
+ return new Response(JSON.stringify(response), {
74
+ status: 201,
75
+ headers: { "Content-Type": "application/json" }
76
+ });
77
+ }
78
+ function noContent() {
79
+ return new Response(null, { status: 204 });
80
+ }
81
+ function error(code, message, status = 400, requestId, details) {
82
+ const response = {
83
+ success: false,
84
+ error: {
85
+ code,
86
+ message,
87
+ details
88
+ },
89
+ requestId
90
+ };
91
+ return new Response(JSON.stringify(response), {
92
+ status,
93
+ headers: { "Content-Type": "application/json" }
94
+ });
95
+ }
96
+ function badRequest(message, requestId, details) {
97
+ return error("BAD_REQUEST", message, 400, requestId, details);
98
+ }
99
+ function unauthorized(message, requestId) {
100
+ return error("UNAUTHORIZED", message ?? kitM("kit_authRequired"), 401, requestId);
101
+ }
102
+ function forbidden(message, requestId) {
103
+ return error("FORBIDDEN", message ?? kitM("kit_accessDenied"), 403, requestId);
104
+ }
105
+ function notFound(message, requestId) {
106
+ return error("NOT_FOUND", message ?? kitM("kit_resourceNotFound"), 404, requestId);
107
+ }
108
+ function conflict(message, requestId) {
109
+ return error("CONFLICT", message, 409, requestId);
110
+ }
111
+ function validationError(errors, requestId) {
112
+ return error("VALIDATION_ERROR", kitM("kit_validationFailed"), 422, requestId, { errors });
113
+ }
114
+ function internalError(message, requestId) {
115
+ return error("INTERNAL_ERROR", message ?? kitM("kit_internalError"), 500, requestId);
116
+ }
117
+ function redirect(url, status = 302) {
118
+ return new Response(null, {
119
+ status,
120
+ headers: { Location: url }
121
+ });
122
+ }
123
+ function fromResult(result, httpStatusMap, requestId) {
124
+ if (result.success) {
125
+ return ok(result.data, requestId);
126
+ }
127
+ const { code, message } = result.error;
128
+ const status = httpStatusMap?.[code] ?? 400;
129
+ return error(String(code), message, status, requestId);
130
+ }
131
+ function fromError(haiError, requestId) {
132
+ return error(String(haiError.code), haiError.message, haiError.httpStatus ?? 400, requestId);
133
+ }
134
+
135
+ // src/kit-utils.ts
136
+ function isResponseLike(value) {
137
+ if (value instanceof Response)
138
+ return true;
139
+ if (typeof value === "object" && value !== null && "status" in value && "headers" in value && typeof value.text === "function" && typeof value.json === "function") {
140
+ return true;
141
+ }
142
+ return false;
143
+ }
144
+ function isSvelteKitControlFlow(value) {
145
+ if (isResponseLike(value))
146
+ return false;
147
+ if (typeof value === "object" && value !== null && "status" in value) {
148
+ const status = value.status;
149
+ return typeof status === "number";
150
+ }
151
+ return false;
152
+ }
153
+
154
+ // src/kit-handler.ts
155
+ function handler(fn) {
156
+ return async (event) => {
157
+ try {
158
+ return await fn(event);
159
+ } catch (error2) {
160
+ if (isResponseLike(error2)) {
161
+ return error2;
162
+ }
163
+ if (isSvelteKitControlFlow(error2)) {
164
+ throw error2;
165
+ }
166
+ const normalizedError = error2 instanceof Error ? {
167
+ name: error2.name,
168
+ message: error2.message,
169
+ stack: error2.stack,
170
+ cause: error2.cause
171
+ } : error2;
172
+ core.logger.error("Request handler error", {
173
+ error: normalizedError,
174
+ path: event.url.pathname,
175
+ method: event.request.method
176
+ });
177
+ return internalError();
178
+ }
179
+ };
180
+ }
181
+
182
+ // src/kit-contract.ts
183
+ function defineEndpoint(def) {
184
+ return def;
185
+ }
186
+ function fromContract(endpoint, fn) {
187
+ return handler(async (event) => {
188
+ let raw;
189
+ if (endpoint.method === "GET") {
190
+ raw = Object.fromEntries(event.url.searchParams);
191
+ } else {
192
+ try {
193
+ raw = await event.request.json();
194
+ } catch {
195
+ raw = {};
196
+ }
197
+ }
198
+ const parsed = endpoint.input.safeParse(raw);
199
+ if (!parsed.success) {
200
+ const errors = parsed.error.issues.map((issue) => ({
201
+ field: issue.path.join(".") || "_",
202
+ message: issue.message
203
+ }));
204
+ return validationError(errors);
205
+ }
206
+ const result = await fn(parsed.data, event);
207
+ return ok(result);
208
+ });
209
+ }
210
+ function loggingMiddleware(config = {}) {
211
+ const { logBody = false, logResponse = false, redactFields = ["password", "token", "secret", "authorization", "apikey", "api_key", "creditcard", "credit_card"] } = config;
212
+ return async (context, next) => {
213
+ const { event, requestId } = context;
214
+ const startTime = Date.now();
215
+ let clientIp = "unknown";
216
+ try {
217
+ clientIp = event.getClientAddress?.() ?? "unknown";
218
+ } catch {
219
+ }
220
+ const logData = {
221
+ requestId,
222
+ method: event.request.method,
223
+ path: event.url.pathname,
224
+ query: Object.fromEntries(event.url.searchParams),
225
+ userAgent: event.request.headers.get("user-agent"),
226
+ ip: clientIp
227
+ };
228
+ if (logBody && event.request.method !== "GET") {
229
+ try {
230
+ const clonedRequest = event.request.clone();
231
+ const body = await clonedRequest.json();
232
+ logData.body = redactObject(body, redactFields);
233
+ } catch {
234
+ }
235
+ }
236
+ core.logger.trace("Incoming request", { ...logData });
237
+ const response = await next();
238
+ const duration = Date.now() - startTime;
239
+ const responseLogData = {
240
+ requestId,
241
+ status: response.status,
242
+ duration
243
+ };
244
+ if (logResponse) {
245
+ responseLogData.headers = Object.fromEntries(response.headers);
246
+ }
247
+ core.logger.trace("Request completed", { ...responseLogData });
248
+ return response;
249
+ };
250
+ }
251
+ function redactObject(obj, fields) {
252
+ if (typeof obj !== "object" || obj === null) {
253
+ return obj;
254
+ }
255
+ if (Array.isArray(obj)) {
256
+ return obj.map((item) => redactObject(item, fields));
257
+ }
258
+ if (obj instanceof Date || obj instanceof RegExp) {
259
+ return obj;
260
+ }
261
+ const result = {};
262
+ for (const [key, value] of Object.entries(obj)) {
263
+ if (fields.includes(key.toLowerCase())) {
264
+ result[key] = "[REDACTED]";
265
+ } else if (typeof value === "object" && value !== null) {
266
+ result[key] = redactObject(value, fields);
267
+ } else {
268
+ result[key] = value;
269
+ }
270
+ }
271
+ return result;
272
+ }
273
+ var MemoryRateLimitStore = class {
274
+ store = /* @__PURE__ */ new Map();
275
+ cleanupTimer = null;
276
+ /**
277
+ * 启动定期清理过期条目
278
+ *
279
+ * @param intervalMs - 清理间隔(毫秒)
280
+ */
281
+ startCleanup(intervalMs) {
282
+ if (this.cleanupTimer)
283
+ return;
284
+ this.cleanupTimer = setInterval(() => {
285
+ const now = Date.now();
286
+ for (const [key, entry] of this.store) {
287
+ if (entry.resetAt < now) {
288
+ this.store.delete(key);
289
+ }
290
+ }
291
+ }, intervalMs);
292
+ if (this.cleanupTimer && typeof this.cleanupTimer === "object" && "unref" in this.cleanupTimer) {
293
+ this.cleanupTimer.unref();
294
+ }
295
+ }
296
+ get(key) {
297
+ return this.store.get(key);
298
+ }
299
+ set(key, entry) {
300
+ this.store.set(key, entry);
301
+ }
302
+ delete(key) {
303
+ this.store.delete(key);
304
+ }
305
+ /**
306
+ * 原子自增(内存实现为同步操作,天然原子)
307
+ *
308
+ * @param key - 限流键
309
+ * @param windowMs - 窗口时长
310
+ * @returns 自增后的条目
311
+ */
312
+ increment(key, windowMs) {
313
+ const now = Date.now();
314
+ let entry = this.store.get(key);
315
+ if (!entry || entry.resetAt < now) {
316
+ entry = { count: 1, resetAt: now + windowMs };
317
+ } else {
318
+ entry.count++;
319
+ }
320
+ this.store.set(key, entry);
321
+ return entry;
322
+ }
323
+ };
324
+ function rateLimitMiddleware(config) {
325
+ const {
326
+ windowMs,
327
+ maxRequests,
328
+ keyGenerator = (event) => {
329
+ try {
330
+ return event.getClientAddress?.() ?? "unknown";
331
+ } catch {
332
+ return "unknown";
333
+ }
334
+ },
335
+ onLimitReached
336
+ } = config;
337
+ const store = config.store ?? (() => {
338
+ const memStore = new MemoryRateLimitStore();
339
+ memStore.startCleanup(windowMs);
340
+ return memStore;
341
+ })();
342
+ return async (context, next) => {
343
+ const { event, requestId } = context;
344
+ const key = keyGenerator(event);
345
+ const now = Date.now();
346
+ let entry;
347
+ if (store.increment) {
348
+ entry = await store.increment(key, windowMs);
349
+ } else {
350
+ const existing = await store.get(key);
351
+ if (!existing || existing.resetAt < now) {
352
+ entry = { count: 1, resetAt: now + windowMs };
353
+ } else {
354
+ entry = { ...existing, count: existing.count + 1 };
355
+ }
356
+ await store.set(key, entry);
357
+ }
358
+ const remaining = Math.max(0, maxRequests - entry.count);
359
+ const resetTime = Math.ceil(entry.resetAt / 1e3);
360
+ if (entry.count > maxRequests) {
361
+ core.logger.warn("Rate limit exceeded", { key, requestId });
362
+ if (onLimitReached) {
363
+ return onLimitReached(event);
364
+ }
365
+ return new Response(
366
+ JSON.stringify({
367
+ success: false,
368
+ error: {
369
+ code: "RATE_LIMIT_EXCEEDED",
370
+ message: kitM("kit_rateLimitExceeded")
371
+ },
372
+ requestId
373
+ }),
374
+ {
375
+ status: 429,
376
+ headers: {
377
+ "Content-Type": "application/json",
378
+ "X-RateLimit-Limit": String(maxRequests),
379
+ "X-RateLimit-Remaining": "0",
380
+ "X-RateLimit-Reset": String(resetTime),
381
+ "Retry-After": String(Math.ceil((entry.resetAt - now) / 1e3))
382
+ }
383
+ }
384
+ );
385
+ }
386
+ const response = await next();
387
+ response.headers.set("X-RateLimit-Limit", String(maxRequests));
388
+ response.headers.set("X-RateLimit-Remaining", String(remaining));
389
+ response.headers.set("X-RateLimit-Reset", String(resetTime));
390
+ return response;
391
+ };
392
+ }
393
+ var ENCRYPTED_PREFIX = "enc:";
394
+ function createEncryptedCookieProxy(cookies, config) {
395
+ const { names, symmetric, encryptionKey } = config;
396
+ const logger = core.logger.child({ module: "kit", scope: "cookie-proxy" });
397
+ return new Proxy(cookies, {
398
+ get(target, prop, receiver) {
399
+ if (prop === "get") {
400
+ return (name, opts) => {
401
+ const raw = target.get(name, opts);
402
+ if (!raw || !names.has(name))
403
+ return raw;
404
+ if (!raw.startsWith(ENCRYPTED_PREFIX))
405
+ return raw;
406
+ try {
407
+ const ciphertext = raw.slice(ENCRYPTED_PREFIX.length);
408
+ const separatorIndex = ciphertext.indexOf(":");
409
+ if (separatorIndex === -1)
410
+ return raw;
411
+ const iv = ciphertext.slice(0, separatorIndex);
412
+ const encrypted = ciphertext.slice(separatorIndex + 1);
413
+ const result = symmetric.decryptWithIV(encrypted, encryptionKey, iv);
414
+ if (!result.success || typeof result.data !== "string") {
415
+ logger.warn("Cookie decryption failed, returning raw value", { name });
416
+ return raw;
417
+ }
418
+ return result.data;
419
+ } catch {
420
+ logger.warn("Cookie decryption error, returning raw value", { name });
421
+ return raw;
422
+ }
423
+ };
424
+ }
425
+ if (prop === "set") {
426
+ return (name, value, opts) => {
427
+ if (names.has(name)) {
428
+ try {
429
+ const result = symmetric.encryptWithIV(value, encryptionKey);
430
+ if (result.success && result.data) {
431
+ value = `${ENCRYPTED_PREFIX}${result.data.iv}:${result.data.ciphertext}`;
432
+ } else {
433
+ logger.warn("Cookie encryption failed, storing plaintext", { name });
434
+ }
435
+ } catch {
436
+ logger.warn("Cookie encryption error, storing plaintext", { name });
437
+ }
438
+ }
439
+ return target.set(name, value, opts);
440
+ };
441
+ }
442
+ return Reflect.get(target, prop, receiver);
443
+ }
444
+ });
445
+ }
446
+
447
+ // src/hooks/kit-handle.ts
448
+ function generateId(prefix) {
449
+ const timestamp = Date.now().toString(36);
450
+ const random = crypto.randomUUID().replace(/-/g, "").substring(0, 8);
451
+ return `${prefix}_${timestamp}${random}`;
452
+ }
453
+ function createHandle(config = {}) {
454
+ const {
455
+ auth: authConfig,
456
+ rateLimit: rateLimitConfig,
457
+ logging: loggingConfig = true,
458
+ crypto: cryptoConfig,
459
+ onError,
460
+ guards: customGuards = [],
461
+ middleware: customMiddleware = [],
462
+ a2a: a2aInput
463
+ } = config;
464
+ const a2aResolved = resolveA2AConfig(a2aInput);
465
+ if (authConfig?.cookieName || authConfig?.operations) {
466
+ configureAuth({ cookieName: authConfig.cookieName, operations: authConfig.operations });
467
+ }
468
+ const guards = [];
469
+ if (authConfig?.protectedPaths?.length) {
470
+ guards.push({
471
+ guard: buildAuthGuard(authConfig.verifyToken, authConfig.loginUrl),
472
+ paths: authConfig.protectedPaths,
473
+ exclude: authConfig.publicPaths
474
+ });
475
+ }
476
+ guards.push(...customGuards);
477
+ const builtinMiddleware = [];
478
+ if (loggingConfig) {
479
+ const logOpts = typeof loggingConfig === "object" ? loggingConfig : { logBody: false };
480
+ builtinMiddleware.push(loggingMiddleware(logOpts));
481
+ }
482
+ if (rateLimitConfig) {
483
+ builtinMiddleware.push(rateLimitMiddleware({
484
+ windowMs: rateLimitConfig.windowMs ?? 6e4,
485
+ maxRequests: rateLimitConfig.maxRequests ?? 100
486
+ }));
487
+ }
488
+ const allMiddleware = [...builtinMiddleware, ...customMiddleware];
489
+ const finalMiddleware = buildTransportMiddleware(allMiddleware, cryptoConfig);
490
+ const cookieProxyConfig = buildCookieProxyConfig(cryptoConfig);
491
+ return async ({ event, resolve }) => {
492
+ const requestId = generateId("req");
493
+ const locals = event.locals;
494
+ locals.requestId = requestId;
495
+ if (cookieProxyConfig) {
496
+ const proxiedCookies = createEncryptedCookieProxy(event.cookies, cookieProxyConfig);
497
+ Object.defineProperty(event, "cookies", {
498
+ value: proxiedCookies,
499
+ writable: true,
500
+ configurable: true
501
+ });
502
+ }
503
+ try {
504
+ if (a2aResolved) {
505
+ const a2aResponse = await handleA2ARequest(event, requestId, a2aResolved);
506
+ if (a2aResponse)
507
+ return a2aResponse;
508
+ }
509
+ let session;
510
+ if (authConfig) {
511
+ const token = getAccessToken(event.request, event.cookies);
512
+ if (token) {
513
+ session = await authConfig.verifyToken(token) ?? void 0;
514
+ locals.session = session;
515
+ if (session) {
516
+ locals.accessToken = token;
517
+ }
518
+ }
519
+ }
520
+ for (const guardConfig of guards) {
521
+ const guardResult = await executeGuard(guardConfig, event, session);
522
+ if (!guardResult.allowed) {
523
+ if (guardResult.redirect) {
524
+ return new Response(null, {
525
+ status: 302,
526
+ headers: { Location: guardResult.redirect }
527
+ });
528
+ }
529
+ return new Response(
530
+ JSON.stringify({
531
+ success: false,
532
+ error: {
533
+ code: "FORBIDDEN",
534
+ message: guardResult.message ?? kitM("kit_accessDenied")
535
+ },
536
+ requestId
537
+ }),
538
+ {
539
+ status: guardResult.status ?? 403,
540
+ headers: { "Content-Type": "application/json" }
541
+ }
542
+ );
543
+ }
544
+ }
545
+ const context = {
546
+ event,
547
+ session,
548
+ requestId
549
+ };
550
+ const response = await executeMiddlewareChain(
551
+ finalMiddleware,
552
+ context,
553
+ () => resolve(event)
554
+ );
555
+ response.headers.set("X-Request-Id", requestId);
556
+ return response;
557
+ } catch (error2) {
558
+ if (isSvelteKitControlFlow(error2)) {
559
+ throw error2;
560
+ }
561
+ core.logger.error("Request failed", { requestId, error: error2 instanceof Error ? error2.message : error2 });
562
+ if (onError) {
563
+ return onError(error2, event);
564
+ }
565
+ return new Response(
566
+ JSON.stringify({
567
+ success: false,
568
+ error: {
569
+ code: "INTERNAL_ERROR",
570
+ message: kitM("kit_internalError")
571
+ },
572
+ requestId
573
+ }),
574
+ {
575
+ status: 500,
576
+ headers: { "Content-Type": "application/json" }
577
+ }
578
+ );
579
+ }
580
+ };
581
+ }
582
+ function buildAuthGuard(verifyToken, loginUrl) {
583
+ return (event, session) => {
584
+ if (session) {
585
+ return { allowed: true };
586
+ }
587
+ const isApiRoute = event.url.pathname.startsWith("/api/");
588
+ if (isApiRoute) {
589
+ return {
590
+ allowed: false,
591
+ message: kitM("kit_authRequired"),
592
+ status: 401
593
+ };
594
+ }
595
+ if (loginUrl) {
596
+ const returnUrl = encodeURIComponent(event.url.pathname + event.url.search);
597
+ return {
598
+ allowed: false,
599
+ redirect: `${loginUrl}?returnUrl=${returnUrl}`
600
+ };
601
+ }
602
+ return {
603
+ allowed: false,
604
+ message: kitM("kit_authRequired"),
605
+ status: 401
606
+ };
607
+ };
608
+ }
609
+ async function executeGuard(config, event, session) {
610
+ const { guard, paths, exclude } = config;
611
+ const pathname = event.url.pathname;
612
+ if (exclude?.some((pattern) => matchPath(pathname, pattern))) {
613
+ return { allowed: true };
614
+ }
615
+ if (paths && !paths.some((pattern) => matchPath(pathname, pattern))) {
616
+ return { allowed: true };
617
+ }
618
+ return guard(event, session);
619
+ }
620
+ async function executeMiddlewareChain(middleware, context, final) {
621
+ if (middleware.length === 0) {
622
+ return final();
623
+ }
624
+ const [current, ...rest] = middleware;
625
+ return current(context, () => executeMiddlewareChain(rest, context, final));
626
+ }
627
+ function matchPath(pathname, pattern) {
628
+ if (pattern.endsWith("/*")) {
629
+ const base = pattern.slice(0, -2);
630
+ return pathname === base || pathname.startsWith(`${base}/`);
631
+ }
632
+ if (pattern.endsWith("/**")) {
633
+ const base = pattern.slice(0, -3);
634
+ return pathname === base || pathname.startsWith(`${base}/`);
635
+ }
636
+ return pathname === pattern;
637
+ }
638
+ function buildTransportMiddleware(userMiddleware, cryptoConfig) {
639
+ if (!cryptoConfig?.transport)
640
+ return userMiddleware;
641
+ const transportOpts = typeof cryptoConfig.transport === "object" ? cryptoConfig.transport : {};
642
+ const transportMw = transportEncryptionMiddleware({
643
+ enabled: true,
644
+ crypto: cryptoConfig.crypto,
645
+ keyExchangePath: transportOpts.keyExchangePath,
646
+ excludePaths: transportOpts.excludePaths,
647
+ encryptResponse: transportOpts.encryptResponse,
648
+ requireEncryption: transportOpts.requireEncryption
649
+ });
650
+ return [transportMw, ...userMiddleware];
651
+ }
652
+ function buildCookieProxyConfig(cryptoConfig) {
653
+ if (!cryptoConfig?.encryptedCookies?.length)
654
+ return null;
655
+ const key = cryptoConfig.cookieEncryptionKey ?? (typeof process !== "undefined" ? process.env?.HAI_KIT_COOKIE_KEY : void 0);
656
+ if (!key) {
657
+ core.logger.warn("Cookie encryption configured but no key provided (set crypto.cookieEncryptionKey or HAI_KIT_COOKIE_KEY env)");
658
+ return null;
659
+ }
660
+ return {
661
+ names: new Set(cryptoConfig.encryptedCookies),
662
+ symmetric: cryptoConfig.crypto.symmetric,
663
+ encryptionKey: key
664
+ };
665
+ }
666
+ function sequence(...handles) {
667
+ const filtered = handles.filter(Boolean);
668
+ if (filtered.length === 0) {
669
+ return ({ event, resolve }) => resolve(event);
670
+ }
671
+ if (filtered.length === 1) {
672
+ return filtered[0];
673
+ }
674
+ return async ({ event, resolve }) => {
675
+ return filtered.reduceRight(
676
+ (next, handle) => (event2) => handle({ event: event2, resolve: next }),
677
+ resolve
678
+ )(event);
679
+ };
680
+ }
681
+ function extractZodIssues(error2) {
682
+ const zodError = error2;
683
+ return zodError.issues ?? zodError.errors ?? [];
684
+ }
685
+ function zodIssuesToFormErrors(issues) {
686
+ return issues.map((issue) => ({
687
+ field: issue.path.join("."),
688
+ message: issue.message
689
+ }));
690
+ }
691
+ function createValidationResult(error2) {
692
+ return {
693
+ valid: false,
694
+ errors: zodIssuesToFormErrors(extractZodIssues(error2))
695
+ };
696
+ }
697
+ async function validateForm(request, schema) {
698
+ try {
699
+ const contentType = request.headers.get("content-type") ?? "";
700
+ let data;
701
+ if (contentType.includes("application/json")) {
702
+ data = await request.json();
703
+ } else if (contentType.includes("application/x-www-form-urlencoded") || contentType.includes("multipart/form-data")) {
704
+ const formData = await request.formData();
705
+ data = Object.fromEntries(formData);
706
+ } else {
707
+ return {
708
+ valid: false,
709
+ errors: [{ field: "_", message: kitM("kit_unsupportedContentType") }]
710
+ };
711
+ }
712
+ const result = schema.safeParse(data);
713
+ if (result.success) {
714
+ return { valid: true, data: result.data, errors: [] };
715
+ }
716
+ return createValidationResult(result.error);
717
+ } catch {
718
+ return {
719
+ valid: false,
720
+ errors: [{ field: "_", message: kitM("kit_parseBodyFailed") }]
721
+ };
722
+ }
723
+ }
724
+ function validateQuery(url, schema) {
725
+ const data = Object.fromEntries(url.searchParams);
726
+ const result = schema.safeParse(data);
727
+ if (result.success) {
728
+ return { valid: true, data: result.data, errors: [] };
729
+ }
730
+ return createValidationResult(result.error);
731
+ }
732
+ function validateParams(params, schema) {
733
+ const result = schema.safeParse(params);
734
+ if (result.success) {
735
+ return { valid: true, data: result.data, errors: [] };
736
+ }
737
+ return createValidationResult(result.error);
738
+ }
739
+ async function validateFormOrFail(request, schema) {
740
+ const result = await validateForm(request, schema);
741
+ if (!result.valid || !result.data) {
742
+ throw badRequest(
743
+ result.errors[0]?.message ?? kitM("kit_validationFailed"),
744
+ void 0,
745
+ { errors: result.errors }
746
+ );
747
+ }
748
+ return result.data;
749
+ }
750
+ function validateQueryOrFail(url, schema) {
751
+ const result = validateQuery(url, schema);
752
+ if (!result.valid || !result.data) {
753
+ throw badRequest(
754
+ result.errors[0]?.message ?? kitM("kit_validationFailed"),
755
+ void 0,
756
+ { errors: result.errors }
757
+ );
758
+ }
759
+ return result.data;
760
+ }
761
+ function validateParamsOrFail(params, schema) {
762
+ const result = validateParams(params, schema);
763
+ if (!result.valid || !result.data) {
764
+ throw badRequest(
765
+ result.errors[0]?.message ?? kitM("kit_validationFailed"),
766
+ void 0,
767
+ { errors: result.errors }
768
+ );
769
+ }
770
+ return result.data;
771
+ }
772
+ var IdParamSchema = z.object({
773
+ id: z.string().min(1, kitM("kit_idRequired"))
774
+ });
775
+ var MAX_PAGE_SIZE = 100;
776
+ var PaginationQuerySchema = z.object({
777
+ page: z.coerce.number().int().min(1).default(1),
778
+ pageSize: z.coerce.number().int().min(1).max(MAX_PAGE_SIZE).default(20),
779
+ search: z.string().optional()
780
+ });
781
+
782
+ // src/kit-main.ts
783
+ var kit = {
784
+ // ─── Handle Hook ───
785
+ /** 创建 SvelteKit Handle Hook(含 auth / logging / rateLimit 内置配置) */
786
+ createHandle,
787
+ /** 组合多个 Handle */
788
+ sequence,
789
+ /** API Handler 包装器(自动错误边界) */
790
+ handler,
791
+ /** 基于 API 契约创建类型安全的路由 handler */
792
+ fromContract,
793
+ // ─── 路由守卫 ───
794
+ guard: {
795
+ /** 要求权限,不满足时 throw Response(SvelteKit 控制流) */
796
+ require: requirePermission,
797
+ /** 检查会话是否具有指定权限(布尔) */
798
+ check: hasPermission
799
+ },
800
+ // ─── API 响应 ───
801
+ response: {
802
+ /** 200 成功 */
803
+ ok,
804
+ /** 201 创建成功 */
805
+ created,
806
+ /** 204 无内容 */
807
+ noContent,
808
+ /** 自定义错误响应 */
809
+ error,
810
+ /** 400 BadRequest */
811
+ badRequest,
812
+ /** 401 Unauthorized */
813
+ unauthorized,
814
+ /** 403 Forbidden */
815
+ forbidden,
816
+ /** 404 NotFound */
817
+ notFound,
818
+ /** 409 Conflict */
819
+ conflict,
820
+ /** 422 验证错误 */
821
+ validationError,
822
+ /** 500 InternalError */
823
+ internalError,
824
+ /** 重定向 */
825
+ redirect,
826
+ /** 将 HaiResult<T> 转为标准 API Response(支持 httpStatusMap) */
827
+ fromResult,
828
+ /** 将模块错误码映射为标准 HTTP Response */
829
+ fromError
830
+ },
831
+ // ─── 验证 ───
832
+ validate: {
833
+ /** 验证请求体(JSON/表单),失败 throw Response */
834
+ body: validateFormOrFail,
835
+ /** 验证查询参数,失败 throw Response */
836
+ query: validateQueryOrFail,
837
+ /** 验证路径参数,失败 throw Response */
838
+ params: validateParamsOrFail,
839
+ /** 路径参数 id Schema({id: string}) */
840
+ IdParamSchema,
841
+ /** 通用分页查询 Schema(page / pageSize / search) */
842
+ PaginationQuerySchema
843
+ },
844
+ // ─── 客户端工具 ───
845
+ client: {
846
+ /** 创建统一客户端(CSRF + 传输加密透明合并) */
847
+ create: createKitClient
848
+ },
849
+ // ─── 认证工具 ───
850
+ auth: {
851
+ /** 服务端登录(密码):内部调用 iam.auth.login + 自动写入 Token Cookie */
852
+ login,
853
+ /** 服务端登录(OTP 验证码):内部调用 iam.auth.loginWithOtp + 自动写入 Token Cookie */
854
+ loginWithOtp,
855
+ /** 服务端登录(LDAP):内部调用 iam.auth.loginWithLdap + 自动写入 Token Cookie */
856
+ loginWithLdap,
857
+ /** 服务端登录(API Key):内部调用 iam.auth.loginWithApiKey + 自动写入 Token Cookie */
858
+ loginWithApiKey,
859
+ /** 服务端注册并登录:内部调用 iam.auth.registerAndLogin + 自动写入 Token Cookie */
860
+ registerAndLogin,
861
+ /** 服务端登出:内部调用 iam.auth.logout + 清除 Token Cookie */
862
+ logout,
863
+ /** 写入浏览器端 Access Token(客户端 login/register 用) */
864
+ setBrowserToken,
865
+ /** 清除浏览器端 Access Token(客户端 logout 用) */
866
+ clearBrowserToken,
867
+ /** 创建浏览器端 Token 存储器(自定义 key 时使用) */
868
+ createTokenStore,
869
+ /** 创建浏览器端同源请求自动附加 Authorization 的 HandleFetch */
870
+ createHandleFetch
871
+ },
872
+ // ─── CRUD ───
873
+ crud: {
874
+ /** 定义 CRUD 资源(声明式配置 → 操作对象) */
875
+ define: defineCrud
876
+ },
877
+ // ─── i18n ───
878
+ i18n: {
879
+ /** 统一设置所有 hai 模块的默认语言 */
880
+ setLocale: setAllModulesLocale
881
+ }
882
+ };
883
+
884
+ export { IdParamSchema, PaginationQuerySchema, defineEndpoint, kit, matchPermission };
885
+ //# sourceMappingURL=index.js.map
886
+ //# sourceMappingURL=index.js.map