vafast 0.6.2 → 0.7.1

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 (90) hide show
  1. package/README.md +75 -20
  2. package/dist/{base-server-D7ny0Kwm.d.mts → base-server-CeJ8LTBk.d.mts} +2 -2
  3. package/dist/{base64url-DNUGwekK.d.mts → base64url-CAmasWF0.d.mts} +1 -1
  4. package/dist/{component-route-Bxb-08X7.d.mts → component-route-CGbmQm5P.d.mts} +2 -2
  5. package/dist/{component-server-DQ3nZWFc.d.mts → component-server-CKcXIvMg.d.mts} +4 -4
  6. package/dist/{contract-CuruqP6h.d.mts → contract-C-xQoZ6r.d.mts} +1 -1
  7. package/dist/{contract-Cwmmo-Nn.mjs → contract-vSyKiRwz.mjs} +2 -2
  8. package/dist/contract-vSyKiRwz.mjs.map +1 -0
  9. package/dist/{defineRoute-DyPa9FHa.d.mts → defineRoute-B9VdaoQA.d.mts} +49 -5
  10. package/dist/defineRoute.d.mts +2 -2
  11. package/dist/defineRoute.mjs +107 -74
  12. package/dist/defineRoute.mjs.map +1 -1
  13. package/dist/{dependency-manager-DIN9X0Gj.d.mts → dependency-manager-mqzLAocb.d.mts} +1 -1
  14. package/dist/{formats-DDDSFWP0.d.mts → formats-Ca7ASaYH.d.mts} +1 -1
  15. package/dist/{go-await-DPtVBug4.d.mts → go-await-Dz1CRSTT.d.mts} +1 -1
  16. package/dist/{html-renderer-DhQxRuyi.d.mts → html-renderer-Up52eIS6.d.mts} +1 -1
  17. package/dist/{index-Cek4HyXL.d.mts → index-CRU-u6NT.d.mts} +5 -5
  18. package/dist/index.d.mts +21 -21
  19. package/dist/index.mjs +6 -7
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/middleware/component-router.d.mts +1 -1
  22. package/dist/{middleware-DXssDt1F.d.mts → middleware-BTg4GbjC.d.mts} +2 -2
  23. package/dist/middleware.d.mts +1 -1
  24. package/dist/monitoring/index.d.mts +3 -3
  25. package/dist/monitoring/native-monitor.d.mts +3 -3
  26. package/dist/node-server/index.d.mts +1 -1
  27. package/dist/node-server/index.mjs +3 -3
  28. package/dist/node-server/request.mjs +1 -1
  29. package/dist/node-server/response.mjs +1 -1
  30. package/dist/node-server/serve.d.mts +1 -1
  31. package/dist/node-server/serve.mjs +2 -2
  32. package/dist/{parsers-8hIAx0OV.d.mts → parsers-BAQtDA1q.d.mts} +1 -1
  33. package/dist/{radix-tree-CccjvyTP.mjs → radix-tree-dyn3qDFX.mjs} +35 -15
  34. package/dist/radix-tree-dyn3qDFX.mjs.map +1 -0
  35. package/dist/{request-D202oxO9.mjs → request-DEWtcK8t.mjs} +1 -1
  36. package/dist/{request-D202oxO9.mjs.map → request-DEWtcK8t.mjs.map} +1 -1
  37. package/dist/{response-DNdvtn-K.mjs → response-BlHLmKys.mjs} +1 -1
  38. package/dist/{response-DNdvtn-K.mjs.map → response-BlHLmKys.mjs.map} +1 -1
  39. package/dist/{response-F-VxN-cB.d.mts → response-lI0YZoia.d.mts} +2 -2
  40. package/dist/{route-registry-CAX54I8j.d.mts → route-registry-C6h13Mks.d.mts} +3 -3
  41. package/dist/router/index.mjs +1 -1
  42. package/dist/router/radix-tree.d.mts +12 -1
  43. package/dist/router/radix-tree.mjs +1 -1
  44. package/dist/{serve-BNSr7-5v.mjs → serve-DVlDG92Y.mjs} +3 -3
  45. package/dist/{serve-BNSr7-5v.mjs.map → serve-DVlDG92Y.mjs.map} +1 -1
  46. package/dist/{serve-Ds8Px1wD.d.mts → serve-Dw-GHkc3.d.mts} +1 -1
  47. package/dist/serve.d.mts +1 -1
  48. package/dist/serve.mjs +2 -2
  49. package/dist/server/base-server.d.mts +1 -1
  50. package/dist/server/component-server.d.mts +1 -1
  51. package/dist/server/index.d.mts +5 -5
  52. package/dist/server/index.mjs +2 -2
  53. package/dist/server/server-factory.d.mts +3 -3
  54. package/dist/server/server-factory.mjs +2 -2
  55. package/dist/server/server.d.mts +2 -2
  56. package/dist/server/server.mjs +1 -1
  57. package/dist/{server-DWndB63Z.mjs → server-B4YDcNJy.mjs} +2 -2
  58. package/dist/{server-DWndB63Z.mjs.map → server-B4YDcNJy.mjs.map} +1 -1
  59. package/dist/{server-CAhwnEPW.mjs → server-BNpY3NH6.mjs} +2 -2
  60. package/dist/{server-CAhwnEPW.mjs.map → server-BNpY3NH6.mjs.map} +1 -1
  61. package/dist/{server-3uMr8Ujo.d.mts → server-CQ-_WgQN.d.mts} +4 -4
  62. package/dist/sse-D77CKcsH.d.mts +45 -0
  63. package/dist/types/component-route.d.mts +1 -1
  64. package/dist/types/index.d.mts +2 -2
  65. package/dist/types/types.d.mts +1 -1
  66. package/dist/{types-D4pCpFZ_.d.mts → types-aczawQFE.d.mts} +1 -1
  67. package/dist/utils/base64url.d.mts +1 -1
  68. package/dist/utils/contract.d.mts +1 -1
  69. package/dist/utils/contract.mjs +1 -1
  70. package/dist/utils/dependency-manager.d.mts +1 -1
  71. package/dist/utils/formats.d.mts +1 -1
  72. package/dist/utils/go-await.d.mts +1 -1
  73. package/dist/utils/html-renderer.d.mts +1 -1
  74. package/dist/utils/index.d.mts +13 -13
  75. package/dist/utils/index.mjs +2 -3
  76. package/dist/utils/parsers.d.mts +1 -1
  77. package/dist/utils/response.d.mts +1 -1
  78. package/dist/utils/route-registry.d.mts +2 -2
  79. package/dist/utils/sse.d.mts +2 -3
  80. package/dist/utils/sse.mjs +1 -5
  81. package/dist/utils/validators/validators.d.mts +1 -1
  82. package/dist/{validators-BFC6S_fr.d.mts → validators-D5KJCSZr.d.mts} +1 -1
  83. package/package.json +1 -1
  84. package/dist/contract-Cwmmo-Nn.mjs.map +0 -1
  85. package/dist/radix-tree-CccjvyTP.mjs.map +0 -1
  86. package/dist/sse-C0_ODr4_.mjs +0 -111
  87. package/dist/sse-C0_ODr4_.mjs.map +0 -1
  88. package/dist/sse-CjIH9WjQ.d.mts +0 -81
  89. /package/dist/{component-route-BiUHBq7a.mjs → component-route-DdKFowzp.mjs} +0 -0
  90. /package/dist/{index-Dflz2i1X.d.mts → index-D4DUvwPf.d.mts} +0 -0
package/README.md CHANGED
@@ -474,13 +474,74 @@ export default { fetch: server.fetch };" > index.ts && bun index.ts
474
474
  ## 🎯 核心功能
475
475
 
476
476
  - ⚡ **JIT 编译验证器** - Schema 验证器编译缓存,避免重复编译
477
- - 🌲 **Radix Tree 路由** - O(k) 时间复杂度的高效路由匹配
477
+ - 🌲 **Radix Tree 路由** - O(k) 时间复杂度的高效路由匹配(详见[路由规则](#路由匹配规则))
478
478
  - 🎯 **快速请求解析** - 优化的 Query/Cookie 解析,比标准方法快 2x
479
479
  - 🔒 **端到端类型安全** - 完整的 TypeScript 类型推断
480
480
  - 🧩 **灵活中间件系统** - 可组合的中间件架构
481
481
  - 📡 **SSE 流式响应** - 内置 Server-Sent Events 支持,适用于 AI 聊天、进度更新等场景
482
482
  - 📦 **零配置** - 开箱即用,无需复杂配置
483
483
 
484
+ ### 路由匹配规则
485
+
486
+ Vafast 使用 Radix Tree 实现高效路由匹配,支持以下特性:
487
+
488
+ **1. 路由类型**
489
+
490
+ ```typescript
491
+ // 静态路由
492
+ '/users'
493
+ '/api/v1/health'
494
+
495
+ // 动态参数 (:param)
496
+ '/users/:id'
497
+ '/posts/:postId/comments/:commentId'
498
+
499
+ // 通配符 (* 或 *name)
500
+ '/files/*' // 匿名通配符,params['*']
501
+ '/static/*filepath' // 命名通配符,params['filepath']
502
+ ```
503
+
504
+ **2. 优先级规则(与 Hono/Fastify 一致)**
505
+
506
+ ```
507
+ 静态路由 > 动态参数 > 通配符
508
+ ```
509
+
510
+ ```typescript
511
+ // 注册顺序不影响优先级
512
+ router.register('GET', '/users/:id', dynamicHandler);
513
+ router.register('GET', '/users/admin', staticHandler); // 后注册
514
+
515
+ // 匹配结果
516
+ GET /users/admin → staticHandler ✅ 静态优先
517
+ GET /users/123 → dynamicHandler
518
+ ```
519
+
520
+ **3. 同一位置支持不同参数名**
521
+
522
+ 不同路由在同一位置可以使用不同的参数名,每个路由独立返回其定义的参数名:
523
+
524
+ ```typescript
525
+ // 同一位置(/sessions/ 后)使用不同参数名
526
+ router.register('PUT', '/sessions/:id', updateHandler);
527
+ router.register('GET', '/sessions/:sessionId/messages', messagesHandler);
528
+
529
+ // 每个路由返回各自定义的参数名
530
+ PUT /sessions/123 → params = { id: '123' }
531
+ GET /sessions/456/messages → params = { sessionId: '456' }
532
+
533
+ // CRUD 场景完全支持
534
+ router.register('GET', '/users/:userId', getHandler);
535
+ router.register('PUT', '/users/:id', updateHandler);
536
+ router.register('DELETE', '/users/:uid', deleteHandler);
537
+
538
+ GET /users/1 → { userId: '1' }
539
+ PUT /users/2 → { id: '2' }
540
+ DELETE /users/3 → { uid: '3' }
541
+ ```
542
+
543
+ > 💡 参数名冲突时会输出警告(建议保持一致),但不影响功能。
544
+
484
545
  ### 返回值与错误处理
485
546
 
486
547
  Vafast 提供简洁、对称的响应 API:
@@ -528,33 +589,27 @@ defineRoute({
528
589
 
529
590
  ### SSE 流式响应
530
591
 
531
- 内置 `createSSEHandler` 支持 Server-Sent Events,适用于 AI 聊天、进度更新等场景:
592
+ 通过 `sse: true` 显式声明 SSE 端点,适用于 AI 聊天、进度更新等场景:
532
593
 
533
594
  ```typescript
534
- import { createSSEHandler, defineRoute, defineRoutes, Type } from 'vafast'
595
+ import { defineRoute, defineRoutes, Type } from 'vafast'
535
596
 
536
- // 创建 SSE handler
537
- const progressHandler = createSSEHandler(
538
- { params: Type.Object({ taskId: Type.String() }) },
539
- async function* ({ params }) {
540
- yield { event: 'start', data: { taskId: params.taskId } }
541
-
542
- for (let i = 0; i <= 100; i += 10) {
543
- yield { data: { progress: i } }
544
- await new Promise(r => setTimeout(r, 100))
545
- }
546
-
547
- yield { event: 'complete', data: { message: 'Done!' } }
548
- }
549
- )
550
-
551
- // 在 defineRoute 中使用
552
597
  const routes = defineRoutes([
553
598
  defineRoute({
554
599
  method: 'GET',
555
600
  path: '/tasks/:taskId/progress',
601
+ sse: true, // 显式声明 SSE 端点
556
602
  schema: { params: Type.Object({ taskId: Type.String() }) },
557
- handler: progressHandler,
603
+ handler: async function* ({ params }) {
604
+ yield { event: 'start', data: { taskId: params.taskId } }
605
+
606
+ for (let i = 0; i <= 100; i += 10) {
607
+ yield { data: { progress: i } }
608
+ await new Promise(r => setTimeout(r, 100))
609
+ }
610
+
611
+ yield { event: 'complete', data: { message: 'Done!' } }
612
+ },
558
613
  }),
559
614
  ])
560
615
  ```
@@ -1,4 +1,4 @@
1
- import { r as Middleware } from "./types-D4pCpFZ_.mjs";
1
+ import { r as Middleware } from "./types-aczawQFE.mjs";
2
2
 
3
3
  //#region src/server/base-server.d.ts
4
4
 
@@ -36,4 +36,4 @@ declare abstract class BaseServer {
36
36
  }
37
37
  //#endregion
38
38
  export { BaseServer as t };
39
- //# sourceMappingURL=base-server-D7ny0Kwm.d.mts.map
39
+ //# sourceMappingURL=base-server-CeJ8LTBk.d.mts.map
@@ -3,4 +3,4 @@ declare function base64urlEncode(str: string): string;
3
3
  declare function base64urlDecode(str: string): string;
4
4
  //#endregion
5
5
  export { base64urlEncode as n, base64urlDecode as t };
6
- //# sourceMappingURL=base64url-DNUGwekK.d.mts.map
6
+ //# sourceMappingURL=base64url-CAmasWF0.d.mts.map
@@ -1,4 +1,4 @@
1
- import { r as Middleware } from "./types-D4pCpFZ_.mjs";
1
+ import { r as Middleware } from "./types-aczawQFE.mjs";
2
2
 
3
3
  //#region src/types/component-route.d.ts
4
4
 
@@ -28,4 +28,4 @@ interface FlattenedComponentRoute extends ComponentRoute {
28
28
  }
29
29
  //#endregion
30
30
  export { FlattenedComponentRoute as n, NestedComponentRoute as r, ComponentRoute as t };
31
- //# sourceMappingURL=component-route-Bxb-08X7.d.mts.map
31
+ //# sourceMappingURL=component-route-CGbmQm5P.d.mts.map
@@ -1,6 +1,6 @@
1
- import { r as NestedComponentRoute, t as ComponentRoute } from "./component-route-Bxb-08X7.mjs";
2
- import { t as BaseServer } from "./base-server-D7ny0Kwm.mjs";
3
- import { t as DependencyManager } from "./dependency-manager-DIN9X0Gj.mjs";
1
+ import { r as NestedComponentRoute, t as ComponentRoute } from "./component-route-CGbmQm5P.mjs";
2
+ import { t as BaseServer } from "./base-server-CeJ8LTBk.mjs";
3
+ import { t as DependencyManager } from "./dependency-manager-mqzLAocb.mjs";
4
4
 
5
5
  //#region src/server/component-server.d.ts
6
6
 
@@ -35,4 +35,4 @@ declare class ComponentServer extends BaseServer {
35
35
  }
36
36
  //#endregion
37
37
  export { ComponentServer as t };
38
- //# sourceMappingURL=component-server-DQ3nZWFc.d.mts.map
38
+ //# sourceMappingURL=component-server-CKcXIvMg.d.mts.map
@@ -71,4 +71,4 @@ declare function generateAITools(routes: readonly unknown[]): AITool[];
71
71
  declare function getApiSpec(routesOrContext?: readonly unknown[] | Record<string, unknown>): ApiSpec;
72
72
  //#endregion
73
73
  export { generateAITools as n, getApiSpec as r, ApiSpec as t };
74
- //# sourceMappingURL=contract-CuruqP6h.d.mts.map
74
+ //# sourceMappingURL=contract-C-xQoZ6r.d.mts.map
@@ -16,7 +16,7 @@ function generateSpec(routes) {
16
16
  };
17
17
  if (r.name) spec.name = r.name;
18
18
  if (r.description) spec.description = r.description;
19
- if (r.sse === true || r.handler?.__sse?.__brand === "SSE") spec.sse = true;
19
+ if (r.sse === true) spec.sse = true;
20
20
  if (r.schema) {
21
21
  const schema = r.schema;
22
22
  if (schema.body || schema.query || schema.params || schema.headers || schema.cookies || schema.response) {
@@ -113,4 +113,4 @@ function getRoutesFromRegistry() {
113
113
 
114
114
  //#endregion
115
115
  export { getApiSpec as n, generateAITools as t };
116
- //# sourceMappingURL=contract-Cwmmo-Nn.mjs.map
116
+ //# sourceMappingURL=contract-vSyKiRwz.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-vSyKiRwz.mjs","names":[],"sources":["../src/utils/contract.ts"],"sourcesContent":["/**\n * API Spec 生成器\n *\n * 用于生成 API 规范,支持:\n * - 跨仓库类型同步\n * - AI 工具函数生成\n * - Swagger/OpenAPI 文档生成\n *\n * @example\n * ```typescript\n * import { getApiSpec } from 'vafast'\n *\n * // 方式 1:直接作为 handler(推荐,自动从全局 Registry 获取)\n * const allRoutes = [\n * ...routes,\n * { method: 'GET', path: '/api-spec', handler: getApiSpec }\n * ]\n *\n * // 方式 2:显式传参(灵活场景,如只暴露公开 API)\n * { handler: () => getApiSpec(publicRoutes) }\n * ```\n */\n\nimport type { TSchema } from '@sinclair/typebox'\nimport type { RouteSchema } from '../defineRoute'\nimport { getRouteRegistry } from './route-registry'\n\n/** 路由规范信息 */\ninterface RouteSpec {\n method: string\n path: string\n name?: string\n description?: string\n /** 是否为 SSE 端点(Server-Sent Events) */\n sse?: boolean\n schema?: {\n body?: TSchema\n query?: TSchema\n params?: TSchema\n headers?: TSchema\n cookies?: TSchema\n response?: TSchema\n }\n}\n\n/** API 规范 */\nexport interface ApiSpec {\n version: string\n generatedAt: string\n routes: RouteSpec[]\n}\n\n/** AI 工具函数格式 */\ninterface AITool {\n name: string\n description?: string\n parameters?: TSchema\n}\n\n/**\n * 从路由数组生成 API 规范\n */\nfunction generateSpec(routes: readonly unknown[]): ApiSpec {\n const routeSpecs: RouteSpec[] = []\n\n for (const route of routes) {\n const r = route as {\n method?: string\n path?: string\n name?: string\n description?: string\n schema?: RouteSchema\n sse?: boolean\n }\n\n if (!r.method || !r.path) continue\n\n // 跳过 spec 接口本身\n if (r.path === '/api-spec' || r.path === '/__spec__') continue\n\n const spec: RouteSpec = {\n method: r.method,\n path: r.path,\n }\n\n // 直接从路由获取 name 和 description\n if (r.name) spec.name = r.name\n if (r.description) spec.description = r.description\n\n // SSE 标记\n if (r.sse === true) {\n spec.sse = true\n }\n\n // 直接从路由获取 schema\n if (r.schema) {\n const schema = r.schema\n if (schema.body || schema.query || schema.params || schema.headers || schema.cookies || schema.response) {\n spec.schema = {}\n if (schema.body) spec.schema.body = schema.body\n if (schema.query) spec.schema.query = schema.query\n if (schema.params) spec.schema.params = schema.params\n if (schema.headers) spec.schema.headers = schema.headers\n if (schema.cookies) spec.schema.cookies = schema.cookies\n if (schema.response) spec.schema.response = schema.response\n }\n }\n\n routeSpecs.push(spec)\n }\n\n return {\n version: '1.0.0',\n generatedAt: new Date().toISOString(),\n routes: routeSpecs,\n }\n}\n\n/**\n * 从路由数组生成 AI 工具函数\n *\n * 可直接用于 OpenAI Function Calling / Claude Tools\n *\n * @example\n * ```typescript\n * const tools = generateAITools(routes)\n * // [{ name: 'get_users', description: '获取用户列表', parameters: {...} }]\n * ```\n */\nexport function generateAITools(routes: readonly unknown[]): AITool[] {\n const tools: AITool[] = []\n\n for (const route of routes) {\n const r = route as {\n method?: string\n path?: string\n name?: string\n description?: string\n schema?: RouteSchema\n }\n\n if (!r.method || !r.path) continue\n if (r.path === '/api-spec' || r.path === '/__spec__') continue\n\n // 使用 name 或从 path 生成\n const name = r.name || pathToToolName(r.method, r.path)\n\n const tool: AITool = { name }\n if (r.description) tool.description = r.description\n\n // GET 用 query,其他用 body\n if (r.schema) {\n if (r.method === 'GET' && r.schema.query) {\n tool.parameters = r.schema.query\n } else if (r.schema.body) {\n tool.parameters = r.schema.body\n }\n }\n\n tools.push(tool)\n }\n\n return tools\n}\n\n/** 从路径生成工具名 */\nfunction pathToToolName(method: string, path: string): string {\n const segments = path.split('/').filter(Boolean)\n const cleanSegments = segments\n .map(s => s.startsWith(':') ? '' : s)\n .filter(Boolean)\n\n const prefix = method.toLowerCase()\n const suffix = cleanSegments.join('_')\n\n return suffix ? `${prefix}_${suffix}` : prefix\n}\n\n/**\n * 获取 API 规范\n *\n * 支持多种调用方式:\n * 1. 直接作为 handler:自动从全局 Registry 获取(推荐)\n * 2. 无参调用:同上,用于 CLI/测试\n * 3. 有参调用:显式传递路由数组(灵活场景)\n *\n * @param routesOrContext - 可选,路由数组或 handler context。不传则从全局 Registry 获取\n * @returns ApiSpec 对象\n *\n * @example\n * ```typescript\n * import { getApiSpec } from 'vafast'\n *\n * // 方式 1:直接作为 handler(推荐,最简洁)\n * { method: 'GET', path: '/api-spec', handler: getApiSpec }\n *\n * // 方式 2:显式传参(只暴露公开 API)\n * { handler: () => getApiSpec(publicRoutes) }\n *\n * // 方式 3:本地使用(CLI、测试)\n * const spec = getApiSpec()\n * ```\n */\nexport function getApiSpec(routesOrContext?: readonly unknown[] | Record<string, unknown>): ApiSpec {\n // 智能检测:是路由数组还是 handler context\n // 路由数组:Array && 第一个元素有 method 属性\n const isRoutesArray = Array.isArray(routesOrContext) &&\n routesOrContext.length > 0 &&\n typeof (routesOrContext[0] as Record<string, unknown>)?.method === 'string'\n\n const routeList = isRoutesArray\n ? routesOrContext\n : getRoutesFromRegistry()\n\n return generateSpec(routeList)\n}\n\n/**\n * 从全局 Registry 获取路由列表\n * 包含 schema 信息(Registry 存储完整路由)\n */\nfunction getRoutesFromRegistry(): readonly unknown[] {\n try {\n const registry = getRouteRegistry()\n return registry.getAll()\n } catch {\n // Registry 未初始化时返回空数组\n return []\n }\n}\n"],"mappings":";;;;;;AA8DA,SAAS,aAAa,QAAqC;CACzD,MAAM,aAA0B,EAAE;AAElC,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,IAAI;AASV,MAAI,CAAC,EAAE,UAAU,CAAC,EAAE,KAAM;AAG1B,MAAI,EAAE,SAAS,eAAe,EAAE,SAAS,YAAa;EAEtD,MAAM,OAAkB;GACtB,QAAQ,EAAE;GACV,MAAM,EAAE;GACT;AAGD,MAAI,EAAE,KAAM,MAAK,OAAO,EAAE;AAC1B,MAAI,EAAE,YAAa,MAAK,cAAc,EAAE;AAGxC,MAAI,EAAE,QAAQ,KACZ,MAAK,MAAM;AAIb,MAAI,EAAE,QAAQ;GACZ,MAAM,SAAS,EAAE;AACjB,OAAI,OAAO,QAAQ,OAAO,SAAS,OAAO,UAAU,OAAO,WAAW,OAAO,WAAW,OAAO,UAAU;AACvG,SAAK,SAAS,EAAE;AAChB,QAAI,OAAO,KAAM,MAAK,OAAO,OAAO,OAAO;AAC3C,QAAI,OAAO,MAAO,MAAK,OAAO,QAAQ,OAAO;AAC7C,QAAI,OAAO,OAAQ,MAAK,OAAO,SAAS,OAAO;AAC/C,QAAI,OAAO,QAAS,MAAK,OAAO,UAAU,OAAO;AACjD,QAAI,OAAO,QAAS,MAAK,OAAO,UAAU,OAAO;AACjD,QAAI,OAAO,SAAU,MAAK,OAAO,WAAW,OAAO;;;AAIvD,aAAW,KAAK,KAAK;;AAGvB,QAAO;EACL,SAAS;EACT,8BAAa,IAAI,MAAM,EAAC,aAAa;EACrC,QAAQ;EACT;;;;;;;;;;;;;AAcH,SAAgB,gBAAgB,QAAsC;CACpE,MAAM,QAAkB,EAAE;AAE1B,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,IAAI;AAQV,MAAI,CAAC,EAAE,UAAU,CAAC,EAAE,KAAM;AAC1B,MAAI,EAAE,SAAS,eAAe,EAAE,SAAS,YAAa;EAKtD,MAAM,OAAe,EAAE,MAFV,EAAE,QAAQ,eAAe,EAAE,QAAQ,EAAE,KAAK,EAE1B;AAC7B,MAAI,EAAE,YAAa,MAAK,cAAc,EAAE;AAGxC,MAAI,EAAE,QACJ;OAAI,EAAE,WAAW,SAAS,EAAE,OAAO,MACjC,MAAK,aAAa,EAAE,OAAO;YAClB,EAAE,OAAO,KAClB,MAAK,aAAa,EAAE,OAAO;;AAI/B,QAAM,KAAK,KAAK;;AAGlB,QAAO;;;AAIT,SAAS,eAAe,QAAgB,MAAsB;CAE5D,MAAM,gBADW,KAAK,MAAM,IAAI,CAAC,OAAO,QAAQ,CAE7C,KAAI,MAAK,EAAE,WAAW,IAAI,GAAG,KAAK,EAAE,CACpC,OAAO,QAAQ;CAElB,MAAM,SAAS,OAAO,aAAa;CACnC,MAAM,SAAS,cAAc,KAAK,IAAI;AAEtC,QAAO,SAAS,GAAG,OAAO,GAAG,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4B1C,SAAgB,WAAW,iBAAyE;AAWlG,QAAO,aARe,MAAM,QAAQ,gBAAgB,IAClD,gBAAgB,SAAS,KACzB,OAAQ,gBAAgB,IAAgC,WAAW,WAGjE,kBACA,uBAAuB,CAEG;;;;;;AAOhC,SAAS,wBAA4C;AACnD,KAAI;AAEF,SADiB,kBAAkB,CACnB,QAAQ;SAClB;AAEN,SAAO,EAAE"}
@@ -2,6 +2,15 @@ import { Static, TSchema } from "@sinclair/typebox";
2
2
 
3
3
  //#region src/defineRoute.d.ts
4
4
 
5
+ /** SSE 事件 */
6
+ interface SSEEventType<T = unknown> {
7
+ event?: string;
8
+ data: T;
9
+ id?: string;
10
+ retry?: number;
11
+ }
12
+ /** SSE Generator 类型 */
13
+ type SSEGeneratorType<TSchema$1 extends RouteSchema = RouteSchema> = (ctx: HandlerContext<TSchema$1>) => AsyncGenerator<SSEEventType<unknown>, void, unknown>;
5
14
  /** 路由 Schema 配置 */
6
15
  interface RouteSchema {
7
16
  body?: TSchema;
@@ -47,6 +56,12 @@ interface HandlerContext<TSchema$1 extends RouteSchema = RouteSchema> {
47
56
  type HandlerContextWithExtra<TSchema$1 extends RouteSchema, TExtra> = HandlerContext<TSchema$1> & TExtra;
48
57
  /** HTTP 方法 */
49
58
  type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS" | "HEAD";
59
+ /** Handler 上下文类型(带中间件扩展) */
60
+ type HandlerCtx<TSchema$1 extends RouteSchema, TMiddleware extends readonly AnyMiddleware[]> = HandlerContextWithExtra<TSchema$1, MergeMiddlewareContexts<TMiddleware>>;
61
+ /** 普通 Handler 类型 */
62
+ type NormalHandler<TSchema$1 extends RouteSchema, TReturn, TMiddleware extends readonly AnyMiddleware[]> = (ctx: HandlerCtx<TSchema$1, TMiddleware>) => TReturn | Promise<TReturn>;
63
+ /** SSE Generator Handler 类型(直接写 async function*,无需 createSSEHandler 包装) */
64
+ type SSEHandler<TSchema$1 extends RouteSchema, TMiddleware extends readonly AnyMiddleware[]> = (ctx: HandlerCtx<TSchema$1, TMiddleware>) => AsyncGenerator<SSEEventType<unknown>, void, unknown>;
50
65
  /** 叶子路由配置(有 method 和 handler) */
51
66
  interface LeafRouteConfig<TMethod extends HTTPMethod = HTTPMethod, TPath extends string = string, TSchema$1 extends RouteSchema = RouteSchema, TReturn = unknown, TMiddleware extends readonly AnyMiddleware[] = readonly AnyMiddleware[]> {
52
67
  readonly method: TMethod;
@@ -54,7 +69,22 @@ interface LeafRouteConfig<TMethod extends HTTPMethod = HTTPMethod, TPath extends
54
69
  readonly name?: string;
55
70
  readonly description?: string;
56
71
  readonly schema?: TSchema$1;
57
- readonly handler: (ctx: HandlerContextWithExtra<TSchema$1, MergeMiddlewareContexts<TMiddleware>>) => TReturn | Promise<TReturn>;
72
+ /**
73
+ * 是否为 SSE 端点(显式声明,推荐)
74
+ *
75
+ * 设置为 true 时,handler 应返回 AsyncGenerator:
76
+ * ```typescript
77
+ * sse: true,
78
+ * handler: async function* (ctx) { yield { data: ... } }
79
+ * ```
80
+ */
81
+ readonly sse?: boolean;
82
+ /**
83
+ * Handler 支持两种写法:
84
+ * 1. 普通函数: `async (ctx) => { return result }`
85
+ * 2. SSE Generator(需配合 sse: true): `async function* (ctx) { yield { data: ... } }`
86
+ */
87
+ readonly handler: NormalHandler<TSchema$1, TReturn, TMiddleware> | SSEHandler<TSchema$1, TMiddleware>;
58
88
  readonly middleware?: TMiddleware;
59
89
  readonly docs?: {
60
90
  tags?: string[];
@@ -79,6 +109,8 @@ interface ProcessedRoute {
79
109
  name?: string;
80
110
  description?: string;
81
111
  schema?: RouteSchema;
112
+ /** 是否为 SSE 端点 */
113
+ sse?: boolean;
82
114
  handler: (req: Request) => Promise<Response>;
83
115
  middleware?: readonly AnyMiddleware[];
84
116
  docs?: {
@@ -135,9 +167,11 @@ declare function defineRoute<const TSchema$1 extends RouteSchema, TReturn, const
135
167
  readonly name?: string;
136
168
  readonly description?: string;
137
169
  readonly schema?: TSchema$1;
170
+ /** 是否为 SSE 端点,设置为 true 时 handler 应返回 AsyncGenerator */
171
+ readonly sse?: boolean;
138
172
  /** 显式声明上下文类型(用于父级中间件注入的场景) */
139
173
  readonly context?: TContext;
140
- readonly handler: (ctx: HandlerContextWithExtra<TSchema$1, TContext & MergeMiddlewareContexts<TMiddleware>>) => TReturn | Promise<TReturn>;
174
+ readonly handler: (ctx: HandlerContextWithExtra<TSchema$1, TContext & MergeMiddlewareContexts<TMiddleware>>) => TReturn | Promise<TReturn> | AsyncGenerator<SSEEventType<unknown>, void, unknown>;
141
175
  readonly middleware?: TMiddleware;
142
176
  readonly docs?: {
143
177
  tags?: string[];
@@ -199,13 +233,23 @@ declare function defineRoute<const TMiddleware extends readonly AnyMiddleware[],
199
233
  * })
200
234
  * ```
201
235
  */
236
+ /** withContext Handler 上下文类型 */
237
+ type WithContextHandlerCtx<TSchema$1 extends RouteSchema, TContext extends object, TMiddleware extends readonly AnyMiddleware[]> = HandlerContextWithExtra<TSchema$1, TContext & MergeMiddlewareContexts<TMiddleware>>;
238
+ /** withContext 支持的 Handler 类型(普通函数 + SSE Generator) */
239
+ type WithContextHandler<TSchema$1 extends RouteSchema, TContext extends object, TMiddleware extends readonly AnyMiddleware[], TReturn> = ((ctx: WithContextHandlerCtx<TSchema$1, TContext, TMiddleware>) => TReturn | Promise<TReturn>) | ((ctx: WithContextHandlerCtx<TSchema$1, TContext, TMiddleware>) => AsyncGenerator<SSEEventType<unknown>, void, unknown>);
202
240
  declare function withContext<TContext extends object, TExtensions extends object = object>(): <const TSchema$1 extends RouteSchema, TReturn, const TMiddleware extends readonly AnyMiddleware[], TMethod extends HTTPMethod = HTTPMethod, TPath extends string = string>(config: {
203
241
  readonly method: TMethod;
204
242
  readonly path: TPath;
205
243
  readonly name?: string;
206
244
  readonly description?: string;
207
245
  readonly schema?: TSchema$1;
208
- readonly handler: (ctx: HandlerContextWithExtra<TSchema$1, TContext & MergeMiddlewareContexts<TMiddleware>>) => TReturn | Promise<TReturn>;
246
+ /**
247
+ * 是否为 SSE 端点
248
+ * 设置为 true 时,handler 应返回 AsyncGenerator
249
+ */
250
+ readonly sse?: boolean;
251
+ /** Handler 支持两种写法:普通函数 或 SSE Generator(需配合 sse: true) */
252
+ readonly handler: WithContextHandler<TSchema$1, TContext, TMiddleware, TReturn>;
209
253
  readonly middleware?: TMiddleware;
210
254
  readonly docs?: {
211
255
  tags?: string[];
@@ -248,5 +292,5 @@ type InferableRoute<TMethod extends string = string, TPath extends string = stri
248
292
  readonly middleware?: ReadonlyArray<AnyMiddleware>;
249
293
  };
250
294
  //#endregion
251
- export { ProcessedRoute as a, RoutesWithSource as c, defineRoute as d, defineRoutes as f, NestedRouteConfig as i, TypedMiddleware as l, InferableRoute as n, Route as o, withContext as p, LeafRouteConfig as r, RouteSchema as s, HandlerContext as t, defineMiddleware as u };
252
- //# sourceMappingURL=defineRoute-DyPa9FHa.d.mts.map
295
+ export { ProcessedRoute as a, RoutesWithSource as c, TypedMiddleware as d, defineMiddleware as f, withContext as h, NestedRouteConfig as i, SSEEventType as l, defineRoutes as m, InferableRoute as n, Route as o, defineRoute as p, LeafRouteConfig as r, RouteSchema as s, HandlerContext as t, SSEGeneratorType as u };
296
+ //# sourceMappingURL=defineRoute-B9VdaoQA.d.mts.map
@@ -1,2 +1,2 @@
1
- import { a as ProcessedRoute, c as RoutesWithSource, d as defineRoute, f as defineRoutes, i as NestedRouteConfig, l as TypedMiddleware, n as InferableRoute, o as Route, p as withContext, r as LeafRouteConfig, s as RouteSchema, t as HandlerContext, u as defineMiddleware } from "./defineRoute-DyPa9FHa.mjs";
2
- export { HandlerContext, InferableRoute, LeafRouteConfig, NestedRouteConfig, ProcessedRoute, Route, RouteSchema, RoutesWithSource, TypedMiddleware, defineMiddleware, defineRoute, defineRoutes, withContext };
1
+ import { a as ProcessedRoute, c as RoutesWithSource, d as TypedMiddleware, f as defineMiddleware, h as withContext, i as NestedRouteConfig, l as SSEEventType, m as defineRoutes, n as InferableRoute, o as Route, p as defineRoute, r as LeafRouteConfig, s as RouteSchema, t as HandlerContext, u as SSEGeneratorType } from "./defineRoute-B9VdaoQA.mjs";
2
+ export { HandlerContext, InferableRoute, LeafRouteConfig, NestedRouteConfig, ProcessedRoute, Route, RouteSchema, RoutesWithSource, SSEEventType, SSEGeneratorType, TypedMiddleware, defineMiddleware, defineRoute, defineRoutes, withContext };
@@ -54,37 +54,114 @@ function autoResponse(result) {
54
54
  }
55
55
  return new Response(null, { status: 204 });
56
56
  }
57
- /** 创建包装后的 handler */
57
+ /**
58
+ * 构建 HandlerContext(公共逻辑,供普通 handler 和 SSE handler 复用)
59
+ */
60
+ async function buildHandlerContext(req, schema) {
61
+ const query = parseQuery(req);
62
+ const headers = parseHeaders(req);
63
+ const cookies = parseCookies(req);
64
+ const params = req.params || {};
65
+ let body = void 0;
66
+ if (req.method !== "GET" && req.method !== "HEAD") try {
67
+ body = await parseBody(req);
68
+ } catch {}
69
+ const data = {
70
+ body,
71
+ query,
72
+ params,
73
+ headers,
74
+ cookies
75
+ };
76
+ if (schema && (schema.body || schema.query || schema.params || schema.headers || schema.cookies)) validateAllSchemas(schema, data);
77
+ const extraCtx = req.__locals || {};
78
+ return {
79
+ req,
80
+ body,
81
+ query,
82
+ params,
83
+ headers,
84
+ cookies,
85
+ ...extraCtx
86
+ };
87
+ }
88
+ /** 创建包装后的 handler(普通路由) */
58
89
  function wrapHandler(schema, userHandler) {
59
90
  if (schema && (schema.body || schema.query || schema.params || schema.headers || schema.cookies)) precompileSchemas(schema);
60
91
  return async (req) => {
61
92
  try {
62
- const query = parseQuery(req);
63
- const headers = parseHeaders(req);
64
- const cookies = parseCookies(req);
65
- const params = req.params || {};
66
- let body = void 0;
67
- if (req.method !== "GET" && req.method !== "HEAD") try {
68
- body = await parseBody(req);
69
- } catch {}
70
- const data = {
71
- body,
72
- query,
73
- params,
74
- headers,
75
- cookies
76
- };
77
- if (schema && (schema.body || schema.query || schema.params || schema.headers || schema.cookies)) validateAllSchemas(schema, data);
78
- const extraCtx = req.__locals || {};
79
- return autoResponse(await userHandler({
80
- req,
81
- body,
82
- query,
83
- params,
84
- headers,
85
- cookies,
86
- ...extraCtx
87
- }));
93
+ return autoResponse(await userHandler(await buildHandlerContext(req, schema)));
94
+ } catch (error) {
95
+ if (error instanceof VafastError) throw error;
96
+ if (error instanceof Error && error.message.includes("验证失败")) return json({
97
+ code: 400,
98
+ message: error.message
99
+ }, 400);
100
+ return json({
101
+ code: 500,
102
+ message: error instanceof Error ? error.message : "未知错误"
103
+ }, 500);
104
+ }
105
+ };
106
+ }
107
+ /**
108
+ * 格式化 SSE 事件为字符串(内联,避免循环依赖)
109
+ */
110
+ function formatSSEEvent(event) {
111
+ const lines = [];
112
+ if (event.id !== void 0) lines.push(`id: ${event.id}`);
113
+ if (event.event !== void 0) lines.push(`event: ${event.event}`);
114
+ if (event.retry !== void 0) lines.push(`retry: ${event.retry}`);
115
+ const dataStr = typeof event.data === "string" ? event.data : JSON.stringify(event.data);
116
+ for (const line of dataStr.split("\n")) lines.push(`data: ${line}`);
117
+ return lines.join("\n") + "\n\n";
118
+ }
119
+ /**
120
+ * 将 AsyncGenerator 包装为 SSE Handler
121
+ *
122
+ * 支持用户直接写 `async function* (ctx) { yield ... }` 而无需 createSSEHandler
123
+ */
124
+ function wrapGeneratorToSSEHandler(generator) {
125
+ return async (ctx) => {
126
+ const stream = new ReadableStream({ async start(controller) {
127
+ const encoder = new TextEncoder();
128
+ try {
129
+ for await (const event of generator(ctx)) controller.enqueue(encoder.encode(formatSSEEvent(event)));
130
+ } catch (error) {
131
+ const errorEvent = formatSSEEvent({
132
+ event: "error",
133
+ data: { error: error instanceof Error ? error.message : "未知错误" }
134
+ });
135
+ controller.enqueue(encoder.encode(errorEvent));
136
+ } finally {
137
+ controller.close();
138
+ }
139
+ } });
140
+ return new Response(stream, { headers: {
141
+ "Content-Type": "text/event-stream",
142
+ "Cache-Control": "no-cache",
143
+ "Connection": "keep-alive",
144
+ "X-Accel-Buffering": "no"
145
+ } });
146
+ };
147
+ }
148
+ /**
149
+ * 创建 SSE handler 的请求包装器
150
+ *
151
+ * SSE handler 与普通 handler 使用相同的上下文构建流程(buildHandlerContext),
152
+ * 确保:
153
+ * - body/query/params 解析一致
154
+ * - schema 验证统一
155
+ * - 中间件注入的上下文(如 userInfo)自动可用
156
+ * - 错误处理统一
157
+ *
158
+ * 唯一区别:SSE handler 返回的是 SSE Stream Response,而非普通 JSON Response
159
+ */
160
+ function wrapSSEHandler(schema, sseHandler) {
161
+ if (schema && (schema.body || schema.query || schema.params || schema.headers || schema.cookies)) precompileSchemas(schema);
162
+ return async (req) => {
163
+ try {
164
+ return await sseHandler(await buildHandlerContext(req, schema));
88
165
  } catch (error) {
89
166
  if (error instanceof VafastError) throw error;
90
167
  if (error instanceof Error && error.message.includes("验证失败")) return json({
@@ -112,50 +189,6 @@ function isNestedRoute(route) {
112
189
  function defineRoute(config) {
113
190
  return config;
114
191
  }
115
- /**
116
- * 创建带预设上下文类型的路由定义器
117
- *
118
- * 用于父级中间件注入上下文的场景,定义一次,多处复用
119
- *
120
- * @example
121
- * ```typescript
122
- * // 1. 在 middleware/index.ts 中定义
123
- * export const defineAuthRoute = withContext<{ userInfo: UserInfo }>()
124
- *
125
- * // 2. 在路由文件中使用
126
- * defineAuthRoute({
127
- * method: 'GET',
128
- * path: '/profile',
129
- * handler: ({ userInfo }) => {
130
- * // userInfo 自动有类型!
131
- * return { id: userInfo.id }
132
- * }
133
- * })
134
- * ```
135
- */
136
- /**
137
- * 带扩展类型的路由定义器
138
- *
139
- * @typeParam TContext - Handler 上下文类型(如 { userInfo: UserInfo })
140
- * @typeParam TExtensions - 路由扩展字段类型(如 { webhook?: boolean })
141
- *
142
- * @example
143
- * ```typescript
144
- * // 定义扩展类型
145
- * interface WebhookExtension {
146
- * webhook?: boolean | { eventKey?: string }
147
- * }
148
- *
149
- * // 使用扩展
150
- * const defineRoute = withContext<MyContext, WebhookExtension>()
151
- * defineRoute({
152
- * method: 'POST',
153
- * path: '/create',
154
- * webhook: true, // TypeScript 严格检查
155
- * handler: ...
156
- * })
157
- * ```
158
- */
159
192
  function withContext() {
160
193
  return (config) => {
161
194
  return config;
@@ -170,18 +203,18 @@ function flattenRoutes(routes, parentPath = "", parentMiddleware = [], parent) {
170
203
  const fullPath = parentPath + route.path;
171
204
  const mergedMiddleware = [...parentMiddleware, ...route.middleware || []];
172
205
  if (isLeafRoute(route)) {
173
- const isSSE = route.handler?.__sse?.__brand === "SSE";
206
+ const isSSE = route.sse === true;
174
207
  const processed = {
175
208
  method: route.method,
176
209
  path: fullPath,
177
210
  name: route.name,
178
211
  description: route.description,
179
212
  schema: route.schema,
180
- handler: isSSE ? route.handler : wrapHandler(route.schema, route.handler),
213
+ sse: isSSE || void 0,
214
+ handler: isSSE ? wrapSSEHandler(route.schema, wrapGeneratorToSSEHandler(route.handler)) : wrapHandler(route.schema, route.handler),
181
215
  middleware: mergedMiddleware.length > 0 ? mergedMiddleware : void 0,
182
216
  docs: route.docs
183
217
  };
184
- if (isSSE) processed.handler.__sse = { __brand: "SSE" };
185
218
  if (parent) processed.parent = parent;
186
219
  const knownKeys = [
187
220
  "method",