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.
- package/README.md +75 -20
- package/dist/{base-server-D7ny0Kwm.d.mts → base-server-CeJ8LTBk.d.mts} +2 -2
- package/dist/{base64url-DNUGwekK.d.mts → base64url-CAmasWF0.d.mts} +1 -1
- package/dist/{component-route-Bxb-08X7.d.mts → component-route-CGbmQm5P.d.mts} +2 -2
- package/dist/{component-server-DQ3nZWFc.d.mts → component-server-CKcXIvMg.d.mts} +4 -4
- package/dist/{contract-CuruqP6h.d.mts → contract-C-xQoZ6r.d.mts} +1 -1
- package/dist/{contract-Cwmmo-Nn.mjs → contract-vSyKiRwz.mjs} +2 -2
- package/dist/contract-vSyKiRwz.mjs.map +1 -0
- package/dist/{defineRoute-DyPa9FHa.d.mts → defineRoute-B9VdaoQA.d.mts} +49 -5
- package/dist/defineRoute.d.mts +2 -2
- package/dist/defineRoute.mjs +107 -74
- package/dist/defineRoute.mjs.map +1 -1
- package/dist/{dependency-manager-DIN9X0Gj.d.mts → dependency-manager-mqzLAocb.d.mts} +1 -1
- package/dist/{formats-DDDSFWP0.d.mts → formats-Ca7ASaYH.d.mts} +1 -1
- package/dist/{go-await-DPtVBug4.d.mts → go-await-Dz1CRSTT.d.mts} +1 -1
- package/dist/{html-renderer-DhQxRuyi.d.mts → html-renderer-Up52eIS6.d.mts} +1 -1
- package/dist/{index-Cek4HyXL.d.mts → index-CRU-u6NT.d.mts} +5 -5
- package/dist/index.d.mts +21 -21
- package/dist/index.mjs +6 -7
- package/dist/index.mjs.map +1 -1
- package/dist/middleware/component-router.d.mts +1 -1
- package/dist/{middleware-DXssDt1F.d.mts → middleware-BTg4GbjC.d.mts} +2 -2
- package/dist/middleware.d.mts +1 -1
- package/dist/monitoring/index.d.mts +3 -3
- package/dist/monitoring/native-monitor.d.mts +3 -3
- package/dist/node-server/index.d.mts +1 -1
- package/dist/node-server/index.mjs +3 -3
- package/dist/node-server/request.mjs +1 -1
- package/dist/node-server/response.mjs +1 -1
- package/dist/node-server/serve.d.mts +1 -1
- package/dist/node-server/serve.mjs +2 -2
- package/dist/{parsers-8hIAx0OV.d.mts → parsers-BAQtDA1q.d.mts} +1 -1
- package/dist/{radix-tree-CccjvyTP.mjs → radix-tree-dyn3qDFX.mjs} +35 -15
- package/dist/radix-tree-dyn3qDFX.mjs.map +1 -0
- package/dist/{request-D202oxO9.mjs → request-DEWtcK8t.mjs} +1 -1
- package/dist/{request-D202oxO9.mjs.map → request-DEWtcK8t.mjs.map} +1 -1
- package/dist/{response-DNdvtn-K.mjs → response-BlHLmKys.mjs} +1 -1
- package/dist/{response-DNdvtn-K.mjs.map → response-BlHLmKys.mjs.map} +1 -1
- package/dist/{response-F-VxN-cB.d.mts → response-lI0YZoia.d.mts} +2 -2
- package/dist/{route-registry-CAX54I8j.d.mts → route-registry-C6h13Mks.d.mts} +3 -3
- package/dist/router/index.mjs +1 -1
- package/dist/router/radix-tree.d.mts +12 -1
- package/dist/router/radix-tree.mjs +1 -1
- package/dist/{serve-BNSr7-5v.mjs → serve-DVlDG92Y.mjs} +3 -3
- package/dist/{serve-BNSr7-5v.mjs.map → serve-DVlDG92Y.mjs.map} +1 -1
- package/dist/{serve-Ds8Px1wD.d.mts → serve-Dw-GHkc3.d.mts} +1 -1
- package/dist/serve.d.mts +1 -1
- package/dist/serve.mjs +2 -2
- package/dist/server/base-server.d.mts +1 -1
- package/dist/server/component-server.d.mts +1 -1
- package/dist/server/index.d.mts +5 -5
- package/dist/server/index.mjs +2 -2
- package/dist/server/server-factory.d.mts +3 -3
- package/dist/server/server-factory.mjs +2 -2
- package/dist/server/server.d.mts +2 -2
- package/dist/server/server.mjs +1 -1
- package/dist/{server-DWndB63Z.mjs → server-B4YDcNJy.mjs} +2 -2
- package/dist/{server-DWndB63Z.mjs.map → server-B4YDcNJy.mjs.map} +1 -1
- package/dist/{server-CAhwnEPW.mjs → server-BNpY3NH6.mjs} +2 -2
- package/dist/{server-CAhwnEPW.mjs.map → server-BNpY3NH6.mjs.map} +1 -1
- package/dist/{server-3uMr8Ujo.d.mts → server-CQ-_WgQN.d.mts} +4 -4
- package/dist/sse-D77CKcsH.d.mts +45 -0
- package/dist/types/component-route.d.mts +1 -1
- package/dist/types/index.d.mts +2 -2
- package/dist/types/types.d.mts +1 -1
- package/dist/{types-D4pCpFZ_.d.mts → types-aczawQFE.d.mts} +1 -1
- package/dist/utils/base64url.d.mts +1 -1
- package/dist/utils/contract.d.mts +1 -1
- package/dist/utils/contract.mjs +1 -1
- package/dist/utils/dependency-manager.d.mts +1 -1
- package/dist/utils/formats.d.mts +1 -1
- package/dist/utils/go-await.d.mts +1 -1
- package/dist/utils/html-renderer.d.mts +1 -1
- package/dist/utils/index.d.mts +13 -13
- package/dist/utils/index.mjs +2 -3
- package/dist/utils/parsers.d.mts +1 -1
- package/dist/utils/response.d.mts +1 -1
- package/dist/utils/route-registry.d.mts +2 -2
- package/dist/utils/sse.d.mts +2 -3
- package/dist/utils/sse.mjs +1 -5
- package/dist/utils/validators/validators.d.mts +1 -1
- package/dist/{validators-BFC6S_fr.d.mts → validators-D5KJCSZr.d.mts} +1 -1
- package/package.json +1 -1
- package/dist/contract-Cwmmo-Nn.mjs.map +0 -1
- package/dist/radix-tree-CccjvyTP.mjs.map +0 -1
- package/dist/sse-C0_ODr4_.mjs +0 -111
- package/dist/sse-C0_ODr4_.mjs.map +0 -1
- package/dist/sse-CjIH9WjQ.d.mts +0 -81
- /package/dist/{component-route-BiUHBq7a.mjs → component-route-DdKFowzp.mjs} +0 -0
- /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
|
-
|
|
592
|
+
通过 `sse: true` 显式声明 SSE 端点,适用于 AI 聊天、进度更新等场景:
|
|
532
593
|
|
|
533
594
|
```typescript
|
|
534
|
-
import {
|
|
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:
|
|
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-
|
|
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-
|
|
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-
|
|
6
|
+
//# sourceMappingURL=base64url-CAmasWF0.d.mts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as Middleware } from "./types-
|
|
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-
|
|
31
|
+
//# sourceMappingURL=component-route-CGbmQm5P.d.mts.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { r as NestedComponentRoute, t as ComponentRoute } from "./component-route-
|
|
2
|
-
import { t as BaseServer } from "./base-server-
|
|
3
|
-
import { t as DependencyManager } from "./dependency-manager-
|
|
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-
|
|
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-
|
|
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
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
252
|
-
//# sourceMappingURL=defineRoute-
|
|
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
|
package/dist/defineRoute.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as ProcessedRoute, c as RoutesWithSource, d as
|
|
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 };
|
package/dist/defineRoute.mjs
CHANGED
|
@@ -54,37 +54,114 @@ function autoResponse(result) {
|
|
|
54
54
|
}
|
|
55
55
|
return new Response(null, { status: 204 });
|
|
56
56
|
}
|
|
57
|
-
/**
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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.
|
|
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
|
-
|
|
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",
|