vafast 0.6.2 → 0.7.0
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 +13 -19
- 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 +4 -5
- 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/{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/radix-tree.d.mts +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/server-factory.d.mts +3 -3
- package/dist/server/server.d.mts +2 -2
- 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/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
|
@@ -528,33 +528,27 @@ defineRoute({
|
|
|
528
528
|
|
|
529
529
|
### SSE 流式响应
|
|
530
530
|
|
|
531
|
-
|
|
531
|
+
通过 `sse: true` 显式声明 SSE 端点,适用于 AI 聊天、进度更新等场景:
|
|
532
532
|
|
|
533
533
|
```typescript
|
|
534
|
-
import {
|
|
534
|
+
import { defineRoute, defineRoutes, Type } from 'vafast'
|
|
535
535
|
|
|
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
536
|
const routes = defineRoutes([
|
|
553
537
|
defineRoute({
|
|
554
538
|
method: 'GET',
|
|
555
539
|
path: '/tasks/:taskId/progress',
|
|
540
|
+
sse: true, // 显式声明 SSE 端点
|
|
556
541
|
schema: { params: Type.Object({ taskId: Type.String() }) },
|
|
557
|
-
handler:
|
|
542
|
+
handler: async function* ({ params }) {
|
|
543
|
+
yield { event: 'start', data: { taskId: params.taskId } }
|
|
544
|
+
|
|
545
|
+
for (let i = 0; i <= 100; i += 10) {
|
|
546
|
+
yield { data: { progress: i } }
|
|
547
|
+
await new Promise(r => setTimeout(r, 100))
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
yield { event: 'complete', data: { message: 'Done!' } }
|
|
551
|
+
},
|
|
558
552
|
}),
|
|
559
553
|
])
|
|
560
554
|
```
|
|
@@ -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",
|
package/dist/defineRoute.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defineRoute.mjs","names":[],"sources":["../src/defineRoute.ts"],"sourcesContent":["/**\n * 路由定义 - Schema 在路由级别定义,支持嵌套路由和中间件类型推断\n *\n * @example\n * ```typescript\n * // 定义带类型的中间件(函数式风格,通过 next 传递上下文)\n * const authMiddleware = defineMiddleware<{ user: User }>((req, next) => {\n * const user = getUser(req)\n * return next({ user }) // 通过 next 参数传递上下文\n * })\n *\n * // 路由自动推断中间件注入的类型\n * const routes = defineRoutes([\n * defineRoute({\n * path: '/api',\n * middleware: [authMiddleware],\n * children: [\n * defineRoute({\n * method: 'GET',\n * path: '/profile',\n * handler: ({ user }) => ({ name: user.name }) // ✅ user 有类型\n * })\n * ]\n * })\n * ])\n * ```\n */\n\nimport type { TSchema, Static } from \"@sinclair/typebox\";\nimport { parseBody, parseQuery, parseHeaders, parseCookies } from \"./utils/parsers\";\nimport { validateAllSchemas, precompileSchemas } from \"./utils/validators/validators\";\nimport { json } from \"./utils/response\";\nimport { VafastError } from \"./middleware\";\n\n// ============= Schema 类型 =============\n\n/** 路由 Schema 配置 */\nexport interface RouteSchema {\n body?: TSchema;\n query?: TSchema;\n params?: TSchema;\n headers?: TSchema;\n cookies?: TSchema;\n /** 响应类型 schema(用于类型同步,运行时不做校验) */\n response?: TSchema;\n}\n\n/** 从 Schema 推断类型 */\ntype InferSchemaType<T extends RouteSchema> = {\n body: T[\"body\"] extends TSchema ? Static<T[\"body\"]> : unknown;\n query: T[\"query\"] extends TSchema ? Static<T[\"query\"]> : Record<string, string>;\n params: T[\"params\"] extends TSchema ? Static<T[\"params\"]> : Record<string, string>;\n headers: T[\"headers\"] extends TSchema ? Static<T[\"headers\"]> : Record<string, string>;\n cookies: T[\"cookies\"] extends TSchema ? Static<T[\"cookies\"]> : Record<string, string>;\n};\n\n// ============= 中间件类型系统 =============\n\n/** 带类型标记的中间件 */\nexport interface TypedMiddleware<TContext extends object = object> {\n (req: Request, next: (ctx?: TContext) => Promise<Response>): Response | Promise<Response>;\n /** 类型标记(仅编译时使用) */\n __context?: TContext;\n}\n\n/** 普通中间件(无类型注入) */\ntype PlainMiddleware = (req: Request, next: () => Promise<Response>) => Response | Promise<Response>;\n\n/** 任意中间件类型 */\ntype AnyMiddleware = TypedMiddleware<object> | PlainMiddleware;\n\n/** 从中间件提取上下文类型 */\ntype ExtractMiddlewareContext<T> = T extends TypedMiddleware<infer C> ? C : object;\n\n/** 合并中间件数组的上下文类型 */\ntype MergeMiddlewareContexts<T extends readonly unknown[]> =\n T extends readonly [infer First, ...infer Rest]\n ? ExtractMiddlewareContext<First> & MergeMiddlewareContexts<Rest>\n : object;\n\n// ============= Handler 上下文 =============\n\n/** Handler 上下文(包含 schema 推断) */\nexport interface HandlerContext<TSchema extends RouteSchema = RouteSchema> {\n req: Request;\n body: InferSchemaType<TSchema>[\"body\"];\n query: InferSchemaType<TSchema>[\"query\"];\n params: InferSchemaType<TSchema>[\"params\"];\n headers: InferSchemaType<TSchema>[\"headers\"];\n cookies: InferSchemaType<TSchema>[\"cookies\"];\n}\n\n/** Handler 上下文(带中间件注入的额外类型) */\ntype HandlerContextWithExtra<TSchema extends RouteSchema, TExtra> =\n HandlerContext<TSchema> & TExtra;\n\n// ============= 路由配置类型 =============\n\n/** HTTP 方法 */\ntype HTTPMethod = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\" | \"OPTIONS\" | \"HEAD\";\n\n/** 叶子路由配置(有 method 和 handler) */\nexport interface LeafRouteConfig<\n TMethod extends HTTPMethod = HTTPMethod,\n TPath extends string = string,\n TSchema extends RouteSchema = RouteSchema,\n TReturn = unknown,\n TMiddleware extends readonly AnyMiddleware[] = readonly AnyMiddleware[]\n> {\n readonly method: TMethod;\n readonly path: TPath;\n readonly name?: string;\n readonly description?: string;\n readonly schema?: TSchema;\n readonly handler: (\n ctx: HandlerContextWithExtra<TSchema, MergeMiddlewareContexts<TMiddleware>>\n ) => TReturn | Promise<TReturn>;\n readonly middleware?: TMiddleware;\n readonly docs?: {\n tags?: string[];\n security?: unknown[];\n responses?: Record<string, unknown>;\n };\n}\n\n/** 嵌套路由配置(有 children,无 method 和 handler) */\nexport interface NestedRouteConfig<\n TPath extends string = string,\n TMiddleware extends readonly AnyMiddleware[] = readonly AnyMiddleware[]\n> {\n readonly path: TPath;\n readonly name?: string;\n readonly description?: string;\n readonly middleware?: TMiddleware;\n readonly children: ReadonlyArray<RouteConfigResult>;\n}\n\n/** defineRoute 返回的类型 */\ntype RouteConfigResult =\n | LeafRouteConfig<HTTPMethod, string, RouteSchema, unknown, readonly AnyMiddleware[]>\n | NestedRouteConfig<string, readonly AnyMiddleware[]>;\n\n/** 处理后的扁平路由 */\nexport interface ProcessedRoute {\n method: HTTPMethod;\n path: string;\n name?: string;\n description?: string;\n schema?: RouteSchema;\n handler: (req: Request) => Promise<Response>;\n middleware?: readonly AnyMiddleware[];\n docs?: {\n tags?: string[];\n security?: unknown[];\n responses?: Record<string, unknown>;\n };\n /** 允许任意扩展(兼容 Route 类型) */\n [key: string]: unknown;\n}\n\n/** Route 类型别名(便于用户使用) */\nexport type Route = ProcessedRoute;\n\n// ============= defineMiddleware =============\n\n/**\n * 定义带类型的中间件(函数式风格)\n *\n * 通过 next() 参数传递上下文,更符合函数式编程风格\n *\n * @example\n * ```typescript\n * type AuthContext = { user: { id: string; name: string } }\n *\n * const authMiddleware = defineMiddleware<AuthContext>((req, next) => {\n * const user = getUserFromToken(req)\n * return next({ user }) // 通过 next 传递上下文\n * })\n * ```\n */\nexport function defineMiddleware<TContext extends object = object>(\n handler: (\n req: Request,\n next: (ctx?: TContext) => Promise<Response>\n ) => Promise<Response>\n): TypedMiddleware<TContext> {\n // 包装成标准中间件签名\n const middleware = ((req: Request, originalNext: () => Promise<Response>) => {\n // 包装 next,接收上下文参数并存储到 req.__locals\n const nextWithContext = (ctx?: TContext): Promise<Response> => {\n if (ctx) {\n const target = req as unknown as { __locals?: object };\n target.__locals = { ...(target.__locals || {}), ...ctx };\n }\n return originalNext();\n };\n return handler(req, nextWithContext);\n }) as TypedMiddleware<TContext>;\n\n return middleware;\n}\n\n// ============= 响应处理 =============\n\n/** 自动转换返回值为 Response */\nfunction autoResponse(result: unknown): Response {\n if (result instanceof Response) return result;\n if (result === null || result === undefined) return new Response(null, { status: 204 });\n if (typeof result === \"string\") {\n return new Response(result, { headers: { \"Content-Type\": \"text/plain; charset=utf-8\" } });\n }\n if (typeof result === \"number\" || typeof result === \"boolean\") {\n return new Response(String(result), { headers: { \"Content-Type\": \"text/plain; charset=utf-8\" } });\n }\n if (typeof result === \"object\") {\n const obj = result as Record<string, unknown>;\n if (\"data\" in obj && (\"status\" in obj || \"headers\" in obj)) {\n const { data, status = 200, headers = {} } = obj;\n if (data === null || data === undefined) {\n return new Response(null, { status: status === 200 ? 204 : (status as number), headers: headers as HeadersInit });\n }\n return json(data, status as number, headers as Record<string, string>);\n }\n return json(result);\n }\n return new Response(null, { status: 204 });\n}\n\n/** 创建包装后的 handler */\nfunction wrapHandler<TSchema extends RouteSchema>(\n schema: TSchema | undefined,\n userHandler: (ctx: HandlerContext<TSchema>) => unknown | Promise<unknown>\n): (req: Request) => Promise<Response> {\n if (schema && (schema.body || schema.query || schema.params || schema.headers || schema.cookies)) {\n precompileSchemas(schema);\n }\n\n return async (req: Request): Promise<Response> => {\n try {\n const query = parseQuery(req);\n const headers = parseHeaders(req);\n const cookies = parseCookies(req);\n const params = ((req as unknown as Record<string, unknown>).params as Record<string, string>) || {};\n\n let body: unknown = undefined;\n if (req.method !== \"GET\" && req.method !== \"HEAD\") {\n try {\n body = await parseBody(req);\n } catch {\n // 忽略解析错误\n }\n }\n\n const data = { body, query, params, headers, cookies };\n if (schema && (schema.body || schema.query || schema.params || schema.headers || schema.cookies)) {\n validateAllSchemas(schema, data);\n }\n\n // 获取中间件注入的上下文\n const extraCtx = (req as unknown as { __locals?: unknown }).__locals || {};\n\n const result = await userHandler({\n req,\n body: body as HandlerContext<TSchema>[\"body\"],\n query: query as HandlerContext<TSchema>[\"query\"],\n params: params as HandlerContext<TSchema>[\"params\"],\n headers: headers as HandlerContext<TSchema>[\"headers\"],\n cookies: cookies as HandlerContext<TSchema>[\"cookies\"],\n ...extraCtx,\n } as HandlerContext<TSchema>);\n\n return autoResponse(result);\n } catch (error) {\n // 如果是 VafastError,重新抛出让错误处理中间件处理\n if (error instanceof VafastError) {\n throw error;\n }\n if (error instanceof Error && error.message.includes(\"验证失败\")) {\n return json({ code: 400, message: error.message }, 400);\n }\n return json({ code: 500, message: error instanceof Error ? error.message : \"未知错误\" }, 500);\n }\n };\n}\n\n// ============= 判断路由类型 =============\n\n/** 判断是否为叶子路由 */\nfunction isLeafRoute(route: RouteConfigResult): route is LeafRouteConfig {\n return \"method\" in route && \"handler\" in route;\n}\n\n/** 判断是否为嵌套路由 */\nfunction isNestedRoute(route: RouteConfigResult): route is NestedRouteConfig {\n return \"children\" in route;\n}\n\n// ============= defineRoute 函数(支持重载) =============\n\n/**\n * 定义叶子路由(有 method 和 handler),支持中间件类型推断和显式上下文类型\n *\n * @example\n * ```typescript\n * // 方式1:通过中间件自动推断上下文\n * defineRoute({\n * method: 'GET',\n * path: '/profile',\n * middleware: [authMiddleware],\n * handler: ({ user }) => { ... } // user 来自 authMiddleware\n * })\n *\n * // 方式2:显式声明上下文类型(用于父级中间件注入的场景)\n * defineRoute({\n * method: 'GET',\n * path: '/profile',\n * context: {} as { userInfo: UserInfo },\n * handler: ({ userInfo }) => { ... } // userInfo 有类型\n * })\n * ```\n */\nexport function defineRoute<\n const TSchema extends RouteSchema,\n TReturn,\n const TMiddleware extends readonly AnyMiddleware[],\n TContext extends object = object,\n TMethod extends HTTPMethod = HTTPMethod,\n TPath extends string = string\n>(config: {\n readonly method: TMethod;\n readonly path: TPath;\n readonly name?: string;\n readonly description?: string;\n readonly schema?: TSchema;\n /** 显式声明上下文类型(用于父级中间件注入的场景) */\n readonly context?: TContext;\n readonly handler: (\n ctx: HandlerContextWithExtra<TSchema, TContext & MergeMiddlewareContexts<TMiddleware>>\n ) => TReturn | Promise<TReturn>;\n readonly middleware?: TMiddleware;\n readonly docs?: {\n tags?: string[];\n security?: unknown[];\n responses?: Record<string, unknown>;\n };\n}): LeafRouteConfig<TMethod, TPath, TSchema, TReturn, TMiddleware>;\n\n/**\n * 定义嵌套路由(有 children),支持中间件类型推断\n */\nexport function defineRoute<\n const TMiddleware extends readonly AnyMiddleware[],\n TPath extends string = string\n>(config: {\n readonly path: TPath;\n readonly name?: string;\n readonly description?: string;\n readonly middleware?: TMiddleware;\n readonly children: ReadonlyArray<RouteConfigResult>;\n}): NestedRouteConfig<TPath, TMiddleware>;\n\n/**\n * defineRoute 实现\n */\nexport function defineRoute(config: {\n readonly method?: HTTPMethod;\n readonly path: string;\n readonly name?: string;\n readonly description?: string;\n readonly schema?: RouteSchema;\n readonly context?: object;\n readonly handler?: (ctx: HandlerContext<RouteSchema>) => unknown | Promise<unknown>;\n readonly middleware?: readonly AnyMiddleware[];\n readonly children?: ReadonlyArray<RouteConfigResult>;\n readonly docs?: {\n tags?: string[];\n security?: unknown[];\n responses?: Record<string, unknown>;\n };\n}): RouteConfigResult {\n return config as RouteConfigResult;\n}\n\n// ============= withContext 工厂函数 =============\n\n/**\n * 创建带预设上下文类型的路由定义器\n *\n * 用于父级中间件注入上下文的场景,定义一次,多处复用\n *\n * @example\n * ```typescript\n * // 1. 在 middleware/index.ts 中定义\n * export const defineAuthRoute = withContext<{ userInfo: UserInfo }>()\n *\n * // 2. 在路由文件中使用\n * defineAuthRoute({\n * method: 'GET',\n * path: '/profile',\n * handler: ({ userInfo }) => {\n * // userInfo 自动有类型!\n * return { id: userInfo.id }\n * }\n * })\n * ```\n */\n/**\n * 带扩展类型的路由定义器\n *\n * @typeParam TContext - Handler 上下文类型(如 { userInfo: UserInfo })\n * @typeParam TExtensions - 路由扩展字段类型(如 { webhook?: boolean })\n *\n * @example\n * ```typescript\n * // 定义扩展类型\n * interface WebhookExtension {\n * webhook?: boolean | { eventKey?: string }\n * }\n *\n * // 使用扩展\n * const defineRoute = withContext<MyContext, WebhookExtension>()\n * defineRoute({\n * method: 'POST',\n * path: '/create',\n * webhook: true, // TypeScript 严格检查\n * handler: ...\n * })\n * ```\n */\nexport function withContext<\n TContext extends object,\n TExtensions extends object = object\n>() {\n return <\n const TSchema extends RouteSchema,\n TReturn,\n const TMiddleware extends readonly AnyMiddleware[],\n TMethod extends HTTPMethod = HTTPMethod,\n TPath extends string = string\n >(config: {\n readonly method: TMethod;\n readonly path: TPath;\n readonly name?: string;\n readonly description?: string;\n readonly schema?: TSchema;\n readonly handler: (\n ctx: HandlerContextWithExtra<TSchema, TContext & MergeMiddlewareContexts<TMiddleware>>\n ) => TReturn | Promise<TReturn>;\n readonly middleware?: TMiddleware;\n readonly docs?: {\n tags?: string[];\n security?: unknown[];\n responses?: Record<string, unknown>;\n };\n } & TExtensions): LeafRouteConfig<TMethod, TPath, TSchema, TReturn, TMiddleware> => {\n return config as unknown as LeafRouteConfig<TMethod, TPath, TSchema, TReturn, TMiddleware>;\n };\n}\n\n// ============= 扁平化嵌套路由 =============\n\n/** 父级路由信息 */\ninterface ParentRouteInfo {\n /** 父级路由路径 */\n path: string;\n /** 父级路由名称 */\n name?: string;\n /** 父级路由描述 */\n description?: string;\n}\n\n/**\n * 递归扁平化路由,合并路径和中间件\n */\nfunction flattenRoutes(\n routes: ReadonlyArray<RouteConfigResult>,\n parentPath: string = \"\",\n parentMiddleware: readonly AnyMiddleware[] = [],\n parent?: ParentRouteInfo\n): ProcessedRoute[] {\n const result: ProcessedRoute[] = [];\n\n for (const route of routes) {\n const fullPath = parentPath + route.path;\n const mergedMiddleware = [...parentMiddleware, ...(route.middleware || [])];\n\n if (isLeafRoute(route)) {\n // 检测 SSE handler(handler 上有 __sse 标记)\n const originalHandler = route.handler as unknown as { __sse?: { readonly __brand: 'SSE' } };\n const isSSE = originalHandler?.__sse?.__brand === 'SSE';\n \n // 基础属性\n const processed: ProcessedRoute = {\n method: route.method,\n path: fullPath,\n name: route.name,\n description: route.description,\n schema: route.schema,\n // SSE handler 不需要包装,它自己处理请求和响应\n handler: isSSE \n ? (route.handler as unknown as (req: Request) => Promise<Response>)\n : wrapHandler(route.schema, route.handler as (ctx: HandlerContext<RouteSchema>) => unknown),\n middleware: mergedMiddleware.length > 0 ? mergedMiddleware : undefined,\n docs: route.docs,\n };\n \n // 保留 SSE 标记到 handler 上,供契约生成使用\n if (isSSE) {\n (processed.handler as unknown as { __sse: { readonly __brand: 'SSE' } }).__sse = { __brand: 'SSE' };\n }\n \n // 添加父级路由信息\n if (parent) {\n processed.parent = parent;\n }\n // 复制扩展属性(如 webhook, permission 等)\n const knownKeys = ['method', 'path', 'name', 'description', 'schema', 'handler', 'middleware', 'docs'];\n for (const key of Object.keys(route)) {\n if (!knownKeys.includes(key)) {\n processed[key] = (route as unknown as Record<string, unknown>)[key];\n }\n }\n result.push(processed);\n } else if (isNestedRoute(route)) {\n // 传递当前路由信息作为子路由的 parent\n const currentParent: ParentRouteInfo = {\n path: fullPath,\n name: route.name,\n description: route.description,\n };\n result.push(...flattenRoutes(route.children, fullPath, mergedMiddleware, currentParent));\n }\n }\n\n return result;\n}\n\n// ============= defineRoutes 函数 =============\n\n/** 带原始类型信息的路由数组 */\nexport type RoutesWithSource<T extends readonly RouteConfigResult[]> = ProcessedRoute[] & { __source: T };\n\n/**\n * 定义路由数组,支持嵌套路由\n *\n * 使用 `const T` 泛型自动保留字面量类型,无需手动添加 `as const`\n *\n * @example\n * ```typescript\n * const routes = defineRoutes([\n * defineRoute({ method: 'GET', path: '/users', handler: ... }),\n * defineRoute({ method: 'POST', path: '/users', handler: ... }),\n * ])\n *\n * // 类型推断自动工作,无需 as const\n * type Api = InferEden<typeof routes>\n * ```\n */\nexport function defineRoutes<const T extends readonly RouteConfigResult[]>(\n routes: T\n): RoutesWithSource<T> {\n const processed = flattenRoutes(routes);\n // 附加原始类型信息(仅用于类型推断,运行时不使用)\n return Object.assign(processed, { __source: routes }) as RoutesWithSource<T>;\n}\n\n// ============= 用于 API Client 的类型推断 =============\n\n/** 可推断的路由类型(供 vafast-api-client 使用) */\nexport type InferableRoute<\n TMethod extends string = string,\n TPath extends string = string,\n TReturn = unknown,\n TSchema extends RouteSchema = RouteSchema\n> = {\n readonly method: TMethod;\n readonly path: TPath;\n readonly name?: string;\n readonly description?: string;\n readonly schema?: TSchema;\n readonly handler: {\n __returnType: TReturn;\n __schema: TSchema;\n };\n readonly middleware?: ReadonlyArray<AnyMiddleware>;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"defineRoute.mjs","names":[],"sources":["../src/defineRoute.ts"],"sourcesContent":["/**\n * 路由定义 - Schema 在路由级别定义,支持嵌套路由和中间件类型推断\n *\n * @example\n * ```typescript\n * // 定义带类型的中间件(函数式风格,通过 next 传递上下文)\n * const authMiddleware = defineMiddleware<{ user: User }>((req, next) => {\n * const user = getUser(req)\n * return next({ user }) // 通过 next 参数传递上下文\n * })\n *\n * // 路由自动推断中间件注入的类型\n * const routes = defineRoutes([\n * defineRoute({\n * path: '/api',\n * middleware: [authMiddleware],\n * children: [\n * defineRoute({\n * method: 'GET',\n * path: '/profile',\n * handler: ({ user }) => ({ name: user.name }) // ✅ user 有类型\n * })\n * ]\n * })\n * ])\n * ```\n */\n\nimport type { TSchema, Static } from \"@sinclair/typebox\";\nimport { parseBody, parseQuery, parseHeaders, parseCookies } from \"./utils/parsers\";\nimport { validateAllSchemas, precompileSchemas } from \"./utils/validators/validators\";\nimport { json } from \"./utils/response\";\nimport { VafastError } from \"./middleware\";\n\n// ============= SSE 事件类型(内联定义,避免循环依赖) =============\n\n/** SSE 事件 */\nexport interface SSEEventType<T = unknown> {\n event?: string;\n data: T;\n id?: string;\n retry?: number;\n}\n\n/** SSE Generator 类型 */\nexport type SSEGeneratorType<TSchema extends RouteSchema = RouteSchema> = (\n ctx: HandlerContext<TSchema>\n) => AsyncGenerator<SSEEventType<unknown>, void, unknown>;\n\n// ============= Schema 类型 =============\n\n/** 路由 Schema 配置 */\nexport interface RouteSchema {\n body?: TSchema;\n query?: TSchema;\n params?: TSchema;\n headers?: TSchema;\n cookies?: TSchema;\n /** 响应类型 schema(用于类型同步,运行时不做校验) */\n response?: TSchema;\n}\n\n/** 从 Schema 推断类型 */\ntype InferSchemaType<T extends RouteSchema> = {\n body: T[\"body\"] extends TSchema ? Static<T[\"body\"]> : unknown;\n query: T[\"query\"] extends TSchema ? Static<T[\"query\"]> : Record<string, string>;\n params: T[\"params\"] extends TSchema ? Static<T[\"params\"]> : Record<string, string>;\n headers: T[\"headers\"] extends TSchema ? Static<T[\"headers\"]> : Record<string, string>;\n cookies: T[\"cookies\"] extends TSchema ? Static<T[\"cookies\"]> : Record<string, string>;\n};\n\n// ============= 中间件类型系统 =============\n\n/** 带类型标记的中间件 */\nexport interface TypedMiddleware<TContext extends object = object> {\n (req: Request, next: (ctx?: TContext) => Promise<Response>): Response | Promise<Response>;\n /** 类型标记(仅编译时使用) */\n __context?: TContext;\n}\n\n/** 普通中间件(无类型注入) */\ntype PlainMiddleware = (req: Request, next: () => Promise<Response>) => Response | Promise<Response>;\n\n/** 任意中间件类型 */\ntype AnyMiddleware = TypedMiddleware<object> | PlainMiddleware;\n\n/** 从中间件提取上下文类型 */\ntype ExtractMiddlewareContext<T> = T extends TypedMiddleware<infer C> ? C : object;\n\n/** 合并中间件数组的上下文类型 */\ntype MergeMiddlewareContexts<T extends readonly unknown[]> =\n T extends readonly [infer First, ...infer Rest]\n ? ExtractMiddlewareContext<First> & MergeMiddlewareContexts<Rest>\n : object;\n\n// ============= Handler 上下文 =============\n\n/** Handler 上下文(包含 schema 推断) */\nexport interface HandlerContext<TSchema extends RouteSchema = RouteSchema> {\n req: Request;\n body: InferSchemaType<TSchema>[\"body\"];\n query: InferSchemaType<TSchema>[\"query\"];\n params: InferSchemaType<TSchema>[\"params\"];\n headers: InferSchemaType<TSchema>[\"headers\"];\n cookies: InferSchemaType<TSchema>[\"cookies\"];\n}\n\n/** Handler 上下文(带中间件注入的额外类型) */\ntype HandlerContextWithExtra<TSchema extends RouteSchema, TExtra> =\n HandlerContext<TSchema> & TExtra;\n\n// ============= 路由配置类型 =============\n\n/** HTTP 方法 */\ntype HTTPMethod = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\" | \"OPTIONS\" | \"HEAD\";\n\n/** Handler 上下文类型(带中间件扩展) */\ntype HandlerCtx<TSchema extends RouteSchema, TMiddleware extends readonly AnyMiddleware[]> =\n HandlerContextWithExtra<TSchema, MergeMiddlewareContexts<TMiddleware>>;\n\n/** 普通 Handler 类型 */\ntype NormalHandler<TSchema extends RouteSchema, TReturn, TMiddleware extends readonly AnyMiddleware[]> = (\n ctx: HandlerCtx<TSchema, TMiddleware>\n) => TReturn | Promise<TReturn>;\n\n/** SSE Generator Handler 类型(直接写 async function*,无需 createSSEHandler 包装) */\ntype SSEHandler<TSchema extends RouteSchema, TMiddleware extends readonly AnyMiddleware[]> = (\n ctx: HandlerCtx<TSchema, TMiddleware>\n) => AsyncGenerator<SSEEventType<unknown>, void, unknown>;\n\n/** 叶子路由配置(有 method 和 handler) */\nexport interface LeafRouteConfig<\n TMethod extends HTTPMethod = HTTPMethod,\n TPath extends string = string,\n TSchema extends RouteSchema = RouteSchema,\n TReturn = unknown,\n TMiddleware extends readonly AnyMiddleware[] = readonly AnyMiddleware[]\n> {\n readonly method: TMethod;\n readonly path: TPath;\n readonly name?: string;\n readonly description?: string;\n readonly schema?: TSchema;\n /** \n * 是否为 SSE 端点(显式声明,推荐)\n * \n * 设置为 true 时,handler 应返回 AsyncGenerator:\n * ```typescript\n * sse: true,\n * handler: async function* (ctx) { yield { data: ... } }\n * ```\n */\n readonly sse?: boolean;\n /** \n * Handler 支持两种写法:\n * 1. 普通函数: `async (ctx) => { return result }`\n * 2. SSE Generator(需配合 sse: true): `async function* (ctx) { yield { data: ... } }`\n */\n readonly handler: NormalHandler<TSchema, TReturn, TMiddleware> | SSEHandler<TSchema, TMiddleware>;\n readonly middleware?: TMiddleware;\n readonly docs?: {\n tags?: string[];\n security?: unknown[];\n responses?: Record<string, unknown>;\n };\n}\n\n/** 嵌套路由配置(有 children,无 method 和 handler) */\nexport interface NestedRouteConfig<\n TPath extends string = string,\n TMiddleware extends readonly AnyMiddleware[] = readonly AnyMiddleware[]\n> {\n readonly path: TPath;\n readonly name?: string;\n readonly description?: string;\n readonly middleware?: TMiddleware;\n readonly children: ReadonlyArray<RouteConfigResult>;\n}\n\n/** defineRoute 返回的类型 */\ntype RouteConfigResult =\n | LeafRouteConfig<HTTPMethod, string, RouteSchema, unknown, readonly AnyMiddleware[]>\n | NestedRouteConfig<string, readonly AnyMiddleware[]>;\n\n/** 处理后的扁平路由 */\nexport interface ProcessedRoute {\n method: HTTPMethod;\n path: string;\n name?: string;\n description?: string;\n schema?: RouteSchema;\n /** 是否为 SSE 端点 */\n sse?: boolean;\n handler: (req: Request) => Promise<Response>;\n middleware?: readonly AnyMiddleware[];\n docs?: {\n tags?: string[];\n security?: unknown[];\n responses?: Record<string, unknown>;\n };\n /** 允许任意扩展(兼容 Route 类型) */\n [key: string]: unknown;\n}\n\n/** Route 类型别名(便于用户使用) */\nexport type Route = ProcessedRoute;\n\n// ============= defineMiddleware =============\n\n/**\n * 定义带类型的中间件(函数式风格)\n *\n * 通过 next() 参数传递上下文,更符合函数式编程风格\n *\n * @example\n * ```typescript\n * type AuthContext = { user: { id: string; name: string } }\n *\n * const authMiddleware = defineMiddleware<AuthContext>((req, next) => {\n * const user = getUserFromToken(req)\n * return next({ user }) // 通过 next 传递上下文\n * })\n * ```\n */\nexport function defineMiddleware<TContext extends object = object>(\n handler: (\n req: Request,\n next: (ctx?: TContext) => Promise<Response>\n ) => Promise<Response>\n): TypedMiddleware<TContext> {\n // 包装成标准中间件签名\n const middleware = ((req: Request, originalNext: () => Promise<Response>) => {\n // 包装 next,接收上下文参数并存储到 req.__locals\n const nextWithContext = (ctx?: TContext): Promise<Response> => {\n if (ctx) {\n const target = req as unknown as { __locals?: object };\n target.__locals = { ...(target.__locals || {}), ...ctx };\n }\n return originalNext();\n };\n return handler(req, nextWithContext);\n }) as TypedMiddleware<TContext>;\n\n return middleware;\n}\n\n// ============= 响应处理 =============\n\n/** 自动转换返回值为 Response */\nfunction autoResponse(result: unknown): Response {\n if (result instanceof Response) return result;\n if (result === null || result === undefined) return new Response(null, { status: 204 });\n if (typeof result === \"string\") {\n return new Response(result, { headers: { \"Content-Type\": \"text/plain; charset=utf-8\" } });\n }\n if (typeof result === \"number\" || typeof result === \"boolean\") {\n return new Response(String(result), { headers: { \"Content-Type\": \"text/plain; charset=utf-8\" } });\n }\n if (typeof result === \"object\") {\n const obj = result as Record<string, unknown>;\n if (\"data\" in obj && (\"status\" in obj || \"headers\" in obj)) {\n const { data, status = 200, headers = {} } = obj;\n if (data === null || data === undefined) {\n return new Response(null, { status: status === 200 ? 204 : (status as number), headers: headers as HeadersInit });\n }\n return json(data, status as number, headers as Record<string, string>);\n }\n return json(result);\n }\n return new Response(null, { status: 204 });\n}\n\n/**\n * 构建 HandlerContext(公共逻辑,供普通 handler 和 SSE handler 复用)\n */\nasync function buildHandlerContext<TSchema extends RouteSchema>(\n req: Request,\n schema: TSchema | undefined\n): Promise<HandlerContext<TSchema>> {\n const query = parseQuery(req);\n const headers = parseHeaders(req);\n const cookies = parseCookies(req);\n const params = ((req as unknown as Record<string, unknown>).params as Record<string, string>) || {};\n\n let body: unknown = undefined;\n if (req.method !== \"GET\" && req.method !== \"HEAD\") {\n try {\n body = await parseBody(req);\n } catch {\n // 忽略解析错误\n }\n }\n\n const data = { body, query, params, headers, cookies };\n if (schema && (schema.body || schema.query || schema.params || schema.headers || schema.cookies)) {\n validateAllSchemas(schema, data);\n }\n\n // 获取中间件注入的上下文\n const extraCtx = (req as unknown as { __locals?: unknown }).__locals || {};\n\n return {\n req,\n body: body as HandlerContext<TSchema>[\"body\"],\n query: query as HandlerContext<TSchema>[\"query\"],\n params: params as HandlerContext<TSchema>[\"params\"],\n headers: headers as HandlerContext<TSchema>[\"headers\"],\n cookies: cookies as HandlerContext<TSchema>[\"cookies\"],\n ...extraCtx,\n } as HandlerContext<TSchema>;\n}\n\n/** 创建包装后的 handler(普通路由) */\nfunction wrapHandler<TSchema extends RouteSchema>(\n schema: TSchema | undefined,\n userHandler: (ctx: HandlerContext<TSchema>) => unknown | Promise<unknown>\n): (req: Request) => Promise<Response> {\n if (schema && (schema.body || schema.query || schema.params || schema.headers || schema.cookies)) {\n precompileSchemas(schema);\n }\n\n return async (req: Request): Promise<Response> => {\n try {\n const ctx = await buildHandlerContext(req, schema);\n const result = await userHandler(ctx);\n return autoResponse(result);\n } catch (error) {\n // 如果是 VafastError,重新抛出让错误处理中间件处理\n if (error instanceof VafastError) {\n throw error;\n }\n if (error instanceof Error && error.message.includes(\"验证失败\")) {\n return json({ code: 400, message: error.message }, 400);\n }\n return json({ code: 500, message: error instanceof Error ? error.message : \"未知错误\" }, 500);\n }\n };\n}\n\n/**\n * SSE Handler 函数类型\n * \n * SSE handler 与普通 handler 使用统一的上下文模型,接收完整的 HandlerContext。\n * 框架自动完成:body 解析、schema 验证、中间件上下文注入。\n * SSE handler 只需关注流式响应逻辑,返回 SSE Stream Response。\n */\ntype SSEHandlerFn<TSchema extends RouteSchema = RouteSchema> = \n (ctx: HandlerContext<TSchema>) => Promise<Response>;\n\n/**\n * 格式化 SSE 事件为字符串(内联,避免循环依赖)\n */\nfunction formatSSEEvent(event: SSEEventType): string {\n const lines: string[] = [];\n if (event.id !== undefined) lines.push(`id: ${event.id}`);\n if (event.event !== undefined) lines.push(`event: ${event.event}`);\n if (event.retry !== undefined) lines.push(`retry: ${event.retry}`);\n \n const dataStr = typeof event.data === 'string' ? event.data : JSON.stringify(event.data);\n for (const line of dataStr.split('\\n')) {\n lines.push(`data: ${line}`);\n }\n return lines.join('\\n') + '\\n\\n';\n}\n\n/**\n * 将 AsyncGenerator 包装为 SSE Handler\n * \n * 支持用户直接写 `async function* (ctx) { yield ... }` 而无需 createSSEHandler\n */\nfunction wrapGeneratorToSSEHandler<TSchema extends RouteSchema>(\n generator: (ctx: HandlerContext<TSchema>) => AsyncGenerator<SSEEventType<unknown>, void, unknown>\n): SSEHandlerFn<TSchema> {\n return async (ctx: HandlerContext<TSchema>): Promise<Response> => {\n const stream = new ReadableStream({\n async start(controller) {\n const encoder = new TextEncoder();\n try {\n for await (const event of generator(ctx)) {\n controller.enqueue(encoder.encode(formatSSEEvent(event)));\n }\n } catch (error) {\n const errorEvent = formatSSEEvent({\n event: 'error',\n data: { error: error instanceof Error ? error.message : '未知错误' }\n });\n controller.enqueue(encoder.encode(errorEvent));\n } finally {\n controller.close();\n }\n }\n });\n\n return new Response(stream, {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n 'X-Accel-Buffering': 'no',\n }\n });\n };\n}\n\n/**\n * 创建 SSE handler 的请求包装器\n * \n * SSE handler 与普通 handler 使用相同的上下文构建流程(buildHandlerContext),\n * 确保:\n * - body/query/params 解析一致\n * - schema 验证统一\n * - 中间件注入的上下文(如 userInfo)自动可用\n * - 错误处理统一\n * \n * 唯一区别:SSE handler 返回的是 SSE Stream Response,而非普通 JSON Response\n */\nfunction wrapSSEHandler<TSchema extends RouteSchema>(\n schema: TSchema | undefined,\n sseHandler: SSEHandlerFn<TSchema>\n): (req: Request) => Promise<Response> {\n if (schema && (schema.body || schema.query || schema.params || schema.headers || schema.cookies)) {\n precompileSchemas(schema);\n }\n\n return async (req: Request): Promise<Response> => {\n try {\n // 与普通 handler 使用相同的上下文构建逻辑\n const ctx = await buildHandlerContext(req, schema);\n // SSE handler 返回 SSE Stream Response\n return await sseHandler(ctx);\n } catch (error) {\n // 如果是 VafastError,重新抛出让错误处理中间件处理\n if (error instanceof VafastError) {\n throw error;\n }\n if (error instanceof Error && error.message.includes(\"验证失败\")) {\n return json({ code: 400, message: error.message }, 400);\n }\n return json({ code: 500, message: error instanceof Error ? error.message : \"未知错误\" }, 500);\n }\n };\n}\n\n// ============= 判断路由类型 =============\n\n/** 判断是否为叶子路由 */\nfunction isLeafRoute(route: RouteConfigResult): route is LeafRouteConfig {\n return \"method\" in route && \"handler\" in route;\n}\n\n/** 判断是否为嵌套路由 */\nfunction isNestedRoute(route: RouteConfigResult): route is NestedRouteConfig {\n return \"children\" in route;\n}\n\n// ============= defineRoute 函数(支持重载) =============\n\n/**\n * 定义叶子路由(有 method 和 handler),支持中间件类型推断和显式上下文类型\n *\n * @example\n * ```typescript\n * // 方式1:通过中间件自动推断上下文\n * defineRoute({\n * method: 'GET',\n * path: '/profile',\n * middleware: [authMiddleware],\n * handler: ({ user }) => { ... } // user 来自 authMiddleware\n * })\n *\n * // 方式2:显式声明上下文类型(用于父级中间件注入的场景)\n * defineRoute({\n * method: 'GET',\n * path: '/profile',\n * context: {} as { userInfo: UserInfo },\n * handler: ({ userInfo }) => { ... } // userInfo 有类型\n * })\n * ```\n */\nexport function defineRoute<\n const TSchema extends RouteSchema,\n TReturn,\n const TMiddleware extends readonly AnyMiddleware[],\n TContext extends object = object,\n TMethod extends HTTPMethod = HTTPMethod,\n TPath extends string = string\n>(config: {\n readonly method: TMethod;\n readonly path: TPath;\n readonly name?: string;\n readonly description?: string;\n readonly schema?: TSchema;\n /** 是否为 SSE 端点,设置为 true 时 handler 应返回 AsyncGenerator */\n readonly sse?: boolean;\n /** 显式声明上下文类型(用于父级中间件注入的场景) */\n readonly context?: TContext;\n readonly handler: (\n ctx: HandlerContextWithExtra<TSchema, TContext & MergeMiddlewareContexts<TMiddleware>>\n ) => TReturn | Promise<TReturn> | AsyncGenerator<SSEEventType<unknown>, void, unknown>;\n readonly middleware?: TMiddleware;\n readonly docs?: {\n tags?: string[];\n security?: unknown[];\n responses?: Record<string, unknown>;\n };\n}): LeafRouteConfig<TMethod, TPath, TSchema, TReturn, TMiddleware>;\n\n/**\n * 定义嵌套路由(有 children),支持中间件类型推断\n */\nexport function defineRoute<\n const TMiddleware extends readonly AnyMiddleware[],\n TPath extends string = string\n>(config: {\n readonly path: TPath;\n readonly name?: string;\n readonly description?: string;\n readonly middleware?: TMiddleware;\n readonly children: ReadonlyArray<RouteConfigResult>;\n}): NestedRouteConfig<TPath, TMiddleware>;\n\n/**\n * defineRoute 实现\n */\nexport function defineRoute(config: {\n readonly method?: HTTPMethod;\n readonly path: string;\n readonly name?: string;\n readonly description?: string;\n readonly schema?: RouteSchema;\n readonly sse?: boolean;\n readonly context?: object;\n readonly handler?: (ctx: HandlerContext<RouteSchema>) => unknown | Promise<unknown> | AsyncGenerator<unknown, void, unknown>;\n readonly middleware?: readonly AnyMiddleware[];\n readonly children?: ReadonlyArray<RouteConfigResult>;\n readonly docs?: {\n tags?: string[];\n security?: unknown[];\n responses?: Record<string, unknown>;\n };\n}): RouteConfigResult {\n return config as RouteConfigResult;\n}\n\n// ============= withContext 工厂函数 =============\n\n/**\n * 创建带预设上下文类型的路由定义器\n *\n * 用于父级中间件注入上下文的场景,定义一次,多处复用\n *\n * @example\n * ```typescript\n * // 1. 在 middleware/index.ts 中定义\n * export const defineAuthRoute = withContext<{ userInfo: UserInfo }>()\n *\n * // 2. 在路由文件中使用\n * defineAuthRoute({\n * method: 'GET',\n * path: '/profile',\n * handler: ({ userInfo }) => {\n * // userInfo 自动有类型!\n * return { id: userInfo.id }\n * }\n * })\n * ```\n */\n/**\n * 带扩展类型的路由定义器\n *\n * @typeParam TContext - Handler 上下文类型(如 { userInfo: UserInfo })\n * @typeParam TExtensions - 路由扩展字段类型(如 { webhook?: boolean })\n *\n * @example\n * ```typescript\n * // 定义扩展类型\n * interface WebhookExtension {\n * webhook?: boolean | { eventKey?: string }\n * }\n *\n * // 使用扩展\n * const defineRoute = withContext<MyContext, WebhookExtension>()\n * defineRoute({\n * method: 'POST',\n * path: '/create',\n * webhook: true, // TypeScript 严格检查\n * handler: ...\n * })\n * ```\n */\n/** withContext Handler 上下文类型 */\ntype WithContextHandlerCtx<TSchema extends RouteSchema, TContext extends object, TMiddleware extends readonly AnyMiddleware[]> =\n HandlerContextWithExtra<TSchema, TContext & MergeMiddlewareContexts<TMiddleware>>;\n\n/** withContext 支持的 Handler 类型(普通函数 + SSE Generator) */\ntype WithContextHandler<TSchema extends RouteSchema, TContext extends object, TMiddleware extends readonly AnyMiddleware[], TReturn> =\n | ((ctx: WithContextHandlerCtx<TSchema, TContext, TMiddleware>) => TReturn | Promise<TReturn>)\n | ((ctx: WithContextHandlerCtx<TSchema, TContext, TMiddleware>) => AsyncGenerator<SSEEventType<unknown>, void, unknown>);\n\nexport function withContext<\n TContext extends object,\n TExtensions extends object = object\n>() {\n return <\n const TSchema extends RouteSchema,\n TReturn,\n const TMiddleware extends readonly AnyMiddleware[],\n TMethod extends HTTPMethod = HTTPMethod,\n TPath extends string = string\n >(config: {\n readonly method: TMethod;\n readonly path: TPath;\n readonly name?: string;\n readonly description?: string;\n readonly schema?: TSchema;\n /** \n * 是否为 SSE 端点\n * 设置为 true 时,handler 应返回 AsyncGenerator\n */\n readonly sse?: boolean;\n /** Handler 支持两种写法:普通函数 或 SSE Generator(需配合 sse: true) */\n readonly handler: WithContextHandler<TSchema, TContext, TMiddleware, TReturn>;\n readonly middleware?: TMiddleware;\n readonly docs?: {\n tags?: string[];\n security?: unknown[];\n responses?: Record<string, unknown>;\n };\n } & TExtensions): LeafRouteConfig<TMethod, TPath, TSchema, TReturn, TMiddleware> => {\n return config as unknown as LeafRouteConfig<TMethod, TPath, TSchema, TReturn, TMiddleware>;\n };\n}\n\n// ============= 扁平化嵌套路由 =============\n\n/** 父级路由信息 */\ninterface ParentRouteInfo {\n /** 父级路由路径 */\n path: string;\n /** 父级路由名称 */\n name?: string;\n /** 父级路由描述 */\n description?: string;\n}\n\n/**\n * 递归扁平化路由,合并路径和中间件\n */\nfunction flattenRoutes(\n routes: ReadonlyArray<RouteConfigResult>,\n parentPath: string = \"\",\n parentMiddleware: readonly AnyMiddleware[] = [],\n parent?: ParentRouteInfo\n): ProcessedRoute[] {\n const result: ProcessedRoute[] = [];\n\n for (const route of routes) {\n const fullPath = parentPath + route.path;\n const mergedMiddleware = [...parentMiddleware, ...(route.middleware || [])];\n\n if (isLeafRoute(route)) {\n // SSE 检测:只通过显式 sse: true 声明\n const routeWithSSE = route as typeof route & { sse?: boolean };\n const isSSE = routeWithSSE.sse === true;\n \n // 基础属性\n const processed: ProcessedRoute = {\n method: route.method,\n path: fullPath,\n name: route.name,\n description: route.description,\n schema: route.schema,\n sse: isSSE || undefined, // SSE 标记\n handler: isSSE \n ? wrapSSEHandler(route.schema, wrapGeneratorToSSEHandler(route.handler as (ctx: HandlerContext<RouteSchema>) => AsyncGenerator<SSEEventType<unknown>, void, unknown>))\n : wrapHandler(route.schema, route.handler as (ctx: HandlerContext<RouteSchema>) => unknown),\n middleware: mergedMiddleware.length > 0 ? mergedMiddleware : undefined,\n docs: route.docs,\n };\n \n // 添加父级路由信息\n if (parent) {\n processed.parent = parent;\n }\n // 复制扩展属性(如 webhook, permission 等)\n const knownKeys = ['method', 'path', 'name', 'description', 'schema', 'handler', 'middleware', 'docs'];\n for (const key of Object.keys(route)) {\n if (!knownKeys.includes(key)) {\n processed[key] = (route as unknown as Record<string, unknown>)[key];\n }\n }\n result.push(processed);\n } else if (isNestedRoute(route)) {\n // 传递当前路由信息作为子路由的 parent\n const currentParent: ParentRouteInfo = {\n path: fullPath,\n name: route.name,\n description: route.description,\n };\n result.push(...flattenRoutes(route.children, fullPath, mergedMiddleware, currentParent));\n }\n }\n\n return result;\n}\n\n// ============= defineRoutes 函数 =============\n\n/** 带原始类型信息的路由数组 */\nexport type RoutesWithSource<T extends readonly RouteConfigResult[]> = ProcessedRoute[] & { __source: T };\n\n/**\n * 定义路由数组,支持嵌套路由\n *\n * 使用 `const T` 泛型自动保留字面量类型,无需手动添加 `as const`\n *\n * @example\n * ```typescript\n * const routes = defineRoutes([\n * defineRoute({ method: 'GET', path: '/users', handler: ... }),\n * defineRoute({ method: 'POST', path: '/users', handler: ... }),\n * ])\n *\n * // 类型推断自动工作,无需 as const\n * type Api = InferEden<typeof routes>\n * ```\n */\nexport function defineRoutes<const T extends readonly RouteConfigResult[]>(\n routes: T\n): RoutesWithSource<T> {\n const processed = flattenRoutes(routes);\n // 附加原始类型信息(仅用于类型推断,运行时不使用)\n return Object.assign(processed, { __source: routes }) as RoutesWithSource<T>;\n}\n\n// ============= 用于 API Client 的类型推断 =============\n\n/** 可推断的路由类型(供 vafast-api-client 使用) */\nexport type InferableRoute<\n TMethod extends string = string,\n TPath extends string = string,\n TReturn = unknown,\n TSchema extends RouteSchema = RouteSchema\n> = {\n readonly method: TMethod;\n readonly path: TPath;\n readonly name?: string;\n readonly description?: string;\n readonly schema?: TSchema;\n readonly handler: {\n __returnType: TReturn;\n __schema: TSchema;\n };\n readonly middleware?: ReadonlyArray<AnyMiddleware>;\n};\n"],"mappings":"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"}
|